feat: add support for Awesome Copilot Collections as APM packages#22
feat: add support for Awesome Copilot Collections as APM packages#22danielmeppiel merged 9 commits intomainfrom
Conversation
… with integration tests
There was a problem hiding this comment.
Pull Request Overview
This PR implements support for virtual collection packages in APM, extending the existing virtual file package feature. It enables users to install curated collections of files (prompts, instructions, chat modes) directly from repositories without installing the full package.
Key changes:
- Added collection manifest parser and data models
- Implemented collection package downloading in GitHub downloader
- Fixed orphan detection to properly recognize virtual packages (both files and collections)
- Added comprehensive integration tests for virtual package orphan detection and collection installation
Reviewed Changes
Copilot reviewed 6 out of 6 changed files in this pull request and generated 8 comments.
Show a summary per file
| File | Description |
|---|---|
src/apm_cli/deps/collection_parser.py |
New module for parsing .collection.yml manifests with validation |
src/apm_cli/deps/github_downloader.py |
Added download_collection_package() method to download and organize collection files |
src/apm_cli/cli.py |
Updated orphan detection logic to handle virtual packages with correct naming |
tests/integration/test_virtual_package_orphan_detection.py |
New integration tests verifying virtual packages aren't flagged as orphans |
tests/integration/test_collection_install.py |
New integration tests for collection installation and manifest parsing |
pyproject.toml |
Added slow test marker for long-running integration tests |
| 'agent': 'agents', | ||
| 'context': 'contexts', | ||
| } | ||
| return kind_to_subdir.get(self.kind.lower(), 'prompts') # Default to prompts |
There was a problem hiding this comment.
The default fallback to 'prompts' for unknown kinds is implicit behavior that could lead to unexpected file placement. Consider raising a warning or error for unknown kinds instead of silently defaulting, or document this behavior in the docstring.
| manifest_content = self.download_raw_file(dep_ref, collection_manifest_path, ref) | ||
| except RuntimeError as e: | ||
| # Try .yaml extension as fallback | ||
| if ".collection.yml" in str(e): |
There was a problem hiding this comment.
The error message check is fragile and could match unrelated errors containing this substring. Consider catching specific exception types or checking error codes rather than string matching in the exception message.
| apm_yml_content = f"""name: {package_name} | ||
| version: 1.0.0 | ||
| description: {manifest.description} | ||
| author: {dep_ref.repo_url.split('/')[0]} | ||
| """ |
There was a problem hiding this comment.
The manifest.description is inserted directly into the YAML content without escaping. If the description contains special YAML characters (like colons, quotes, or newlines), this could produce invalid YAML or injection vulnerabilities. Use proper YAML serialization instead of string formatting.
| apm_yml_content += f"\ntags:\n" | ||
| for tag in manifest.tags: |
There was a problem hiding this comment.
Tags are inserted directly into YAML without escaping. Tags could contain special YAML characters that break the format or introduce injection issues. Use proper YAML serialization (e.g., yaml.dump()) instead of manual string concatenation.
| # Build expected installed packages set (same logic as _check_orphaned_packages) | ||
| expected_installed = set() | ||
| for dep in declared_deps: | ||
| repo_parts = dep.repo_url.split('/') | ||
| if len(repo_parts) >= 2: | ||
| org_name = repo_parts[0] | ||
| if dep.is_virtual: | ||
| package_name = dep.get_virtual_package_name() | ||
| expected_installed.add(f"{org_name}/{package_name}") | ||
| else: | ||
| repo_name = repo_parts[1] |
There was a problem hiding this comment.
This logic is duplicated across three test functions and also mirrors the implementation in cli.py. Consider extracting this into a shared test helper function to reduce code duplication and ensure consistency.
| import shutil | ||
|
|
There was a problem hiding this comment.
Import of 'shutil' is not used.
| import shutil |
| recognized and not flagged as orphaned when they are declared in apm.yml. | ||
| """ | ||
|
|
||
| import tempfile |
There was a problem hiding this comment.
Import of 'tempfile' is not used.
| import tempfile |
| """ | ||
|
|
||
| import tempfile | ||
| from pathlib import Path |
There was a problem hiding this comment.
Import of 'Path' is not used.
| from pathlib import Path |
- Refactored package validation to use GitHubPackageDownloader for virtual packages. - Enhanced dependency resolution to support namespaced package directories. - Added logic to create minimal apm.yml for zero-config execution of virtual packages. - Introduced auto-install feature that downloads virtual packages on first run. - Implemented tests for auto-install functionality, including end-to-end scenarios. - Added validation methods for checking the existence of virtual packages. - Updated GitHubPackageDownloader to handle API requests for downloading files. - Created integration tests for the auto-install feature and guardrailing scenarios.
…nagement and retry logic
…opilot CLI and improve Codex CLI command
…ze execution time
…rove runtime efficiency
… comprehensive coverage
This pull request implements support for installing "collections" from Awesome Copilot repo as virtual packages in the APM CLI, allowing users to fetch and organize sets of related prompt/instruction/chatmode files from GitHub repositories via a manifest. It introduces a parser for
.collection.ymlfiles, adds logic to download and validate collections, and provides integration tests for these new capabilities. Additionally, it refines orphaned package detection and updates test markers.