diff --git a/README.md b/README.md index 0d6d47f276..1f2983081c 100644 --- a/README.md +++ b/README.md @@ -42,10 +42,10 @@ Spec Kit combines ready-to-use processes with composable mechanisms: | Capability | Spec Kit mechanism | | --- | --- | | Connect to or switch between coding agents | [Integration](https://github.github.io/spec-kit/integrations/index.html) | -| Add a command, quality gate, or complete process | [Extension](https://github.github.io/spec-kit/reference/extensions.html) | -| Connect a process to an external tool or service | [Extension](https://github.github.io/spec-kit/reference/extensions.html) | -| React to coding-agent lifecycle events | [Extension runtime event](https://github.github.io/spec-kit/reference/extensions.html#extension-runtime-events) | -| Run extension commands before or after Spec Kit commands | [Extension hook](https://github.github.io/spec-kit/reference/extensions.html#project-extension-and-hook-configuration) | +| Add a command, quality gate, or complete process | [Extension](https://github.github.io/spec-kit/extensions/overview.html) | +| Connect a process to an external tool or service | [Extension](https://github.github.io/spec-kit/extensions/overview.html) | +| React to coding-agent lifecycle events | [Extension runtime event](https://github.github.io/spec-kit/extensions/api.html#extension-runtime-events) | +| Run extension commands before or after Spec Kit commands | [Extension hook](https://github.github.io/spec-kit/extensions/api.html#hook-configuration) | | Adapt templates, commands, terminology, or methodology | [Preset](https://github.github.io/spec-kit/reference/presets.html) | | Enforce organizational or regulatory standards | [Preset](https://github.github.io/spec-kit/reference/presets.html) | | Override a template for one project | [Project-local override](https://github.github.io/spec-kit/guides/customization.html#project-local-overrides-and-resolution) | diff --git a/docs/README.md b/docs/README.md index 6849107b38..b6250153d2 100644 --- a/docs/README.md +++ b/docs/README.md @@ -34,7 +34,12 @@ To build the documentation locally: - `guides/customization.md` - Choosing and combining customization building blocks - `reference/catalogs.md` - Catalog discovery, trust, private sources, and precedence - `extensions/overview.md` - Extension user guide +- `extensions/cli.md` - Extension management CLI reference +- `extensions/catalogs.md` - Extension catalog configuration, trust, and release schema +- `extensions/developing.md` - Extension authoring guide +- `extensions/api.md` - Extension manifest, API, hook, and runtime event contracts - `extensions/publishing.md` - Extension publishing and catalog submission guide +- `community/extensions.md` - Community extension listing - `integrations/index.md` - Integration directory and shared setup guidance - `integrations/cli.md` - Shared integration management CLI reference - `integrations/.md` - Agent-specific setup, invocation, options, and limitations diff --git a/docs/extensions/api.md b/docs/extensions/api.md index 682221c4f4..adb18e2472 100644 --- a/docs/extensions/api.md +++ b/docs/extensions/api.md @@ -9,7 +9,7 @@ Technical reference for Spec Kit extension system APIs and manifest schema. 3. [Command File Format](#command-file-format) 4. [Configuration Schema](#configuration-schema) 5. [Hook System](#hook-system) -6. [CLI Commands](#cli-commands) +6. [Extension Runtime Events](#extension-runtime-events) --- @@ -25,12 +25,12 @@ schema_version: "1.0" # Required extension: id: string # Required, pattern: ^[a-z0-9-]+$ name: string # Required, human-readable name - version: string # Required, semantic version (X.Y.Z) - description: string # Required, brief description (<200 chars) - author: string # Required - repository: string # Required, valid URL - license: string # Required (e.g., "MIT", "Apache-2.0") - homepage: string # Optional, valid URL + version: string # Required, PEP 440 version; SemVer recommended + description: string # Required, brief description + author: string # Optional + repository: string # Optional, source URL + license: string # Optional (e.g., "MIT", "Apache-2.0") + homepage: string # Optional, homepage URL requires: speckit_version: string # Required, version specifier (>=X.Y.Z) @@ -43,7 +43,7 @@ provides: commands: # At least one of commands/templates/scripts/hooks/events required - name: string # Required, pattern: ^speckit\.[a-z0-9-]+\.[a-z0-9-]+$ file: string # Required, relative path to command file - description: string # Required + description: string # Optional templates: # Optional, array of declared templates. Always resolve # as "replace" -- 'strategy' is not an authorable field here. @@ -67,7 +67,7 @@ provides: hooks: # Optional, event hooks. Each event accepts either form below. event_name: # e.g., "after_specify", "after_plan", "after_tasks", "after_implement" command: string # Command to execute - priority: integer # Optional, >= 1, default 10 (lower runs first) + priority: integer # Optional, >= 1, default 10 (HookExecutor sort metadata) optional: boolean # Default: true prompt: string # Prompt text for optional hooks description: string # Hook description @@ -78,11 +78,19 @@ hooks: # Optional, event hooks. Each event accepts either form - command: string priority: integer +events: # Optional, coding-agent lifecycle events + session_start: + command: string # Command template with a runnable scripts entry + tags: # Optional, array of tags (2-10 recommended) - string defaults: # Optional, default configuration values key: value # Any YAML structure + +config: # Optional, separate from provides.config + defaults: # Merged over top-level defaults + key: value ``` ### Field Specifications @@ -98,10 +106,11 @@ defaults: # Optional, default configuration values #### `extension.version` - **Type**: string -- **Format**: Semantic versioning (X.Y.Z) +- **Format**: PEP 440; `MAJOR.MINOR.PATCH` is recommended for releases - **Description**: Extension version - **Examples**: `1.0.0`, `0.9.5`, `2.1.3` -- **Invalid**: `v1.0`, `1.0`, `1.0.0-beta` +- **Also accepted**: `v1.0`, `1.0`, `1.0.0-beta` +- **Invalid**: `latest`, `not-a-version` #### `requires.speckit_version` @@ -112,7 +121,12 @@ defaults: # Optional, default configuration values - `>=0.1.0` - Any version 0.1.0 or higher - `>=0.1.0,<2.0.0` - Version 0.1.x or 1.x - `==0.1.0` - Exactly 0.1.0 -- **Invalid**: `0.1.0`, `>= 0.1.0` (space), `latest` +- **Also accepted**: `>= 0.1.0` (whitespace after the operator) +- **Invalid**: `0.1.0`, `latest` + +Manifest loading requires a non-empty string here; the compatibility check +parses it as a `packaging.specifiers.SpecifierSet` and checks the installed +Spec Kit version. #### `provides.commands[].name` @@ -121,7 +135,14 @@ defaults: # Optional, default configuration values - **Description**: Namespaced command name - **Format**: `speckit.{extension-id}.{command-name}` - **Examples**: `speckit.jira.specstoissues`, `speckit.linear.sync` -- **Invalid**: `jira.specstoissues`, `speckit.command`, `speckit.jira.CreateIssues` +- **Invalid**: `another-extension.command`, `speckit.jira.CreateIssues` + +Legacy two-segment names `speckit.command` and `.command` are +normalized to `speckit..command` with a warning. Authors should +use canonical names rather than relying on this compatibility behavior. +Installation also checks that primary names belong to the extension's +namespace and rejects exact command-name or alias collisions with installed +extensions, plus normalized output-name collisions with core commands. #### `provides.templates[].name` / `provides.scripts[].name` @@ -153,7 +174,19 @@ defaults: # Optional, default configuration values - **Value**: A single hook mapping, or a list of hook mappings to register multiple commands on one event - **Description**: Hooks that execute at lifecycle events - **Events**: Defined by core spec-kit commands -- **Ordering**: Within an event, hooks run by ascending `priority` (integer ≥ 1, default 10; lower runs first; equal priorities keep authoring order via a stable sort) +- **Ordering**: Current command templates surface hooks in configured YAML order. + `HookExecutor.get_hooks_for_event()` instead returns ascending `priority` + (integer ≥ 1, default 10), with stable authoring order for ties. See + [Hook configuration](#hook-configuration) for execution limitations. + +#### `events` + +- **Type**: object +- **Keys**: Canonical coding-agent event names +- **Value**: An event mapping referencing a runnable command +- **Description**: Integration-agnostic declarations translated to installed + coding agents' native event configuration; see + [Extension runtime events](#extension-runtime-events). --- @@ -233,16 +266,26 @@ is_installed = registry.is_installed(extension_id: str) # bool "extensions": { "jira": { "version": "1.0.0", - "source": "catalog", + "source": {"kind": "catalog", "catalog": "my-org"}, "manifest_hash": "sha256...", "enabled": true, - "registered_commands": ["speckit.jira.specstoissues", ...], - "installed_at": "2026-01-28T..." + "priority": 10, + "registered_commands": { + "claude": ["speckit.jira.specstoissues"] + }, + "registered_skills": [], + "installed_at": "2026-01-28T00:00:00" } } } ``` +`registered_commands` maps integration keys to registered command names; +`registered_skills` records supplemental skill names. The installer stores +`"local"` as raw local provenance, or a catalog object as shown above. +The CLI's normalized `--json` output uses a separate +[public source shape](cli.md#list-installed-extensions). + ### ExtensionManager **Module**: `specify_cli.extensions` @@ -507,20 +550,24 @@ Additional notes and tips. ### Frontmatter Fields ```yaml -description: string # Required, brief command description +description: string # Recommended, brief command description tools: [string] # Optional, MCP tools required ``` ### Special Variables - `$ARGUMENTS` - Placeholder for user-provided arguments -- Extension context automatically injected: +- Markdown command output includes extension context comments: ```markdown ``` +Generated skills instead carry provenance in their frontmatter's +`metadata.source` and do not automatically include these HTML comments. +Other output formats use their integration-specific renderer. + --- ## Configuration Schema @@ -559,15 +606,27 @@ field_mappings: ### Config Layers -1. **Extension Defaults** (from `extension.yml` `defaults` section) +1. **Extension Defaults** (top-level `defaults` merged with `config.defaults`, + with `config.defaults` winning on conflicts) 2. **Project Config** (`{extension-id}-config.yml`) -3. **Local Override** (`{extension-id}-config.local.yml`, gitignored) +3. **Local Override** (`local-config.yml`, gitignored) 4. **Environment Variables** (`SPECKIT_{EXTENSION}_*`) +`ConfigManager` performs this merge when called; extension commands and scripts +must use it or implement their own loading. It does not interpolate `${...}` +strings in YAML or validate a `config_schema` field. Configuration validation +belongs to the extension's commands or scripts. + ### Environment Variable Pattern Format: `SPECKIT_{EXTENSION}_{KEY}` +The extension ID is uppercased with hyphens changed to underscores. Each +underscore-separated component after that prefix becomes a nested lowercase +key: `SPECKIT_JIRA_PROJECT_KEY` sets `project.key`, not `project_key`. +Environment values remain strings. When installed extension IDs have overlapping +normalized prefixes, the longer matching extension ID owns the variable. + Examples: - `SPECKIT_JIRA_PROJECT_KEY` @@ -646,19 +705,65 @@ Hooks are prompts run in the same session; they reuse the resolved `BUG_SLUG` an ### Hook Configuration -**In `.specify/extensions.yml`**: +Spec Kit stores project-level extension registration and hook configuration in +`.specify/extensions.yml`. The `installed` list records installed extension IDs, +`settings` stores project-wide extension settings, and `hooks` groups registered +hooks by event. ```yaml +installed: + - git + - my-extension + +settings: + auto_execute_hooks: true + hooks: - after_tasks: - - extension: jira - command: speckit.jira.specstoissues + before_implement: + - extension: git + command: speckit.git.commit enabled: true optional: true - prompt: "Create Jira issues from tasks?" - description: "..." + priority: 10 + prompt: "Commit outstanding changes before implementation?" + description: "Auto-commit before implementation" condition: null -``` + + after_implement: + - extension: my-extension + command: speckit.my-extension.verify + enabled: true + optional: false + priority: 5 + description: "Run verification after implementation" +``` + +`auto_execute_hooks` defaults to `true`, but is reserved and is not consulted +when hooks are surfaced or invoked. + +| Field | Description | +| --- | --- | +| `extension` | ID of the extension that registered the hook. | +| `command` | Extension command associated with the hook. | +| `enabled` | Whether the hook is active. Hooks with `enabled: false` are skipped. | +| `optional` | If `true`, the hook is presented with its `prompt` and can be skipped. If `false`, it is emitted as an automatic hook with `EXECUTE_COMMAND` markers. | +| `priority` | Integer metadata >= 1, default `10`. Current command templates use configured YAML order, not priority sorting. | +| `prompt` | Message shown when asking whether to run an optional hook. | +| `description` | Human-readable explanation of the hook. | +| `condition` | Optional expression evaluated by `HookExecutor`: `config.` or `env.` with `is set`, `==`, or `!=`. Current command templates do not evaluate conditions and skip hooks with a non-empty condition. | + +Hook event names identify when a hook is invoked, generally using +`before_` or `after_`, such as `before_implement`, +`after_implement`, `before_tasks`, and `after_tasks`. + +Extension manifests reject invalid hook priorities during installation. +For existing registrations, `HookExecutor.get_hooks_for_event()` sorts using +`normalize_priority()`: missing values, booleans, values that `int()` rejects +(including non-finite floats), and values less than `1` fall back to `10`. +Numeric strings and finite floats are coerced with `int()`. +Lower values are returned first, with stable authoring order for ties. +Current command templates read hook lists directly instead of using that +Python API, so they surface hooks in configured YAML order. ### Hook Message Format @@ -683,129 +788,32 @@ EXECUTE_COMMAND: {command} --- -## CLI Commands - -### extension list - -**Usage**: `specify extension list [OPTIONS]` - -**Options**: - -- `--available` - Show available extensions from catalog -- `--all` - Show both installed and available - -**Output**: List of installed extensions with metadata - -### extension catalog list - -**Usage**: `specify extension catalog list` - -Lists all active catalogs in the current catalog stack, showing name, description, URL, priority, and `install_allowed` status. - -### extension catalog add - -**Usage**: `specify extension catalog add URL [OPTIONS]` - -**Options**: - -- `--name NAME` - Catalog name (required) -- `--priority INT` - Priority (lower = higher priority, default: 10) -- `--install-allowed / --no-install-allowed` - Allow installs from this catalog (default: false) -- `--description TEXT` - Optional description of the catalog - -**Arguments**: - -- `URL` - Catalog URL (must use HTTPS) - -Adds a catalog entry to `.specify/extension-catalogs.yml`. - -### extension catalog remove - -**Usage**: `specify extension catalog remove NAME` - -**Arguments**: - -- `NAME` - Catalog name to remove +## Extension Runtime Events -Removes a catalog entry from `.specify/extension-catalogs.yml`. +Extensions can react to coding-agent lifecycle events by declaring a top-level +`events` mapping in `extension.yml`: -### extension add - -**Usage**: `specify extension add EXTENSION [OPTIONS]` - -**Options**: - -- `--from URL` - Install from custom URL -- `--dev PATH` - Install from local directory - -**Arguments**: - -- `EXTENSION` - Extension name or URL - -**Note**: Extensions from catalogs with `install_allowed: false` cannot be installed via this command. - -### extension remove - -**Usage**: `specify extension remove EXTENSION [OPTIONS]` - -**Options**: - -- `--keep-config` - Preserve config files -- `--force` - Skip confirmation - -**Arguments**: - -- `EXTENSION` - Extension ID - -### extension search - -**Usage**: `specify extension search [QUERY] [OPTIONS]` - -Searches all active catalogs simultaneously. Results include source catalog name and install_allowed status. - -**Options**: - -- `--tag TAG` - Filter by tag -- `--author AUTHOR` - Filter by author -- `--verified` - Show only verified extensions - -**Arguments**: - -- `QUERY` - Optional search query - -### extension info - -**Usage**: `specify extension info EXTENSION` - -Shows source catalog and install_allowed status. - -**Arguments**: - -- `EXTENSION` - Extension ID - -### extension update - -**Usage**: `specify extension update [EXTENSION]` - -**Arguments**: - -- `EXTENSION` - Optional, extension ID (default: all) - -### extension enable - -**Usage**: `specify extension enable EXTENSION` - -**Arguments**: - -- `EXTENSION` - Extension ID - -### extension disable - -**Usage**: `specify extension disable EXTENSION` - -**Arguments**: - -- `EXTENSION` - Extension ID +```yaml +events: + session_start: + command: speckit.my-extension.initialize + pre_tool_use: + command: speckit.my-extension.validate +``` + +Event declarations are integration-agnostic. Spec Kit refreshes every installed +event-capable integration, and each integration translates canonical event names +into its coding agent's native event configuration. The supported canonical +events are `session_start`, `pre_tool_use`, `post_tool_use`, +`user_prompt_submit`, `stop`, and `session_end`; an integration may support +only a subset. + +Runtime events are distinct from extension hooks. Events react to the coding +agent's lifecycle and execute the referenced command template's `scripts` +entry directly. The referenced command must therefore provide a runnable +script; its Markdown instructions are not sent to the coding agent. Hooks +instead surface extension commands before or after Spec Kit commands such as +`plan`, `tasks`, and `implement`. --- @@ -881,7 +889,7 @@ satisfied = version_satisfies("1.2.3", ">=1.0.0,<2.0.0") # bool │ ├── {extension-id}/ # Extension directory │ │ ├── extension.yml # Manifest │ │ ├── {ext}-config.yml # User config -│ │ ├── {ext}-config.local.yml # Local overrides (gitignored) +│ │ ├── local-config.yml # Local overrides (gitignored) │ │ ├── {ext}-config.template.yml # Template │ │ ├── commands/ # Command files │ │ │ └── *.md @@ -889,7 +897,7 @@ satisfied = version_satisfies("1.2.3", ">=1.0.0,<2.0.0") # bool │ │ │ └── *.sh │ │ ├── docs/ # Documentation │ │ └── README.md -│ └── extensions.yml # Project extension config +├── extensions.yml # Project extension config └── scripts/ # (existing spec-kit) .claude/ @@ -902,3 +910,11 @@ satisfied = version_satisfies("1.2.3", ">=1.0.0,<2.0.0") # bool *Last Updated: 2026-01-28* *API Version: 1.0* *Spec Kit Version: 0.1.0* + +## See also + +- [Developing extensions](developing.md) +- [Extension CLI reference](cli.md) +- [Extension catalogs](catalogs.md) +- [Extension user guide](overview.md) +- [Publishing extensions](publishing.md) diff --git a/docs/extensions/catalogs.md b/docs/extensions/catalogs.md new file mode 100644 index 0000000000..4278ebaa40 --- /dev/null +++ b/docs/extensions/catalogs.md @@ -0,0 +1,337 @@ +# Extension Catalogs + +Extension catalogs control where `specify extension search` and `add` look for +extensions. A catalog stack is an ordered list of sources searched together. +Lower catalog priority numbers take precedence when the same extension ID +appears in more than one source. + +## Trust model: discovery-only vs. install sources + +Catalogs come in two kinds, and the distinction is a **security boundary**: + +- **Install sources** (`install_allowed: true`) are catalogs you trust as a place + to install from. The built-in `default` catalog is one, as is a catalog you + author and vet yourself. +- **Discovery-only** catalogs (`install_allowed: false`) are searchable surfaces + for finding extensions, but are not installable. The built-in `community` + catalog is already active for search; you do not need to add it. + +| Priority | Built-in catalog | Install allowed | Purpose | +| --- | --- | --- | --- | +| 1 | `catalog.json` (`default`) | Yes | Installable extensions | +| 2 | `catalog.community.json` (`community`) | No | Discover community extensions | + +Community entries are unvetted third-party code. Maintainers check catalog +metadata, not extension code. You must vet an extension before installing it. + +> [!WARNING] +> Do not flip an unvetted discovery-only catalog to `install_allowed`. That +> defeats the separation between discovery and installation. + +There are two ways to install something found through the community catalog: + +1. **Install a single vetted release directly.** Get the candidate archive URL + from `specify extension info `, which prints a "Candidate archive" URL + for uninstalled discovery-only entries. Treat the URL as untrusted until you have reviewed + that release archive, then install it: + + ```bash + specify extension info + specify extension add --from + ``` + +2. **Curate your own catalog.** For a governed, reusable organization source, + review the releases yourself and mark only that controlled catalog + `install_allowed: true`. + +## Catalog resolution order + +The active stack is selected in this order: + +1. **Environment variable:** `SPECKIT_CATALOG_URL` overrides the entire stack. +2. **Project config:** `.specify/extension-catalogs.yml`. +3. **User config:** `~/.specify/extension-catalogs.yml`. +4. **Built-in defaults:** `default` and `community`. + +An existing project configuration takes precedence over user configuration and +built-in defaults. Empty or malformed files, including `catalogs: []`, fail +closed with a validation error rather than falling back to another source. +Remove the configuration file to use the next source in the resolution order, +or populate it with valid catalog entries. +Within the selected stack, catalogs are checked by priority; the first matching +extension ID wins. Exact-version lookup does not fall back to another catalog +when the winning entry lacks the requested version. + +## Managing catalogs + +```bash +specify extension catalog list +specify extension catalog --help + +# Add an internal source you own and vet +specify extension catalog add \ + --name "internal" \ + --priority 2 \ + --install-allowed \ + --description "Our approved extensions" \ + https://internal.company.com/spec-kit/catalog.json + +# Add a discovery-only source (the default permission) +specify extension catalog add \ + --name "partner" \ + --priority 5 \ + https://partner.example.com/spec-kit/catalog.json + +specify extension catalog remove internal +``` + +These commands edit the project's `.specify/extension-catalogs.yml`. +Re-adding the same named catalog with identical settings succeeds without +changing configuration; different settings are rejected. The complete option +reference is in [Catalog management](cli.md#catalog-management). + +You can also edit the configuration directly: + +```yaml +catalogs: + - name: "default" + url: "https://raw.githubusercontent.com/github/spec-kit/main/extensions/catalog.json" + priority: 1 + 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: "Our approved extensions" + + - name: "community" + url: "https://raw.githubusercontent.com/github/spec-kit/main/extensions/catalog.community.json" + priority: 3 + install_allowed: false + description: "Community-contributed extensions (discovery only)" +``` + +## Organization catalog customization + +Organizations use private catalogs to control available releases, integrate +internal tools, meet compliance requirements, or support air-gapped networks. + +### Create a catalog + +Create a `catalog.json` file containing the releases your team has vetted: + +```json +{ + "schema_version": "1.0", + "updated_at": "2026-02-03T00:00:00Z", + "catalog_url": "https://your-org.com/spec-kit/catalog.json", + "extensions": { + "jira": { + "name": "Jira Integration", + "id": "jira", + "description": "Create Jira issues from spec-kit artifacts", + "author": "Your Organization", + "version": "2.1.0", + "download_url": "https://github.com/your-org/spec-kit-jira/archive/refs/tags/v2.1.0.zip", + "repository": "https://github.com/your-org/spec-kit-jira", + "license": "MIT", + "requires": { + "speckit_version": ">=0.1.0", + "tools": [ + {"name": "atlassian-mcp-server", "required": true} + ] + }, + "provides": { + "commands": 3, + "hooks": 1 + }, + "tags": ["jira", "atlassian", "issue-tracking"], + "verified": true + }, + "internal-tool": { + "name": "Internal Tool Integration", + "id": "internal-tool", + "description": "Connect to internal company systems", + "author": "Your Organization", + "version": "1.0.0", + "download_url": "https://internal.your-org.com/extensions/internal-tool-1.0.0.zip", + "repository": "https://github.internal.your-org.com/spec-kit-internal", + "license": "Proprietary", + "requires": { + "speckit_version": ">=0.1.0" + }, + "provides": { + "commands": 2 + }, + "tags": ["internal", "proprietary"], + "verified": true + } + } +} +``` + +### Host and configure the catalog + +| Method | URL example | Use case | +| --- | --- | --- | +| GitHub Pages | `https://your-org.github.io/spec-kit-catalog/catalog.json` | Public or org-visible | +| Internal web server | `https://internal.company.com/spec-kit/catalog.json` | Corporate network | +| S3/cloud storage | `https://s3.amazonaws.com/your-bucket/catalog.json` | Cloud-hosted teams | +| Local file server | `http://localhost:8000/catalog.json` | Development/testing | + +URLs must use HTTPS, except localhost for testing. +Add your catalog with `specify extension catalog add`, or use: + +```yaml +# .specify/extension-catalogs.yml +catalogs: + - name: "my-org" + url: "https://your-org.com/spec-kit/catalog.json" + priority: 1 + install_allowed: true + description: "Our approved extensions" +``` + +For CI/CD or a single-catalog environment, replace the whole stack: + +```bash +export SPECKIT_CATALOG_URL="https://your-org.com/spec-kit/catalog.json" +``` + +Check the result: + +```bash +specify extension catalog list +specify extension search +specify extension add jira +``` + +### Private GitHub-hosted catalogs + +`GH_TOKEN` or `GITHUB_TOKEN` authenticates requests to GitHub-hosted URLs +(`raw.githubusercontent.com`, `github.com`, `api.github.com`, and +`codeload.github.com`). Private catalog JSON and extension ZIPs require access +to the relevant repositories. Non-GitHub catalog URLs are fetched without +these credentials. + +```bash +export GITHUB_TOKEN=$(gh auth token) +specify extension search jira +specify extension add jira-sync +``` + +### Curated and air-gapped environments + +A catalog can list only the approved extensions, such as `jira` and `github`. +Configure it as the sole catalog if only those entries should appear in search. +Private/proprietary extensions can point to internal archive hosts; public +catalog submission is optional. + +For air-gapped networks, download vetted extension ZIPs to an internal file +server, create a catalog pointing to those internal URLs, and host the catalog +inside the network. For example, an entry can use +`"download_url": "https://files.internal/spec-kit/jira-2.1.0.zip"`. + +### Development and testing + +```bash +python -m http.server 8000 --directory ./my-catalog/ +``` + +In another terminal: + +```bash +export SPECKIT_CATALOG_URL="http://localhost:8000/catalog.json" +specify extension add my-new-extension +``` + +Catalog configuration does not prevent direct installation: + +```bash +specify extension add jira +specify extension add --from https://github.com/someone/spec-kit-ext/archive/refs/tags/v1.0.0.zip +specify extension add --dev /path/to/extension +``` + +Direct URL installation shows a security warning because the archive is not +being selected through your configured catalog. Vet it before installing. + +## Catalog entry schema + +The following describes the catalog fields; community submission requirements +also require complete descriptive metadata. + +The catalog root must contain `schema_version` and an `extensions` mapping. +Each mapping key is the authoritative extension ID; a repeated `id` field +inside the entry is optional. + +| Field | Type | Required | Description | +| --- | --- | --- | --- | +| `name` | string | Yes | Human-readable name | +| `id` | string | No | Optional repetition of the authoritative mapping key | +| `version` | string | Yes | Current extension version | +| `download_url` | string | For archive releases | URL to the release archive; bundled entries can omit it | +| `bundled` | boolean | No | Marks an extension shipped with Spec Kit, resolved from the running distribution | +| `repository` | string | No | Source code URL (required for community submission) | +| `description` | string | Yes | Brief description; community submissions require under 200 characters | +| `author` | string | No | Author/organization (required for submission) | +| `license` | string | No | SPDX license identifier (required for submission) | +| `homepage`, `documentation`, `changelog` | string | No | Related URLs | +| `sha256` | string | No | Archive SHA-256 digest; verified before install when supplied | +| `requires.speckit_version` | string | No | Version constraint (required for submission) | +| `requires.tools` | array | No | Objects with required `name`, optional `version`, and `required` (default `false`) | +| `provides.commands`, `provides.hooks` | number | No | Capability counts | +| `tags` | array | No | Search tags (2-10 recommended) | +| `verified` | boolean | No | Metadata flag set by maintainers, default `false`; not a code-audit guarantee | +| `created_at`, `updated_at` | string | No | ISO 8601 timestamps | +| `releases` | object | No | Historical releases keyed by version | + +### Historical releases + +Catalogs may keep the current release in the top-level fields and add older +releases in a `releases` mapping. Older single-version catalogs continue to +work unchanged. Each historical release needs its own download URL and SHA-256 +digest; release-specific requirements or provided capabilities must be placed +in that release's record rather than inherited from the current one. As with +current releases, a digest may use a case-insensitive `sha256:` prefix and +surrounding whitespace. + +```json +{ + "extensions": { + "my-extension": { + "name": "My Extension", + "version": "0.5.1", + "download_url": "https://example.com/my-extension-0.5.1.zip", + "sha256": "<64-character SHA-256 for 0.5.1>", + "releases": { + "0.4.12": { + "download_url": "https://example.com/my-extension-0.4.12.zip", + "sha256": "<64-character SHA-256 for 0.4.12>" + } + } + } + } +} +``` + +The example omits other metadata for brevity. The current version must not be +repeated in `releases`; malformed or duplicate release records are rejected. +Equivalent PEP 440 spellings, such as `v1.0` and `1.0`, select the same release; +the catalog's advertised spelling remains visible. + +Catalog-backed bundle extension pins also select historical releases from the +winning source, using that release's own archive and digest and checking the +downloaded manifest's ID and version. Bundled extensions follow the packaged +version policy: a newer compatible packaged patch can be used with a warning. +See the [bundle reference](../reference/bundles.md) for bundle installation. + +## See also + +- [Extension user guide](overview.md) +- [Extension CLI reference](cli.md) +- [Publishing extensions](publishing.md) +- [Community extensions](../community/extensions.md) +- [Shared catalog reference](../reference/catalogs.md) diff --git a/docs/extensions/cli.md b/docs/extensions/cli.md new file mode 100644 index 0000000000..24812a0806 --- /dev/null +++ b/docs/extensions/cli.md @@ -0,0 +1,235 @@ +# Extension CLI Reference + +Complete reference for managing extensions with `specify extension`. +All extension commands require a project already initialized with `specify init`. + +## Search Available Extensions + +```bash +specify extension search [query] +``` + +| Option | Description | +| ------------ | ------------------------------------ | +| `--tag` | Filter by tag | +| `--author` | Filter by author | +| `--verified` | Show only verified extensions | + +Searches all active catalogs for extensions matching the query. Without a query, lists all available extensions. + +## Install an Extension + +```bash +specify extension add +specify extension add --from +specify extension add --dev +``` + +| Option | Description | +| --------------- | -------------------------------------------------------- | +| `--dev` | Install from a local directory (for development) | +| `--from ` | Install from a custom URL instead of the catalog | +| `--version ` | Install an exact version advertised by a catalog | +| `--force` | Overwrite if the extension is already installed | +| `--priority `| Resolution priority (default: 10; lower = higher precedence) | + +Installs an extension from the catalog, a URL, or a local directory. Extension commands are registered with the active AI coding agent integration. For `generic`, invocations use the configured `--commands-dir`: flat command files by default, or `speckit-/SKILL.md` with `--skills`. + +The positional name is required for catalog installs, and `--dev` requires a +local path. With `--from`, the positional name can be omitted; the downloaded +manifest supplies the installed extension ID. + +If a generic integration refresh cannot produce every extension invocation (for example, because a command or skill is user-modified or its source is missing), it warns and restores that extension's prior registered artifacts. Other extensions can still refresh. + +An unqualified catalog install still selects the advertised current version. +`--version` uses only the winning catalog source for that extension ID; it does +not fall back to a lower-priority source when the requested version is absent. +Discovery-only catalogs remain non-installable. `--version` cannot be combined +with `--dev` or the direct-URL `--from` option. For `--version` catalog installs, +the downloaded archive's extension ID and version are compared with the +requested catalog ID and version before installation. Unqualified catalog +installs and direct `--from` installs validate the manifest but do not compare +its ID or version with expected catalog values. + +## Remove an Extension + +```bash +specify extension remove +``` + +| Option | Description | +| --------------- | ---------------------------------------------- | +| `--keep-config` | Preserve configuration files during removal | +| `--force` | Skip confirmation prompt | + +Removes an installed extension. Configuration files are backed up by default; use `--keep-config` to leave them in place or `--force` to skip the confirmation. + +## List Installed Extensions + +```bash +specify extension list +specify extension list --json +``` + +| Option | Description | +| ------------- | -------------------------------------------------- | +| `--available` | Show installed extensions and an installation hint; does not query catalogs | +| `--all` | Same current behavior as `--available` | +| `--json` | Write installed extensions as JSON | + +Lists installed extensions with their status, version, and command counts. + +Although help describes `--available` and `--all` as including catalog entries, +the current handler does not fetch or list uninstalled extensions. Use +`specify extension search` to discover available extensions. + +`--json` writes a JSON array to stdout. Every item has the keys `id`, `name`, +`description`, `version`, `author`, `priority`, `enabled`, `source`, and +`provides`. `author` is `null` when absent; `source` is `{"kind":"local"}` +for local, legacy, or malformed provenance, or +`{"kind":"catalog","catalog":""}` for a valid catalog source. +Extension `provides` contains `commands`, `templates`, `scripts`, and `hooks` +counts. `--available` and `--all` do not broaden JSON output beyond installed +extensions. On success, `--json` writes exactly one array to stdout and exits +0. A runtime failure after option parsing writes exactly one +`{"error":"..."}` object to stderr and exits 1. If parsing raises a usage +error and the raw `--json` token is present, it writes that JSON error object +to stderr and preserves the usage exit code (normally 2). Without `--json`, +including for help, the existing human-readable behavior is unchanged. + +## Extension Info + +```bash +specify extension info +specify extension info --versions +specify extension info --json +``` + +Shows detailed information about an installed or available extension, including its description, version, commands, and configuration. +`--versions` lists the current and historical versions advertised by the +winning catalog source; it labels discovery-only sources as non-installable. +Equivalent PEP 440 version spellings (for example, `v1.0` and `1.0`) select +the same release; the catalog's advertised spelling remains visible. + +See [Historical releases](catalogs.md#historical-releases) for the catalog +format used to advertise older versions and bundle-pin behavior. + +`--json` describes one installed extension and writes a single JSON object to +stdout. `` is matched as without `--json`: the extension ID first, then a +unique display name, ignoring case. The object has the same `id`, `name`, +`description`, `version`, `author`, `priority`, `enabled`, and `source` keys as +the matching `specify extension list --json` item. In place of the `provides` +counts it has `commands`, `templates`, `scripts`, and `hooks` arrays. Command, +template, and script entries have `name`, `description` (`""` when absent), +`source` (`{"layer": "extension", "extensionId": ""}`, the extension that +provides it; the top-level `source` is where it was installed from), and +`sourcePath` (the manifest `file`, +relative to the extension directory); script entries also have `runtimes` when +the manifest declares them. Extension entries carry no `strategy`, because +extension-provided files always replace. Each hook entry has `trigger` (the +hook event, such as `after_tasks`), `targetCommand`, `optional` (default +`true`), and `priority` (default `10`). When an event declares the same command +more than once, the last declaration wins, as it does when hooks are +registered. `--json` cannot be combined with `--versions` (usage error, exit 2). +An extension that is not installed, an ambiguous name, or a missing project +writes one `{"error":"..."}` object to stderr and exits 1; usage errors keep +their exit code (normally 2), as with `list --json`. + +## Update Extensions + +```bash +specify extension update [] +``` + +Updates a specific extension, or all installed extensions if no name is given. + +Bundled extensions (such as `agent-context` and `git`) have no download URL; their updates install from the copy shipped with the running spec-kit release. When the catalog advertises a newer version than your spec-kit release ships, the update is reported as requiring a spec-kit upgrade first. + +For `generic`, a failed update restores hash-owned invocations from previously configured `--commands-dir` locations as well as the current location, even if another integration is now active. + +## Enable / Disable an Extension + +```bash +specify extension enable +specify extension disable +``` + +Disable an extension without removing it. Disabled extensions are excluded from +registry-driven artifact resolution and future integration registration, and +their declarative hooks are marked disabled in `.specify/extensions.yml`. +Native runtime-event configuration is refreshed on a best-effort basis after +these state changes. If a refresh fails, the command warns, but a stale native +hook may remain active and still execute. Retry the affected integration's +refresh with `specify integration upgrade `. + +Non-generic integrations can retain generated command or skill files that +remain invocable until an integration +switch, uninstall, or reconciliation removes them. Disabling is not a universal +execution block. Hook-only extensions can be installed, enabled, and disabled +even if generic command-output settings are missing or invalid; extensions with +commands still require valid settings. Re-enable with `enable`. + +For `generic`, disabling removes hash-owned invocations even after the output directory moves, but preserves unrelated same-named files in the new directory. + +## Set Extension Priority + +```bash +specify extension set-priority +``` + +Changes the artifact resolution priority of an extension. Lower numbers take +precedence for contributed artifacts such as templates and scripts; ties are +ordered by extension ID. Installation rejects exact command-name or alias +collisions with installed extensions, and normalized output-name collisions with +core commands. Priority does not permit duplicate command names to be installed. + +## Catalog Management + +Extension catalogs control where `search` and `add` look for extensions. Catalogs are checked in priority order (lower number = higher precedence). + +Discovery-only catalogs can be searched but cannot be installed from. Only +enable installation for a source you own and vet. See the +[catalog trust model](catalogs.md#trust-model-discovery-only-vs-install-sources) +before installing third-party extensions. + +### List Catalogs + +```bash +specify extension catalog list +``` + +Shows all active catalogs in the stack with their priorities and install permissions. + +### Add a Catalog + +```bash +specify extension catalog add +``` + +| Option | Description | +| ------------------------------------ | -------------------------------------------------- | +| `--name ` | Required. Unique name for the catalog | +| `--priority ` | Priority (default: 10; lower = higher precedence) | +| `--install-allowed / --no-install-allowed` | Mark the catalog as a trusted install source. Only enable for a catalog you own and vet; leave off (the default) for discovery-only sources. Never enable it for an unvetted public catalog. | +| `--description ` | Optional description | + +Adds a catalog to the project's `.specify/extension-catalogs.yml`. + +Re-adding the same named catalog with identical settings succeeds without changing the configuration; different settings are rejected. + +### Remove a Catalog + +```bash +specify extension catalog remove +``` + +Removes a catalog from the project configuration. + +## See also + +- [Extension user guide](overview.md) +- [Extension catalogs](catalogs.md) +- [Extension API reference](api.md) +- [Developing extensions](developing.md) +- [Publishing extensions](publishing.md) +- [Community extensions](../community/extensions.md) diff --git a/docs/extensions/developing.md b/docs/extensions/developing.md index ed95e2c2c3..7bacc94b48 100644 --- a/docs/extensions/developing.md +++ b/docs/extensions/developing.md @@ -25,7 +25,7 @@ schema_version: "1.0" extension: id: "my-ext" # Lowercase, alphanumeric + hyphens only name: "My Extension" - version: "1.0.0" # Semantic versioning + version: "1.0.0" # SemVer recommended; PEP 440 accepted description: "My custom extension" author: "Your Name" repository: "https://github.com/you/spec-kit-my-ext" @@ -161,7 +161,7 @@ Extension metadata block. - `id`: Extension identifier (lowercase, alphanumeric, hyphens) - `name`: Human-readable name -- `version`: Semantic version (e.g., "1.0.0") +- `version`: PEP 440 version (SemVer recommended, e.g., "1.0.0") - `description`: Short description **Optional sub-fields**: @@ -246,11 +246,44 @@ Each event accepts a single hook object or a list of hook objects (multiple comm Hook object: - `command`: Command to execute (typically from `provides.commands`, but can reference any registered command) -- `priority`: Run order within the event (integer ≥ 1, default 10; lower runs first; equal priorities keep authoring order) +- `priority`: Sort metadata for `HookExecutor` (integer ≥ 1, default 10); + current command templates surface hooks in configured YAML order instead - `optional`: If true, prompt user before executing - `prompt`: Prompt text for optional hooks - `description`: Hook description -- `condition`: Execution condition (future) +- `condition`: Expression evaluated by `HookExecutor`; current command + templates skip hooks with non-empty conditions rather than evaluating them + +See the [hook configuration contract](api.md#hook-configuration) for all fields +and the distinction between the Python API and command-template behavior. + +#### `events` + +Runtime events react to the coding agent's lifecycle, rather than a Spec Kit +command. Declare canonical names at the top level and reference command +templates with runnable `scripts` entries: + +```yaml +events: + session_start: + command: speckit.my-ext.initialize +``` + +For example, `commands/initialize.md` can declare: + +```yaml +--- +description: "Initialize the extension session" +scripts: + sh: scripts/initialize.sh + ps: scripts/initialize.ps1 +--- +``` + +Include that command in `provides.commands` and supply the scripts in your +extension. Runtime events execute the script directly; they do not send the +command's Markdown body to the coding agent. Each integration supports a subset +of canonical events. See the [runtime event contract](api.md#extension-runtime-events). #### `tags` @@ -260,10 +293,6 @@ Array of tags for catalog discovery. Default extension configuration values. -#### `config_schema` - -JSON Schema for validating extension configuration. - --- ## Command File Format @@ -272,7 +301,7 @@ JSON Schema for validating extension configuration. ```yaml --- -description: "Command description" # Required +description: "Command description" # Recommended tools: # Optional - 'tool-name/function' scripts: # Optional @@ -342,7 +371,9 @@ For other integrations' invocation formats, see the shared ### Script Path Rewriting -Extension commands use relative paths that get rewritten during registration: +The registrar normalizes helper paths while rendering commands. A +repository-root reference with two or more `../` segments targets the +project's core scripts. **In extension**: @@ -351,14 +382,18 @@ scripts: sh: ../../scripts/bash/helper.sh ``` -**After registration**: +Use `{SCRIPT}` in the command body to invoke the selected script variant. +For this example, the generated invocation is: -```yaml -scripts: - sh: .specify/scripts/bash/helper.sh +```bash +.specify/scripts/bash/helper.sh ``` -This allows scripts to reference core spec-kit scripts. +Bare `scripts/helper.sh` instead targets +`.specify/extensions//scripts/helper.sh` for an extension-owned +script. The generated output format may omit the original `scripts` +frontmatter after resolving `{SCRIPT}`; do not rely on that metadata surviving +in the installed command or skill. --- @@ -376,42 +411,42 @@ This allows scripts to reference core spec-kit scripts. api: endpoint: "https://api.example.com" timeout: 30 + key: "" # Set with SPECKIT_MY_EXT_API_KEY; do not commit credentials features: feature_a: true feature_b: false - -credentials: - # DO NOT commit credentials! - # Use environment variables instead - api_key: "${MY_EXT_API_KEY}" ``` ### Config Loading In your command, load config with layered precedence: -1. Extension defaults (`extension.yml` → `defaults`) +1. Extension defaults (`extension.yml` → `defaults`, then `config.defaults` + for any conflicting keys) 2. Project config (`.specify/extensions/my-ext/my-ext-config.yml`) -3. Local overrides (`.specify/extensions/my-ext/my-ext-config.local.yml` - gitignored) +3. Local overrides (`.specify/extensions/my-ext/local-config.yml` - gitignored) 4. Environment variables (`SPECKIT_MY_EXT_*`) -**Example loading script**: +For Python code running in an environment with `specify-cli` installed, use +the existing loader rather than duplicating the merge: -```bash -#!/usr/bin/env bash -EXT_DIR=".specify/extensions/my-ext" +```python +from pathlib import Path +from specify_cli.extensions import ConfigManager -# Load and merge config -config=$(yq eval '.' "$EXT_DIR/my-ext-config.yml" -o=json) +config = ConfigManager(Path.cwd(), "my-ext").get_config() +endpoint = config.get("api", {}).get("endpoint") +``` -# Apply env overrides -if [ -n "${SPECKIT_MY_EXT_API_KEY:-}" ]; then - config=$(echo "$config" | jq ".api.api_key = \"$SPECKIT_MY_EXT_API_KEY\"") -fi +Run this from the project root, or pass that root explicitly. Environment +variable components become nested keys: `SPECKIT_MY_EXT_API_KEY` overrides +`api.key`. Values remain strings; YAML `${...}` placeholders are not expanded. +Commands implemented in other languages must provide their own loading if they +need this layering. -echo "$config" -``` +Spec Kit does not consume `config_schema` to validate configuration. +Validate required values and types in your extension's commands or scripts. --- @@ -486,15 +521,22 @@ The following `.gitignore` features are **not applicable** in this context: ### Extension Version -- **Format**: Semantic versioning (MAJOR.MINOR.PATCH) +- **Format**: PEP 440; SemVer (`MAJOR.MINOR.PATCH`) is recommended for releases - **Valid**: `1.0.0`, `0.1.0`, `2.5.3` -- **Invalid**: `1.0`, `v1.0.0`, `1.0.0-beta` +- **Also accepted by the CLI**: `1.0`, `v1.0.0`, `1.0.0-beta` +- **Invalid**: `latest`, `not-a-version` + +Community submissions have stricter release-version requirements; see +[Submission validation](publishing.md#submission-validation). ### Command Name - **Pattern**: `^speckit\.[a-z0-9-]+\.[a-z0-9-]+$` - **Valid**: `speckit.my-ext.hello`, `speckit.tool.cmd` -- **Invalid**: `my-ext.hello` (missing prefix), `speckit.hello` (no extension namespace) +- **Invalid**: `other-ext.hello` (wrong namespace), `speckit.my-ext.Create` (uppercase) + +Legacy `my-ext.hello` and `speckit.hello` are normalized to +`speckit.my-ext.hello` with warnings. Use canonical names in new manifests. ### Command File Path @@ -587,7 +629,7 @@ def test_command_files_exist(): specify extension add --dev spec-kit-my-ext/ ``` -### Option 2: ZIP Archive (Future) +### Option 2: ZIP Archive Create ZIP archive and host on GitHub Releases: @@ -609,8 +651,9 @@ Submit to the community catalog for public discovery: 2. **File an issue** using the [Extension Submission](https://github.com/github/spec-kit/issues/new?template=extension_submission.yml) template 3. **After review**, a maintainer updates the catalog and your extension becomes available: - Users can browse `catalog.community.json` to discover your extension - - Users copy the entry to their own `catalog.json` - - Users install with: `specify extension add my-ext` (from their catalog) + - Users vet the release archive before installing it directly or adding it + to their own trusted catalog + - Users install with `--from ` or from their vetted catalog See the [Extension Publishing Guide](publishing.md) for detailed submission instructions. @@ -739,7 +782,8 @@ hooks: description: "Analyze tasks after generation" ``` -Multiple commands on one event, ordered by `priority` (lower runs first): +Multiple commands on one event, surfaced in YAML order by current command +templates (`HookExecutor` instead sorts by `priority`, lower first): ```yaml # extension.yml @@ -806,7 +850,8 @@ reinstall the extension to trigger registration. - **Issues**: Report bugs at GitHub repository - **Discussions**: Ask questions in GitHub Discussions -- **Examples**: See `spec-kit-jira` for full-featured example (Phase B) +- **Examples**: See the bundled [GitHub](https://github.com/github/spec-kit/tree/main/extensions/github) + and [bug](https://github.com/github/spec-kit/tree/main/extensions/bug) extensions --- @@ -820,3 +865,11 @@ reinstall the extension to trigger registration. Happy extending! 🚀 > Agent invocation examples on this page use GitHub Copilot's default skills syntax (`/speckit-*`). For other integrations and modes, see [Command invocation](../integrations/cli.md#command-invocation). + +## See also + +- [Extension API reference](api.md) +- [Extension CLI reference](cli.md) +- [Extension catalogs](catalogs.md) +- [Publishing extensions](publishing.md) +- [Extension user guide](overview.md) diff --git a/docs/extensions/overview.md b/docs/extensions/overview.md index 033e5c2f4b..14bce70127 100644 --- a/docs/extensions/overview.md +++ b/docs/extensions/overview.md @@ -1,6 +1,8 @@ # Extensions Overview -Complete guide for using Spec Kit extensions to enhance your workflow. +Guide for using Spec Kit extensions to add commands, external tool integrations, +quality gates, and other capabilities beyond the built-in Spec-Driven +Development process. ## Table of Contents @@ -34,43 +36,54 @@ Extensions are modular packages that add new commands and functionality to Spec - **Community Driven**: Anyone can create and share extensions - **Version Controlled**: Extensions are versioned independently +### Specification artifact maintenance + +An extension can define commands or hooks that reconcile `spec.md`, `plan.md`, +and `tasks.md` during or after feature delivery. Any such edits follow the +extension's documented behavior and the project's governance; they are not +performed by, or added to the responsibilities of, the core Spec Kit commands. +The team's persistence model separately determines how artifacts are maintained +after delivery. + --- ## Getting Started ### Prerequisites -- Spec Kit version 0.1.0 or higher -- A spec-kit project (directory with `.specify/` folder) +- A current Spec Kit release compatible with the extension's + `requires.speckit_version` constraint +- A project initialized with `specify init` (directory with `.specify/` folder) ### Check Your Version ```bash specify version -# Should show 0.1.0 or higher ``` ### First Extension -Let's install the Jira extension as an example: +Start with the bundled GitHub extension: ```bash # 1. Search for the extension -specify extension search jira +specify extension search github # 2. Get detailed information -specify extension info jira +specify extension info github # 3. Install it -specify extension add jira +specify extension add github +``` -# 4. Configure it -vim .specify/extensions/jira/jira-config.yml +The extension requires Git and a configured GitHub MCP server. Once those are +available, invoke `/speckit-github-taskstoissues` in GitHub Copilot chat to +convert a feature's `tasks.md` into GitHub issues. This is a chat command, not +a terminal command. -# 5. Use it -# (Skills are now available in GitHub Copilot) -/speckit-jira-specstoissues -``` +The Jira examples below illustrate a separately vetted third-party extension. +They assume you have installed it from a reviewed archive or configured your own +trusted catalog; Jira is discovery-only in the built-in community catalog. --- @@ -78,6 +91,10 @@ vim .specify/extensions/jira/jira-config.yml `specify extension search` searches **all active catalogs** simultaneously, including the community catalog by default. Results are annotated with their source catalog and install status. +The community catalog is discovery-only. Vet a release before installing it +directly or adding it to your own trusted catalog; see the +[catalog trust model](catalogs.md#trust-model-discovery-only-vs-install-sources). + ### Browse All Extensions ```bash @@ -143,18 +160,25 @@ Shows: ### Install from Catalog ```bash -# By name (from catalog) -specify extension add jira +# Install a bundled extension by name +specify extension add github ``` -This will: +For an extension advertised by an install-enabled catalog, installation will: -1. Download the extension from GitHub +1. Download the advertised archive (bundled extensions use the shipped copy) 2. Validate the manifest 3. Check compatibility with your spec-kit version -4. Install to `.specify/extensions/jira/` +4. Install to `.specify/extensions//` 5. Register commands with your coding agent -6. Create config template +6. Scaffold any declared configuration templates without overwriting existing + configuration + +After vetting Jira, a catalog you control can advertise it as installable: + +```bash +specify extension add jira +``` ### Install from URL @@ -170,8 +194,21 @@ specify extension add --from https://github.com/org/spec-kit-ex specify extension add --dev /path/to/extension ``` +### Install a specific catalog version + +```bash +specify extension info jira --versions +specify extension add jira --version +``` + +The requested version must be advertised by the winning install-enabled +catalog entry. See the [CLI reference](cli.md#install-an-extension) for +selection rules and option restrictions. + ### Installation Output +Illustrative output for a Jira release installed from a vetted source: + ```text ✓ Extension installed successfully! @@ -242,6 +279,16 @@ To execute: `/speckit-jira-specstoissues` You can then choose to run the hook or skip it. +Mandatory hooks are surfaced as automatic commands. Current command templates +use configured YAML order, skip disabled hooks and hooks with non-empty +conditions, and do not consult the reserved `auto_execute_hooks` setting. +See the [hook configuration contract](api.md#hook-configuration) for details. + +Extensions can also react to coding-agent lifecycle events, such as session +start or tool use. Unlike hooks, runtime events execute scripts directly rather +than sending Markdown instructions to the coding agent. See +[Extension runtime events](api.md#extension-runtime-events). + --- ## Managing Extensions @@ -289,12 +336,20 @@ Update these extensions? [y/N]: ```bash # Disable without removing specify extension disable jira +``` -✓ Extension 'jira' disabled +Declarative hooks are marked disabled in `.specify/extensions.yml`. Native +runtime-event configuration is then refreshed on a best-effort basis. A refresh +failure produces a warning but can leave a stale native hook active and able to +execute. Retry the affected integration with `specify integration upgrade `. +Re-enable the extension with `specify extension enable jira`. -Commands will no longer be available. Hooks will not execute. -To re-enable: specify extension enable jira -``` +For the generic integration, disabling also removes owned command or skill +files. Other integrations can retain generated files that remain invocable +until an integration switch, uninstall, or reconciliation removes them. +Disabled extensions are excluded from registry-driven artifact resolution and +future integration registration; disabling is not a universal file-removal or +execution-blocking mechanism. ### Re-enable Extension @@ -336,7 +391,8 @@ Extensions can have multiple configuration files: Configuration is merged in this order (highest priority last): -1. **Extension defaults** (from `extension.yml`) +1. **Extension defaults** (top-level `defaults` merged with `config.defaults`, + with `config.defaults` winning on conflicts) 2. **Project config** (`jira-config.yml`) 3. **Local overrides** (`local-config.yml`) 4. **Environment variables** (`SPECKIT_JIRA_*`) @@ -367,7 +423,11 @@ project: export SPECKIT_JIRA_PROJECT_KEY="DEVTEST" ``` -Final resolved config uses `DEVTEST` from environment variable. +When the extension uses `ConfigManager` or equivalent loading, the final +resolved config uses `DEVTEST` from the environment variable. Spec Kit does not +automatically inject merged configuration into arbitrary commands or scripts; +the extension must load it. YAML `${...}` strings are not interpolated by +`ConfigManager`. See [Config loading](developing.md#config-loading). ### Project-Wide Extension Settings @@ -381,7 +441,7 @@ installed: # Global settings settings: - auto_execute_hooks: true + auto_execute_hooks: true # Reserved; currently not consulted # Hook configuration # Available events: before_specify, after_specify, before_plan, after_plan, @@ -400,351 +460,8 @@ hooks: prompt: "Create Jira issues from tasks?" ``` -### Core Environment Variables - -In addition to extension-specific environment variables (`SPECKIT_{EXT_ID}_*`), spec-kit supports core environment variables: - -| Variable | Description | Default | -|----------|-------------|---------| -| `SPECKIT_CATALOG_URL` | Override the full catalog stack with a single URL (backward compat) | Built-in default stack | -| `GH_TOKEN` / `GITHUB_TOKEN` | GitHub token for authenticated requests to GitHub-hosted URLs (`raw.githubusercontent.com`, `github.com`, `api.github.com`, `codeload.github.com`). Required when your catalog JSON or extension ZIPs are hosted in a private GitHub repository. | None | - -#### Example: Using a custom catalog for testing - -```bash -# Point to a local or alternative catalog (replaces the full stack) -export SPECKIT_CATALOG_URL="http://localhost:8000/catalog.json" - -# Or use a staging catalog -export SPECKIT_CATALOG_URL="https://example.com/staging/catalog.json" -``` - -#### Example: Using a private GitHub-hosted catalog - -```bash -# Authenticate with a token (gh CLI, PAT, or GITHUB_TOKEN in CI) -export GITHUB_TOKEN=$(gh auth token) - -# Search a private catalog added via `specify extension catalog add` -specify extension search jira - -# Install from a private catalog -specify extension add jira-sync -``` - -The token is attached automatically to requests targeting GitHub domains. Non-GitHub catalog URLs are always fetched without credentials. - ---- - -## Extension Catalogs - -Spec Kit uses a **catalog stack** — an ordered list of catalogs searched simultaneously. By default, two catalogs are active: - -| Priority | Catalog | Install Allowed | Purpose | -|----------|---------|-----------------|---------| -| 1 | `catalog.json` (default) | ✅ Yes | Curated extensions available for installation | -| 2 | `catalog.community.json` (community) | ❌ No (discovery only) | Browse community extensions | - -### Listing Active Catalogs - -```bash -specify extension catalog list -``` - -### Managing Catalogs via CLI - -You can view the main catalog management commands using `--help`: - -```text -specify extension catalog --help - - Usage: specify extension catalog [OPTIONS] COMMAND [ARGS]... - - Manage extension catalogs -╭─ Options ────────────────────────────────────────────────────────────────────────╮ -│ --help Show this message and exit. │ -╰──────────────────────────────────────────────────────────────────────────────────╯ -╭─ Commands ───────────────────────────────────────────────────────────────────────╮ -│ list List all active extension catalogs. │ -│ add Add a catalog to .specify/extension-catalogs.yml. │ -│ remove Remove a catalog from .specify/extension-catalogs.yml. │ -╰──────────────────────────────────────────────────────────────────────────────────╯ -``` - -### Adding a Catalog (Project-scoped) - -```bash -# Add an internal catalog that allows installs -specify extension catalog add \ - --name "internal" \ - --priority 2 \ - --install-allowed \ - https://internal.company.com/spec-kit/catalog.json - -# Add a discovery-only catalog -specify extension catalog add \ - --name "partner" \ - --priority 5 \ - https://partner.example.com/spec-kit/catalog.json -``` - -This creates or updates `.specify/extension-catalogs.yml`. - -### Removing a Catalog - -```bash -specify extension catalog remove internal -``` - -### Manual Config File - -You can also edit `.specify/extension-catalogs.yml` directly: - -```yaml -catalogs: - - name: "default" - url: "https://raw.githubusercontent.com/github/spec-kit/main/extensions/catalog.json" - priority: 1 - 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 - install_allowed: false - description: "Community-contributed extensions (discovery only)" -``` - -A user-level equivalent lives at `~/.specify/extension-catalogs.yml`. Project-level config takes full precedence when it contains one or more catalog entries. An empty `catalogs: []` list falls back to built-in defaults. - -## Organization Catalog Customization - -### Why Customize Your Catalog - -Organizations customize their catalogs to: - -- **Control available extensions** - Curate which extensions your team can install -- **Host private extensions** - Internal tools that shouldn't be public -- **Customize for compliance** - Meet security/audit requirements -- **Support air-gapped environments** - Work without internet access - -### Setting Up a Custom Catalog - -#### 1. Create Your Catalog File - -Create a `catalog.json` file with your extensions: - -```json -{ - "schema_version": "1.0", - "updated_at": "2026-02-03T00:00:00Z", - "catalog_url": "https://your-org.com/spec-kit/catalog.json", - "extensions": { - "jira": { - "name": "Jira Integration", - "id": "jira", - "description": "Create Jira issues from spec-kit artifacts", - "author": "Your Organization", - "version": "2.1.0", - "download_url": "https://github.com/your-org/spec-kit-jira/archive/refs/tags/v2.1.0.zip", - "repository": "https://github.com/your-org/spec-kit-jira", - "license": "MIT", - "requires": { - "speckit_version": ">=0.1.0", - "tools": [ - {"name": "atlassian-mcp-server", "required": true} - ] - }, - "provides": { - "commands": 3, - "hooks": 1 - }, - "tags": ["jira", "atlassian", "issue-tracking"], - "verified": true - }, - "internal-tool": { - "name": "Internal Tool Integration", - "id": "internal-tool", - "description": "Connect to internal company systems", - "author": "Your Organization", - "version": "1.0.0", - "download_url": "https://internal.your-org.com/extensions/internal-tool-1.0.0.zip", - "repository": "https://github.internal.your-org.com/spec-kit-internal", - "license": "Proprietary", - "requires": { - "speckit_version": ">=0.1.0" - }, - "provides": { - "commands": 2 - }, - "tags": ["internal", "proprietary"], - "verified": true - } - } -} -``` - -#### 2. Host the Catalog - -Options for hosting your catalog: - -| Method | URL Example | Use Case | -| ------ | ----------- | -------- | -| GitHub Pages | `https://your-org.github.io/spec-kit-catalog/catalog.json` | Public or org-visible | -| Internal web server | `https://internal.company.com/spec-kit/catalog.json` | Corporate network | -| S3/Cloud storage | `https://s3.amazonaws.com/your-bucket/catalog.json` | Cloud-hosted teams | -| Local file server | `http://localhost:8000/catalog.json` | Development/testing | - -**Security requirement**: URLs must use HTTPS (except `localhost` for testing). - -#### 3. Configure Your Environment - -##### Option A: Catalog stack config file (recommended) - -Add to `.specify/extension-catalogs.yml` in your project: - -```yaml -catalogs: - - name: "my-org" - url: "https://your-org.com/spec-kit/catalog.json" - priority: 1 - install_allowed: true -``` - -Or use the CLI: - -```bash -specify extension catalog add \ - --name "my-org" \ - --install-allowed \ - https://your-org.com/spec-kit/catalog.json -``` - -##### Option B: Environment variable (recommended for CI/CD, single-catalog) - -```bash -# In ~/.bashrc, ~/.zshrc, or CI pipeline -export SPECKIT_CATALOG_URL="https://your-org.com/spec-kit/catalog.json" -``` - -#### 4. Verify Configuration - -```bash -# List active catalogs -specify extension catalog list - -# Search should now show your catalog's extensions -specify extension search - -# Install from your catalog -specify extension add jira -``` - -### Catalog JSON Schema - -Required fields for each extension entry: - -| Field | Type | Required | Description | -| ----- | ---- | -------- | ----------- | -| `name` | string | Yes | Human-readable name | -| `id` | string | Yes | Unique identifier (lowercase, hyphens) | -| `version` | string | Yes | Semantic version (X.Y.Z) | -| `download_url` | string | Yes | URL to ZIP archive | -| `repository` | string | Yes | Source code URL | -| `description` | string | No | Brief description | -| `author` | string | No | Author/organization | -| `license` | string | No | SPDX license identifier | -| `requires.speckit_version` | string | No | Version constraint | -| `requires.tools` | array | No | Required external tools | -| `provides.commands` | number | No | Number of commands | -| `provides.hooks` | number | No | Number of hooks | -| `tags` | array | No | Search tags | -| `verified` | boolean | No | Verification status | - -### Use Cases - -#### Private/Internal Extensions - -Host proprietary extensions that integrate with internal systems: - -```json -{ - "internal-auth": { - "name": "Internal SSO Integration", - "download_url": "https://artifactory.company.com/spec-kit/internal-auth-1.0.0.zip", - "verified": true - } -} -``` - -#### Curated Team Catalog - -Limit which extensions your team can install: - -```json -{ - "extensions": { - "jira": { "..." }, - "github": { "..." } - } -} -``` - -Only `jira` and `github` will appear in `specify extension search`. - -#### Air-Gapped Environments - -For networks without internet access: - -1. Download extension ZIPs to internal file server -2. Create catalog pointing to internal URLs -3. Host catalog on internal web server - -```json -{ - "jira": { - "download_url": "https://files.internal/spec-kit/jira-2.1.0.zip" - } -} -``` - -#### Development/Testing - -Test new extensions before publishing: - -```bash -# Start local server -python -m http.server 8000 --directory ./my-catalog/ - -# Point spec-kit to local catalog -export SPECKIT_CATALOG_URL="http://localhost:8000/catalog.json" - -# Test installation -specify extension add my-new-extension -``` - -### Combining with Direct Installation - -You can still install extensions not in your catalog using `--from`: - -```bash -# From catalog -specify extension add jira - -# Direct URL (bypasses catalog) -specify extension add --from https://github.com/someone/spec-kit-ext/archive/v1.0.0.zip - -# Local development -specify extension add --dev /path/to/extension -``` - -**Note**: Direct URL installation shows a security warning since the extension isn't from your configured catalog. +See the [hook configuration contract](api.md#hook-configuration) for all fields +and current execution limitations. --- @@ -752,15 +469,19 @@ specify extension add --dev /path/to/extension ### Extension Not Found -**Error**: `Extension 'jira' not found in catalog +**Error**: `Extension 'jira' not found in catalog` **Solutions**: 1. Check spelling: `specify extension search jira` -2. Refresh catalog: `specify extension search --help` +2. Inspect the active catalogs: `specify extension catalog list` 3. Check internet connection 4. Extension may not be published yet +`specify extension search --help` displays help; it does not refresh cached +catalog data. Catalog caches normally expire after one hour. Python callers can +explicitly clear them with `ExtensionCatalog.clear_cache()`. + ### Configuration Not Found **Error**: `Jira configuration not found` @@ -918,15 +639,16 @@ env: specify extension update ``` -**Pin versions for stability**: +**Select exact catalog versions for reproducible installation**: -```yaml -# .specify/extensions.yml -installed: - - id: jira - version: "1.0.0" # Pin to specific version +```bash +specify extension add jira --version 1.0.0 ``` +Record the installation command for your team. The `installed` list in +`.specify/extensions.yml` is not a version-lock mechanism; explicitly running +`update` can install a newer version. + ### 6. Minimal Extensions Only install extensions you actively use: @@ -960,7 +682,10 @@ After creating tasks, sync to Jira: ### Q: Do extensions slow down spec-kit? -**A**: No. Extensions are loaded on-demand and only when their commands are used. +**A**: Extension metadata is read during operations such as listing, +installation, and artifact resolution. Hooks can add work around Spec Kit +commands, and runtime events can run scripts during coding-agent lifecycle +events. Their cost depends on the installed extensions and configured behavior. ### Q: Can I create private extensions? @@ -968,15 +693,27 @@ After creating tasks, sync to Jira: ### Q: How do I know if an extension is safe? -**A**: Look for the ✓ Verified badge. Verified extensions are reviewed by maintainers. Always review extension code before installing. +**A**: A verified badge is not a code-audit guarantee. Spec Kit maintainers do +not review, audit, endorse, or support independently maintained extension code. +You must vet the source and release archive before installing; use extensions at +your own discretion. ### Q: Can extensions modify spec-kit core? -**A**: No. Extensions can only add commands and hooks. They cannot modify core functionality. +**A**: Extensions contribute commands, templates, scripts, hooks, and runtime +events without changing Spec Kit's source code. Their commands and hooks can +edit project files under their documented behavior and the project's governance; +see [Specification artifact maintenance](#specification-artifact-maintenance). ### Q: What happens if two extensions have the same command name? -**A**: Extensions use namespaced commands (`speckit.{extension}.{command}`), so conflicts are very rare. The extension system will warn you if conflicts occur. +**A**: Namespaced commands (`speckit.{extension}.{command}`) reduce conflicts. +Installation rejects exact command-name or alias collisions with installed +extensions, and normalized output-name collisions with core commands. +Resolution priority determines precedence for contributed artifacts such as +templates; it does not allow conflicting command names to be installed. +Use [`specify extension set-priority`](cli.md#set-extension-priority) to change +artifact resolution priority. ### Q: Can I contribute to existing extensions? @@ -1009,3 +746,13 @@ After creating tasks, sync to Jira: *Spec Kit Version: 0.1.0* > Agent invocation examples on this page use GitHub Copilot's default skills syntax (`/speckit-*`). For other integrations and modes, see [Command invocation](../integrations/cli.md#command-invocation). + +## See also + +- [Extension CLI reference](cli.md) +- [Extension catalogs](catalogs.md) +- [Community extensions](../community/extensions.md) +- [Developing extensions](developing.md) +- [Extension API reference](api.md) +- [Publishing extensions](publishing.md) +- [Spec persistence models](../concepts/spec-persistence.md) diff --git a/docs/extensions/publishing.md b/docs/extensions/publishing.md index 2fe499093f..a2a2ca41cd 100644 --- a/docs/extensions/publishing.md +++ b/docs/extensions/publishing.md @@ -17,7 +17,8 @@ This guide explains how to publish your extension to the Spec Kit extension cata Before publishing an extension, ensure you have: 1. **Valid Extension**: A working extension with a valid `extension.yml` manifest -2. **Git Repository**: Extension hosted on GitHub (or other public git hosting) +2. **GitHub Repository**: Extension hosted in a public GitHub repository for + community catalog submission 3. **Documentation**: README.md with installation and usage instructions 4. **License**: Open source license file (MIT, Apache 2.0, etc.) 5. **Versioning**: Semantic versioning (e.g., 1.0.0) @@ -83,9 +84,10 @@ tags: # 2-5 relevant tags **Validation Checklist**: -- ✅ `id` is lowercase with hyphens only (no underscores, spaces, or special characters) +- ✅ `id` starts with a lowercase letter and otherwise contains only lowercase + letters, digits, or hyphens (`^[a-z][a-z0-9-]*$` for community submissions) - ✅ `version` follows semantic versioning (X.Y.Z) -- ✅ `description` is concise (under 100 characters) +- ✅ `description` is concise (under 200 characters for community submissions) - ✅ `repository` URL is valid and public - ✅ All command files exist in the extension directory - ✅ Tags are lowercase and descriptive @@ -130,9 +132,13 @@ specify extension add --from https://github.com/your-org/spec-k ### Understanding the Catalogs -Spec Kit uses a dual-catalog system. For details about how catalogs work, see the main [Extensions README](https://github.com/github/spec-kit/blob/main/extensions/README.md#extension-catalogs). +Spec Kit uses a catalog stack with built-in install and discovery sources. +See [Extension catalogs](catalogs.md) for configuration, release metadata, +and trust boundaries. -**For extension publishing**: All community extensions are listed in `extensions/catalog.community.json`. Users browse this catalog and copy extensions they trust into their own `catalog.json`. +**For extension publishing**: All community extensions are listed in +`extensions/catalog.community.json`. Users must vet a release before installing +it directly or copying its entry into their own trusted catalog. ### How to Submit @@ -149,6 +155,28 @@ To submit your extension to the community catalog, file a new issue using the ** > [!IMPORTANT] > Do **not** open a pull request directly to edit `extensions/catalog.community.json`. All community extension submissions must go through the issue template. Submissions that do not use the issue template will be closed, and you will be asked to resubmit using it. +### Submission validation + +Community submissions are stricter than the general extension runtime: + +- The extension ID must match `^[a-z][a-z0-9-]*$`. +- The version must be `X.Y.Z` with digits only and no `v` prefix. The release + tag may include a `v` prefix or a scope, but its version must match the + submitted version. +- The repository must be public and contain `extension.yml`, `README.md`, and + `LICENSE`. +- The download URL must belong to that same GitHub repository and use either + `https://github.com///archive/refs/tags/.zip` or + `https://github.com///releases/download//.zip`. + Floating `releases/latest/` URLs are rejected. +- The archive must be accessible. If a SHA-256 digest is submitted, it must + match the downloaded archive. The automation records the downloaded archive's + digest in the proposed catalog entry even when one was not submitted. + +These are metadata and archive-provenance checks, not a review of extension +code. Other public hosting and private releases can be used with a catalog you +control, but are not accepted through this community submission process. + ### What Happens After You Submit 1. GitHub applies the `triage-must-have` verdict when the issue is opened through the Extension Submission form. This intake automation is specific to extension submissions and does not change the manual triage rubric for other issues. @@ -166,10 +194,6 @@ To submit your extension to the community catalog, file a new issue using the ** > [!NOTE] > Maintainers do **not** review, audit, or test the extension code itself. -### Typical Review Timeline - -- **Review**: 3-7 business days - ### Updating an Existing Extension To update an extension that is already in the catalog (e.g., for a new version), file a new **[Extension Submission](https://github.com/github/spec-kit/issues/new?template=extension_submission.yml)** issue with the updated version, download URL, and any other changed fields. Mention in the issue that this is an update to an existing entry. @@ -254,7 +278,8 @@ When releasing a new version: ### Maintenance -1. **Respond to Issues**: Address issues within 1-2 weeks +1. **Respond to Issues**: Acknowledge and investigate reports as maintainer + capacity permits 2. **Regular Updates**: Keep dependencies updated 3. **Changelog**: Maintain detailed changelog 4. **Deprecation**: Give advance notice for breaking changes @@ -275,11 +300,17 @@ When releasing a new version: A: The main catalog is for public extensions only. For private extensions: - Host your own catalog.json file -- In a Spec Kit project, users add your catalog: `specify extension catalog add https://your-domain.com/catalog.json --name private-catalog` +- After vetting its releases, users add your catalog as an install source: + + ```bash + specify extension catalog add https://your-domain.com/catalog.json \ + --name private-catalog --install-allowed + ``` ### Q: How long does review take? -A: Typically 3-7 business days. Updates to existing extensions are usually faster. +A: Submissions wait for maintainer triage, automated validation, and review of +the generated catalog pull request. There is no promised turnaround. ### Q: What if my extension is rejected? @@ -301,53 +332,19 @@ A: Extensions should be free and open-source. Commercial support/services are al ## Support -- **Catalog Issues**: -- **Extension Template**: (coming soon) +- **Catalog Issues**: +- **Extension Submission**: [Submit or update a community extension](https://github.com/github/spec-kit/issues/new?template=extension_submission.yml) - **Development Guide**: See [Developing extensions](developing.md) - **Community**: Discussions and Q&A --- -## Appendix: Catalog Schema - -### Complete Catalog Entry Schema - -```json -{ - "name": "string (required)", - "id": "string (required, unique)", - "description": "string (required, <200 chars)", - "author": "string (required)", - "version": "string (required, semver)", - "download_url": "string (required, valid URL)", - "sha256": "string (optional, SHA-256 hex digest of the archive at download_url; verified before install)", - "repository": "string (required, valid URL)", - "homepage": "string (optional, valid URL)", - "documentation": "string (optional, valid URL)", - "changelog": "string (optional, valid URL)", - "license": "string (required)", - "requires": { - "speckit_version": "string (required, version specifier)", - "tools": [ - { - "name": "string (required)", - "version": "string (optional, version specifier)", - "required": "boolean (default: false)" - } - ] - }, - "provides": { - "commands": "integer (optional)", - "hooks": "integer (optional)" - }, - "tags": ["array of strings (2-10 tags)"], - "verified": "boolean (default: false, set by maintainers)", - "downloads": "integer (auto-updated)", - "stars": "integer (auto-updated)", - "created_at": "string (ISO 8601 datetime)", - "updated_at": "string (ISO 8601 datetime)" -} -``` +## Catalog metadata + +Use the canonical [catalog entry schema](catalogs.md#catalog-entry-schema), +including [historical release records](catalogs.md#historical-releases) when +advertising older versions. For community submissions, provide all descriptive +fields collected by the issue form. ### Valid Tags @@ -364,3 +361,11 @@ Use 2-5 tags that best describe your extension. *Last Updated: 2026-01-28* *Catalog Format Version: 1.0* + +## See also + +- [Developing extensions](developing.md) +- [Extension API reference](api.md) +- [Extension catalogs](catalogs.md) +- [Extension CLI reference](cli.md) +- [Community extensions](../community/extensions.md) diff --git a/docs/guides/customization.md b/docs/guides/customization.md index f2ab8f53cd..3b049ad84d 100644 --- a/docs/guides/customization.md +++ b/docs/guides/customization.md @@ -46,7 +46,8 @@ Examples include Jira integration, post-implementation code review, V-Model test traceability, and project health diagnostics. The bundled [bug](bugfix.md) and [assess](assessment.md) extensions are first-party examples. Browse [community extensions](../community/extensions.md), or use the -[extension reference](../reference/extensions.md) for management and configuration. +[extension CLI reference](../extensions/cli.md) for management and the +[extension user guide](../extensions/overview.md#configuration) for configuration. ## Presets: change how a process works diff --git a/docs/index.md b/docs/index.md index 9e07cf7da0..36a8269f20 100644 --- a/docs/index.md +++ b/docs/index.md @@ -77,7 +77,7 @@ Including entirely different processes: - **Fiction Book Writing** — novels and long-form fiction, from story bible to submission Presets →   -Extensions →   +Extensions →   Workflows →   Bundles → diff --git a/docs/reference/catalogs.md b/docs/reference/catalogs.md index 7299e6851d..11106c68d9 100644 --- a/docs/reference/catalogs.md +++ b/docs/reference/catalogs.md @@ -54,7 +54,7 @@ project: | Component type | List active catalogs | Detailed reference | | --- | --- | --- | | Integrations | `specify integration catalog list` | [Integrations](../integrations/cli.md#catalog-management) | -| Extensions | `specify extension catalog list` | [Extensions](extensions.md#catalog-management) | +| Extensions | `specify extension catalog list` | [Extensions](../extensions/catalogs.md) | | Presets | `specify preset catalog list` | [Presets](presets.md#catalog-management) | | Workflows | `specify workflow catalog list` | [Workflows](workflows.md#catalog-management) | | Workflow steps | `specify workflow step catalog list` | [Workflows](workflows.md#catalog-management) | diff --git a/docs/reference/extensions.md b/docs/reference/extensions.md deleted file mode 100644 index 72ca64d3dc..0000000000 --- a/docs/reference/extensions.md +++ /dev/null @@ -1,413 +0,0 @@ -# Extensions - -Extensions add new capabilities to Spec Kit — domain-specific commands, external tool integrations, quality gates, and more. They introduce new commands and templates that go beyond the built-in Spec-Driven Development workflow. - -An extension can define commands or hooks that reconcile `spec.md`, `plan.md`, -and `tasks.md` during or after feature delivery. Any such edits follow the -extension's documented behavior and the project's governance; they are not -performed by, or added to the responsibilities of, the core Spec Kit commands. -The team's persistence model separately determines how artifacts are maintained -after delivery. - -## Search Available Extensions - -```bash -specify extension search [query] -``` - -| Option | Description | -| ------------ | ------------------------------------ | -| `--tag` | Filter by tag | -| `--author` | Filter by author | -| `--verified` | Show only verified extensions | - -Searches all active catalogs for extensions matching the query. Without a query, lists all available extensions. - -## Install an Extension - -```bash -specify extension add -``` - -| Option | Description | -| --------------- | -------------------------------------------------------- | -| `--dev` | Install from a local directory (for development) | -| `--from ` | Install from a custom URL instead of the catalog | -| `--version ` | Install an exact version advertised by a catalog | -| `--force` | Overwrite if the extension is already installed | -| `--priority `| Resolution priority (default: 10; lower = higher precedence) | - -Installs an extension from the catalog, a URL, or a local directory. Extension commands are registered with the active AI coding agent integration. For `generic`, invocations use the configured `--commands-dir`: flat command files by default, or `speckit-/SKILL.md` with `--skills`. - -If a generic integration refresh cannot produce every extension invocation (for example, because a command or skill is user-modified or its source is missing), it warns and restores that extension's prior registered artifacts. Other extensions can still refresh. - -An unqualified catalog install still selects the advertised current version. -`--version` uses only the winning catalog source for that extension ID; it does -not fall back to a lower-priority source when the requested version is absent. -Discovery-only catalogs remain non-installable. `--version` cannot be combined -with `--dev` or the direct-URL `--from` option. The downloaded archive's extension -ID and version are checked before installation. - -> **Note:** All extension commands require a project already initialized with `specify init`. - -## Remove an Extension - -```bash -specify extension remove -``` - -| Option | Description | -| --------------- | ---------------------------------------------- | -| `--keep-config` | Preserve configuration files during removal | -| `--force` | Skip confirmation prompt | - -Removes an installed extension. Configuration files are backed up by default; use `--keep-config` to leave them in place or `--force` to skip the confirmation. - -## List Installed Extensions - -```bash -specify extension list -specify extension list --json -``` - -| Option | Description | -| ------------- | -------------------------------------------------- | -| `--available` | Show available (uninstalled) extensions | -| `--all` | Show both installed and available extensions | -| `--json` | Write installed extensions as JSON | - -Lists installed extensions with their status, version, and command counts. - -`--json` writes a JSON array to stdout. Every item has the keys `id`, `name`, -`description`, `version`, `author`, `priority`, `enabled`, `source`, and -`provides`. `author` is `null` when absent; `source` is `{"kind":"local"}` -for local, legacy, or malformed provenance, or -`{"kind":"catalog","catalog":""}` for a valid catalog source. -Extension `provides` contains `commands`, `templates`, `scripts`, and `hooks` -counts. `--available` and `--all` do not broaden JSON output beyond installed -extensions. On success, `--json` writes exactly one array to stdout and exits -0. A runtime failure after option parsing writes exactly one -`{"error":"..."}` object to stderr and exits 1. If parsing raises a usage -error and the raw `--json` token is present, it writes that JSON error object -to stderr and preserves the usage exit code (normally 2). Without `--json`, -including for help, the existing human-readable behavior is unchanged. - -## Extension Info - -```bash -specify extension info -specify extension info --versions -specify extension info --json -``` - -Shows detailed information about an installed or available extension, including its description, version, commands, and configuration. -`--versions` lists the current and historical versions advertised by the -winning catalog source; it labels discovery-only sources as non-installable. -Equivalent PEP 440 version spellings (for example, `v1.0` and `1.0`) select -the same release; the catalog's advertised spelling remains visible. - -Catalogs may keep the current release in the existing top-level fields and add -historical releases in a `releases` mapping. Older single-version catalogs -continue to work unchanged. Each historical release needs its own download URL -and SHA-256 digest; release-specific requirements or provided capabilities must -be placed in that release's record rather than inherited from the current one. -As with current releases, a digest may use a case-insensitive `sha256:` prefix -and surrounding whitespace. - -```json -{ - "extensions": { - "my-extension": { - "name": "My Extension", - "version": "0.5.1", - "download_url": "https://example.com/my-extension-0.5.1.zip", - "sha256": "<64-character SHA-256 for 0.5.1>", - "releases": { - "0.4.12": { - "download_url": "https://example.com/my-extension-0.4.12.zip", - "sha256": "<64-character SHA-256 for 0.4.12>" - } - } - } - } -} -``` - -The example omits other catalog metadata for brevity. The current version must -not be repeated in `releases`; malformed or duplicate release records are -rejected. Bundle pins still use the current catalog resolution path until the -separate bundle work described in [#4719](https://github.com/github/spec-kit/issues/4719) -adds exact-version component lookup. - -`--json` describes one installed extension and writes a single JSON object to -stdout. `` is matched as without `--json`: the extension ID first, then a -unique display name, ignoring case. The object has the same `id`, `name`, -`description`, `version`, `author`, `priority`, `enabled`, and `source` keys as -the matching `specify extension list --json` item. In place of the `provides` -counts it has `commands`, `templates`, `scripts`, and `hooks` arrays. Command, -template, and script entries have `name`, `description` (`""` when absent), -`source` (`{"layer": "extension", "extensionId": ""}`, the extension that -provides it; the top-level `source` is where it was installed from), and -`sourcePath` (the manifest `file`, -relative to the extension directory); script entries also have `runtimes` when -the manifest declares them. Extension entries carry no `strategy`, because -extension-provided files always replace. Each hook entry has `trigger` (the -hook event, such as `after_tasks`), `targetCommand`, `optional` (default -`true`), and `priority` (default `10`). When an event declares the same command -more than once, the last declaration wins, as it does when hooks are -registered. `--json` cannot be combined with `--versions` (usage error, exit 2). -An extension that is not installed, an ambiguous name, or a missing project -writes one `{"error":"..."}` object to stderr and exits 1; usage errors keep -their exit code (normally 2), as with `list --json`. - -## Update Extensions - -```bash -specify extension update [] -``` - -Updates a specific extension, or all installed extensions if no name is given. - -Bundled extensions (such as `agent-context` and `git`) have no download URL; their updates install from the copy shipped with the running spec-kit release. When the catalog advertises a newer version than your spec-kit release ships, the update is reported as requiring a spec-kit upgrade first. - -For `generic`, a failed update restores hash-owned invocations from previously configured `--commands-dir` locations as well as the current location, even if another integration is now active. - -## Enable / Disable an Extension - -```bash -specify extension enable -specify extension disable -``` - -Disable an extension without removing it. Disabled extensions are not loaded and their commands are not available. Hook-only extensions can be installed, enabled, and disabled even if generic command-output settings are missing or invalid; extensions with commands still require valid settings. Re-enable with `enable`. - -For `generic`, disabling removes hash-owned invocations even after the output directory moves, but preserves unrelated same-named files in the new directory. - -## Set Extension Priority - -```bash -specify extension set-priority -``` - -Changes the resolution priority of an extension. When multiple extensions provide a command with the same name, the extension with the lowest priority number takes precedence. - -## Catalog Management - -Extension catalogs control where `search` and `add` look for extensions. Catalogs are checked in priority order (lower number = higher precedence). - -### Trust model: discovery-only vs. install sources - -Catalogs come in two kinds, and the distinction is a **security boundary**, not a limitation: - -- **Install sources** (`install_allowed: true`) — catalogs you trust as a place to install from. The built-in `default` (official) catalog is one, as is any catalog you author and vet yourself. -- **Discovery-only** catalogs (`install_allowed: false`) — searchable surfaces for *finding* extensions, but not installable. The built-in `community` catalog is discovery-only and is already active for `search` out of the box; you do not need to add it. - -`community` is intentionally discovery-only because it is an open, unvetted list. Making everything in it one-command-installable would mean pulling arbitrary third-party code with no review. - -> **Do not flip a discovery-only catalog to `install_allowed`.** That defeats the entire point of separating discovery from installation. There are two correct ways to install something you found via `community`: -> -> 1. **Install a single vetted extension directly** with `--from` (no catalog authoring needed). Get the candidate archive URL from `specify extension info ` — for a discovery-only entry it prints a "Candidate archive" URL. Review that release archive, then install it: -> -> ```bash -> specify extension info # shows the candidate archive URL -> specify extension add --from -> ``` -> -> Treat the URL as untrusted until you have vetted it — it comes from an unvetted catalog. -> 2. **Curate your own catalog** you control and vet, and mark *that* catalog `install_allowed: true` — for when you want a governed, reusable install source (e.g. for an org). - -### List Catalogs - -```bash -specify extension catalog list -``` - -Shows all active catalogs in the stack with their priorities and install permissions. - -### Add a Catalog - -```bash -specify extension catalog add -``` - -| Option | Description | -| ------------------------------------ | -------------------------------------------------- | -| `--name ` | Required. Unique name for the catalog | -| `--priority ` | Priority (default: 10; lower = higher precedence) | -| `--install-allowed / --no-install-allowed` | Mark the catalog as a trusted install source. Only enable for a catalog you own and vet; leave off (the default) for discovery-only sources. Never enable it for an unvetted public catalog. | -| `--description ` | Optional description | - -Adds a catalog to the project's `.specify/extension-catalogs.yml`. - -Re-adding the same named catalog with identical settings succeeds without changing the configuration; different settings are rejected. - -### Remove a Catalog - -```bash -specify extension catalog remove -``` - -Removes a catalog from the project configuration. - -### Catalog Resolution Order - -Catalogs are resolved in this order (first match wins): - -1. **Environment variable** — `SPECKIT_CATALOG_URL` overrides all catalogs -2. **Project config** — `.specify/extension-catalogs.yml` -3. **User config** — `~/.specify/extension-catalogs.yml` -4. **Built-in defaults** — official `default` catalog (install-allowed) + `community` catalog (discovery-only) - -Example `.specify/extension-catalogs.yml` for a catalog you own and vet: - -```yaml -catalogs: - - name: "my-org-catalog" - url: "https://example.com/catalog.json" - priority: 5 - install_allowed: true - description: "Our approved extensions" -``` - -## Extension Configuration - -Most extensions include configuration files in their install directory: - -```text -.specify/extensions// -├── -config.yml # Project config (version controlled) -├── -config.local.yml # Local overrides (gitignored) -└── -config.template.yml # Template reference -``` - -Configuration is merged in this order (highest priority last): - -1. **Extension defaults** (from `extension.yml`) -2. **Project config** (`-config.yml`) -3. **Local overrides** (`-config.local.yml`) -4. **Environment variables** (`SPECKIT__*`) - -To set up configuration for a newly installed extension, copy the template: - -```bash -cp .specify/extensions//-config.template.yml \ - .specify/extensions//-config.yml -``` - -## Extension Runtime Events - -Extensions can react to coding-agent lifecycle events by declaring a top-level -`events` mapping in `extension.yml`: - -```yaml -events: - session_start: - command: speckit.my-extension.initialize - pre_tool_use: - command: speckit.my-extension.validate -``` - -Event declarations are integration-agnostic. Spec Kit refreshes every installed -event-capable integration, and each integration translates the canonical event -names into its coding agent's native event configuration. Supported canonical -events are `session_start`, `pre_tool_use`, `post_tool_use`, -`user_prompt_submit`, `stop`, and `session_end`; each integration may support -only a subset. - -Runtime events are distinct from extension hooks. Events react to the coding -agent's lifecycle and execute the referenced command template's `scripts` -entry directly. The referenced command must therefore provide a runnable -script; its Markdown instructions are not sent to the coding agent. Hooks -instead surface extension commands before or after Spec Kit commands such as -`plan`, `tasks`, and `implement`. - -## Project Extension and Hook Configuration - -Spec Kit stores project-level extension registration and hook configuration in: - -```text -.specify/extensions.yml -``` - -The file contains installed extensions, global settings, and hooks that are surfaced before or after Spec Kit commands. - -```yaml -installed: - - git - - my-extension - -settings: - auto_execute_hooks: true - -hooks: - before_implement: - - extension: git - command: speckit.git.commit - enabled: true - optional: true - priority: 10 - prompt: "Commit outstanding changes before implementation?" - description: "Auto-commit before implementation" - - after_implement: - - extension: my-extension - command: speckit.my-extension.verify - enabled: true - optional: false - priority: 5 - description: "Run verification after implementation" -``` - -### Configuration fields - -The top-level `installed` list records extensions installed in the project. The `settings` mapping stores project-wide extension settings, and `hooks` groups hook registrations by event. - -`auto_execute_hooks` defaults to `true`, but is currently reserved and is not consulted when hooks are surfaced or invoked. - -Each hook entry supports the following fields: - -| Field | Description | -| --- | --- | -| `extension` | ID of the extension that registered the hook. | -| `command` | Extension command associated with the hook. | -| `enabled` | Whether the hook is active. Hooks with `enabled: false` are skipped. | -| `optional` | Whether the hook is optional. If `true`, the hook is presented with its `prompt` and can be skipped; if `false`, the hook is emitted as an automatic hook (includes `EXECUTE_COMMAND` markers). | -| `priority` | Priority metadata for the hook. Registered hook entries use integer values >= 1; entries installed from manifests default to `10` when no priority is declared. Current command templates surface hooks in their configured YAML order and do not sort them by `priority`. | -| `prompt` | Message shown when asking whether to run an optional hook. | -| `description` | Human-readable explanation of what the hook does. | -| `condition` | Optional expression evaluated by `HookExecutor` (using `config.` or `env.` with `is set`, `==`, or `!=`). Current command templates do not evaluate conditions and skip hooks with a non-empty condition. | - -Hook event names identify when a hook is invoked. They generally use `before_` or `after_`, such as `before_implement`, `after_implement`, `before_tasks`, and `after_tasks`. - -Extension manifests reject invalid hook priorities during installation. For existing `.specify/extensions.yml` entries, `HookExecutor.get_hooks_for_event()` sorts with `normalize_priority()`: missing values, booleans, non-numeric values rejected by `int()`, and values less than `1` fall back to `10`; numeric strings and finite floats are coerced with `int()`, while non-finite floats are unsupported and may fail instead of falling back. - -`HookExecutor.get_hooks_for_event()` returns hooks ordered by `priority`, with lower values first. However, current command templates read hook lists directly and surface them in their configured YAML order rather than using priority ordering. - -## FAQ - -### Why can't I find an extension with `search`? - -Check the spelling of the extension name. The extension may not be published yet, or it may be in a catalog you haven't added. Use `specify extension catalog list` to see which catalogs are active. - -### Why doesn't the extension command appear in my AI coding agent? - -Verify the extension is installed and enabled with `specify extension list`. If it shows as installed, restart your AI coding agent — it may need to reload for it to take effect. - -### How do I set up extension configuration? - -Copy the config template that ships with the extension: - -```bash -cp .specify/extensions//-config.template.yml \ - .specify/extensions//-config.yml -``` - -See [Extension Configuration](#extension-configuration) for details on config layers and overrides. - -### How do I resolve an incompatible version error? - -Update Spec Kit to the version required by the extension. - -### Who maintains extensions? - -Most extensions are independently created and maintained by their respective authors. The Spec Kit maintainers do not review, audit, endorse, or support extension code. Review an extension's source code before installing and use at your own discretion. For issues with a specific extension, contact its author or file an issue on the extension's repository. diff --git a/docs/reference/overview.md b/docs/reference/overview.md index d10ffc3251..3cf7324595 100644 --- a/docs/reference/overview.md +++ b/docs/reference/overview.md @@ -36,7 +36,7 @@ explicit discovery and installation policies. Extensions add new capabilities to Spec Kit — domain-specific commands, external tool integrations, quality gates, and more. They are discovered through catalogs and can be installed, updated, enabled, disabled, or removed independently. Multiple extensions can coexist in a single project. -[Extensions reference →](extensions.md) +[Extension CLI reference →](../extensions/cli.md) ## Presets diff --git a/docs/toc.yml b/docs/toc.yml index c1f11b3ccc..31085d637d 100644 --- a/docs/toc.yml +++ b/docs/toc.yml @@ -141,6 +141,10 @@ items: - name: Overview href: extensions/overview.md + - name: CLI Reference + href: extensions/cli.md + - name: Catalogs + href: extensions/catalogs.md - name: Developing href: extensions/developing.md - name: API Reference @@ -161,8 +165,6 @@ href: integrations/cli.md - name: Catalogs href: reference/catalogs.md - - name: Extensions - href: reference/extensions.md - name: Presets href: reference/presets.md - name: Artifacts diff --git a/docs/upgrade.md b/docs/upgrade.md index 943d9f41a1..453d63aa2e 100644 --- a/docs/upgrade.md +++ b/docs/upgrade.md @@ -154,7 +154,7 @@ Run: specify extension update ``` -With no extension argument, this updates all installed extensions. Use `specify extension update ` to update only one extension. See the [extensions reference](reference/extensions.md#update-extensions) for details. +With no extension argument, this updates all installed extensions. Use `specify extension update ` to update only one extension. See the [extension CLI reference](extensions/cli.md#update-extensions) for details. ### Fallback: re-run init