From c427ea4d8f845513b2157809e72e3dd80663dc6c Mon Sep 17 00:00:00 2001 From: Manfred Riem <15701806+mnriem@users.noreply.github.com> Date: Thu, 15 Oct 2026 12:19:25 -0500 Subject: [PATCH 1/5] docs: document extension architecture Assisted-by: GitHub Copilot (model: GPT-5.6 Sol, autonomous) Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: a4e66b41-773d-4a2c-a0e9-0f3e56ef4132 --- design/cli.md | 6 +- design/extension.md | 393 ++++++ extensions/RFC-EXTENSION-SYSTEM.md | 1962 ---------------------------- 3 files changed, 396 insertions(+), 1965 deletions(-) create mode 100644 design/extension.md delete mode 100644 extensions/RFC-EXTENSION-SYSTEM.md diff --git a/design/cli.md b/design/cli.md index 1cba8f9285..e4df66f282 100644 --- a/design/cli.md +++ b/design/cli.md @@ -10,9 +10,9 @@ operation defined by CLI surface; it does not redefine semantic validation, orchestration, results, warnings, errors, or side effects. -The extension hierarchy supplies the reference command-group shape used in -this document. Do not create extra modules merely to make a small command -conform visually. +The [extension architecture](extension.md) supplies the reference command-group +shape used in this document. Do not create extra modules merely to make a +small command conform visually. ## Design goals diff --git a/design/extension.md b/design/extension.md new file mode 100644 index 0000000000..9b4a629549 --- /dev/null +++ b/design/extension.md @@ -0,0 +1,393 @@ +# Extension Architecture + +This document defines the implemented architecture of Spec Kit extensions. +Extensions are project-local content packages that contribute commands, skills, +templates, scripts, hooks, runtime events, and configuration without adding +Python code to the extension runtime. + +The implementation and executable tests are the source of truth for this +document. The command adapter conventions in +[Specify CLI Command Architecture](cli.md), the multi-adapter boundary in +[Shared Command Application Architecture](shared.md), MCP exposure in +[Specify MCP Command Architecture](mcp.md), and agent output formats in +[Agent Integration Design](integration.md) remain authoritative for those +respective concerns. + +## Responsibilities and boundaries + +The extension domain under `src/specify_cli/extensions/` owns: + +- parsing and validating `extension.yml`; +- compatibility and command-namespace validation; +- project-local installation, enablement, disablement, update, and removal; +- the installed-extension registry and catalog stack; +- registration and cleanup of extension commands and generated skills; +- layered extension configuration; +- declarative hook registration; and +- extension lifecycle CLI commands. + +Adjacent domains own behavior that extensions participate in: + +- integrations render extension commands or skills into the active agent's + native layout and refresh those outputs when the active integration changes; +- presets and artifacts resolve extension-provided commands, templates, and + scripts as layers in the composed artifact stack; +- events validate and project extension-declared runtime events into supported + agent-native event configuration; +- bundles install and remove extension components by calling the extension + manager rather than reimplementing extension lifecycle behavior; and +- shared download, archive, authentication, file-change, and path-safety + helpers provide common infrastructure. + +An extension is declarative content. It does not register Python plugins, +replace CLI handlers, or import executable package code. External executable +agent adapters are integrations and follow +[the separate integration trust model](integration.md#external-adapter-package-contract). + +## Manifest contract + +Every extension root contains `extension.yml` with schema version `1.0`. The +required top-level mappings are: + +```yaml +schema_version: "1.0" +extension: + id: example + name: Example + version: "1.0.0" + description: Example extension +requires: + speckit_version: ">=1.0" +provides: + commands: + - name: speckit.example.run + file: commands/speckit.example.run.md +``` + +`extension.id` is a lowercase alphanumeric-and-hyphen slug. The version is +parsed as a PEP 440 version, and `requires.speckit_version` is enforced against +the installed CLI. Optional extension metadata includes authoring, +categorization, effect, repository, license, tags, and support information. +Most optional metadata is descriptive; it does not grant trust or filesystem +access. + +An extension must provide at least one command, hook, event, template, or +script. Supported behavioral declarations are: + +- `provides.commands`: canonical `speckit..` names, + source files, descriptions, and optional aliases; +- `provides.templates`: named template files; +- `provides.scripts`: named script entries with optional runtime declarations + from `bash`, `powershell`, and `python`; +- `provides.config`: templates scaffolded into the installed extension + directory without overwriting existing files; +- `hooks`: one or more command bindings for a Spec Kit workflow event; and +- `events`: validated agent-runtime event bindings handled by the events + domain. + +Manifest paths are relative to the extension root. Absolute paths, traversal, +and unsafe command or alias output names are rejected. Templates and scripts +always have `replace` semantics when they participate in artifact resolution; +extensions cannot author preset composition strategies. + +Primary command names must use the extension's namespace. Installation rejects +names and aliases that collide with core commands or another installed +extension after normalizing them to the agent output form. A small set of +legacy two-segment command names is normalized to the canonical form with a +warning; hook and event command references are rewritten consistently. + +## Project state and filesystem layout + +Extension state is project-local: + +```text +.specify/ +├── extension-catalogs.yml # optional project catalog stack +├── extensions.yml # installed IDs and declarative hook bindings +└── extensions/ + ├── .registry # installed lifecycle and output ownership metadata + ├── .cache/ # catalog metadata and downloaded archives + ├── .backup/ # removed config and update rollback material + ├── .rescue-staging-/ # temporary durable kept-config rescue + └── / + ├── extension.yml + ├── commands/ + ├── templates/ + ├── scripts/ + └── *-config.yml +``` + +The JSON `.registry` is the lifecycle source of truth for installed +extensions. Each valid entry records version, source provenance, manifest +hash, enablement, priority, installation time, registered command outputs, +registered skills, and, where needed, hashes proving ownership of generic +integration artifacts. Lower numeric priority has higher precedence; ties are +ordered by extension ID. + +`.specify/extensions.yml` is a separate YAML projection used by declarative +hooks. It contains a normalized installed list, hook bindings, and settings. +It does not replace `.registry`. + +Catalog configuration resolves in this order: + +1. `SPECKIT_CATALOG_URL`, which replaces the whole stack; +2. `.specify/extension-catalogs.yml`; +3. `~/.specify/extension-catalogs.yml`; then +4. the built-in installable catalog followed by the discovery-only community + catalog. + +Extension configuration merges, from lowest to highest precedence: + +1. `config.defaults` in `extension.yml`; +2. `.specify/extensions//-config.yml`; +3. `.specify/extensions//local-config.yml`; and +4. `SPECKIT__*` environment variables. + +Configuration files are treated as data. Invalid or unreadable YAML degrades +to an empty layer. Environment variables are nested by underscore-separated +keys, and longer installed extension IDs win ambiguous prefix matches. + +Removal and force-reinstallation preserve only top-level files named +`*-config.yml` or `*-config.local.yml`. Consequently, the runtime +`local-config.yml` override is not covered by that destructive-lifecycle +preservation convention. + +## Lifecycle and data flow + +### Discovery + +`specify extension search` and `info` merge active catalogs in ascending +priority order. The first catalog defining an extension ID wins. Fetch +failures are isolated per catalog; the operation fails only when no active +catalog can be fetched. Catalog caches are project-local and expire after one +hour; malformed or unreadable cache entries are ignored and refetched. + +The built-in community catalog is discovery-only. Its entries may be listed +and inspected but cannot be installed until the user chooses a direct URL or +places the extension in an install-enabled catalog they control. + +### Installation + +Installation accepts: + +- a bundled extension shipped with the current Spec Kit distribution; +- a local directory, including development mode; +- an install-enabled catalog entry; or +- an explicitly trusted HTTPS archive URL. + +All routes converge on `ExtensionManager.install_from_directory()` after safe +archive extraction when applicable. The manager: + +1. validates the manifest, compatibility, priority, command namespace, and + install conflicts; +2. validates active-integration registration settings and unsafe existing + output collisions; +3. preserves eligible configuration before any destructive force reinstall; +4. copies the extension under `.specify/extensions//`, honoring + `.extensionignore`; +5. renders commands or skills for the active integration; +6. registers hooks and the installed projection in `extensions.yml`; +7. commits lifecycle and ownership metadata to `.registry`; +8. restores executable bits for project scripts; and +9. lets the CLI scaffold declared configuration templates without overwriting + existing targets. + +Development installs may use symlinks to rendered caches where the selected +integration and platform support them. Integrations that prohibit such +symlinks receive ordinary files instead. + +### Enablement and integration changes + +Disabled extensions remain installed but are excluded from normal command, +hook, event, and artifact behavior. Enable and disable update registry state +and hook bindings. For the generic integration, output ownership must be +proven before generated artifacts are removed or replaced. + +An extension install targets the active integration recorded in project state. +Projects predating that state may fall back to detected agent directories; +present but invalid active-integration state fails closed and registers no +agent outputs. Selecting, switching, upgrading, or removing integrations +reconciles enabled extension outputs for the affected active agent through the +integration lifecycle. Those post-commit reconciliation steps are +best-effort and surface warnings rather than rolling back a successful +integration operation. + +### Update + +Update discovery compares the installed registry version with the winning +catalog release and the bundled copy, while enforcing Spec Kit compatibility. +Exact catalog releases remain bound to the winning catalog source. + +Before mutation, the update command preflights the replacement archive and +backs up the extension directory, registry entry, eligible configuration, +hook projection, command files, skills, and symlinks. A failed destructive +update attempts to restore the prior package and all tracked outputs. Backup +directories are unique per attempt; cleanup failures leave an isolated backup +and produce a warning rather than contradicting a committed or restored state. + +Update orchestration currently lives in CLI-private +`_command_update_*` modules. It is not a shared typed operation. + +### Removal + +Removal unregisters tracked command and skill outputs, removes hook bindings, +and deletes the registry entry. By default it copies eligible top-level config +files to `.specify/extensions/.backup//` before deleting the package. +`--keep-config` leaves those eligible files in the extension directory and +writes a provenance marker so a later install can distinguish intentional +preservation from a partial failed installation. + +## Command, skill, hook, and event delivery + +Extension command source is agent-neutral Markdown. The active integration's +registrar converts it to the agent's command, recipe, TOML, YAML, or +`SKILL.md` layout, rewrites extension-relative asset paths to the installed +extension directory, resolves command references, and records the generated +outputs in `.registry`. The output formats and integration ownership rules are +defined in [Agent Integration Design](integration.md). + +When project initialization enabled skills, each primary extension command may +be rendered as a `speckit--/SKILL.md` skill. Aliases are +command outputs, not separate skills. Existing user-owned skill directories +are not overwritten without a verified extension ownership path. + +Hooks are declarative AI-agent follow-up instructions, not direct subprocess +execution. `HookExecutor` filters enabled bindings, evaluates the limited +`config.*` and `env.*` condition grammar, orders hooks by priority, and renders +the invocation syntax for the active integration. `execute_hook()` returns +execution metadata; the calling AI agent performs the command. + +Runtime `events` are a distinct mechanism. The events domain validates their +shape, merges enabled extension declarations with integration defaults and +project overrides, and writes supported agent-native event configuration. +Event dispatch resolves the declared command template and may execute its +frontmatter script under the project process user's permissions with path +confinement and timeouts. + +## Artifact registration and resolution + +Extensions contribute to the same artifact inventory and resolver used by +presets and core assets. For commands, templates, and scripts, the resolution +order is: + +1. project overrides; +2. installed presets; +3. enabled extensions in registry priority order; then +4. core assets. + +An extension manifest declaration is authoritative. A declared but missing or +unsafe file is not silently replaced by a conventionally named file. For +legacy compatibility, undeclared files under `commands/`, `templates/`, or +`scripts/` may still be discovered by convention. + +Extension layers always use `replace` semantics. Artifact listing and info +project their manifest provenance, active stack entry, concrete source path, +and hook bindings without changing resolver behavior. A corrupt extension +registry causes artifact resolution to fail closed. + +When the registry is healthy, the legacy resolver also admits safe +unregistered extension directories at default priority `10`. This compatibility +path is not equivalent to installation: it has no lifecycle or generated +output ownership metadata. + +## Bundled extensions and bundles + +First-party bundled extensions live under `extensions//` in the source +tree and are copied into `specify_cli/core_pack/extensions//` when the +wheel is built. Source and packaged lookup use the same asset resolver. +`extensions/catalog.json` advertises bundled identity and version; contract +tests require catalog and manifest versions to agree so updates are neither +hidden nor repeatedly offered. + +A Spec Kit bundle may reference an extension component. The bundle installer +delegates to `ExtensionManager` for bundled or catalog installation, +configuration scaffolding, refresh, and removal. Bundles do not define a +second extension format or lifecycle. + +## Trust and security boundaries + +Extension content is trusted project content once installed. Commands run in +the AI agent's context, and runtime event scripts run with the permissions of +the invoking process. Manifest validation and path confinement reduce unsafe +packaging and output writes; they are not a sandbox. + +Network and package trust rules are: + +- catalog and archive URLs require HTTPS, except loopback HTTP for local + development; +- catalog redirects are validated at every hop; +- configured authentication providers may add credentials for matching hosts, + and redirect handling does not forward them to untrusted hosts; +- catalog responses and downloads are size-bounded; +- archives reject traversal, links, and unsupported layouts during extraction; +- a catalog `sha256`, when present, must match the downloaded archive; +- direct URL installation is default-deny and requires interactive + confirmation or the explicit initialization trust flag; and +- catalog `install_allowed` controls whether metadata is an install source, + but does not certify extension safety. + +Filesystem operations validate containment and reject unsafe symlinked roots +for sensitive writes. Removal and refresh use registry records, generated +frontmatter, hashes, or expected symlink targets to prove ownership before +deleting user-adjacent command and skill artifacts. + +## Failure and transactional guarantees + +Expected manifest, compatibility, catalog, and lifecycle failures use +`ValidationError`, `CompatibilityError`, or `ExtensionError`; CLI adapters map +them to user-facing messages and non-zero exits. + +The implementation provides targeted transactional guarantees rather than one +global filesystem transaction: + +- validation and conflict checks precede destructive force removal; +- kept configuration is durably staged and restored across copy failures and + interrupted retries; +- generic integration registration rolls back generated outputs, hooks, and + registry state on commit failure; +- update backs up every tracked lifecycle surface before mutation and restores + it on failure; and +- corrupted registries fail closed in artifact resolution even though registry + construction normalizes malformed content so repair-oriented lifecycle + commands can continue. + +Best-effort cleanup may leave backups or caches after a successful commit or +rollback. Such leftovers are not treated as active extension state. + +## Supported external surfaces + +The supported extension surfaces are: + +- `specify extension ...` and `specify extension catalog ...`; +- repeated `specify init --extension ...` inputs; +- extension components in bundle manifests; +- `specify artifact list`, `info`, and `lookup` for composed extension + contributions; +- installed command or skill outputs consumed through the selected agent + integration; and +- the declarative `extension.yml` package format. + +Extension lifecycle operations do not currently have shared typed application +operations or first-class MCP adapters. The experimental MCP server registers +version and artifact tools; its artifact list and info tools can report +extension contributions, but extension add, update, remove, enablement, +catalog, and configuration lifecycle remain CLI-only. This is the implemented +boundary, independent of the broader adapter rules in +[the MCP design](mcp.md). + +The Python classes in `specify_cli.extensions` are internal domain APIs used by +Spec Kit itself. They are not a separately versioned third-party SDK. + +## Non-goals + +The implemented extension system does not: + +- execute arbitrary Python plugins from extension packages; +- sandbox command prompts or event scripts; +- treat catalog inclusion as a security endorsement; +- allow extensions to replace core CLI or MCP registration; +- define preset-style append, prepend, or wrap composition strategies; +- install dependencies or arbitrary tools declared in manifest metadata; +- make every manifest metadata field an enforced runtime contract; or +- guarantee all lifecycle operations are globally atomic across every + filesystem and agent integration. diff --git a/extensions/RFC-EXTENSION-SYSTEM.md b/extensions/RFC-EXTENSION-SYSTEM.md deleted file mode 100644 index dd4c97e8a2..0000000000 --- a/extensions/RFC-EXTENSION-SYSTEM.md +++ /dev/null @@ -1,1962 +0,0 @@ -# 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.jira.sync"] # 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, - "priority": 10 - } - } -} -``` - -**Priority Field**: Extensions are ordered by `priority` (lower = higher precedence). Default is 10. Used for template resolution when multiple extensions provide the same template. - -### 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 Integration (v1.0.0) - jira - Create Jira issues from spec-kit artifacts - Commands: 3 | Hooks: 2 | Priority: 10 | Status: Enabled - - ✓ Linear Integration (v0.9.0) - linear - Create Linear issues from spec-kit artifacts - Commands: 1 | Hooks: 1 | Priority: 10 | 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 a remote URL (archive). Does not accept Git repositories directly. -- `--dev`: Install from a local path in development mode (the PATH is the positional `extension` argument). -- `--priority NUMBER`: Set resolution priority (lower = higher precedence, default 10) - -#### `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 -``` - -#### `specify extension set-priority NAME PRIORITY` - -Change the resolution priority of an installed extension. - -```bash -$ specify extension set-priority jira 5 - -✓ Extension 'Jira Integration' priority changed: 10 → 5 - -Lower priority = higher precedence in template resolution -``` - -**Priority Values:** - -- Lower numbers = higher precedence (checked first in resolution) -- Default priority is 10 -- Must be a positive integer (1 or higher) - -**Use Cases:** - -- Ensure a critical extension's templates take precedence -- Override default resolution order when multiple extensions provide similar templates - ---- - -## 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 -``` - -**Migration alias** (if needed): - -```yaml -# extension.yml -provides: - commands: - - name: "speckit.github.taskstoissues" - file: "commands/taskstoissues.md" - aliases: ["speckit.github.sync-taskstoissues"] # Alternate namespaced entry point -``` - -AI agents register both names, so callers can migrate to the alternate alias without relying on deprecated global shortcuts like `/speckit.taskstoissues`. - ---- - -## 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? From 8372ec5739ff6bd30a7a58c975b136db8b5f6f48 Mon Sep 17 00:00:00 2001 From: Manfred Riem <15701806+mnriem@users.noreply.github.com> Date: Thu, 15 Oct 2026 12:52:46 -0500 Subject: [PATCH 2/5] docs: correct extension lifecycle guarantees Assisted-by: GitHub Copilot (model: GPT-5.6 Sol, autonomous) Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: a4e66b41-773d-4a2c-a0e9-0f3e56ef4132 --- design/extension.md | 49 ++++++++++++++++++++++++++++++--------------- 1 file changed, 33 insertions(+), 16 deletions(-) diff --git a/design/extension.md b/design/extension.md index 9b4a629549..11f9d20ba3 100644 --- a/design/extension.md +++ b/design/extension.md @@ -91,10 +91,13 @@ always have `replace` semantics when they participate in artifact resolution; extensions cannot author preset composition strategies. Primary command names must use the extension's namespace. Installation rejects -names and aliases that collide with core commands or another installed -extension after normalizing them to the agent output form. A small set of -legacy two-segment command names is normalized to the canonical form with a -warning; hook and event command references are rewritten consistently. +names and aliases that exactly match a name from another installed extension, +and rejects names whose normalized agent output form conflicts with a core +command. Installed-extension comparisons are not normalized, so differently +spelled names can still converge on the same output path for a particular +renderer. A small set of legacy two-segment command names is normalized to the +canonical form with a warning; hook and event command references are rewritten +consistently. ## Project state and filesystem layout @@ -148,9 +151,9 @@ to an empty layer. Environment variables are nested by underscore-separated keys, and longer installed extension IDs win ambiguous prefix matches. Removal and force-reinstallation preserve only top-level files named -`*-config.yml` or `*-config.local.yml`. Consequently, the runtime -`local-config.yml` override is not covered by that destructive-lifecycle -preservation convention. +`*-config.yml` or `*-config.local.yml`. The runtime `local-config.yml` +override matches the first suffix and is covered by that preservation +convention. ## Lifecycle and data flow @@ -198,10 +201,14 @@ symlinks receive ordinary files instead. ### Enablement and integration changes -Disabled extensions remain installed but are excluded from normal command, -hook, event, and artifact behavior. Enable and disable update registry state -and hook bindings. For the generic integration, output ownership must be -proven before generated artifacts are removed or replaced. +Disabled extensions remain installed and are excluded from registry-driven +artifact resolution, hook and event projection, and future integration +registration. Enable and disable update registry state and hook bindings. For +the generic integration, disablement also removes owned invocation artifacts +after proving ownership. Other integrations retain already-generated command +and skill files until an integration switch, uninstall, or other reconciliation +removes them, so those stale files may remain invocable while the extension is +disabled. An extension install targets the active integration recorded in project state. Projects predating that state may fall back to detected agent directories; @@ -246,10 +253,17 @@ extension directory, resolves command references, and records the generated outputs in `.registry`. The output formats and integration ownership rules are defined in [Agent Integration Design](integration.md). -When project initialization enabled skills, each primary extension command may -be rendered as a `speckit--/SKILL.md` skill. Aliases are -command outputs, not separate skills. Existing user-owned skill directories -are not overwritten without a verified extension ownership path. +Native registration follows the selected integration's output flavor. It +renders primary names and aliases, so integrations whose native command format +is `SKILL.md` create separate primary and alias skill directories. Non-generic +native registration writes its selected destination and may replace an +existing file; the generic integration applies its ownership checks first. + +Command-backed integrations configured to use skills take a separate +supplemental path. That path renders only primary commands as +`speckit--/SKILL.md`. A normal install skips an existing +unverified skill file or directory, while an expected development symlink or a +forced integration refresh may be replaced. Hooks are declarative AI-agent follow-up instructions, not direct subprocess execution. `HookExecutor` filters enabled bindings, evaluates the limited @@ -340,7 +354,10 @@ them to user-facing messages and non-zero exits. The implementation provides targeted transactional guarantees rather than one global filesystem transaction: -- validation and conflict checks precede destructive force removal; +- manifest, compatibility, priority, namespace, conflict, and active-output + checks precede destructive force removal. `.extensionignore` is loaded + afterward, so an unreadable or invalid ignore file can still fail after the + prior package has been removed; - kept configuration is durably staged and restored across copy failures and interrupted retries; - generic integration registration rolls back generated outputs, hooks, and From 0550d541d3d42c2f2db491dc9324e1495f113d75 Mon Sep 17 00:00:00 2001 From: Manfred Riem <15701806+mnriem@users.noreply.github.com> Date: Thu, 15 Oct 2026 13:58:01 -0500 Subject: [PATCH 3/5] docs: qualify extension delivery guarantees Assisted-by: GitHub Copilot (model: GPT-5.6 Sol, autonomous) Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: a4e66b41-773d-4a2c-a0e9-0f3e56ef4132 --- design/extension.md | 49 +++++++++++++++++++++++++++++---------------- 1 file changed, 32 insertions(+), 17 deletions(-) diff --git a/design/extension.md b/design/extension.md index 11f9d20ba3..b64ba3305d 100644 --- a/design/extension.md +++ b/design/extension.md @@ -183,17 +183,21 @@ archive extraction when applicable. The manager: 1. validates the manifest, compatibility, priority, command namespace, and install conflicts; -2. validates active-integration registration settings and unsafe existing - output collisions; +2. when the generic integration is active, validates its registration + settings, command sources, and unsafe existing output collisions; 3. preserves eligible configuration before any destructive force reinstall; 4. copies the extension under `.specify/extensions//`, honoring `.extensionignore`; 5. renders commands or skills for the active integration; 6. registers hooks and the installed projection in `extensions.yml`; 7. commits lifecycle and ownership metadata to `.registry`; -8. restores executable bits for project scripts; and -9. lets the CLI scaffold declared configuration templates without overwriting - existing targets. +8. restores executable bits for project scripts. + +Configuration scaffolding is a caller-owned follow-up rather than a shared +manager step. `extension add`, `extension enable`, and bundle installation call +`scaffold_config()` without overwriting existing targets. Extension +installation during `init` and extension update paths do not scaffold declared +configuration templates. Development installs may use symlinks to rendered caches where the selected integration and platform support them. Integrations that prohibit such @@ -266,10 +270,17 @@ unverified skill file or directory, while an expected development symlink or a forced integration refresh may be replaced. Hooks are declarative AI-agent follow-up instructions, not direct subprocess -execution. `HookExecutor` filters enabled bindings, evaluates the limited -`config.*` and `env.*` condition grammar, orders hooks by priority, and renders -the invocation syntax for the active integration. `execute_hook()` returns -execution metadata; the calling AI agent performs the command. +execution. `HookExecutor` is a helper API that filters enabled bindings, +evaluates the limited `config.*` and `env.*` condition grammar, orders hooks by +priority, and renders the invocation syntax for the active integration. +`execute_hook()` returns execution metadata; the calling AI agent performs the +command. + +The shipped core command templates do not call `HookExecutor`. They read +`.specify/extensions.yml` directly, preserve configured YAML order, skip +disabled hooks, and skip every hook with a non-empty condition rather than +evaluating it. For remaining hooks they emit optional or mandatory agent +instructions, and the active AI agent performs any mandatory command. Runtime `events` are a distinct mechanism. The events domain validates their shape, merges enabled extension declarations with integration defaults and @@ -340,10 +351,14 @@ Network and package trust rules are: - catalog `install_allowed` controls whether metadata is an install source, but does not certify extension safety. -Filesystem operations validate containment and reject unsafe symlinked roots -for sensitive writes. Removal and refresh use registry records, generated -frontmatter, hashes, or expected symlink targets to prove ownership before -deleting user-adjacent command and skill artifacts. +Guarded archive, package, integration, removal, and refresh paths validate +containment or prove ownership with registry records, generated frontmatter, +hashes, or expected symlink targets before mutating user-adjacent artifacts. +This is not a universal property of registry persistence: +`ExtensionRegistry._save()` does not itself validate containment of the +`.specify/extensions` root. Without an active integration file-change observer, +a symlinked root is followed and `.registry` can be written outside the +project. ## Failure and transactional guarantees @@ -354,10 +369,10 @@ them to user-facing messages and non-zero exits. The implementation provides targeted transactional guarantees rather than one global filesystem transaction: -- manifest, compatibility, priority, namespace, conflict, and active-output - checks precede destructive force removal. `.extensionignore` is loaded - afterward, so an unreadable or invalid ignore file can still fail after the - prior package has been removed; +- manifest, compatibility, priority, namespace, conflict, and generic + active-output checks precede destructive force removal. `.extensionignore` + is loaded afterward, so an unreadable or invalid ignore file can still fail + after the prior package has been removed; - kept configuration is durably staged and restored across copy failures and interrupted retries; - generic integration registration rolls back generated outputs, hooks, and From 3375ff6e7e33ee01a4bf979d84f5ef046162e9d3 Mon Sep 17 00:00:00 2001 From: Manfred Riem <15701806+mnriem@users.noreply.github.com> Date: Fri, 16 Oct 2026 15:10:36 -0500 Subject: [PATCH 4/5] docs: clarify extension update resolution Assisted-by: GitHub Copilot (model: GPT-5.6 Sol, autonomous) Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: a4e66b41-773d-4a2c-a0e9-0f3e56ef4132 --- design/extension.md | 29 ++++++++++++++++++----------- 1 file changed, 18 insertions(+), 11 deletions(-) diff --git a/design/extension.md b/design/extension.md index b64ba3305d..8eb6a29199 100644 --- a/design/extension.md +++ b/design/extension.md @@ -226,13 +226,16 @@ integration operation. ### Update Update discovery compares the installed registry version with the winning -catalog release and the bundled copy, while enforcing Spec Kit compatibility. -Exact catalog releases remain bound to the winning catalog source. - -Before mutation, the update command preflights the replacement archive and -backs up the extension directory, registry entry, eligible configuration, -hook projection, command files, skills, and symlinks. A failed destructive -update attempts to restore the prior package and all tracked outputs. Backup +install-enabled catalog release and any required bundled copy. It does not +filter candidates for Spec Kit compatibility, so an incompatible release may +still be offered. Exact catalog releases remain bound to the winning catalog +source. + +After selection and before mutation, the update command preflights the +replacement archive, including manifest and Spec Kit compatibility checks, and +backs up the extension directory, registry entry, eligible configuration, hook +projection, command files, skills, and symlinks. A failed destructive update +attempts to restore the prior package and all tracked outputs. Backup directories are unique per attempt; cleanup failures leave an isolated backup and produce a warning rather than contradicting a committed or restored state. @@ -300,10 +303,14 @@ order is: 3. enabled extensions in registry priority order; then 4. core assets. -An extension manifest declaration is authoritative. A declared but missing or -unsafe file is not silently replaced by a conventionally named file. For -legacy compatibility, undeclared files under `commands/`, `templates/`, or -`scripts/` may still be discovered by convention. +A successfully loaded and validated extension manifest declaration is +authoritative. A declared but missing or unsafe file is not silently replaced +by a conventionally named file. For legacy compatibility, an absent, +unreadable, or invalid manifest reopens conventional lookup, as does a valid +manifest that does not declare the requested artifact. Commands and scripts +are searched under `commands/` and `scripts/`; templates are searched under +`templates/` and at the extension root. Consequently, an undeclared root-level +template can precede a core template. Extension layers always use `replace` semantics. Artifact listing and info project their manifest provenance, active stack entry, concrete source path, From dcfe2f5313d1ab9647af81315cc0342091b7fdd0 Mon Sep 17 00:00:00 2001 From: Manfred Riem <15701806+mnriem@users.noreply.github.com> Date: Mon, 19 Oct 2026 07:26:10 -0500 Subject: [PATCH 5/5] docs: clarify extension command hierarchy Assisted-by: GitHub Copilot (model: GPT-5.6 Sol, autonomous) Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: a4e66b41-773d-4a2c-a0e9-0f3e56ef4132 --- design/cli.md | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/design/cli.md b/design/cli.md index e4df66f282..f5720e2bb4 100644 --- a/design/cli.md +++ b/design/cli.md @@ -10,9 +10,12 @@ operation defined by CLI surface; it does not redefine semantic validation, orchestration, results, warnings, errors, or side effects. -The [extension architecture](extension.md) supplies the reference command-group -shape used in this document. Do not create extra modules merely to make a -small command conform visually. +The extension command hierarchy under +[Nested command groups](#nested-command-groups) supplies the reference shape +used in this document. The separate +[Extension Architecture](extension.md) defines extension-domain behavior and +boundaries. Do not create extra modules merely to make a small command conform +visually. ## Design goals