# RFC: Spec Kit Extension System **Status**: Implemented **Author**: Stats Perform Engineering **Created**: 2026-01-28 **Updated**: 2026-03-11 --- ## Table of Contents 1. [Summary](#summary) 2. [Motivation](#motivation) 3. [Design Principles](#design-principles) 4. [Architecture Overview](#architecture-overview) 5. [Extension Manifest Specification](#extension-manifest-specification) 6. [Extension Lifecycle](#extension-lifecycle) 7. [Command Registration](#command-registration) 8. [Configuration Management](#configuration-management) 9. [Hook System](#hook-system) 10. [Extension Discovery & Catalog](#extension-discovery--catalog) 11. [CLI Commands](#cli-commands) 12. [Compatibility & Versioning](#compatibility--versioning) 13. [Security Considerations](#security-considerations) 14. [Migration Strategy](#migration-strategy) 15. [Implementation Phases](#implementation-phases) 16. [Resolved Questions](#resolved-questions) 17. [Open Questions (Remaining)](#open-questions-remaining) 18. [Appendices](#appendices) --- ## Summary Introduce an extension system to Spec Kit that allows modular integration with external tools (Jira, Linear, Azure DevOps, etc.) without bloating the core framework. Extensions are self-contained packages installed into `.specify/extensions/` with declarative manifests, versioned independently, and discoverable through a central catalog. --- ## Motivation ### Current Problems 1. **Monolithic Growth**: Adding Jira integration to core spec-kit creates: - Large configuration files affecting all users - Dependencies on Jira MCP server for everyone - Merge conflicts as features accumulate 2. **Limited Flexibility**: Different organizations use different tools: - GitHub Issues vs Jira vs Linear vs Azure DevOps - Custom internal tools - No way to support all without bloat 3. **Maintenance Burden**: Every integration adds: - Documentation complexity - Testing matrix expansion - Breaking change surface area 4. **Community Friction**: External contributors can't easily add integrations without core repo PR approval and release cycles. ### Goals 1. **Modularity**: Core spec-kit remains lean, extensions are opt-in 2. **Extensibility**: Clear API for building new integrations 3. **Independence**: Extensions version/release separately from core 4. **Discoverability**: Central catalog for finding extensions 5. **Safety**: Validation, compatibility checks, sandboxing --- ## Design Principles ### 1. Convention Over Configuration - Standard directory structure (`.specify/extensions/{name}/`) - Declarative manifest (`extension.yml`) - Predictable command naming (`speckit.{extension}.{command}`) ### 2. Fail-Safe Defaults - Missing extensions gracefully degrade (skip hooks) - Invalid extensions warn but don't break core functionality - Extension failures isolated from core operations ### 3. Backward Compatibility - Core commands remain unchanged - Extensions additive only (no core modifications) - Old projects work without extensions ### 4. Developer Experience - Simple installation: `specify extension add jira` - Clear error messages for compatibility issues - Local development mode for testing extensions ### 5. Security First - Extensions run in same context as AI agent (trust boundary) - Manifest validation prevents malicious code - Verify signatures for official extensions (future) --- ## Architecture Overview ### Directory Structure ```text project/ ├── .specify/ │ ├── scripts/ # Core scripts (unchanged) │ ├── templates/ # Core templates (unchanged) │ ├── memory/ # Session memory │ ├── extensions/ # Extensions directory (NEW) │ │ ├── .registry # Installed extensions metadata (NEW) │ │ ├── jira/ # Jira extension │ │ │ ├── extension.yml # Manifest │ │ │ ├── jira-config.yml # Extension config │ │ │ ├── commands/ # Command files │ │ │ ├── scripts/ # Helper scripts │ │ │ └── docs/ # Documentation │ │ └── linear/ # Linear extension (example) │ └── extensions.yml # Project extension configuration (NEW) └── .gitignore # Ignore local extension configs ``` ### Component Diagram ```text ┌─────────────────────────────────────────────────────────┐ │ Spec Kit Core │ │ ┌──────────────────────────────────────────────────┐ │ │ │ CLI (specify) │ │ │ │ - init, check │ │ │ │ - extension add/remove/list/update ← NEW │ │ │ └──────────────────────────────────────────────────┘ │ │ ┌──────────────────────────────────────────────────┐ │ │ │ Extension Manager ← NEW │ │ │ │ - Discovery, Installation, Validation │ │ │ │ - Command Registration, Hook Execution │ │ │ └──────────────────────────────────────────────────┘ │ │ ┌──────────────────────────────────────────────────┐ │ │ │ Core Commands │ │ │ │ - /speckit.specify │ │ │ │ - /speckit.tasks │ │ │ │ - /speckit.implement │ │ │ └─────────┬────────────────────────────────────────┘ │ └────────────┼────────────────────────────────────────────┘ │ Hook Points (after_tasks, after_implement) ↓ ┌─────────────────────────────────────────────────────────┐ │ Extensions │ │ ┌──────────────────────────────────────────────────┐ │ │ │ Jira Extension │ │ │ │ - /speckit.jira.specstoissues │ │ │ │ - /speckit.jira.discover-fields │ │ │ └──────────────────────────────────────────────────┘ │ │ ┌──────────────────────────────────────────────────┐ │ │ │ Linear Extension │ │ │ │ - /speckit.linear.sync │ │ │ └──────────────────────────────────────────────────┘ │ └─────────────────────────────────────────────────────────┘ │ Calls external tools ↓ ┌─────────────────────────────────────────────────────────┐ │ External Tools │ │ - Jira MCP Server │ │ - Linear API │ │ - GitHub API │ └─────────────────────────────────────────────────────────┘ ``` --- ## Extension Manifest Specification ### Schema: `extension.yml` ```yaml # Extension Manifest Schema v1.0 # All extensions MUST include this file at root # Schema version for compatibility schema_version: "1.0" # Extension metadata (REQUIRED) extension: id: "jira" # Unique identifier (lowercase, alphanumeric, hyphens) name: "Jira Integration" # Human-readable name version: "1.0.0" # Semantic version description: "Create Jira Epics, Stories, and Issues from spec-kit artifacts" author: "Stats Perform" # Author/organization repository: "https://github.com/statsperform/spec-kit-jira" license: "MIT" # SPDX license identifier homepage: "https://github.com/statsperform/spec-kit-jira/blob/main/README.md" # Compatibility requirements (REQUIRED) requires: # Spec-kit version (semantic version range) speckit_version: ">=0.1.0,<2.0.0" # External tools required by extension tools: - name: "jira-mcp-server" required: true version: ">=1.0.0" # Optional: version constraint description: "Jira MCP server for API access" install_url: "https://github.com/your-org/jira-mcp-server" check_command: "jira --version" # Optional: CLI command to verify # Core spec-kit commands this extension depends on commands: - "speckit.tasks" # Extension needs tasks command # Core scripts required scripts: - "check-prerequisites.sh" # What this extension provides (REQUIRED) provides: # Commands added to AI agent commands: - name: "speckit.jira.specstoissues" file: "commands/specstoissues.md" description: "Create Jira hierarchy from spec and tasks" aliases: ["speckit.specstoissues"] # Alternate names - name: "speckit.jira.discover-fields" file: "commands/discover-fields.md" description: "Discover Jira custom fields for configuration" - name: "speckit.jira.sync-status" file: "commands/sync-status.md" description: "Sync task completion status to Jira" # Configuration files config: - name: "jira-config.yml" template: "jira-config.template.yml" description: "Jira integration configuration" required: true # User must configure before use # Helper scripts scripts: - name: "parse-jira-config.sh" file: "scripts/parse-jira-config.sh" description: "Parse jira-config.yml to JSON" executable: true # Make executable on install # Extension configuration defaults (OPTIONAL) defaults: project: key: null # No default, user must configure hierarchy: issue_type: "subtask" update_behavior: mode: "update" sync_completion: true # Configuration schema for validation (OPTIONAL) config_schema: type: "object" required: ["project"] properties: project: type: "object" required: ["key"] properties: key: type: "string" pattern: "^[A-Z]{2,10}$" description: "Jira project key (e.g., MSATS)" # Integration hooks (OPTIONAL) hooks: # Hook fired after /speckit.tasks completes after_tasks: command: "speckit.jira.specstoissues" optional: true prompt: "Create Jira issues from tasks?" description: "Automatically create Jira hierarchy after task generation" # Hook fired after /speckit.implement completes after_implement: command: "speckit.jira.sync-status" optional: true prompt: "Sync completion status to Jira?" # Tags for discovery (OPTIONAL) tags: - "issue-tracking" - "jira" - "atlassian" - "project-management" # Changelog URL (OPTIONAL) changelog: "https://github.com/statsperform/spec-kit-jira/blob/main/CHANGELOG.md" # Support information (OPTIONAL) support: documentation: "https://github.com/statsperform/spec-kit-jira/blob/main/docs/" issues: "https://github.com/statsperform/spec-kit-jira/issues" discussions: "https://github.com/statsperform/spec-kit-jira/discussions" email: "support@statsperform.com" ``` ### Validation Rules 1. **MUST have** `schema_version`, `extension`, `requires`, `provides` 2. **MUST follow** semantic versioning for `version` 3. **MUST have** unique `id` (no conflicts with other extensions) 4. **MUST declare** all external tool dependencies 5. **SHOULD include** `config_schema` if extension uses config 6. **SHOULD include** `support` information 7. Command `file` paths **MUST be** relative to extension root 8. Hook `command` names **MUST match** a command in `provides.commands` --- ## Extension Lifecycle ### 1. Discovery ```bash specify extension search jira # Searches catalog for extensions matching "jira" ``` **Process:** 1. Fetch extension catalog from GitHub 2. Filter by search term (name, tags, description) 3. Display results with metadata ### 2. Installation ```bash specify extension add jira ``` **Process:** 1. **Resolve**: Look up extension in catalog 2. **Download**: Fetch extension package (ZIP from GitHub release) 3. **Validate**: Check manifest schema, compatibility 4. **Extract**: Unpack to `.specify/extensions/jira/` 5. **Configure**: Copy config templates 6. **Register**: Add commands to AI agent config 7. **Record**: Update `.specify/extensions/.registry` **Registry Format** (`.specify/extensions/.registry`): ```json { "schema_version": "1.0", "extensions": { "jira": { "version": "1.0.0", "installed_at": "2026-01-28T14:30:00Z", "source": "catalog", "manifest_hash": "sha256:abc123...", "enabled": true } } } ``` ### 3. Configuration ```bash # User edits extension config vim .specify/extensions/jira/jira-config.yml ``` **Config discovery order:** 1. Extension defaults (`extension.yml` → `defaults`) 2. Project config (`jira-config.yml`) 3. Local overrides (`jira-config.local.yml` - gitignored) 4. Environment variables (`SPECKIT_JIRA_*`) ### 4. Usage ```bash claude > /speckit.jira.specstoissues ``` **Command resolution:** 1. AI agent finds command in `.claude/commands/speckit.jira.specstoissues.md` 2. Command file references extension scripts/config 3. Extension executes with full context ### 5. Update ```bash specify extension update jira ``` **Process:** 1. Check catalog for newer version 2. Download new version 3. Validate compatibility 4. Back up current config 5. Extract new version (preserve config) 6. Re-register commands 7. Update registry ### 6. Removal ```bash specify extension remove jira ``` **Process:** 1. Confirm with user (show what will be removed) 2. Unregister commands from AI agent 3. Remove from `.specify/extensions/jira/` 4. Update registry 5. Optionally preserve config for reinstall --- ## Command Registration ### Per-Agent Registration Extensions provide **universal command format** (Markdown-based), and CLI converts to agent-specific format during registration. #### Universal Command Format **Location**: Extension's `commands/specstoissues.md` ```markdown --- # Universal metadata (parsed by all agents) description: "Create Jira hierarchy from spec and tasks" tools: - 'jira-mcp-server/epic_create' - 'jira-mcp-server/story_create' scripts: sh: ../../scripts/bash/check-prerequisites.sh --json ps: ../../scripts/powershell/check-prerequisites.ps1 -Json --- # Command implementation ## User Input $ARGUMENTS ## Steps 1. Load jira-config.yml 2. Parse spec.md and tasks.md 3. Create Jira items ``` #### Claude Code Registration **Output**: `.claude/commands/speckit.jira.specstoissues.md` ```markdown --- description: "Create Jira hierarchy from spec and tasks" tools: - 'jira-mcp-server/epic_create' - 'jira-mcp-server/story_create' scripts: sh: .specify/scripts/bash/check-prerequisites.sh --json ps: .specify/scripts/powershell/check-prerequisites.ps1 -Json --- # Command implementation (copied from extension) ## User Input $ARGUMENTS ## Steps 1. Load jira-config.yml from .specify/extensions/jira/ 2. Parse spec.md and tasks.md 3. Create Jira items ``` **Transformation:** - Copy frontmatter with adjustments - Rewrite script paths (relative to repo root) - Add extension context (config location) #### Gemini CLI Registration **Output**: `.gemini/commands/speckit.jira.specstoissues.toml` ```toml [command] name = "speckit.jira.specstoissues" description = "Create Jira hierarchy from spec and tasks" [command.tools] tools = [ "jira-mcp-server/epic_create", "jira-mcp-server/story_create" ] [command.script] sh = ".specify/scripts/bash/check-prerequisites.sh --json" ps = ".specify/scripts/powershell/check-prerequisites.ps1 -Json" [command.template] content = """ # Command implementation ## User Input {{args}} ## Steps 1. Load jira-config.yml from .specify/extensions/jira/ 2. Parse spec.md and tasks.md 3. Create Jira items """ ``` **Transformation:** - Convert Markdown frontmatter to TOML - Convert `$ARGUMENTS` to `{{args}}` - Rewrite script paths ### Registration Code **Location**: `src/specify_cli/extensions.py` ```python def register_extension_commands( project_path: Path, ai_assistant: str, manifest: dict ) -> None: """Register extension commands with AI agent.""" agent_config = AGENT_CONFIG.get(ai_assistant) if not agent_config: console.print(f"[yellow]Unknown agent: {ai_assistant}[/yellow]") return ext_id = manifest['extension']['id'] ext_dir = project_path / ".specify" / "extensions" / ext_id agent_commands_dir = project_path / agent_config['folder'].rstrip('/') / "commands" agent_commands_dir.mkdir(parents=True, exist_ok=True) for cmd_info in manifest['provides']['commands']: cmd_name = cmd_info['name'] source_file = ext_dir / cmd_info['file'] if not source_file.exists(): console.print(f"[red]Command file not found:[/red] {cmd_info['file']}") continue # Convert to agent-specific format if ai_assistant == "claude": dest_file = agent_commands_dir / f"{cmd_name}.md" convert_to_claude(source_file, dest_file, ext_dir) elif ai_assistant == "gemini": dest_file = agent_commands_dir / f"{cmd_name}.toml" convert_to_gemini(source_file, dest_file, ext_dir) elif ai_assistant == "copilot": dest_file = agent_commands_dir / f"{cmd_name}.md" convert_to_copilot(source_file, dest_file, ext_dir) # ... other agents console.print(f" ✓ Registered: {cmd_name}") def convert_to_claude( source: Path, dest: Path, ext_dir: Path ) -> None: """Convert universal command to Claude format.""" # Parse universal command content = source.read_text() frontmatter, body = parse_frontmatter(content) # Adjust script paths (relative to repo root) if 'scripts' in frontmatter: for key in frontmatter['scripts']: frontmatter['scripts'][key] = adjust_path_for_repo_root( frontmatter['scripts'][key] ) # Inject extension context body = inject_extension_context(body, ext_dir) # Write Claude command dest.write_text(render_frontmatter(frontmatter) + "\n" + body) ``` --- ## Configuration Management ### Configuration File Hierarchy ```yaml # .specify/extensions/jira/jira-config.yml (Project config) project: key: "MSATS" hierarchy: issue_type: "subtask" defaults: epic: labels: ["spec-driven", "typescript"] ``` ```yaml # .specify/extensions/jira/jira-config.local.yml (Local overrides - gitignored) project: key: "MYTEST" # Override for local testing ``` ```bash # Environment variables (highest precedence) export SPECKIT_JIRA_PROJECT_KEY="DEVTEST" ``` ### Config Loading Function **Location**: Extension command (e.g., `commands/specstoissues.md`) ````markdown ## Load Configuration 1. Run helper script to load and merge config: ```bash config_json=$(bash .specify/extensions/jira/scripts/parse-jira-config.sh) echo "$config_json" ``` 1. Parse JSON and use in subsequent steps ```` **Script**: `.specify/extensions/jira/scripts/parse-jira-config.sh` ```bash #!/usr/bin/env bash set -euo pipefail EXT_DIR=".specify/extensions/jira" CONFIG_FILE="$EXT_DIR/jira-config.yml" LOCAL_CONFIG="$EXT_DIR/jira-config.local.yml" # Start with defaults from extension.yml defaults=$(yq eval '.defaults' "$EXT_DIR/extension.yml" -o=json) # Merge project config if [ -f "$CONFIG_FILE" ]; then project_config=$(yq eval '.' "$CONFIG_FILE" -o=json) defaults=$(echo "$defaults $project_config" | jq -s '.[0] * .[1]') fi # Merge local config if [ -f "$LOCAL_CONFIG" ]; then local_config=$(yq eval '.' "$LOCAL_CONFIG" -o=json) defaults=$(echo "$defaults $local_config" | jq -s '.[0] * .[1]') fi # Apply environment variable overrides if [ -n "${SPECKIT_JIRA_PROJECT_KEY:-}" ]; then defaults=$(echo "$defaults" | jq ".project.key = \"$SPECKIT_JIRA_PROJECT_KEY\"") fi # Output merged config as JSON echo "$defaults" ``` ### Config Validation **In command file**: ````markdown ## Validate Configuration 1. Load config (from previous step) 2. Validate against schema from extension.yml: ```python import jsonschema schema = load_yaml(".specify/extensions/jira/extension.yml")['config_schema'] config = json.loads(config_json) try: jsonschema.validate(config, schema) except jsonschema.ValidationError as e: print(f"❌ Invalid jira-config.yml: {e.message}") print(f" Path: {'.'.join(str(p) for p in e.path)}") exit(1) ``` 1. Proceed with validated config ```` --- ## Hook System ### Hook Definition **In extension.yml:** ```yaml hooks: after_tasks: command: "speckit.jira.specstoissues" optional: true prompt: "Create Jira issues from tasks?" description: "Automatically create Jira hierarchy" condition: "config.project.key is set" ``` ### Hook Registration **During extension installation**, record hooks in project config: **File**: `.specify/extensions.yml` (project-level extension config) ```yaml # Extensions installed in this project installed: - jira - linear # Global extension settings settings: auto_execute_hooks: true # Prompt for optional hooks after commands # Hook configuration hooks: after_tasks: - extension: jira command: speckit.jira.specstoissues enabled: true optional: true prompt: "Create Jira issues from tasks?" after_implement: - extension: jira command: speckit.jira.sync-status enabled: true optional: true prompt: "Sync completion status to Jira?" ``` ### Hook Execution **In core command** (e.g., `templates/commands/tasks.md`): Add at end of command: ````markdown ## Extension Hooks After task generation completes, check for registered hooks: ```bash # Check if extensions.yml exists and has after_tasks hooks if [ -f ".specify/extensions.yml" ]; then # Parse hooks for after_tasks hooks=$(yq eval '.hooks.after_tasks[] | select(.enabled == true)' .specify/extensions.yml -o=json) if [ -n "$hooks" ]; then echo "" echo "📦 Extension hooks available:" # Iterate hooks echo "$hooks" | jq -c '.' | while read -r hook; do extension=$(echo "$hook" | jq -r '.extension') command=$(echo "$hook" | jq -r '.command') optional=$(echo "$hook" | jq -r '.optional') prompt_text=$(echo "$hook" | jq -r '.prompt') if [ "$optional" = "true" ]; then # Prompt user echo "" read -p "$prompt_text (y/n) " -n 1 -r echo if [[ $REPLY =~ ^[Yy]$ ]]; then echo "▶ Executing: $command" # Let AI agent execute the command # (AI agent will see this and execute) echo "EXECUTE_COMMAND: $command" fi else # Auto-execute mandatory hooks echo "▶ Executing: $command (required)" echo "EXECUTE_COMMAND: $command" fi done fi fi ``` ```` **AI Agent Handling:** The AI agent sees `EXECUTE_COMMAND: speckit.jira.specstoissues` in output and automatically invokes that command. **Alternative**: Direct call in agent context (if agent supports it): ```python # In AI agent's command execution engine def execute_command_with_hooks(command_name: str, args: str): # Execute main command result = execute_command(command_name, args) # Check for hooks hooks = load_hooks_for_phase(f"after_{command_name}") for hook in hooks: if hook.optional: if confirm(hook.prompt): execute_command(hook.command, args) else: execute_command(hook.command, args) return result ``` ### Hook Conditions Extensions can specify **conditions** for hooks: ```yaml hooks: after_tasks: command: "speckit.jira.specstoissues" optional: true condition: "config.project.key is set and config.enabled == true" ``` **Condition evaluation** (in hook executor): ```python def should_execute_hook(hook: dict, config: dict) -> bool: """Evaluate hook condition.""" condition = hook.get('condition') if not condition: return True # No condition = always eligible # Simple expression evaluator # "config.project.key is set" → check if config['project']['key'] exists # "config.enabled == true" → check if config['enabled'] is True return eval_condition(condition, config) ``` --- ## Extension Discovery & Catalog ### Dual Catalog System Spec Kit uses two catalog files with different purposes: #### User Catalog (`catalog.json`) **URL**: `https://raw.githubusercontent.com/github/spec-kit/main/extensions/catalog.json` - **Purpose**: Organization's curated catalog of approved extensions - **Default State**: Empty by design - users populate with extensions they trust - **Usage**: Primary catalog (priority 1, `install_allowed: true`) in the default stack - **Control**: Organizations maintain their own fork/version for their teams #### Community Reference Catalog (`catalog.community.json`) **URL**: `https://raw.githubusercontent.com/github/spec-kit/main/extensions/catalog.community.json` - **Purpose**: Reference catalog of available community-contributed extensions - **Verification**: Community extensions may have `verified: false` initially - **Status**: Active - open for community contributions - **Submission**: Via Pull Request following the Extension Publishing Guide - **Usage**: Secondary catalog (priority 2, `install_allowed: false`) in the default stack — discovery only **How It Works (default stack):** 1. **Discover**: `specify extension search` searches both catalogs — community extensions appear automatically 2. **Review**: Evaluate community extensions for security, quality, and organizational fit 3. **Curate**: Copy approved entries from community catalog to your `catalog.json`, or add to `.specify/extension-catalogs.yml` with `install_allowed: true` 4. **Install**: Use `specify extension add ` — only allowed from `install_allowed: true` catalogs This approach gives organizations full control over which extensions can be installed while still providing community discoverability out of the box. ### Catalog Format **Format** (same for both catalogs): ```json { "schema_version": "1.0", "updated_at": "2026-01-28T14:30:00Z", "extensions": { "jira": { "name": "Jira Integration", "id": "jira", "description": "Create Jira Epics, Stories, and Issues from spec-kit artifacts", "author": "Stats Perform", "version": "1.0.0", "download_url": "https://github.com/statsperform/spec-kit-jira/releases/download/v1.0.0/spec-kit-jira-1.0.0.zip", "repository": "https://github.com/statsperform/spec-kit-jira", "homepage": "https://github.com/statsperform/spec-kit-jira/blob/main/README.md", "documentation": "https://github.com/statsperform/spec-kit-jira/blob/main/docs/", "changelog": "https://github.com/statsperform/spec-kit-jira/blob/main/CHANGELOG.md", "license": "MIT", "requires": { "speckit_version": ">=0.1.0,<2.0.0", "tools": [ { "name": "jira-mcp-server", "version": ">=1.0.0" } ] }, "tags": ["issue-tracking", "jira", "atlassian", "project-management"], "verified": true, "downloads": 1250, "stars": 45 }, "linear": { "name": "Linear Integration", "id": "linear", "description": "Sync spec-kit tasks with Linear issues", "author": "Community", "version": "0.9.0", "download_url": "https://github.com/example/spec-kit-linear/releases/download/v0.9.0/spec-kit-linear-0.9.0.zip", "repository": "https://github.com/example/spec-kit-linear", "requires": { "speckit_version": ">=0.1.0" }, "tags": ["issue-tracking", "linear"], "verified": false } } } ``` ### Catalog Discovery Commands ```bash # List all available extensions specify extension search # Search by keyword specify extension search jira # Search by tag specify extension search --tag issue-tracking # Show extension details specify extension info jira ``` ### Custom Catalogs Spec Kit supports a **catalog stack** — an ordered list of catalogs that the CLI merges and searches across. This allows organizations to maintain their own org-approved extensions alongside an internal catalog and community discovery, all at once. #### Catalog Stack Resolution The active catalog stack is resolved in this order (first match wins): 1. **`SPECKIT_CATALOG_URL` environment variable** — single catalog replacing all defaults (backward compat) 2. **Project-level `.specify/extension-catalogs.yml`** — full control for the project 3. **User-level `~/.specify/extension-catalogs.yml`** — personal defaults 4. **Built-in default stack** — `catalog.json` (install_allowed: true) + `catalog.community.json` (install_allowed: false) #### Default Built-in Stack When no config file exists, the CLI uses: | Priority | Catalog | install_allowed | Purpose | |----------|---------|-----------------|---------| | 1 | `catalog.json` (default) | `true` | Curated extensions available for installation | | 2 | `catalog.community.json` (community) | `false` | Discovery only — browse but not install | This means `specify extension search` surfaces community extensions out of the box, while `specify extension add` is still restricted to entries from catalogs with `install_allowed: true`. #### `.specify/extension-catalogs.yml` Config File ```yaml catalogs: - name: "default" url: "https://raw.githubusercontent.com/github/spec-kit/main/extensions/catalog.json" priority: 1 # Highest — only approved entries can be installed install_allowed: true description: "Built-in catalog of installable extensions" - name: "internal" url: "https://internal.company.com/spec-kit/catalog.json" priority: 2 install_allowed: true description: "Internal company extensions" - name: "community" url: "https://raw.githubusercontent.com/github/spec-kit/main/extensions/catalog.community.json" priority: 3 # Lowest — discovery only, not installable install_allowed: false description: "Community-contributed extensions (discovery only)" ``` A user-level equivalent lives at `~/.specify/extension-catalogs.yml`. When a project-level config is present with one or more catalog entries, it takes full control and the built-in defaults are not applied. An empty `catalogs: []` list is treated the same as no config file, falling back to defaults. #### Catalog CLI Commands ```bash # List active catalogs with name, URL, priority, and install_allowed specify extension catalog list # Add a catalog (project-scoped) specify extension catalog add --name "internal" --install-allowed \ https://internal.company.com/spec-kit/catalog.json # Add a discovery-only catalog specify extension catalog add --name "community" \ https://raw.githubusercontent.com/github/spec-kit/main/extensions/catalog.community.json # Remove a catalog specify extension catalog remove internal # Show which catalog an extension came from specify extension info jira # → Source catalog: default ``` #### Merge Conflict Resolution When the same extension `id` appears in multiple catalogs, the higher-priority (lower priority number) catalog wins. Extensions from lower-priority catalogs with the same `id` are ignored. #### `install_allowed: false` Behavior Extensions from discovery-only catalogs are shown in `specify extension search` results but cannot be installed directly: ``` ⚠ 'linear' is available in the 'community' catalog but installation is not allowed from that catalog. To enable installation, add 'linear' to an approved catalog (install_allowed: true) in .specify/extension-catalogs.yml. ``` #### `SPECKIT_CATALOG_URL` (Backward Compatibility) The `SPECKIT_CATALOG_URL` environment variable still works — it is treated as a single `install_allowed: true` catalog, **replacing both defaults** for full backward compatibility: ```bash # Point to your organization's catalog export SPECKIT_CATALOG_URL="https://internal.company.com/spec-kit/catalog.json" # All extension commands now use your custom catalog specify extension search # Uses custom catalog specify extension add jira # Installs from custom catalog ``` **Requirements:** - URL must use HTTPS (HTTP only allowed for localhost testing) - Catalog must follow the standard catalog.json schema - Must be publicly accessible or accessible within your network **Example for testing:** ```bash # Test with localhost during development export SPECKIT_CATALOG_URL="http://localhost:8000/catalog.json" specify extension search ``` --- ## CLI Commands ### `specify extension` Subcommands #### `specify extension list` List installed extensions in current project. ```bash $ specify extension list Installed Extensions: ✓ jira (v1.0.0) - Jira Integration Commands: 3 | Hooks: 2 | Status: Enabled ✓ linear (v0.9.0) - Linear Integration Commands: 1 | Hooks: 1 | Status: Enabled ``` **Options:** - `--available`: Show available (not installed) extensions from catalog - `--all`: Show both installed and available #### `specify extension search [QUERY]` Search extension catalog. ```bash $ specify extension search jira Found 1 extension: ┌─────────────────────────────────────────────────────────┐ │ jira (v1.0.0) ✓ Verified │ │ Jira Integration │ │ │ │ Create Jira Epics, Stories, and Issues from spec-kit │ │ artifacts │ │ │ │ Author: Stats Perform │ │ Tags: issue-tracking, jira, atlassian │ │ Downloads: 1,250 │ │ │ │ Repository: github.com/statsperform/spec-kit-jira │ │ Documentation: github.com/.../docs │ └─────────────────────────────────────────────────────────┘ Install: specify extension add jira ``` **Options:** - `--tag TAG`: Filter by tag - `--author AUTHOR`: Filter by author - `--verified`: Show only verified extensions #### `specify extension info NAME` Show detailed information about an extension. ```bash $ specify extension info jira Jira Integration (jira) v1.0.0 Description: Create Jira Epics, Stories, and Issues from spec-kit artifacts Author: Stats Perform License: MIT Repository: https://github.com/statsperform/spec-kit-jira Documentation: https://github.com/statsperform/spec-kit-jira/blob/main/docs/ Requirements: • Spec Kit: >=0.1.0,<2.0.0 • Tools: jira-mcp-server (>=1.0.0) Provides: Commands: • speckit.jira.specstoissues - Create Jira hierarchy from spec and tasks • speckit.jira.discover-fields - Discover Jira custom fields • speckit.jira.sync-status - Sync task completion status Hooks: • after_tasks - Prompt to create Jira issues • after_implement - Prompt to sync status Tags: issue-tracking, jira, atlassian, project-management Downloads: 1,250 | Stars: 45 | Verified: ✓ Install: specify extension add jira ``` #### `specify extension add NAME` Install an extension. ```bash $ specify extension add jira Installing extension: Jira Integration ✓ Downloaded spec-kit-jira-1.0.0.zip (245 KB) ✓ Validated manifest ✓ Checked compatibility (spec-kit 0.1.0 ≥ 0.1.0) ✓ Extracted to .specify/extensions/jira/ ✓ Registered 3 commands with claude ✓ Installed config template (jira-config.yml) ⚠ Configuration required: Edit .specify/extensions/jira/jira-config.yml to set your Jira project key Extension installed successfully! Next steps: 1. Configure: vim .specify/extensions/jira/jira-config.yml 2. Discover fields: /speckit.jira.discover-fields 3. Use commands: /speckit.jira.specstoissues ``` **Options:** - `--from URL`: Install from custom URL or Git repo - `--version VERSION`: Install specific version - `--dev PATH`: Install from local path (development mode) - `--no-register`: Skip command registration (manual setup) #### `specify extension remove NAME` Uninstall an extension. ```bash $ specify extension remove jira ⚠ This will remove: • 3 commands from AI agent • Extension directory: .specify/extensions/jira/ • Config file: jira-config.yml (will be backed up) Continue? (yes/no): yes ✓ Unregistered commands ✓ Backed up config to .specify/extensions/.backup/jira-config.yml ✓ Removed extension directory ✓ Updated registry Extension removed successfully. To reinstall: specify extension add jira ``` **Options:** - `--keep-config`: Don't remove config file - `--force`: Skip confirmation #### `specify extension update [NAME]` Update extension(s) to latest version. ```bash $ specify extension update jira Checking for updates... jira: 1.0.0 → 1.1.0 available Changes in v1.1.0: • Added support for custom workflows • Fixed issue with parallel tasks • Improved error messages Update? (yes/no): yes ✓ Downloaded spec-kit-jira-1.1.0.zip ✓ Validated manifest ✓ Backed up current version ✓ Extracted new version ✓ Preserved config file ✓ Re-registered commands Extension updated successfully! Changelog: https://github.com/statsperform/spec-kit-jira/blob/main/CHANGELOG.md#v110 ``` **Options:** - `--all`: Update all extensions - `--check`: Check for updates without installing - `--force`: Force update even if already latest #### `specify extension enable/disable NAME` Enable or disable an extension without removing it. ```bash $ specify extension disable jira ✓ Disabled extension: jira • Commands unregistered (but files preserved) • Hooks will not execute To re-enable: specify extension enable jira ``` --- ## Compatibility & Versioning ### Semantic Versioning Extensions follow [SemVer 2.0.0](https://semver.org/): - **MAJOR**: Breaking changes (command API changes, config schema changes) - **MINOR**: New features (new commands, new config options) - **PATCH**: Bug fixes (no API changes) ### Compatibility Checks **At installation:** ```python def check_compatibility(extension_manifest: dict) -> bool: """Check if extension is compatible with current environment.""" requires = extension_manifest['requires'] # 1. Check spec-kit version current_speckit = get_speckit_version() # e.g., "0.1.5" required_speckit = requires['speckit_version'] # e.g., ">=0.1.0,<2.0.0" if not version_satisfies(current_speckit, required_speckit): raise IncompatibleVersionError( f"Extension requires spec-kit {required_speckit}, " f"but {current_speckit} is installed. " f"Upgrade spec-kit with: uv tool install specify-cli --force" ) # 2. Check required tools for tool in requires.get('tools', []): tool_name = tool['name'] tool_version = tool.get('version') if tool.get('required', True): if not check_tool(tool_name): raise MissingToolError( f"Extension requires tool: {tool_name}\n" f"Install from: {tool.get('install_url', 'N/A')}" ) if tool_version: installed = get_tool_version(tool_name, tool.get('check_command')) if not version_satisfies(installed, tool_version): raise IncompatibleToolVersionError( f"Extension requires {tool_name} {tool_version}, " f"but {installed} is installed" ) # 3. Check required commands for cmd in requires.get('commands', []): if not command_exists(cmd): raise MissingCommandError( f"Extension requires core command: {cmd}\n" f"Update spec-kit to latest version" ) return True ``` ### Deprecation Policy **Extension manifest can mark features as deprecated:** ```yaml provides: commands: - name: "speckit.jira.old-command" file: "commands/old-command.md" deprecated: true deprecated_message: "Use speckit.jira.new-command instead" removal_version: "2.0.0" ``` **At runtime, show warning:** ```text ⚠️ Warning: /speckit.jira.old-command is deprecated Use /speckit.jira.new-command instead This command will be removed in v2.0.0 ``` --- ## Security Considerations ### Trust Model Extensions run with **same privileges as AI agent**: - Can execute shell commands - Can read/write files in project - Can make network requests **Trust boundary**: User must trust extension author. ### Verification **Verified Extensions** (in catalog): - Published by known organizations (GitHub, Stats Perform, etc.) - Code reviewed by spec-kit maintainers - Marked with ✓ badge in catalog **Community Extensions**: - Not verified, use at own risk - Show warning during installation: ```text ⚠️ This extension is not verified. Review code before installing: https://github.com/... Continue? (yes/no): ``` ### Sandboxing (Future) **Phase 2** (not in initial release): - Extensions declare required permissions in manifest - CLI enforces permission boundaries - Example permissions: `filesystem:read`, `network:external`, `env:read` ```yaml # Future extension.yml permissions: - "filesystem:read:.specify/extensions/jira/" # Can only read own config - "filesystem:write:.specify/memory/" # Can write to memory - "network:external:*.atlassian.net" # Can call Jira API - "env:read:SPECKIT_JIRA_*" # Can read own env vars ``` ### Package Integrity **Future**: Sign extension packages with GPG/Sigstore ```yaml # catalog.json "jira": { "download_url": "...", "checksum": "sha256:abc123...", "signature": "https://github.com/.../spec-kit-jira-1.0.0.sig", "signing_key": "https://github.com/statsperform.gpg" } ``` CLI verifies signature before extraction. --- ## Migration Strategy ### Backward Compatibility **Goal**: Existing spec-kit projects work without changes. **Strategy**: 1. **Core commands unchanged**: `/speckit.tasks`, `/speckit.implement`, etc. remain in core 2. **Optional extensions**: Users opt-in to extensions 3. **Gradual migration**: Existing `taskstoissues` stays in core, Jira extension is alternative 4. **Deprecation timeline**: - **v0.2.0**: Introduce extension system, keep core `taskstoissues` - **v0.3.0**: Mark core `taskstoissues` as "legacy" (still works) - **v1.0.0**: Consider removing core `taskstoissues` in favor of extension ### Migration Path for Users **Scenario 1**: User has no `taskstoissues` usage - No migration needed, extensions are opt-in **Scenario 2**: User uses core `taskstoissues` (GitHub Issues) - Works as before - Optional: Migrate to `github-projects` extension for more features **Scenario 3**: User wants Jira (new requirement) - `specify extension add jira` - Configure and use **Scenario 4**: User has custom scripts calling `taskstoissues` - Scripts still work (core command preserved) - Migration guide shows how to call extension commands instead ### Extension Migration Guide **For extension authors** (if core command becomes extension): ```bash # Old (core command) /speckit.taskstoissues # New (extension command) specify extension add github-projects /speckit.github.taskstoissues ``` **Compatibility shim** (if needed): ```yaml # extension.yml provides: commands: - name: "speckit.github.taskstoissues" file: "commands/taskstoissues.md" aliases: ["speckit.taskstoissues"] # Backward compatibility ``` AI agent registers both names, so old scripts work. --- ## Implementation Phases ### Phase 1: Core Extension System ✅ COMPLETED **Goal**: Basic extension infrastructure **Deliverables**: - [x] Extension manifest schema (`extension.yml`) - [x] Extension directory structure - [x] CLI commands: - [x] `specify extension list` - [x] `specify extension add` (from URL and local `--dev`) - [x] `specify extension remove` - [x] Extension registry (`.specify/extensions/.registry`) - [x] Command registration (Claude and 15+ other agents) - [x] Basic validation (manifest schema, compatibility) - [x] Documentation (extension development guide) **Testing**: - [x] Unit tests for manifest parsing - [x] Integration test: Install dummy extension - [x] Integration test: Register commands with Claude ### Phase 2: Jira Extension ✅ COMPLETED **Goal**: First production extension **Deliverables**: - [x] Create `spec-kit-jira` repository - [x] Port Jira functionality to extension - [x] Create `jira-config.yml` template - [x] Commands: - [x] `specstoissues.md` - [x] `discover-fields.md` - [x] `sync-status.md` - [x] Helper scripts - [x] Documentation (README, configuration guide, examples) - [x] Release v3.0.0 **Testing**: - [x] Test on `eng-msa-ts` project - [x] Verify spec→Epic, phase→Story, task→Issue mapping - [x] Test configuration loading and validation - [x] Test custom field application ### Phase 3: Extension Catalog ✅ COMPLETED **Goal**: Discovery and distribution **Deliverables**: - [x] Central catalog (`extensions/catalog.json` in spec-kit repo) - [x] Community catalog (`extensions/catalog.community.json`) - [x] Catalog fetch and parsing with multi-catalog support - [x] CLI commands: - [x] `specify extension search` - [x] `specify extension info` - [x] `specify extension catalog list` - [x] `specify extension catalog add` - [x] `specify extension catalog remove` - [x] Documentation (how to publish extensions) **Testing**: - [x] Test catalog fetch - [x] Test extension search/filtering - [x] Test catalog caching - [x] Test multi-catalog merge with priority ### Phase 4: Advanced Features ✅ COMPLETED **Goal**: Hooks, updates, multi-agent support **Deliverables**: - [x] Hook system (`hooks` in extension.yml) - [x] Hook registration and execution - [x] Project extensions config (`.specify/extensions.yml`) - [x] CLI commands: - [x] `specify extension update` (with atomic backup/restore) - [x] `specify extension enable/disable` - [x] Command registration for multiple agents (15+ agents including Claude, Copilot, Gemini, Cursor, etc.) - [x] Extension update notifications (version comparison) - [x] Configuration layer resolution (project, local, env) **Additional features implemented beyond original RFC**: - [x] **Display name resolution**: All commands accept extension display names in addition to IDs - [x] **Ambiguous name handling**: User-friendly tables when multiple extensions match a name - [x] **Atomic update with rollback**: Full backup of extension dir, commands, hooks, and registry with automatic rollback on failure - [x] **Pre-install ID validation**: Validates extension ID from ZIP before installing (security) - [x] **Enabled state preservation**: Disabled extensions stay disabled after update - [x] **Registry update/restore methods**: Clean API for enable/disable and rollback operations - [x] **Catalog error fallback**: `extension info` falls back to local info when catalog unavailable - [x] **`_install_allowed` flag**: Discovery-only catalogs can't be used for installation - [x] **Cache invalidation**: Cache invalidated when `SPECKIT_CATALOG_URL` changes **Testing**: - [x] Test hooks in core commands - [x] Test extension updates (preserve config) - [x] Test multi-agent registration - [x] Test atomic rollback on update failure - [x] Test enabled state preservation - [x] Test display name resolution ### Phase 5: Polish & Documentation ✅ COMPLETED **Goal**: Production ready **Deliverables**: - [x] Comprehensive documentation: - [x] User guide (EXTENSION-USER-GUIDE.md) - [x] Extension development guide (EXTENSION-DEV-GUIDE.md) - [x] Extension API reference (EXTENSION-API-REFERENCE.md) - [x] Error messages and validation improvements - [x] CLI help text updates **Testing**: - [x] End-to-end testing on multiple projects - [x] 163 unit tests passing --- ## Resolved Questions The following questions from the original RFC have been resolved during implementation: ### 1. Extension Namespace ✅ RESOLVED **Question**: Should extension commands use namespace prefix? **Decision**: **Option C** - Both prefixed and aliases are supported. Commands use `speckit.{extension}.{command}` as canonical name, with optional aliases defined in manifest. **Implementation**: The `aliases` field in `extension.yml` allows extensions to register additional command names. --- ### 2. Config File Location ✅ RESOLVED **Question**: Where should extension configs live? **Decision**: **Option A** - Extension directory (`.specify/extensions/{ext-id}/{ext-id}-config.yml`). This keeps extensions self-contained and easier to manage. **Implementation**: Each extension has its own config file within its directory, with layered resolution (defaults → project → local → env vars). --- ### 3. Command File Format ✅ RESOLVED **Question**: Should extensions use universal format or agent-specific? **Decision**: **Option A** - Universal Markdown format. Extensions write commands once, CLI converts to agent-specific format during registration. **Implementation**: `CommandRegistrar` class handles conversion to 15+ agent formats (Claude, Copilot, Gemini, Cursor, etc.). --- ### 4. Hook Execution Model ✅ RESOLVED **Question**: How should hooks execute? **Decision**: **Option A** - Hooks are registered in `.specify/extensions.yml` and executed by the AI agent when it sees the hook trigger. Hook state (enabled/disabled) is managed per-extension. **Implementation**: `HookExecutor` class manages hook registration and state in `extensions.yml`. --- ### 5. Extension Distribution ✅ RESOLVED **Question**: How should extensions be packaged? **Decision**: **Option A** - ZIP archives downloaded from GitHub releases (via catalog `download_url`). Local development uses `--dev` flag with directory path. **Implementation**: `ExtensionManager.install_from_zip()` handles ZIP extraction and validation. --- ### 6. Multi-Version Support ✅ RESOLVED **Question**: Can multiple versions of same extension coexist? **Decision**: **Option A** - Single version only. Updates replace the existing version with atomic rollback on failure. **Implementation**: `extension update` performs atomic backup/restore to ensure safe updates. --- ## Open Questions (Remaining) ### 1. Sandboxing / Permissions (Future) **Question**: Should extensions declare required permissions? **Options**: - A) No sandboxing (current): Extensions run with same privileges as AI agent - B) Permission declarations: Extensions declare `filesystem:read`, `network:external`, etc. - C) Opt-in sandboxing: Organizations can enable permission enforcement **Status**: Deferred to future version. Currently using trust-based model where users trust extension authors. --- ### 2. Package Signatures (Future) **Question**: Should extensions be cryptographically signed? **Options**: - A) No signatures (current): Trust based on catalog source - B) GPG/Sigstore signatures: Verify package integrity - C) Catalog-level verification: Catalog maintainers verify packages **Status**: Deferred to future version. `checksum` field is available in catalog schema but not enforced. --- ## Appendices ### Appendix A: Example Extension Structure **Complete structure of `spec-kit-jira` extension:** ```text spec-kit-jira/ ├── README.md # Overview, features, installation ├── LICENSE # MIT license ├── CHANGELOG.md # Version history ├── .gitignore # Ignore local configs │ ├── extension.yml # Extension manifest (required) ├── jira-config.template.yml # Config template │ ├── commands/ # Command files │ ├── specstoissues.md # Main command │ ├── discover-fields.md # Helper: Discover custom fields │ └── sync-status.md # Helper: Sync completion status │ ├── scripts/ # Helper scripts │ ├── parse-jira-config.sh # Config loader (bash) │ ├── parse-jira-config.ps1 # Config loader (PowerShell) │ └── validate-jira-connection.sh # Connection test │ ├── docs/ # Documentation │ ├── installation.md # Installation guide │ ├── configuration.md # Configuration reference │ ├── usage.md # Usage examples │ ├── troubleshooting.md # Common issues │ └── examples/ │ ├── eng-msa-ts-config.yml # Real-world config example │ └── simple-project.yml # Minimal config example │ ├── tests/ # Tests (optional) │ ├── test-extension.sh # Extension validation │ └── test-commands.sh # Command execution tests │ └── .github/ # GitHub integration └── workflows/ └── release.yml # Automated releases ``` ### Appendix B: Extension Development Guide (Outline) **Documentation for creating new extensions:** 1. **Getting Started** - Prerequisites (tools needed) - Extension template (cookiecutter) - Directory structure 2. **Extension Manifest** - Schema reference - Required vs optional fields - Versioning guidelines 3. **Command Development** - Universal command format - Frontmatter specification - Template variables - Script references 4. **Configuration** - Config file structure - Schema validation - Layered config resolution - Environment variable overrides 5. **Hooks** - Available hook points - Hook registration - Conditional execution - Best practices 6. **Testing** - Local development setup - Testing with `--dev` flag - Validation checklist - Integration testing 7. **Publishing** - Packaging (ZIP format) - GitHub releases - Catalog submission - Versioning strategy 8. **Examples** - Minimal extension - Extension with hooks - Extension with configuration - Extension with multiple commands ### Appendix C: Compatibility Matrix **Planned support matrix:** | Extension Feature | Spec Kit Version | AI Agent Support | |-------------------|------------------|------------------| | Basic commands | 0.2.0+ | Claude, Gemini, Copilot | | Hooks (after_tasks) | 0.3.0+ | Claude, Gemini | | Config validation | 0.2.0+ | All | | Multiple catalogs | 0.4.0+ | All | | Permissions (sandboxing) | 1.0.0+ | TBD | ### Appendix D: Extension Catalog Schema **Full schema for `catalog.json`:** ```json { "$schema": "http://json-schema.org/draft-07/schema#", "type": "object", "required": ["schema_version", "updated_at", "extensions"], "properties": { "schema_version": { "type": "string", "pattern": "^\\d+\\.\\d+$" }, "updated_at": { "type": "string", "format": "date-time" }, "extensions": { "type": "object", "patternProperties": { "^[a-z0-9-]+$": { "type": "object", "required": ["name", "id", "version", "download_url", "repository"], "properties": { "name": { "type": "string" }, "id": { "type": "string", "pattern": "^[a-z0-9-]+$" }, "description": { "type": "string" }, "author": { "type": "string" }, "version": { "type": "string", "pattern": "^\\d+\\.\\d+\\.\\d+$" }, "download_url": { "type": "string", "format": "uri" }, "repository": { "type": "string", "format": "uri" }, "homepage": { "type": "string", "format": "uri" }, "documentation": { "type": "string", "format": "uri" }, "changelog": { "type": "string", "format": "uri" }, "license": { "type": "string" }, "requires": { "type": "object", "properties": { "speckit_version": { "type": "string" }, "tools": { "type": "array", "items": { "type": "object", "required": ["name"], "properties": { "name": { "type": "string" }, "version": { "type": "string" } } } } } }, "tags": { "type": "array", "items": { "type": "string" } }, "verified": { "type": "boolean" }, "downloads": { "type": "integer" }, "stars": { "type": "integer" }, "checksum": { "type": "string" } } } } } } } ``` --- ## Summary & Next Steps This RFC proposes a comprehensive extension system for Spec Kit that: 1. **Keeps core lean** while enabling unlimited integrations 2. **Supports multiple agents** (Claude, Gemini, Copilot, etc.) 3. **Provides clear extension API** for community contributions 4. **Enables independent versioning** of extensions and core 5. **Includes safety mechanisms** (validation, compatibility checks) ### Immediate Next Steps 1. **Review this RFC** with stakeholders 2. **Gather feedback** on open questions 3. **Refine design** based on feedback 4. **Proceed to Phase A**: Implement core extension system 5. **Then Phase B**: Build Jira extension as proof-of-concept --- ## Questions for Discussion 1. Does the extension architecture meet your needs for Jira integration? 2. Are there additional hook points we should consider? 3. Should we support extension dependencies (extension A requires extension B)? 4. How should we handle extension deprecation/removal from catalog? 5. What level of sandboxing/permissions do we need in v1.0?