Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,19 @@ fix to a broken entry is still an update and needs the same validation. Always p
`download_url` to a release tag (e.g. `.../releases/download/<tag>/...` or
`.../archive/refs/tags/<tag>.zip`); never use `releases/latest/`.

### External agent adapters

External integrations adapt the host's commands and extension/preset
contributions; they do not redistribute a core command inventory. Publish a
standalone package with root `integration.yml` and `__init__.py`, then advertise
its pinned archive URL and preferably its SHA-256 in a catalog. Registering a
catalog only enables discovery/download; importing executable adapter code
requires the user's trust decision and an install-enabled source. Follow the
[integration API and lifecycle design](design/integration.md#external-adapter-package-contract)
and [integration catalog contribution guide](integrations/CONTRIBUTING.md).
Add public-path positive and negative tests rather than injecting test classes
directly into the registry; include fresh-process loading and rollback evidence.

### Branch naming

When an issue exists, name the branch `<type>/<issue-number>-<short-slug>`.
Expand Down
250 changes: 243 additions & 7 deletions design/integration.md

Large diffs are not rendered by default.

110 changes: 107 additions & 3 deletions docs/reference/integrations.md
Original file line number Diff line number Diff line change
Expand Up @@ -77,11 +77,12 @@ specify integration list

| Option | Description |
| ----------- | ----------------------------------------------------------------------------------------------------------------------- |
| `--catalog` | Also browse the catalog (built-in **and** community). Community integrations that are not built in are only shown here. |
| `--catalog` | Also browse the catalog, including external integrations not installed in this project. |

Shows the built-in integrations, which one is currently installed, and whether each requires a CLI tool or is IDE-based.
Shows built-in and trusted installed external integrations, which one is
currently installed, and whether each requires a CLI tool or is IDE-based.
When multiple integrations are installed, the list marks the default integration separately from the other installed integrations.
The list also shows whether each built-in integration is declared multi-install safe.
The list also shows whether each integration is declared multi-install safe.

## Search Available Integrations

Expand Down Expand Up @@ -119,6 +120,7 @@ specify integration install <key>
| `--script sh\|ps\|py` | Script type: `sh` (bash/zsh), `ps` (PowerShell), or `py` (Python) |
| `--force` | Opt in to installing alongside integrations that are not declared multi-install safe |
| `--integration-options` | Integration-specific options (e.g. `--integration-options="--commands-dir .myagent/cmds"`) |
| `--trust-integration` | After reviewing the code, pre-authorize an external adapter's Python execution without the interactive trust prompt |

Installs the specified integration into the current project. If another integration is already installed, the command only proceeds automatically when all involved integrations are declared multi-install safe. Otherwise, use `switch` to replace the default integration or pass `--force` to explicitly opt in to multi-install. If the installation fails partway through, it automatically rolls back to a clean state.

Expand All @@ -134,6 +136,75 @@ Installed extensions and presets are not registered for a non-default integratio

> **Note:** All integration management commands require a project already initialized with `specify init`. To start a new project with a specific agent, use `specify init <project> --integration <key>` instead.

### Catalog-installed external adapters

Register a reviewed catalog in the initialized project, then install its adapter:

```bash
specify integration catalog add https://example.com/catalog.json --name samples
specify integration install sample-agent
specify integration use sample-agent
```

Installation prompts for trust **before** downloading/importing Python. For
automation, explicitly authorize code you have reviewed:

```bash
specify integration install sample-agent --trust-integration
specify integration upgrade sample-agent --trust-integration
```

The source must have `install_allowed: true`. The default community catalog is
discovery-only; neither `--force` nor the trust flag bypasses that policy.
Catalog listing/search/info do not import catalog code. Required external entry
fields are a map key matching the descriptor ID, name, version, description,
and an archive `download_url`; an optional explicit `id` must match the map key;
an archive `sha256` digest is recommended. Downloads support ZIP, tar.gz, and tgz,
HTTPS or loopback HTTP, and the existing authenticated GitHub asset flow.
See the [catalog schema](../../integrations/README.md#catalog-schema).
Search advertises `specify integration install <id>` for install-enabled sources;
discovery-only results do not advertise installation.

A package contains root `integration.yml` and `__init__.py`, not a copied
inventory of Spec Kit's commands. The host renders shared templates through the
adapter and registers installed extension/preset contributions for the default
integration. Code persists under `.specify/integrations/packages/<id>/`,
separately from generated agent files and their manifests. New CLI processes
load the trusted package without consulting the catalog. Missing, modified, or
incompatible code is an error, not a silent fallback. Upgrade installs the
catalog's current adapter version; uninstall removes its persisted code while
preserving modified generated files by default.
Metadata-only `workflow status` and `workflow info` remain available without
loading adapter code, including when an installed adapter is damaged. Workflow
execution and resume still report adapter-loading failures explicitly.

Execution consent is stored in `~/.specify/integration-trust.json`, bound to
the canonical project root, integration ID, and complete verified package
digest. It is checked before loading, even when adapter configuration is
cached. A project's `packages.json` is provenance, not permission; copying
it cannot transfer consent. The managed `.specify/.gitignore` excludes
`integrations/packages/` and `integrations/packages.json`.
After copying a project or changing users, review the adapter and reauthorize
from an install-enabled catalog:

```bash
specify integration upgrade sample-agent --force --trust-integration
```

Forced uninstall also works when package code is untrusted or its entire
directory is missing; it does not import that code.

For initialization, a project/user catalog or `SPECKIT_INTEGRATION_CATALOG_URL`
can supply an external adapter:

```bash
specify init my-project --integration sample-agent --trust-integration
```

Review the [external adapter API](../../design/integration.md#external-adapter-package-contract)
before publishing a package. No pip installation or source-registry edit is
needed for adapters using the host API and standard library.

**Version note:** Controlled multi-install support was introduced in Spec Kit 0.8.5. If `specify integration install <key>` says another integration is already installed and only suggests `switch` or `uninstall`, check your local CLI with `specify version` and upgrade it. Running a one-shot command such as `uvx --from git+https://github.com/github/spec-kit.git specify ...` uses a temporary copy for that command only; it does not update the persistent `specify` executable on your `PATH`.

## Uninstall an Integration
Expand Down Expand Up @@ -192,13 +263,42 @@ specify integration upgrade [<key>]
| `--force` | Overwrite files even if they have been modified |
| `--script sh\|ps\|py` | Script type: `sh` (bash/zsh), `ps` (PowerShell), or `py` (Python) |
| `--integration-options` | Options for the integration |
| `--trust-integration` | Authorize downloading/importing the reviewed replacement external adapter |

Reinstalls an installed integration with updated templates and commands (e.g., after upgrading Spec Kit). Defaults to the default integration; if a key is provided, it must be one of the installed integrations. Detects locally modified files and blocks the upgrade unless `--force` is used. Stale files from the previous install that are no longer needed are removed automatically. Shared templates stay aligned with the default integration even when upgrading a non-default integration.

Enabled extensions and presets are re-registered only when upgrading the currently active (default) integration. A non-default upgrade still refreshes that integration's core commands, but does not re-register its extension or preset layers — `use`/`switch` that integration afterward to rescaffold them.

If the generated-file manifest is missing, upgrade reports that there is nothing
to upgrade and leaves the installed adapter package, generated files, and local
recovery ownership unchanged, including with `--force`. Replacement code is
persisted only after the upgrade regenerates the managed files successfully.

If an upgrade would change an integration between command and skills layouts while preset artifacts are registered for it, the upgrade is rejected before changing files. Remove the affected presets, run the layout-changing upgrade, then reinstall them.

For external adapters, `upgrade --force` and `uninstall --force` can also recover
missing, modified, incompatible, or import-failing installed code using validated
user-local registrar/path ownership metadata, rejecting edited project cleanup
claims and overlap with another integration's root. Without local ownership
proof, old-only generated files are preserved with a manual-cleanup warning.
A trusted replacement can still overwrite files at its declared destination
under `upgrade --force`. Recovery is reported explicitly and does not bypass source policy
or the replacement package's trust decision. Failed lifecycle operations restore
only operation-owned changes. Independent workflow progress and unowned user
files are preserved; conflicting concurrent managed-file edits are reported with
retained recovery snapshots.
Rollback snapshots are lazy and bounded to 128 MiB of file content and 4,096
entries per operation; an oversized snapshot refuses the affected mutation.
No-op operations do not copy agent directories or the installed package store.
Custom adapters must journal writes to existing files through the host's
before-write helpers or `IntegrationManifest.record_file()`. Recording a new
or unchanged file afterward remains supported; an unobserved overwrite is
reported as unrecoverable rather than deleting the resulting file.
Host writes reject symlinked destinations and ancestors before writing; forced
removal of an owned leaf symlink unlinks only the link. Concurrent workflow
dispatch pins the requested project's adapter and verified imports until the
dispatch finishes, without serializing independent agent processes.

## Report Integration Status

```bash
Expand All @@ -224,6 +324,10 @@ list, or records no installed integrations.

Integration catalogs control where the discovery commands (`search` and `info`) look for integrations. Catalogs are checked in priority order.

Catalog management, `integration list --catalog`, and `integration info` do not
execute adapter code. Ordinary integration listing, setup, selection, status,
registration, and workflow dispatch load trusted installed implementations.

### List Catalogs

```bash
Expand Down
6 changes: 6 additions & 0 deletions docs/reference/workflows.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,12 @@ For `failed` and `aborted` runs, the payload includes an `error` field carrying

`completed` and `paused` runs omit the `error` field. The error is persisted in the run's `state.json`, so `specify workflow status <run_id> --json` surfaces the same message after the fact.

Adapter-load failures before run creation have no run ID. I/O failures during
execution or resume instead report the actual run and workflow IDs with a
failed JSON outcome; they are not classified as adapter-load failures. If
saving state fails, the on-disk status may still reflect the last successful
save rather than the reported I/O failure.

> **Note:** Most workflow commands require a project already initialized with `specify init`. The exception is `specify workflow run <local-file.{yml,yaml}>`, which can run outside a project; in that case, run state is stored under the current directory's `.specify/workflows/runs/<run_id>/`.
## Resume a Workflow
Expand Down
95 changes: 72 additions & 23 deletions integrations/CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,9 @@ Community integrations are contributed by external developers and listed in `int

### Prerequisites

1. **Working external integration** — distributed from its own repository; a community catalog listing alone does not make it installable through `specify integration install`
1. **Working external integration** — distribute a standalone ZIP or tar.gz
with root `integration.yml` and `__init__.py`; a discovery-only community
listing does not grant installation permission
2. **Public repository** — hosted on GitHub or similar
3. **`integration.yml` descriptor** — valid descriptor file (see below)
4. **Documentation** — README with usage instructions
Expand All @@ -56,39 +58,55 @@ Every community integration must include an `integration.yml`:
```yaml
schema_version: "1.0"
integration:
id: "my-agent"
name: "My Agent"
id: "sample-agent"
name: "Sample Agent"
version: "1.0.0"
description: "Integration for My Agent"
description: "Adapter for Sample Agent"
author: "your-name"
repository: "https://github.com/your-name/speckit-my-agent"
repository: "https://github.com/your-name/speckit-sample-agent"
license: "MIT"
requires:
speckit_version: ">=0.6.0"
speckit_version: ">=1.1.2.dev0"
tools:
- name: "my-agent"
version: ">=1.0.0"
- name: "sample-agent"
required: true
provides:
commands:
- name: "speckit.specify"
file: "templates/speckit.specify.md"
scripts:
- update-context.sh
```

The root module exports exactly one adapter subclass, whose `key` and
`config.name` match the descriptor. Prefer `SkillsIntegration`,
`MarkdownIntegration`, `TomlIntegration`, or `YamlIntegration` to render the
host templates. Do not duplicate or enumerate Spec Kit's core commands.
Additional relative-import helper modules are allowed; external packages using
only the host API and standard library require neither pip installation nor
changes to the source registry. See the complete
[external adapter contract](../design/integration.md#external-adapter-package-contract).

### Descriptor Validation Rules

| Field | Rule |
|-------|------|
| `schema_version` | Must be `"1.0"` |
| `integration.id` | Lowercase alphanumeric + hyphens (`^[a-z0-9-]+$`) |
| `integration.id` | External package IDs start with a lowercase letter/digit, then lowercase alphanumeric + hyphens (`^[a-z0-9][a-z0-9-]*$`); built-in and Windows device names are reserved |
| `integration.version` | Valid PEP 440 version (parsed with `packaging.version.Version()`) |
| `requires.speckit_version` | Required field; specify a version constraint such as `>=0.6.0` (current validation checks presence only) |
| `provides` | Must include at least one command or script |
| `requires.speckit_version` | Required valid PEP 440 constraint, enforced during install and load |
| `requires.tools` | Optional list; required executables are checked on PATH; version detection belongs to the adapter |
| `provides` | Optional legacy metadata; an adapter need not provide commands or scripts |
| `provides.commands[].name` | String identifier |
| `provides.commands[].file` | Relative path to template file |

Publish a pinned archive `download_url`, preferably with a hexadecimal archive
`sha256` digest. The catalog's ID/name/version/description and any optional
descriptor metadata or requirements must match `integration.yml`.
Never rely on importing a catalog to register your class: discovery does not
execute code. Installation from an install-enabled source requires an explicit
trust decision before import. Catalog maintainers review listing metadata, not
adapter implementations; users must vet the code.
Consent is user-local in `~/.specify/integration-trust.json`, bound to the
canonical project root, adapter ID, and verified package digest. Do not ship a
trust registry or rely on project metadata to authorize execution. A copied
project must reauthorize through a reviewed, install-enabled catalog using
`specify integration upgrade sample-agent --force --trust-integration`.

### Submitting to the Community Catalog

1. **Fork** the [spec-kit repository](https://github.com/github/spec-kit)
Expand All @@ -98,13 +116,14 @@ provides:
{
"schema_version": "1.0",
"integrations": {
"my-agent": {
"id": "my-agent",
"name": "My Agent",
"sample-agent": {
"id": "sample-agent",
"name": "Sample Agent",
"version": "1.0.0",
"description": "Integration for My Agent",
"description": "Adapter for Sample Agent",
"author": "your-name",
"repository": "https://github.com/your-name/speckit-my-agent",
"repository": "https://github.com/your-name/speckit-sample-agent",
"download_url": "https://github.com/your-name/speckit-sample-agent/releases/download/v1.0.0/sample-agent.zip",
"tags": ["cli"]
}
}
Expand All @@ -121,7 +140,7 @@ provides:
To update your integration version in the catalog:

1. Release a new version of your integration
2. Open a PR updating the `version` field in `catalog.community.json`
2. Open a PR updating the version, pinned archive URL, and digest (if provided)
3. Ensure backward compatibility or document breaking changes

## Upgrade Workflow
Expand All @@ -139,4 +158,34 @@ specify integration upgrade

# Force upgrade (overwrites modified files)
specify integration upgrade --force

# Upgrade a reviewed external adapter without a trust prompt
specify integration upgrade sample-agent --trust-integration
```

Test your package through the public path: register a local loopback test
catalog, install its neutral adapter, start a fresh CLI process, register
extension/preset contributions, and exercise command/prompt workflow dispatch
with a harmless process double. Include failures for invalid metadata/classes,
trust denial, unsafe archives, setup errors, and upgrades/uninstall. Keep
package code separate from generated-file manifests and verify rollback and
modified-file preservation.

Use manifest/base-class write helpers so failed lifecycle operations can restore
only the files your adapter changed. Legacy `record_existing()` writes are
covered within the adapter's declared output root; writes elsewhere must use
`record_file()` or host write helpers. Do not claim unrelated user files. Test
metadata-only commands without import side effects, forced recovery of damaged
installed packages, and rollback that preserves independent workflow progress
and concurrent user edits.
Host helpers reject symlinked write destinations; owned leaf links may be
unlinked without following them. Exercise overlapping project dispatch and lazy
relative imports: the host pins the correct adapter for each dispatch without
serializing independent agent processes. Forced recovery uses user-local
registrar/path ownership, not editable project metadata. Without that proof,
old-only artifacts are preserved with an explicit manual-cleanup warning.
Validate optional legacy destinations as canonical project-relative paths; they
cannot use reserved roots, symlinked directories, or home-relative syntax.
Primary and legacy output overlap is checked case-insensitively on all platforms.
For event-capable adapters, test event-only extension add/remove and enable/disable
in fresh CLI processes, including explicit errors when installed code cannot load.
Loading
Loading