Conversation
Implement a complete extension system that allows third-party developers to extend Spec Kit functionality through plugins. ## Core Features - Extension discovery and loading from local and global directories - YAML-based extension manifest (extension.yml) with metadata and capabilities - Command extensions: custom slash commands with markdown templates - Hook system: pre/post hooks for generate, task, and sync operations - Extension catalog for discovering and installing community extensions - SPECKIT_CATALOG_URL environment variable for catalog URL override ## Installation Methods - Catalog install: `specify extension add <name>` - URL install: `specify extension add <name> --from <url>` - Dev install: `specify extension add --dev <path>` ## Implementation - ExtensionManager class for lifecycle management (load, enable, disable) - Support for extension dependencies and version constraints - Configuration layering (global → project → extension) - Hook conditions for conditional execution ## Documentation - RFC with design rationale and architecture decisions - API reference for extension developers - Development guide with examples - User guide for installing and managing extensions - Publishing guide for the extension catalog ## Included - Extension template for bootstrapping new extensions - Comprehensive test suite - Example catalog.json structure Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
This PR implements a comprehensive modular extension system for Spec Kit, enabling third-party developers to create plugins that extend functionality without bloating the core framework.
Changes:
- Complete extension infrastructure with manifest validation, registry, installation/removal, and hook system
- Extension catalog for discovery with search, caching, and metadata management
- CLI commands for managing extensions (add, remove, list, search, info, update, enable, disable)
- Multi-agent support for 16+ AI coding assistants with automatic command registration
- Layered configuration system supporting defaults, project, local, and environment variable overrides
- Comprehensive documentation suite (user guide, API reference, publishing guide, RFC)
- Extension template and test suite with 39 unit tests
Reviewed changes
Copilot reviewed 20 out of 21 changed files in this pull request and generated 8 comments.
Show a summary per file
| File | Description |
|---|---|
| src/specify_cli/extensions.py | Core extension system implementation (1714 lines): manifest validation, registry, manager, catalog, config, hooks |
| src/specify_cli/init.py | CLI integration with 7 extension commands |
| tests/test_extensions.py | Comprehensive test suite with 984 lines covering all components |
| pyproject.toml | Version bump to 0.1.0, added dependencies (pyyaml, packaging), test configuration |
| extensions/template/* | Complete extension template with examples and documentation |
| extensions/*.md | Documentation suite (user guide, API reference, publishing guide, RFC) |
| extensions/catalog.json | Initial catalog with Jira extension |
| CHANGELOG.md | Detailed changelog documenting all new features |
| .gitignore | Extension cache and local config exclusions |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Adds 2-level mode support (Epic → Stories only). Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
|
@mbachorik Can you make it so the catalog.json is empty and it gets populated when adding a specific extension. For organizations they could then ship their own vetted version of catalog.json? Also can you address the markdown linter errors? |
- Fix Zip Slip vulnerability in ZIP extraction with path validation - Fix keep_config option to actually preserve config files on removal - Add URL validation for SPECKIT_CATALOG_URL (HTTPS required, localhost exception) - Add security warning when installing from custom URLs (--from flag) - Empty catalog.json so organizations can ship their own catalogs - Fix markdown linter errors (MD040: add language to code blocks) - Remove redundant import and fix unused variables in tests - Add comment explaining empty except clause for backwards compatibility Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 20 out of 21 changed files in this pull request and generated 5 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
- Explain why default catalog is empty (org control) - Document how to create and host custom catalogs - Add catalog JSON schema reference - Include use cases: private extensions, curated catalogs, air-gapped environments - Add examples for combining catalog with direct installation Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Update test_config_backup_on_remove to use new subdirectory structure (.backup/test-ext/file.yml instead of .backup/test-ext-file.yml) - Update test_full_install_and_remove_workflow to handle registered_commands being a dict keyed by agent name instead of a flat list Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 20 out of 21 changed files in this pull request and generated 2 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Updates in this pushTest FixesFixed two test assertions that were failing due to data structure changes:
All Tests PassingExtension Update Command TestedVerified
|
- Fix localhost URL check to use parsed.hostname instead of netloc.startswith() This correctly handles URLs with ports like localhost:8080 - Fix YAML indentation error in config-template.yml (line 57) - Fix double space typo in example.md (line 172) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Copilot Review Feedback - StatusAddressed in this push (352bd80)
Already addressed in previous commits
Design decisions (not changing)
|
|
@mbachorik Did I miss the change for catalog.json? |
The main catalog.json is intentionally empty so organizations can ship their own curated catalogs. This example file shows the expected schema and structure for creating organization-specific catalogs. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
|
@mnriem Regarding your request: catalog.json is now empty - It only contains the schema structure with no extensions: {
"schema_version": "1.0",
"updated_at": "2026-02-03T00:00:00Z",
"catalog_url": "https://raw.githubusercontent.com/github/spec-kit/main/extensions/catalog.json",
"extensions": {}
}Added catalog.example.json - A reference file showing the expected schema for organizations creating their own catalogs. This includes two sample extension entries (Jira and Linear) demonstrating all the fields. Organizations can:
Markdown linter issues were addressed in earlier commits. Or do you want catalog.json to be completely empty (or non-existent in repo)? |
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 21 out of 22 changed files in this pull request and generated 2 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
src/specify_cli/extensions.py
Outdated
| print( | ||
| "Warning: Using non-default extension catalog. " | ||
| "Only use catalogs from sources you trust.", | ||
| file=sys.stderr, | ||
| ) |
There was a problem hiding this comment.
The warning message about using a non-default catalog is printed to stderr every time get_catalog_url() is called, which could result in duplicate warnings during a single command execution (e.g., if the catalog is fetched multiple times). Consider adding a flag to ensure the warning is only shown once per execution, or move the warning to the point where the environment variable is first detected.
| print( | |
| "Warning: Using non-default extension catalog. " | |
| "Only use catalogs from sources you trust.", | |
| file=sys.stderr, | |
| ) | |
| if not getattr(self, "_non_default_catalog_warning_shown", False): | |
| print( | |
| "Warning: Using non-default extension catalog. " | |
| "Only use catalogs from sources you trust.", | |
| file=sys.stderr, | |
| ) | |
| self._non_default_catalog_warning_shown = True |
src/specify_cli/extensions.py
Outdated
| if operator == "==": | ||
| return str(actual_value) == expected_value | ||
| else: # != | ||
| return str(actual_value) != expected_value |
There was a problem hiding this comment.
In the hook condition evaluation, when comparing config values using == or !=, the code converts actual_value to string with str(actual_value). This means that boolean values (e.g., True) will be compared as strings ("True"), which may not match the expected string value in the condition. For example, if a config has enabled: true and the condition is config.enabled == 'true', this will fail because str(True) is "True" not "true". Consider normalizing the comparison or documenting the exact expected format.
| if operator == "==": | |
| return str(actual_value) == expected_value | |
| else: # != | |
| return str(actual_value) != expected_value | |
| def _normalize_config_value_for_comparison(value: Any) -> str: | |
| # Booleans are represented in config as True/False, but conditions | |
| # are typically written using lowercase 'true'/'false'. | |
| if isinstance(value, bool): | |
| return "true" if value else "false" | |
| return str(value) | |
| normalized_actual = _normalize_config_value_for_comparison(actual_value) | |
| if operator == "==": | |
| return normalized_actual == expected_value | |
| else: # != | |
| return normalized_actual != expected_value |
- Fix Zip Slip vulnerability by using relative_to() for safe path validation - Add HTTPS validation for extension download URLs - Backup both *-config.yml and *-config.local.yml files on remove - Normalize boolean values to lowercase for hook condition comparisons - Show non-default catalog warning only once per instance Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Addressed Copilot Review Feedback (Round 2)Fixed the remaining security and logic issues flagged by Copilot: Security Fixes
Logic Fixes
All 39 tests continue to pass. |
Summary
Implement a complete extension system that allows third-party developers to extend Spec Kit functionality through plugins.
SPECKIT_CATALOG_URLenvironment variable for organization catalog customizationInstallation Methods
specify extension add <name>specify extension add <name> --from <url>specify extension add --dev <path>Extension Management Commands
specify extension list- List installed extensionsspecify extension search [query]- Search catalog for extensionsspecify extension update [name]- Check for and update extensionsspecify extension remove <name>- Remove an extensionOrganization Catalog Customization
The default catalog is intentionally empty, allowing organizations to ship their own curated extension catalogs:
Documentation Included
Also Included
AI Disclosure
Testing
Sample Extension
A sample Jira extension (also written primarily by AI, using GitHub Copilot and Claude) is available at:
https://github.com/mbachorik/spec-kit-jira
Test plan
🤖 Generated with Claude Code