feat(#1245): add capability ecosystem ADR, PRD, and developer documentation (#1248)

Phase 0 of the Capability Ecosystem epic (#1244): the design record and the
third-party-author documentation set, with no runtime or code changes.

- docs/adr/1244-capability-ecosystem.md — architecture decision record
  (amends/extends ADR-857 Decisions 7 & 8)
- docs/prd/1244-capability-ecosystem.md — product requirements
- Diataxis docs: tutorial, how-to (publish/import/version/remove),
  reference (manifest schema, /gsd:capability command, capability matrix),
  explanation (trust model); cross-links added to develop-a-capability.md

Refs #1244

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Tom Boucher
2026-06-14 17:51:43 -04:00
committed by GitHub
parent 10ae85cbbf
commit f52a7a5f77
12 changed files with 2072 additions and 0 deletions

View File

@@ -0,0 +1,178 @@
# ADR-1244 — Capability Ecosystem: third-party authoring, versioned manifests, and URL import/upgrade/remove
- **Status:** Proposed
- **Date:** 2026-06-14
> **Relationship to other ADRs.** This ADR **amends and extends ADR-857 Decisions 7 and 8** — it does not reverse them. ADR-857 D7 deferred third-party code-loading "to its own ADR"; D8 deferred third-party CLI support "to an external loader + trust/validation gate, no rework because runtimes are already descriptors." This *is* that ADR, and it *delivers* that gate. It builds on **ADR-894** (capability declaration format), **ADR-1016** (runtime capability descriptor), and **ADR-58** (InstallPlan seam). Tracked by [#1244](https://github.com/open-gsd/gsd-core/issues/1244). Target release: **1.6.0**.
---
## Context
ADR-857 turned the five-step loop into a **host** with **12 Loop Extension Points** and made every feature a **Capability** — a folder `capabilities/<id>/capability.json` declaring owned skills/agents, lifecycle hooks, a federated config slice, and loop-extension registrations (`step` / `contribution` / `gate`). 32 capabilities ship today (20 `role:feature`, 12 `role:runtime`). The architecture is in place; the **ecosystem is not**.
Three structural facts make third-party capabilities impossible today:
1. **The registry is a build-time artifact.** `scripts/gen-capability-registry.cjs` reads `capabilities/*/capability.json` at build time and emits `gsd-core/bin/lib/capability-registry.cjs`, which is committed and shipped read-only. Every runtime consumer (`config-loader.cjs`, `surface.cjs`, `capability-state.cjs`, command dispatch in `gsd-tools.cjs`) `require()`s that generated file. **Nothing reads `capability.json` at runtime.** A capability that is not in the shipped package literally cannot be *seen* by config federation, surface, state resolution, or dispatch. There is no build step on a user's machine.
2. **Capabilities are unversioned.** `capability.json` carries no `version`. Version lives only at the registry *schema* level (`SCHEMA_VERSION = '1'`) and on the package manifests (`package.json`, `.claude-plugin/plugin.json`, `gemini-extension.json`, stamped by `scripts/sync-manifest-versions.cjs`). "Is there a newer version of this capability?" and "does this capability work with my GSD version?" are both undefined.
3. **There is no per-capability install/upgrade/remove.** The only uninstall surface is whole-product (`bin/install.js --uninstall`), which deletes everything matching the `gsd-*` prefix with **no record of what an install wrote**. Upgrade of one capability, and clean removal of one capability, are impossible.
The maintainer has decided the 1.6.0 scope: **full ecosystem** (the live loader ships in 1.6.0, not just docs) and **full first-party parity** for third-party capabilities (they may ship the same executable surfaces GSD ships — hooks, MCP servers, command modules), mediated by a trust/integrity/consent gate. This raises the security stakes and makes the trust model load-bearing.
The crux for every decision below: **close the build-time/runtime gap with a runtime overlay, and make every executable surface pass through one consent + integrity seam.**
---
## Decisions
### D1 — Versioned capability manifest
`capability.json` gains:
- **`version`** — semver, **required**. The registry rejects a capability without one; a parity test fails the build if any *native* manifest lacks a version.
- **`engines.gsd`** — a semver **range** expressing host compatibility (e.g. `">=1.6.0 <3.0.0"`). Modelled on VS Code's `engines.vscode`. A hard gate at install **and** at load.
- **`compatVersions`** *(optional)* — a capability-version → min-gsd-version table for graceful downgrade. Modelled on Obsidian's `versions.json`. Only meaningful for sources that enumerate versions (git tags, registry, npm).
- **`integrity`** *(optional)* — `sha512-<base64>` of the capability bundle, populated by a registry or recorded at install.
- **`provenance`** *(optional)* — `{ sourceRepo, commit }`; SHOULD be emitted in CI for first-party and curated capabilities.
The build-time validator in `gen-capability-registry.cjs` is extended to enforce these fields. Native capabilities are stamped at release (D6). **Rationale:** versioning is the data substrate every other decision depends on — upgrade, compatibility, integrity, and the matrix all key off it.
### D2 — Runtime Capability Registry overlay
Promote the registry from a frozen data file to a **module with an interface**:
```
loadRegistry({ includeInstalled }) → composed registry
```
It composes **first-party (shipped, frozen) ∪ installed overlay** — third-party manifests read at runtime from a per-scope install root (global: `~/.gsd/capabilities/<id>/`; project: `.gsd/capabilities/<id>/`). The conformance validator (today build-time-only) is **extracted to a runtime-callable `validateCapability()` / `validateCrossCapability()`** and run **at install time over the merged set**, not just the new manifest.
Invariants enforced at install (over first-party ∪ ledger ∪ new):
- **First-party always wins.** An overlay whose `id` collides with a first-party id, or that claims a skill/agent stem already owned, is **rejected**.
- Cross-capability invariants from ADR-894 (owner-uniqueness, config-key exclusivity, artifact-production-uniqueness per point, `requires` acyclic + tier-monotone) re-checked over the merged set.
**Load-time re-gate (default-resilient):** the host can change under an installed overlay (a GSD upgrade renames a loop point, or `engines.gsd` no longer matches). At load, an invalid or incompatible overlay is **skipped with a warning** and flagged in the ledger as needing update — it never crashes the loop. This mirrors the existing defensive skip in `capability-state.cjs`.
**Rationale:** this is the load-bearing unlock. Without a runtime overlay seam, the loader, ledger, and dispatch have nowhere to land. Deletion test: remove the overlay and the third-party-install complexity reappears in every consumer.
### D3 — Capability source resolver (the URL importer)
One seam, `resolveCapabilitySource(spec)`, with one **adapter per source kind**:
| Spec form | Adapter |
|---|---|
| `<name>@<registry>` | registry |
| `https://…/repo.git#<tag>` / `#sha:<40-hex>` | git |
| `npm:@org/pkg@<range>` | npm |
| `https://…/cap-x.y.z.tgz` | tarball |
| `./local/path` | local |
Every adapter follows the same pipeline: **fetch → verify integrity/SHA → check `engines.gsd` → return a staged, validated bundle**. Git/npm sources shell out through the existing `shell-command-projection` seam with bounded timeouts; tarball/registry fetch uses Node's `https` + `crypto`. The trust gate (D5) lives at this single seam. **Rationale:** multiple real adapters = a real seam (not hypothetical); adding a source kind = adding an adapter, not editing the loader.
### D4 — Capability ledger
A per-runtime install manifest, e.g. `~/.claude/.gsd-capabilities.json`, recording per installed capability:
```jsonc
{
"<id>": {
"version": "1.2.0",
"source": "https://github.com/org/cap.git#sha:…",
"integrity": "sha512-…",
"files": ["skills/…", "agents/…"], // owned files written
"sharedEdits": [{ "file": "settings.json", "path": "hooks.PostToolUse[…]" }]
}
}
```
The ledger is the **commit point** for installs/upgrades (atomic write, like `surface.cjs writeSurface`) and the basis for precise removal. It records not only owned files but **fragments written into shared files** (`settings.json` hooks, `mcpServers`) so removal can strip exactly those entries without deleting shared files. A **reconciliation sweep** on next run resolves crash orphans (files not in the ledger; ledger entries with missing files). **Rationale:** "remove by `gsd-*` prefix" has whole-product blast radius and no record of ownership; the ledger gives selective, reversible, crash-safe install.
### D5 — Trust model: artifact parity is full, trust posture is tiered
Third-party capabilities may ship the **same artifacts** first-party ships (full parity, per the maintainer's scope), but **trust is not symmetric**:
- **First-party** is implicitly trusted — it *is* the shipped package.
- **Third-party** requires **explicit, informed, revocable consent + SHA-pinned integrity.**
Hard rules (MUST):
1. **Install never executes capability code.** Staging is copy-only; no `postinstall`-equivalent. (npm `--ignore-scripts` lesson.)
2. **Executable surfaces are disclosed and consented at install.** `hooks`, `mcpServers`, and command modules activate on the *next tool call* — there is no "first use" gate for a hook — so consent must be at install, naming every executable surface. Declining aborts cleanly.
3. **Integrity is verified before extraction** when an `integrity`/SHA is available; mismatch aborts. (npm registry-signature lesson.)
4. **Auto-update is OFF by default** for third-party; enabling it still **re-prompts when the executable set changes** between versions. (VS Code stolen-PAT + silent-auto-update lesson.)
5. **Modules are `require()`'d only from the capability's own install root** — parent-directory traversal in declared paths is rejected.
6. **`gsd-*` (and `gsd-core-*`, `anthropic-*`) ids/prefixes are reserved** — third-party cannot impersonate first-party.
7. **`strictKnownRegistries`** (managed/project config) can lock installs to an allowlist; `[]` means no external installs.
Stated honestly: **there is no sandbox.** Node-level sandboxing is impractical and would defeat full parity. Consent + integrity + reversibility are the barrier. (Obsidian's honest acknowledgment.) **Rationale:** a one-time trust prompt does not make running arbitrary code safe; separating *artifact parity* from *trust posture* is what makes full parity defensible.
### D6 — Upgrade and compatibility
- **Atomic stage-then-swap.** Upgrade fully stages (fetch + verify + validate) before swapping; the **ledger write is the commit point**; a reconciliation sweep handles crash orphans. A mid-upgrade crash leaves either the old or the new version fully intact — never a half-state.
- **Two-layer compatibility.** `engines.gsd` is a **hard gate** (block with a clear message at install and load); `compatVersions` provides **graceful downgrade** to the newest compatible version — but only for sources that enumerate versions (git tags, registry, npm). A bare tarball URL has one version and simply blocks.
- **Native version stamping.** `scripts/sync-manifest-versions.cjs` (or a parallel capability sweep) stamps `version` into native `capabilities/*/capability.json` at release; the existing version-sync regression guard is extended to cover them.
- **"Update available?" is a per-source matrix** (git: fetch tags/manifest; registry: catalog; npm: dist-tags; tarball: not auto-detectable → manual only) — documented, not silently partial.
### D7 — Registry-driven dispatch (sequenced last, behind the gate)
Fulfil ADR-857 D7's deferred "registry over hardcoded switch": `gsd-tools.cjs` / `command-routing-hub.cjs` consult the (overlay-aware) registry's `commands: [{ family, module, router }]` and dispatch via dynamic `require(module)[router]()`. **This is where third-party code executes**, so it is gated by the same consent (D5) and **confined to the capability's install root**. First-party in-tree modules (`graphify`, `intel`, `audit`) collapse onto the same seam (dogfooding). **Sequenced last** because it carries the highest risk.
### D8 — Relationship to ADR-857 (amend, not reverse)
ADR-857 D7/D8 did not *forbid* third-party code — they *deferred* it pending (a) its own ADR and (b) a trust/validation gate. This ADR satisfies both. ADR-857 is updated to mark D7 and D8 **"extended by ADR-1244."** The only substantive change is moving third-party from "deferred" to "delivered, gated." The runtime overlay (D2) is consistent with 857's own direction ("each loop step authored as if it could become a Capability"; "registry over hardcoded switch").
### D9 — The capability matrix
A **generated-from-registry** catalog at `docs/reference/capability-matrix.md`, mapping every capability to `id`, `version`, `tier`, extension points, hook kinds, and `engines.gsd` — kept honest by a drift guard (like `docs/INVENTORY.md`). It includes a documented section where third-party authors register their capability. **Whether GSD operates/advertises a central community registry is left TBD/TBA** (see the PRD); the documentation mechanic ships regardless of that decision.
---
## Consequences
**Positive**
- GSD becomes an actual platform: authors ship capabilities independently; users install/upgrade/remove them without forking or maintainer PRs.
- Versioned manifests give native capabilities a real version surface and make compatibility explicit.
- The overlay + ledger make install reversible and crash-safe; whole-product `--uninstall` is no longer the only removal path.
- The trust gate is concentrated at one seam (D3/D5) — auditable, testable, and the single place the security posture is enforced.
- D7 retires a long-standing hardcoded-switch debt and dogfoods first-party modules onto the same dispatch seam.
**Negative / costs**
- **New permanent attack surface.** URL import + third-party code execution at full parity is the highest-maintenance, highest-risk part of GSD. The trust/integrity/consent model is a forever responsibility.
- Runtime overlay couples the runtime validator to the ADR-894/1016 schema — a generative-parity assertion is required so build-time and runtime validators cannot drift.
- Per-runtime ledger × 16 runtimes multiplies the install/remove test surface (cross-platform fault injection required).
- Documentation breadth (COMMANDS / FEATURES / USER-GUIDE / CONFIGURATION / ARCHITECTURE / AGENTS + the generated matrix).
**Risks & mitigations**
- *Malicious capability via auto-update* → auto-update OFF by default; re-consent on executable-set change; SHA pin.
- *Overlay drift / contract change under an installed capability* → load-time re-gate, skip-with-warning, ledger flag.
- *Half-state install/upgrade* → ledger-as-commit-point + reconciliation sweep.
- *Impersonation* → reserved namespace; `strictKnownRegistries` allowlist.
- *Validator drift* → shared validator module + generative-parity test.
---
## Implementation phases (dependency-ordered)
1. **Versioned manifest** (D1) + native stamping (D6) — the data substrate; the "documentation-release" piece.
2. **Runtime registry overlay** (D2) — the structural unlock.
3. **Source resolver** (D3) + **ledger** (D4) — additive, testable in isolation.
4. **Trust gate** (D5) + **upgrade/compat** (D6).
5. **Registry-driven dispatch** (D7) — last, behind the gate.
6. **Capability matrix** (D9) + full diataxis documentation set.
Each phase ships as its own PR with a changeset and full gate compliance (per CONTRIBUTING: one concern per PR; docs-required for `Added`/`Changed` fragments).
---
## Alternatives considered
1. **Stay build-time only** (third parties fork or upstream-PR). Rejected — no ecosystem; every community capability becomes maintainer burden. This is the status quo 857 D7/D8 flagged.
2. **Declarative-only third-party** (no hooks/MCP/code). Safer (matches 857 D7), but the maintainer chose full parity so authors ship the same power GSD ships — accepting the heavier trust model rather than a capped one.
3. **Centralized-registry-only** (no URL import). Rejected for launch — gates every capability behind maintainer review (the Obsidian one-PR-per-version pitfall). URL/git import keeps distribution decentralized; a registry can layer on top later.
4. **Regenerate the committed registry on the user's machine at install.** Rejected — requires the full build toolchain on every machine and mutates a shipped file; the overlay achieves the same without a build step.

View File

@@ -0,0 +1,302 @@
# The capability trust model
> **Explanation** — This document describes *why* GSD draws its trust
> boundaries where it does, and *what the trade-offs are*. It is not a
> step-by-step guide to installing capabilities; for that, see the how-to
> guides for [importing a capability](../how-to/) and
> [version management](../how-to/). For the decision record, see
> [ADR-1244 D5](../adr/1244-capability-ecosystem.md#d5--trust-model-artifact-parity-is-full-trust-posture-is-tiered).
> For the capability field reference, see the
> [capability matrix](../reference/capability-matrix.md).
---
## The central thesis: artifact parity is not trust parity
GSD 1.6.0 opens the capability platform to third-party authors with **full
artifact parity**: a third-party capability may ship the same executable
surfaces that GSD Core ships — hooks, MCP servers, command modules. This is a
deliberate product choice, and it carries real security weight.
Full parity means a third-party capability, once installed, can execute code
the next time a relevant loop event fires. There is no "first use" gate.
There is no sandbox. The capability author has, in effect, a code-execution
path into your runtime.
The maintainer's response to this is not to deny parity but to draw a sharp
line between two things that are often conflated:
- **Artifact parity** — what a third-party capability is *allowed to ship*.
- **Trust posture** — the evidence and consent required before that capability
*executes* on your machine.
GSD grants full artifact parity. It does not grant symmetric trust. First-party
capabilities are implicitly trusted because they *are* the shipped package —
their provenance is the GSD Core release process itself. Third-party
capabilities require explicit, informed, revocable consent plus SHA-pinned
integrity before any executable surface is activated. These two things are
structurally separate, and keeping them separate is what makes full parity
defensible.
---
## What the ecosystem learnt the hard way
GSD's trust model is not designed in isolation. It is informed by failures in
four ecosystems that tackled the same problem — and each one paid tuition.
### VS Code: auto-update + stolen publisher credentials
VS Code's extension marketplace grants extensions the same permissions as the
editor itself. In 2023 a publisher's personal access token was stolen; the
attacker published a backdoored update to an existing, trusted extension. Every
user with auto-update enabled received the malicious version silently, on the
next launch, with no prompt. The lesson: auto-update for executable surfaces is
a liability when credentials can be compromised, because the user's last
explicit act of trust was for *version N* — not for whatever version N+1
contains.
GSD's response: auto-update is **off by default** for third-party capabilities.
When it is enabled, a change to the *executable set* (the set of hooks, MCP
servers, or command modules the capability declares) triggers a re-consent
prompt before the update applies. Updating a non-executable capability
(documentation, agents, skills) does not require re-consent.
VS Code also has no signature check on VSIX packages. GSD requires an
`integrity` SHA-512 pin in the ledger, verified before extraction.
### npm: the supply-chain attack surface
npm's `postinstall` scripts mean that downloading a package can execute
arbitrary code on the developer's machine — a property that supply-chain
attackers have exploited in the s1ngularity attack class (a malicious package
is published under a name a legitimate package depends on). npm's own
recommendation for sensitive environments is `--ignore-scripts`.
GSD takes a stronger position: **install never executes capability code**,
full stop. Installation is a copy-only staging operation. There is no
`postinstall`-equivalent. A capability's hooks, MCP server, and command
modules are not invoked during install; they are first invoked when the loop
fires after install. This means a malicious payload in an executable surface
cannot be triggered by the act of downloading it — the user has a window
between install and first use to verify what they consented to.
SLSA provenance (the `provenance` field in `capability.json`) provides a
machine-checkable link from a capability bundle back to a specific commit in a
specific source repository. GSD emits provenance for first-party capabilities
in CI and recommends it for curated capabilities; whether to require it for
community-listed third-party capabilities is an open question tied to whether
GSD operates a central registry (see the PRD).
### Obsidian: no sandbox, stated honestly
Obsidian's plugin system does not sandbox plugins. Plugins run in the renderer
process with full Electron API access. Obsidian acknowledges this directly in
its documentation and community materials, and its response is restricted mode
on by default — no community plugins run until the user deliberately disables
restricted mode — plus a human-curated plugin directory that requires a
maintainer review PR for each new plugin.
GSD borrows two things from Obsidian. First, the honesty: **there is no
sandbox**, and this document says so directly rather than implying one. Second,
the principle that explicit opt-in per capability is better than a blanket "all
community plugins are safe" message. GSD does not use restricted mode, but its
consent gate at install serves the same function: executable surfaces are
disclosed and consented to before they activate, not discovered after the fact.
GSD does not borrow Obsidian's centralised review model. Requiring a
maintainer-review PR for every third-party capability is the bottleneck that
makes the Obsidian system painful for authors and creates a PR-queue burden
for maintainers. GSD ships decentralised URL import precisely to avoid that.
### Claude Code: trust prompt + marketplace
Claude Code prompts the user at install for each extension that requires
elevated trust, lists the permissions the extension requests, and maintains a
`strictKnownMarketplaces` allowlist for managed environments where only
reviewed sources are permitted. Claude Code's SHA-pinning mechanic (pinning to
a specific version hash rather than floating on `latest`) is the direct model
for GSD's integrity field.
GSD mirrors the allowlist mechanic as `strictKnownRegistries`, and mirrors the
SHA-pin as the `integrity` field in `capability.json` and the capability
ledger.
---
## Each pillar and its reasoning
### Install never runs code
The most powerful thing GSD can say to a user about a third-party capability
is: "downloading and staging this capability will not execute any of its code."
That guarantee makes the consent step meaningful. If install could run code, a
malicious capability could bypass consent entirely — the install step would be
the attack.
Staging is copy-only: files are extracted to the install root, the manifest is
validated, cross-capability invariants are checked, and the ledger is written.
No hook fires, no module is `require()`'d, no MCP server is started. The
executable surfaces remain inert until the first loop event fires after
consent.
### Consent at install for executable surfaces
Hooks fire on the *next tool call*. There is no first-use gate for a hook —
the point at which a hook would fire for the first time is not a prompt
opportunity; it is already inside a running tool invocation. This means the
consent window is install, not first use.
GSD presents a pre-install summary that names every executable surface the
capability declares (hooks, MCP servers, command modules), their kinds (`step`,
`contribution`, `gate`), and the loop extension points they register into.
Declining aborts the install cleanly. Accepting records the consent in the
ledger.
For non-executable surfaces (skills, agents, workflow files), the disclosure
note explains what they do but consent is lighter — they do not execute code.
### Integrity pinning
An `integrity` field in `capability.json` carries a `sha512-<base64>` digest
of the capability bundle. When present, GSD verifies this digest before
extracting any files. A mismatch aborts the install.
What integrity pinning defends against: a capability hosted at a URL or in a
registry that is later replaced with a different bundle (whether by an attacker
who has compromised the hosting, or by an author publishing a silent breaking
change). The SHA is the commitment — "I consented to *this* bundle, not
whatever is at this URL today."
What it does not defend against: a malicious capability where the author
themselves publishes a bad bundle. The SHA is honest about what you are
installing; it says nothing about whether what you are installing is safe.
### Auto-update off by default, re-consent on executable-set change
When auto-update is enabled for a third-party capability, each update is
checked against the ledger's record of the capability's executable surfaces. If
the set of hooks, MCP servers, or command modules has changed — even if the
update is otherwise benign — auto-update halts and re-prompts. The user is
shown which surfaces were added or removed and must consent before the update
applies.
This directly addresses the VS Code stolen-PAT scenario: even if an attacker
publishes a new version of a capability you have auto-update enabled on, the
new version cannot silently gain a hook that the previous version did not have.
### Install-root confinement
A capability's command modules are `require()`'d only from the capability's own
install root. Declared paths containing parent-directory traversal (`../`) are
rejected at install-time validation. This prevents a capability from loading
code it does not own — whether by accident or by design.
### Reserved namespace
The `gsd-`, `gsd-core-`, and `anthropic-` id prefixes are reserved for
first-party use. A third-party capability that claims one of these prefixes is
rejected at the conformance gate. This prevents impersonation: a malicious
actor cannot publish a capability called `gsd-security` and exploit a user's
implicit trust in the GSD namespace.
### `strictKnownRegistries` for managed environments
Teams or enterprises that want to constrain which capability sources are
permissible can set `strictKnownRegistries` in managed or project config to an
explicit allowlist of URLs or registry names. Setting it to `[]` blocks all
external installs. This gives an administrator a policy lever that operates
before the user even sees a consent prompt.
---
## The honest limitation: there is no sandbox
GSD does not sandbox third-party capability code. The honest reason: Node-level
sandboxing that meaningfully restricts a `require()`'d module — limiting
filesystem access, network access, subprocess spawning — would require either
a separate process with IPC overhead or a VM context that strips the Node
globals capabilities legitimately need (filesystem for writing surface files,
network for MCP, subprocess for hook shell commands). Full artifact parity and
meaningful sandboxing are in tension. The maintainer chose full parity.
What this means in practice: a third-party capability, once consented to and
installed, runs with the same permissions GSD Core itself runs with. It is not
isolated. A capability that wants to exfiltrate data, or modify files outside
its declared scope, can — exactly as a malicious npm package can.
The barrier is not a technical wall. It is:
1. **Consent** — you explicitly approved the executable surfaces this
capability declares before they ran.
2. **Integrity** — the bundle you consented to is the bundle that ran (SHA
verified).
3. **Reversibility** — `gsd capability remove <id>` removes exactly what the
ledger recorded, including entries in shared config files, leaving no
orphaned state.
These three things together mean: you know what you installed, you got what you
were shown, and you can undo it completely. They do not guarantee the content
is safe. The trust model is transparent about this.
---
## Trade-offs: the roads not taken
### Declarative-only third-party capabilities
The safer alternative considered in ADR-1244 was declarative-only third-party
capabilities: skills, agents, and workflow files, but no hooks, MCP servers, or
command modules. A third-party author could extend *what GSD describes* but not
*what it executes*.
The maintainer rejected this. A deploy gate capability, a house-style
verification step, a domain-specific planning contribution — all of these
require hook registration to have any effect on the loop. Declarative-only
third-party capabilities would be second-class citizens, unable to participate
in the parts of GSD where participation matters most. Full parity was the
explicit scope.
The cost of that choice is a permanently elevated security responsibility: URL
import with executable surfaces is the highest-maintenance, highest-risk part
of GSD. The trust model is a forever commitment, not a one-time effort.
### Centralised-registry-only distribution
The alternative to decentralised URL/git import is requiring all third-party
capabilities to go through a GSD-operated curated registry — one PR per
capability, reviewed by the maintainer before listing.
This would meaningfully reduce supply-chain risk (a human reviews every listed
capability) but at a cost the maintainer explicitly rejected: it makes
capability authors dependent on maintainer bandwidth, turns the maintainer into
a gatekeeper for an unbounded tail of stack-specific and house-style
capabilities, and replicates exactly the bottleneck that makes Obsidian's
plugin system painful.
The compromise: URL/git/npm/tarball import ships in 1.6.0 without a curated
registry. Whether GSD later operates or advertises a community registry is an
open question (PRD-1244 §8). If it does, the intent is to separate "official"
(curated) from "community" (consented-but-not-reviewed) tiers, mirroring the
split Claude Code uses for its marketplace.
---
## Summary
The capability trust model rests on a single conceptual move: separating
artifact parity from trust posture. Because those two things are kept separate,
GSD can offer authors the full power of the platform while making users'
security obligations clear and auditable. You consent to executable surfaces
before they run, you can verify the bundle's integrity, and you can remove a
capability completely. GSD does not pretend this is the same as not running
the code at all.
---
## Related documents
- [ADR-1244 D5 — Trust model](../adr/1244-capability-ecosystem.md#d5--trust-model-artifact-parity-is-full-trust-posture-is-tiered)
- [Capability matrix](../reference/capability-matrix.md) — the generated catalogue of all capabilities
- [PRD-1244 §6 — Out of scope](../prd/1244-capability-ecosystem.md#6-scope-160) — why sandboxing is explicitly out of scope
- [ADR-857](../adr/857-capability-system.md) — the 12 loop extension points; D7 and D8 extended by ADR-1244

View File

@@ -234,3 +234,17 @@ Run the Phase 6 capstone test whenever a Capability adds a `when` key or moves a
## Keep the docs with the slice
Every Phase 6 slice that changes capability behaviour must update the relevant docs in the same PR. Use this manual for developer-facing Capability authoring facts, use how-to guides for task flows, and use ADRs only for decisions and trade-offs.
## The capability ecosystem (1.6.0)
From GSD 1.6.0, capabilities are versioned (the `version` field is required in `capability.json`) and can be installed directly from a URL, a git ref, an npm package, or a local path — without modifying the core repo.
- **Tutorial** — [Build your first capability](../tutorials/build-your-first-capability.md): scaffold and install a declarative capability end-to-end in under ten minutes.
- **How-to** — [Publish a capability](../how-to/publish-a-capability.md): package and distribute a capability via a URL or registry.
- **How-to** — [Import a capability from a URL](../how-to/import-a-capability-from-a-url.md): install a third-party capability from a git URL, tarball, or npm package.
- **How-to** — [Version and update a capability](../how-to/version-a-capability.md): manage `version`, `engines.gsd`, and `compatVersions`; use `gsd capability update`.
- **How-to** — [Remove a capability](../how-to/remove-a-capability.md): uninstall cleanly with `gsd capability remove`, including the `--purge-data` option.
- **Reference** — [Capability manifest](../reference/capability-manifest.md): all fields and validation rules for `capability.json`.
- **Reference** — [Capability matrix](../reference/capability-matrix.md): which first-party capabilities exist, their extension points, and their compatibility matrix.
- **Explanation** — [Capability trust model](../explanation/capability-trust-model.md): how declarative and executable capabilities are treated differently at install time.
- **ADR-1244** — `docs/adr/1244-capability-ecosystem.md`: the architectural decision that introduced the installable capability ecosystem.

View File

@@ -0,0 +1,177 @@
# How to import a capability from a URL
This guide is for GSD users who want to install a published capability — from a git repository, an npm package, a tarball, or a local path. It covers running the install command, understanding the pre-install summary, consenting to executable surfaces, verifying integrity, and choosing a scope.
Before installing a third-party capability, read [Capability trust model](../explanation/capability-trust-model.md) to understand how GSD treats external code.
---
## Run the install command
The `install` subcommand accepts several source spec forms. Use whichever matches how the capability was published.
**Git repository at a tag:**
```bash
gsd capability install https://github.com/some-org/gsd-cap-example.git#v1.0.0
```
**Git repository pinned to a commit SHA (fully reproducible):**
```bash
gsd capability install https://github.com/some-org/gsd-cap-example.git#sha:abc123def456...
```
**npm package:**
```bash
gsd capability install npm:@some-org/gsd-cap-example@1.0.0
```
**Tarball at an HTTPS URL:**
```bash
gsd capability install https://example.com/releases/gsd-cap-example-1.0.0.tgz
```
**Local path (for testing a capability you are developing):**
```bash
gsd capability install ./path/to/capability
```
You can also use the slash command form inside a supported runtime (surfaced as `gsd:capability install <spec>` — without the leading `/` in the command palette).
---
## Read the pre-install summary
Before asking for confirmation, GSD fetches the manifest and displays a summary:
```
Capability: Example Planning Step
Version: 1.0.0
Author: Some Org <hello@some-org.example>
Homepage: https://github.com/some-org/gsd-cap-example
License: MIT
engines.gsd: >=1.6.0
Artefacts: 3 files (skills: 1, agents: 1, fragments: 1)
Executable surfaces:
hooks: plan:pre (step), ship:pre (gate, blocking)
MCP servers: none
command modules: none
```
If the capability declares hooks, MCP servers, or command modules, these are listed under **Executable surfaces**. These surfaces run as part of the GSD loop on your machine. Review them carefully.
---
## Consent to executable surfaces
If the capability declares any executable surface — hooks, MCP servers, or command modules — GSD displays a consent prompt:
```
This capability registers executable hooks that will run during your GSD sessions.
Do you consent to installing it? [y/N]
```
If you do not trust the source, type `N` or press Enter to cancel. The capability will not be installed and nothing will be written to disk.
If the capability declares no executable surfaces (skills, agents, and prompt fragments only), GSD installs without a consent prompt.
After initial consent, if you later run `gsd capability update` and the updated version adds new executable surfaces that were not present when you first consented, GSD will prompt for consent again before applying the update.
---
## Verify integrity (recommended for tarballs)
If the capability author has published an `sha512` integrity hash, pass it with `--integrity` to verify the download before extraction:
```bash
gsd capability install https://example.com/releases/gsd-cap-example-1.0.0.tgz \
--integrity sha512-AbCdEf...
```
If the computed hash does not match the value you provide, GSD aborts the install. Nothing is written to disk. This protects against a tampered or corrupted download.
For Git and npm installs, the hosting platform provides its own transport-layer assurance. The `--integrity` flag is most important for tarball URLs hosted outside a verified registry.
---
## Choose a scope
Use `--scope project` to install the capability for the current project only. The files land in `.gsd/capabilities/<id>/` relative to the project root, and the ledger entry goes into the project's local config.
```bash
gsd capability install <spec> --scope project
```
Use `--scope global` (the default) to install for all your projects on this machine. The files land in `~/.gsd/capabilities/<id>/` and the ledger is written per runtime (for example, `~/.claude/.gsd-capabilities.json`).
```bash
gsd capability install <spec> --scope global
```
Project-scoped capabilities take precedence over global ones when both are present. Use project scope when the capability is specific to one codebase, or when you want to pin a version independently of your global install.
---
## Handle a version mismatch
If the capability's `engines.gsd` requirement is not satisfied by your installed GSD version, the install will be blocked:
```
Error: Capability requires gsd >=1.7.0 but you have 1.6.2.
```
In this case, either upgrade GSD with `gsd update` and retry, or ask the capability author whether an older compatible version is available. If the capability publishes `compatVersions`, GSD may offer to install the newest version compatible with your current GSD:
```
A compatible older version (0.9.0, requires gsd >=1.6.0) is available.
Install that instead? [y/N]
```
---
## Handle a blocked install (strictKnownRegistries)
If your organisation has set `strictKnownRegistries` to a non-empty allowlist in your GSD config, installs from sources outside that allowlist will be refused:
```
Error: Source is not in the known-registries allowlist. Contact your GSD administrator.
```
To install the capability, either ask your administrator to add the source to the allowlist, or install from an approved source.
---
## Skip the confirmation prompt
If you are running in a script or CI context and have already inspected the manifest, pass `--yes` to proceed without interactive prompts. Use this only when you are certain about what you are installing.
```bash
gsd capability install <spec> --yes
```
---
## Confirm the installation
After a successful install, verify the capability is active:
```bash
gsd capability list
```
The output shows each installed capability, its version, scope, and enabled status. If the capability did not activate as expected, check that your GSD version satisfies `engines.gsd` and that the capability is not disabled.
---
## Next steps
- [Version and update a capability](./version-a-capability.md) — check for updates with `gsd capability outdated` and apply them with `gsd capability update`.
- [Remove a capability](./remove-a-capability.md) — uninstall with `gsd capability remove`, including the `--purge-data` option.
- [Capability trust model](../explanation/capability-trust-model.md) — the full explanation of how GSD handles trust for first-party and third-party capabilities.
- [Capability manifest](../reference/capability-manifest.md) — field reference for `capability.json`.

View File

@@ -0,0 +1,190 @@
# How to publish a capability so others can install it
This guide is for capability authors who want to distribute their work so other GSD users can install it with `gsd capability install`. It covers preparing the manifest, validating locally, and releasing through each supported distribution channel.
Before publishing, make sure your capability works locally by following [Develop a Capability](./develop-a-capability.md).
---
## Add the required publishing fields
Open your `capabilities/<id>/capability.json` and add these fields if they are not already present.
### `version` (required)
```json
"version": "1.0.0"
```
Use [Semantic Versioning](https://semver.org/). Every published capability must carry a version; GSD will reject installation of a manifest that omits it.
### `engines.gsd` (required)
```json
"engines": {
"gsd": ">=1.6.0"
}
```
Declare the minimum GSD version your capability requires. GSD checks this constraint at both install time and load time and refuses to activate the capability on an incompatible installation. Be as permissive as correctness allows — a tighter range blocks more users.
If you need to offer a graceful downgrade path for users on older GSD versions, you can also declare `compatVersions`:
```json
"compatVersions": {
"1.0.0": ">=1.6.0",
"0.9.0": ">=1.5.0"
}
```
`compatVersions` is only meaningful when your distribution channel enumerates available versions (a registry or a package feed). For Git and tarball releases, the installer downloads the version you point to directly.
### Author and provenance fields (recommended)
These fields are displayed in the pre-install summary that users see before they consent to installation. Filling them in builds trust.
```json
"author": {
"name": "Your Name",
"email": "you@example.com",
"url": "https://example.com"
},
"homepage": "https://github.com/your-org/gsd-cap-example",
"repository": "https://github.com/your-org/gsd-cap-example",
"license": "MIT"
```
`license` must be a valid [SPDX expression](https://spdx.org/licenses/). `keywords` is optional but helps discoverability on registries.
For the full list of manifest fields and their validation rules, see [Capability manifest](../reference/capability-manifest.md).
---
## Namespace reservation
The prefixes `gsd-`, `gsd-core-`, and `anthropic-` are reserved for first-party capabilities. Do not use them as the `id` or package name of a third-party capability.
---
## Validate locally before publishing
Run the registry check to confirm the manifest is well-formed:
```bash
node scripts/gen-capability-registry.cjs --check
```
If you are developing outside the core repo, use the standalone validator when it is available, or install your capability locally and check that `gsd capability list` shows it without errors:
```bash
gsd capability install ./path/to/your-capability --scope project
gsd capability list
```
Fix any validation errors before proceeding.
---
## Choose a distribution channel
### Git repository (recommended for open-source capabilities)
Push your capability to a public Git host. Tag each release:
```bash
git tag v1.0.0
git push origin v1.0.0
```
Consumers install by pointing at the tag:
```bash
gsd capability install https://github.com/your-org/gsd-cap-example.git#v1.0.0
```
For a reproducible pin that cannot be moved, consumers can use a commit SHA instead:
```bash
gsd capability install https://github.com/your-org/gsd-cap-example.git#sha:abc123def456...
```
Publish release notes on your Git host so users know what changed between versions.
### npm package
Publish your capability as an npm package. The package name becomes the npm spec consumers use. Use a scoped package name to make the origin clear:
```bash
npm publish
```
Consumers install using the `npm:` prefix:
```bash
gsd capability install npm:@your-org/gsd-cap-example@1.0.0
```
A version range is also accepted:
```bash
gsd capability install npm:@your-org/gsd-cap-example@^1.0.0
```
### Tarball release
Build a tarball of the capability directory and attach it to a GitHub release or host it on any HTTPS URL:
```bash
tar -czf gsd-cap-example-1.0.0.tgz capabilities/example/
```
Consumers install using the tarball URL:
```bash
gsd capability install https://github.com/your-org/gsd-cap-example/releases/download/v1.0.0/gsd-cap-example-1.0.0.tgz
```
For tarball releases, publishing an integrity hash is strongly recommended (see below).
---
## Compute and publish an integrity hash (recommended for tarballs)
An `sha512` integrity hash lets consumers verify the download has not been tampered with. Compute it with:
```bash
openssl dgst -sha512 -binary gsd-cap-example-1.0.0.tgz | openssl base64 -A | sed 's/^/sha512-/'
```
Publish the resulting string in your release notes. Consumers pass it at install time:
```bash
gsd capability install https://example.com/gsd-cap-example-1.0.0.tgz \
--integrity sha512-<hash>
```
GSD verifies the hash before extracting the archive and aborts if it does not match.
---
## Add provenance (optional)
If your release process produces a provenance record — for example, a GitHub Actions attestation — you can embed it in the manifest so that audit tools can surface it:
```json
"provenance": {
"sourceRepo": "https://github.com/your-org/gsd-cap-example",
"commit": "abc123def456..."
}
```
This is optional metadata. It does not change the install-time trust model.
---
## Next steps
- [Import a capability from a URL](./import-a-capability-from-a-url.md) — walk through installation from the consumer's perspective.
- [Version and update a capability](./version-a-capability.md) — manage `version`, `engines.gsd`, and `compatVersions` across releases.
- [Capability manifest](../reference/capability-manifest.md) — full field reference.
- [Capability trust model](../explanation/capability-trust-model.md) — how GSD treats third-party capabilities at install time.

View File

@@ -0,0 +1,111 @@
# How to remove or disable a capability
This guide covers two distinct operations: **removing** a capability (deletes its files and cleans up all shared configuration it wrote) and **disabling** a capability (toggles it off without touching any files). Choose the one that fits your intent.
---
## Disable a capability (reversible, files kept)
If you want to stop a capability from participating in the loop but may want it back later, disable it:
```bash
gsd capability disable <id>
```
Disabling is a toggle: no files are deleted, no shared configuration is modified. The capability's hooks stop firing, its skills leave the active surface, and its command modules stop responding. To re-activate it:
```bash
gsd capability enable <id>
```
Everything you had before is restored — hook registrations, config keys, contributed agents — without reinstalling.
If you want to toggle a capability off within a single runtime session rather than system-wide, see [Turn a capability off (and keep it off)](turn-a-capability-off.md).
---
## Remove a capability
Removal is permanent within the current install. Use it when you no longer need the capability and want to reclaim its disk footprint and clean its entries from your runtime configuration.
```bash
gsd capability remove <id>
```
### What is removed
GSD uses the **ledger** — a per-runtime record written at install time (for example, `~/.claude/.gsd-capabilities.json`) — as the authoritative list of what the install owns. Removal acts precisely on that record:
- **Owned files** — every file the capability wrote at install (skills, agents, referenced assets) is deleted.
- **Shared configuration fragments** — entries the capability injected into shared files such as `settings.json` (hooks) and `hooks.json` (MCP server registrations) are stripped. Only the capability's own entries are removed; no other capability's hooks or MCP server entries are touched.
- **Federated config keys** — configuration keys that belong to the capability's declared config slice are dropped from the merged config.
### What is NOT removed
- **Shared files themselves.** Files such as `settings.json` and `hooks.json` are edited in place, not deleted. Only the capability's specific entries are excised.
- **Persistent capability data.** Any data the capability wrote during use (databases, caches, runtime artefacts stored outside the install root) is **not** auto-deleted. You must pass `--purge-data` to remove it, and GSD will prompt for confirmation before doing so:
```bash
gsd capability remove <id> --purge-data
```
If you want to keep your data, omit `--purge-data`. The capability's runtime data will remain on disk even after the capability itself is removed.
### Prompts and confirmation
`gsd capability remove` will ask you to confirm before proceeding. Pass `--yes` to skip the prompt in scripts or non-interactive contexts:
```bash
gsd capability remove <id> --yes
```
If the capability also ships persistent data and you pass `--purge-data`, GSD prompts once more specifically for the data deletion, regardless of `--yes`, because that action is irreversible.
---
## Troubleshooting
**If a previous remove was interrupted** (for example, the process was killed mid-run), GSD's reconciliation sweep repairs the orphaned state automatically on the next command invocation. You do not need to intervene manually; running any `gsd capability` command is sufficient to trigger the sweep.
**If the capability ships hooks**, removal strips its hook entries from `settings.json` without affecting any other hook entry. If after removal you still see the capability's hooks listed under `settings.json`, run:
```bash
gsd capability list --json
```
and confirm the capability is no longer present. If it still appears, re-run the remove command — the reconciliation sweep will complete any partial work.
**If you see "capability not found" during remove**, the capability may have been installed under a different scope (global vs. project). Check which scope it was installed under:
```bash
gsd capability list
```
The `--scope` column indicates whether an entry is `global` or `project`. Pass the matching scope explicitly if needed:
```bash
gsd capability remove <id> --scope global
gsd capability remove <id> --scope project
```
---
## Disable vs. remove: a quick comparison
| | `disable` | `remove` |
|---|---|---|
| Files deleted | No | Yes (ledger-recorded files only) |
| Shared config entries removed | No | Yes (capability's entries only) |
| Federated config keys dropped | No | Yes |
| Persistent data deleted | No | Only with `--purge-data` + prompt |
| Reversible without reinstall | Yes (`enable`) | No |
| Use when | You want it back later | You no longer need it |
---
## Related guides
- [How to version and upgrade a capability](version-a-capability.md)
- [Develop a Capability for GSD 1.5+](develop-a-capability.md)
- [Turn a capability off (and keep it off)](turn-a-capability-off.md)
- [Trust model explanation](../adr/1244-capability-ecosystem.md#d5----trust-model-artifact-parity-is-full-trust-posture-is-tiered)

View File

@@ -0,0 +1,141 @@
# How to version and upgrade a capability
This guide covers two separate journeys: how a **capability author** keeps their manifest correctly versioned as GSD evolves, and how a **capability consumer** safely applies updates. Read only the section that matches your role; each stands alone.
---
## If you author a capability
### Choose a version number
Every `capability.json` must carry a `version` field expressed as a [semver](https://semver.org) string. GSD rejects a capability manifest that omits it.
Use the standard semver conventions:
| Kind of change | Version bump | Examples |
|---|---|---|
| Backwards-compatible bug fixes or minor prompt improvements | **patch** (0.0.x) | Fix a typo in an agent instruction; tighten a hook condition. |
| New loop-extension hook, new skill, or new config key — existing consumers unaffected | **minor** (0.x.0) | Add a `verify:post` gate; add a new optional config key. |
| Breaking change to the hook contract, removal of a skill or config key, change of `id` | **major** (x.0.0) | Rename a hook extension point; remove a skill consumers depend on. |
Set the version in your manifest before every release:
```jsonc
{
"id": "my-deploy-gate",
"version": "1.2.0",
"engines": { "gsd": ">=1.6.0 <3.0.0" }
}
```
### Decide when to raise `engines.gsd`
The `engines.gsd` range expresses which GSD host versions your capability is compatible with. GSD enforces this as a hard gate at install time and again at load time.
Raise the lower bound when you start using a GSD feature introduced in a specific release — for example, a loop extension point added in 1.7.0, a new manifest field, or a config federation key that does not exist in older GSD builds. Do not raise it pre-emptively; only raise it when the capability genuinely requires the newer behaviour.
When you do raise the lower bound:
1. Bump `version` (at minimum a minor bump, or a major bump if the change is otherwise breaking).
2. Update `engines.gsd` to reflect the new minimum.
3. Add a `compatVersions` entry (see below).
### Maintain `compatVersions`
`compatVersions` is a capability-version → minimum-GSD-version table that lets GSD offer older consumers a downgrade instead of a hard block:
```jsonc
{
"version": "2.0.0",
"engines": { "gsd": ">=1.7.0 <3.0.0" },
"compatVersions": {
"1.2.0": "1.6.0"
}
}
```
This entry tells GSD: "version 1.2.0 of this capability requires at least GSD 1.6.0." When a consumer's GSD is older than 1.7.0, GSD uses `compatVersions` to offer them version 1.2.0 instead of failing outright.
Add a new entry **only when you change `engines.gsd`** — that is the only moment an older GSD version and a specific capability version become correlated. A `compatVersions` entry is not meaningful for a capability distributed as a bare tarball URL (a tarball exposes a single version and cannot be auto-selected from a table); it is only actionable for sources that enumerate versions: git tags, a registry, or npm.
### Publish a new version
How consumers receive the update depends on your distribution channel.
**Git tag.** Commit the updated `capability.json` (with the new `version` field), then push a tag whose name matches the version:
```bash
git tag v1.2.0
git push origin v1.2.0
```
GSD's git adapter fetches tags to determine what is available. Without a matching tag, the new version is invisible to `gsd capability outdated`.
**npm.** Publish normally. GSD uses `dist-tags` to check for updates, so the standard `npm publish` flow is sufficient:
```bash
npm version 1.2.0
npm publish
```
**New tarball.** Upload the new archive at a URL and communicate the URL to consumers. GSD cannot auto-detect updates for tarball sources — consumers must run `gsd capability update <id> <new-url>` manually after you announce the new URL. If you anticipate frequent updates, consider switching to a git or npm source.
---
## If you consume a capability
### Check for available updates
Run:
```bash
gsd capability outdated
```
GSD contacts the source of each installed capability and reports which ones have a newer version available. Whether an update is detectable depends on the source:
| Source | Auto-detectable? |
|---|---|
| Git (tags / manifest) | Yes — GSD fetches available tags. |
| Registry | Yes — GSD queries the catalogue. |
| npm | Yes — GSD checks `dist-tags`. |
| Tarball URL | **No** — a tarball exposes one version; updates must be applied manually when the author announces a new URL. |
If a capability is installed from a tarball and the author publishes a new version at a different URL, you will need to run `gsd capability update <id> <new-url>` yourself once the author communicates the new address.
### Apply an update
To update a specific capability:
```bash
gsd capability update <id>
```
To update all installed capabilities at once:
```bash
gsd capability update --all
```
Updates are **atomic**: GSD fully fetches and validates the new version before swapping it in. The ledger write is the commit point. If GSD stops mid-update (for example, due to a network failure), the next command run will detect the orphaned state via a reconciliation sweep and restore a consistent install — you will never be left with a half-updated capability.
### Consent when the executable surface changes
If the new version adds or removes hooks, MCP server entries, or command modules compared to the version you have installed, GSD will pause and present a summary of the changes before proceeding. You must confirm explicitly; declining leaves the current version in place.
This re-prompt applies even if you previously consented to auto-update. The consent mechanism is scoped to the declared executable surface of a specific version, so a changed surface is always a fresh decision.
Auto-update is **off by default** for third-party capabilities. If you enable it, the re-prompt on executable-surface change still applies.
### When `engines.gsd` no longer matches
If the new version of a capability requires a GSD version newer than what you have installed, GSD will tell you clearly and — where the source enumerates versions — offer the newest `compatVersions`-compatible version instead. If no compatible version is available, or the source is a bare tarball, you will need to either upgrade GSD or stay on your current capability version.
---
## Related guides
- [How to remove or disable a capability](remove-a-capability.md)
- [Develop a Capability for GSD 1.5+](develop-a-capability.md)
- [Capability manifest reference](../reference/capability-matrix.md)
- [Turn a capability off (and keep it off)](turn-a-capability-off.md)

View File

@@ -0,0 +1,85 @@
# PRD-1244 — Capability Ecosystem
- **Status:** Proposed
- **Date:** 2026-06-14
- **Owner:** Tom Boucher (maintainer)
- **Tracking issue:** [#1244](https://github.com/open-gsd/gsd-core/issues/1244)
- **Architecture:** [ADR-1244](../adr/1244-capability-ecosystem.md)
- **Target release:** 1.6.0
> This PRD captures the *what* and *why*. The *how* lives in [ADR-1244](../adr/1244-capability-ecosystem.md). Where the two overlap, the ADR is authoritative on architecture and this PRD is authoritative on product intent, scope, and success.
---
## 1. Summary
GSD 1.6.0 opens the capability platform (ADR-857) to **third-party authors**. Developers can write a capability, publish it at a URL, and any GSD user can `import` it, keep it `up to date`, and `remove` it cleanly — with versioned manifests, host-compatibility checks, and an explicit trust gate for capabilities that run code. A generated **capability matrix** documents every capability (native and third-party) and gives authors a place to plug in.
## 2. Problem & opportunity
ADR-857 made GSD extensible *in principle* — 12 loop extension points, 32 capabilities — but the platform is closed: the registry is build-time-only, capabilities are unversioned, and there is no per-capability install/upgrade/remove. A solo developer cannot share the capability they built, and cannot adopt one someone else built, without forking GSD or routing a PR through the maintainer.
The opportunity: turn an internal architecture into an **ecosystem**, where the long tail of stack-specific and house-style capabilities lives *outside* the core repo — reducing maintainer burden while expanding what GSD can do for any given user.
## 3. Personas
| Persona | Goal | Today's pain |
|---|---|---|
| **Capability author** (solo dev who extends GSD) | Ship a reusable capability (a deploy gate, a house-style review step, a domain planner contribution) once and reuse it everywhere | No distribution path; must fork GSD or upstream a PR for every change |
| **Capability consumer** (solo dev using GSD) | Add a community/team capability to a project in one step, and keep it current | No install path; no version surface; no clean removal |
| **Maintainer** | Grow GSD's reach without absorbing every extension as permanent core maintenance | Every useful idea becomes an upstream PR and forever-maintenance |
| **Team lead / enterprise admin** | Constrain which capability sources are allowed | No allowlist; no trust controls |
## 4. Goals
- **G1.** A capability author can publish a capability at a Git URL (or npm/tarball/registry) and a consumer can install it with one command.
- **G2.** Capabilities are versioned; consumers can see when an update exists and apply it deliberately.
- **G3.** Host↔capability compatibility is explicit (`engines.gsd`) and enforced, with graceful downgrade where possible.
- **G4.** Installing a capability that runs code (hooks/MCP/command modules) requires informed, explicit consent and integrity verification — and never runs code at install time.
- **G5.** Capabilities can be upgraded atomically and removed cleanly (files + shared-config fragments), with no orphaned state.
- **G6.** A capability matrix documents every capability and gives third parties a documented way to be listed.
## 5. Success metrics
- **Adoption:** ≥1 documented end-to-end author→publish→consumer-install flow works on all tier-1 runtimes (Claude/Codex/Antigravity) at release; the four how-to guides are each independently completable by following only the docs.
- **Safety:** zero code execution during `install` (verified by test); every executable surface is disclosed before consent (verified by test); `strictKnownRegistries: []` blocks all external installs (verified by test).
- **Integrity:** an install with a mismatched `integrity`/SHA aborts (verified by test); auto-update with a changed executable set re-prompts (verified by test).
- **Cleanliness:** `remove` followed by a filesystem audit shows no residual capability files and no leftover entries in shared `settings.json`/`hooks.json` (verified by test).
- **Honesty of the matrix:** the generated matrix never drifts from the registry (drift guard in CI).
## 6. Scope (1.6.0)
**In scope:** versioned `capability.json`; runtime registry overlay; `gsd capability install|update|outdated|remove|disable|list`; source resolver (registry/git/npm/tarball/local); capability ledger; trust/integrity/consent gate; native version stamping; registry-driven dispatch for third-party command families; the generated capability matrix; the full diataxis documentation set.
**Out of scope (explicit non-goals for 1.6.0):**
- **Operating a hosted central community registry.** The *manifest* and *matrix mechanic* ship; whether GSD runs/advertises a curated registry is **TBD/TBA** (see §8). URL/git import does not depend on it.
- **Sandboxing third-party code.** Out of reach technically; the trust model is consent + integrity + reversibility (ADR-1244 D5).
- **Automated malware scanning / safety scorecards.** A possible follow-up if a curated registry is adopted; not in 1.6.0.
- **A capability marketplace UI.** Docs + CLI only.
- **Paid/licensed capabilities, telemetry, or usage analytics.**
## 7. Functional requirements (product-level)
- **FR1.** `install <spec>` accepts registry/git(#tag,#sha)/npm/tarball/local specs; shows a pre-install summary (name, version, author, artifact counts, **executable surfaces**, context-cost note) and requires consent for non-trusted sources.
- **FR2.** `engines.gsd` incompatibility blocks with a clear message and offers the newest `compatVersions`-compatible version when the source enumerates versions.
- **FR3.** `outdated` lists installed capabilities with an available update (per the documented per-source support matrix); `update [--all]` applies updates atomically.
- **FR4.** `remove <id>` deletes exactly what the ledger recorded (files + shared-config fragments) and prompts before deleting persistent capability data; `disable <id>` toggles off without removing files.
- **FR5.** `list` shows installed capabilities with version, source, tier, and enabled/disabled state.
- **FR6.** Reserved namespaces and `strictKnownRegistries` are enforced.
- **FR7.** Third-party capabilities, once installed, are indistinguishable from first-party in surface/config toggling and loop participation (subject to the load-time re-gate).
## 8. Open questions / decisions deferred
- **OQ1 — Advertise a community registry? (TBD/TBA).** The mechanic (versioned manifests + matrix + registry source adapter) ships. The product decision to *operate/advertise* a curated `gsd-capabilities-community` registry — with its review, scanning, and trust implications — is deferred to a follow-up. Recommendation when revisited: separate "official" (curated) from "community" (consented) sources, mirroring Claude Code's marketplace split.
- **OQ2 — Provenance enforcement.** SHOULD for first-party/curated now; whether to *require* provenance for any listed third-party capability is tied to OQ1.
- **OQ3 — Inter-capability dependency resolution depth.** 1.6.0 validates `requires` closure remains satisfiable on install/upgrade; full npm-style transitive version resolution is a candidate follow-up.
## 9. Risks
- **Supply-chain risk** is the headline risk (third-party code at full parity). Mitigated by ADR-1244 D5; accepted by the maintainer as the cost of full parity.
- **Doc-vs-reality drift** — docs describe behavior that ships in phased PRs; docs land *with* the implementing phase, not ahead of it.
- **Maintainer-burden inversion** — if a community registry is later advertised (OQ1), review/scanning load returns; keeping it consented-but-decentralized avoids this.
## 10. Release & rollout
Phased per ADR-1244 (manifest versioning → overlay → resolver+ledger → trust gate → dispatch → docs/matrix). Documentation ships with the implementing phase. The advertising decision (OQ1) is explicitly a separate, later call — 1.6.0 ships the capability to import from any URL and the documentation mechanic, not a GSD-run storefront.

View File

@@ -0,0 +1,244 @@
# Capability Manifest Reference (`capability.json`)
> **Canonical ADRs:** [ADR-1244](../adr/1244-capability-ecosystem.md) · [ADR-894](../adr/894-capability-declaration-format.md) · [ADR-1016](../adr/1016-runtime-capability-descriptor.md)
> **See also:** [How to develop a capability](../how-to/develop-a-capability.md) · [Capability Command Reference](gsd-capability-command.md)
Each capability is a folder `capabilities/<id>/` (or an overlay root `~/.gsd/capabilities/<id>/` / `.gsd/capabilities/<id>/`) containing one `capability.json` declaration.
The file is schema-validated JSON with a common **envelope** plus a **role-typed body** (`role: "feature"` or `role: "runtime"`).
---
## Envelope fields
These fields are present for both `role: "feature"` and `role: "runtime"` capabilities.
| Field | Type | Required | Description |
|---|---|---|---|
| `id` | string (kebab-case) | Yes | Unique identifier; **must equal the folder name**. The prefix `gsd-`, `gsd-core-`, and `anthropic-` are reserved for first-party use. |
| `role` | `"feature"` \| `"runtime"` | Yes | Discriminator that selects the body schema. |
| `version` | semver string | Yes (1.6.0+) | Semantic version of this capability. The registry rejects a manifest without one. |
| `title` | string | No | Short human-readable label. |
| `description` | string | No | Longer summary sentence. |
| `tier` | `"core"` \| `"standard"` \| `"full"` | Yes | **Source of truth** for install-profile membership and surface cluster assignment. `tier` propagates via the `requires`-closure; install profiles are generated from it. |
| `requires` | string[] | No | Capability `id` values this capability depends on. Must exist in the registry, be acyclic, and be tier-monotone (a `core` capability may not require a `standard` or `full` capability; a `standard` capability may not require a `full` capability). |
| `engines` | object | No | Host-compatibility constraint. Sub-field: `gsd` — semver range string (e.g. `">=1.6.0 <3.0.0"`). Acts as a hard gate at install **and** at load; a mismatch blocks installation and causes the overlay to be skipped with a warning at load time. |
| `compatVersions` | object | No | Graceful-downgrade table mapping `"<capVersion>"` to `"<min gsd version>"`. Only meaningful for sources that enumerate versions (git tags, registry, npm); a bare tarball URL carries one version and simply blocks on incompatibility. |
| `integrity` | string | No | `sha512-<base64>` hash of the capability bundle. Verified before extraction when present; mismatch aborts install. |
| `provenance` | object | No | `{ sourceRepo: string, commit: string }`. Emitted in CI for first-party and curated capabilities. |
| `author` | object | No | `{ name: string, email?: string, url?: string }`. |
| `homepage` | string | No | URL. |
| `repository` | string | No | URL. |
| `license` | string | No | SPDX licence identifier (e.g. `"MIT"`). |
| `keywords` | string[] | No | Arbitrary search tags. |
---
## Feature body (`role: "feature"`)
Feature capabilities declare owned artefacts, lifecycle hooks, a federated configuration slice, and loop extension registrations.
### `skills` and `agents`
| Sub-field | Type | Description |
|---|---|---|
| `skills` | string[] | Owned skill stems. Exactly one capability may own each stem across the entire merged registry (first-party ∪ overlay). |
| `agents` | string[] | Owned agent stems. Same uniqueness constraint as skills. |
### `hooks`
Non-loop lifecycle hooks.
| Sub-field | Type | Description |
|---|---|---|
| `event` | string | Hook event name (host-runtime specific). |
| `script` | string | Path to the hook script, relative to the capability root. |
### `config` — federated config-key schema slice
The `config` field is an object whose keys are federated configuration keys contributed by this capability. Each key must be absent from the central `config-schema` and absent from every other capability's `config` object (collision fails the build gate). Each entry has the following shape:
| Property | Type | Description |
|---|---|---|
| `type` | `"boolean"` \| `"string"` \| `"number"` \| `"enum"` | Value type. |
| `default` | (type-consistent) | Default value; must be consistent with `type`. |
| `description` | string | Human-readable explanation of the key's effect. |
| `values` | string[] | **`enum` only.** Exhaustive list of permitted string values. |
### `steps`
Steps run at a loop extension point as independent units. Ordering within a point is derived from `produces`/`consumes` (topological sort; capability-id is the tiebreak).
| Sub-field | Type | Description |
|---|---|---|
| `point` | string | One of the 12 valid loop extension point identifiers (see table below). |
| `ref` | object | Either `{ "skill": "<stem>" }` or `{ "agent": "<stem>" }`. |
| `produces` | string[] | Artefact names this step produces. No two capability steps may produce the same artefact at the same point. |
| `consumes` | string[] | Artefact names this step consumes. |
| `when` | string | Dotted config key; the step is active only when the key is truthy. Evaluated deterministically at render time; phase-context applicability is the skill's own responsibility. |
| `onError` | `"skip"` \| `"halt"` | Behaviour on failure. `"skip"` is the default. Steps are purely additive — they never halt or redirect the host workflow on their own; a blocking precondition is expressed as a `gate`. |
### `contributions`
Contributions inject a fragment into a named agent role's prompt at a loop extension point. Multiple contributions into the same agent role render as ordered labelled blocks (`<contribution from="<id>">…</contribution>`).
| Sub-field | Type | Description |
|---|---|---|
| `point` | string | One of the 12 valid loop extension point identifiers. |
| `into` | string | Agent role name. Must be a role published by that loop extension point in the host contract. |
| `fragment` | object | Either `{ "path": "<relative path>" }` (file content) or `{ "inline": "<string>" }` (literal text). |
| `when` | string | Dotted config key; activates the contribution conditionally. |
| `onError` | `"skip"` \| `"halt"` | Behaviour on failure. |
### `gates`
Gates check a condition at a loop extension point and optionally block progression.
| Sub-field | Type | Description |
|---|---|---|
| `point` | string | One of the 12 valid loop extension point identifiers. |
| `check` | object | One of three forms (see table below). |
| `when` | string | Dotted config key; activates the gate conditionally. |
| `blocking` | boolean | When `true`, a failed check halts the loop at this point. |
| `onError` | `"skip"` \| `"halt"` | Behaviour when the check itself errors. |
**`check` forms:**
| Form | Shape | Blocking permitted | Notes |
|---|---|---|---|
| Query | `{ "query": "<gsd_run query>" }` | Yes | Deterministic first-party code. |
| Predicate | `{ "predicate": { "kind": "artifact-exists" \| "config-equals" \| …, … } }` | Yes | Declarative; no code path. |
| Agent verdict | `{ "agentVerdict": { "ref": …, "prompt": … } }` | No (forced advisory) | LLM evaluation; non-deterministic checks may not halt the loop. |
---
## Valid `point` values
The 12 loop extension points are a **closed, additive-only vocabulary**. Every `steps`, `contributions`, and `gates` entry must use one of these identifiers exactly.
| Point | Phase | Position |
|---|---|---|
| `discuss:pre` | Discuss | Before the discuss step executes |
| `discuss:post` | Discuss | After the discuss step completes |
| `plan:pre` | Plan | Before the plan step executes |
| `plan:post` | Plan | After the plan step completes |
| `execute:pre` | Execute | Before the execute phase begins |
| `execute:wave:pre` | Execute | Before each execution wave |
| `execute:wave:post` | Execute | After each execution wave |
| `execute:post` | Execute | After the execute phase completes |
| `verify:pre` | Verify | Before the verify step executes |
| `verify:post` | Verify | After the verify step completes |
| `ship:pre` | Ship | Before the ship step executes |
| `ship:post` | Ship | After the ship step completes |
---
## Runtime body (`role: "runtime"`)
Runtime capabilities describe how GSD projects its artefacts onto one host CLI. The body is a closed 8-axis (plus 4 install-surface) vocabulary; no feature-only fields (`skills`, `agents`, `steps`, `contributions`, `gates`, `hooks`) are permitted. Full semantic specifications, the closed enum values for each axis, and the 16-runtime worked examples are in [ADR-1016](../adr/1016-runtime-capability-descriptor.md).
| Axis | Field | Type summary |
|---|---|---|
| Config home | `runtime.configHome` | Structured object with `kind` (`dot-home` \| `dot-home-nested` \| `xdg` \| `generic-agents-root`), `name`, optional `parent`, `env[]`, `probe[]`, `probeExists`, `skillsHome`. |
| Config format | `runtime.configFormat` | Closed enum: `settings-json` \| `toml` \| `markdown` \| `markdown-dir` \| `none`. |
| Artefact layout | `runtime.artifactLayout` | Object with `global` and `local` arrays of `ArtifactKind` (`kind`, `destSubpath`, `prefix`, `nesting`, `recursive`, `stage`). |
| Command style | `runtime.commandStyle` | Closed enum: `slash-hyphen` \| `shell-var`. |
| Hooks surface | `runtime.hooksSurface` | Closed enum: `settings-json` \| `codex-hooks-json` \| `cursor-hooks-json` \| `copilot-inline` \| `cline-rules` \| `none`. |
| Sandbox tier | `runtime.sandboxTier` | Closed enum: `none` \| `codex-agent-sandbox`. |
| Support tier | `runtime.supportTier` | Integer: `1` (fully tested first-party) \| `2` (shipped, lower coverage). |
| Install surface | `runtime.installSurface` | Closed enum: `settings-json` \| `codex-toml` \| `copilot-instructions` \| `cline-rules` \| `cursor-hooks-json` \| `profile-marker-only`. |
| Shared settings | `runtime.writesSharedSettings` | boolean. Whether the runtime writes a shared `settings.json`. |
| Permission writer | `runtime.permissionWriter` | `null` \| `"opencode"` \| `"kilo"`. The finish-time permissions-sidecar writer. |
| Extended hook events | `runtime.extendedHookEvents` | string[] over a closed vocabulary: `SubagentStop`, `Stop`, `PreCompact`, `FileChanged`, `BeforeAgent`, `AfterAgent`, `BeforeModel`. |
For a minimal `role: "runtime"` example, see [ADR-1016 §Decision 8](../adr/1016-runtime-capability-descriptor.md).
---
## Conformance invariants
The following invariants are enforced at **build time** by `scripts/gen-capability-registry.cjs` and at **install time** by the runtime-callable `validateCapability()` / `validateCrossCapability()` over the merged first-party ∪ overlay set.
- **`version` is required.** The registry rejects any manifest without a semver `version` field.
- **`id` uniqueness.** No two capabilities may share an `id`. An overlay whose `id` collides with a first-party `id` is rejected; first-party always wins.
- **Skill and agent stem uniqueness.** Exactly one capability may own each skill or agent stem across the entire merged registry.
- **`requires` exist and are acyclic.** Every `id` listed in `requires` must exist in the registry; the dependency graph must be acyclic.
- **`requires` is tier-monotone.** A `core` capability may not require a `standard` or `full` capability. A `standard` capability may not require a `full` capability.
- **`point` values are from the closed set.** Every `point` in `steps`, `contributions`, and `gates` must be one of the 12 identifiers above.
- **`contribution.into` is a published agent role.** The `into` value must be an agent role declared by the host contract for that loop extension point.
- **Config key exclusivity.** A federated config key must be owned by exactly one capability and absent from the central `config-schema`. Presence in both is a collision; a half-migrated key fails the build gate.
- **Artefact production uniqueness per point.** No two capability steps may `produces` the same artefact name at the same loop extension point.
- **`engines.gsd` is a hard gate.** A capability whose `engines.gsd` range does not satisfy the installed GSD version is blocked at install and skipped (with a warning) at load time.
- **Path confinement.** Declared module paths may not use parent-directory traversal (`../`); modules are `require()`'d only from the capability's own install root.
- **Reserved namespace.** Capability `id` values beginning with `gsd-`, `gsd-core-`, or `anthropic-` are reserved; third-party capabilities using these prefixes are rejected.
---
## Example — complete `role: "feature"` capability
The following is the canonical UI design-contract capability from ADR-894. It illustrates all major body sections.
```json
{
"id": "ui",
"role": "feature",
"version": "1.0.0",
"title": "UI design contracts",
"description": "UI-SPEC design contract and retrospective UI audit for frontend phases.",
"tier": "standard",
"requires": [],
"engines": { "gsd": ">=1.6.0" },
"skills": ["ui-phase", "ui-review"],
"agents": ["gsd-ui-checker", "gsd-ui-auditor"],
"hooks": [],
"config": {
"workflow.ui_phase": {
"type": "boolean",
"default": true,
"description": "Enable the UI design-contract gate during planning."
},
"workflow.ui_review": {
"type": "boolean",
"default": true,
"description": "Enable the retrospective UI audit."
},
"workflow.ui_safety_gate": {
"type": "boolean",
"default": true,
"description": "Block execution on unmet UI-SPEC contracts."
}
},
"steps": [
{
"point": "plan:pre",
"ref": { "skill": "ui-phase" },
"produces": ["UI-SPEC.md"],
"consumes": ["CONTEXT.md"],
"when": "workflow.ui_phase",
"onError": "skip"
},
{
"point": "verify:post",
"ref": { "skill": "ui-review" },
"produces": ["UI-REVIEW.md"],
"consumes": ["UI-SPEC.md"],
"when": "workflow.ui_review",
"onError": "skip"
}
],
"contributions": [],
"gates": [
{
"point": "execute:wave:post",
"check": { "query": "ui.safety-gate" },
"when": "workflow.ui_safety_gate",
"blocking": true,
"onError": "halt"
}
]
}
```
Notes on this example:
- `when` on each hook references its own config key; whether the phase is actually a frontend phase is decided inside `ui-phase` (self-gate).
- The `plan:pre` step self-skips on non-frontend phases, producing no `UI-SPEC.md`; the `execute:wave:post` gate's `ui.safety-gate` query passes gracefully when no `UI-SPEC.md` exists.
- A `contribution` follows this shape: `{ "point": "plan:pre", "into": "planner", "fragment": { "path": "loop/threat-model.md" }, "when": "workflow.security_enforcement" }`.

View File

@@ -0,0 +1,155 @@
# Capability matrix reference
> **Generated file — do not edit by hand.**
> This matrix is generated from the capability registry by
> `scripts/gen-capability-matrix.cjs` (introduced in 1.6.0) and kept honest
> by a drift guard in CI. Any manual edit will be overwritten on the next
> generation run. To change a capability's declared metadata, edit the
> corresponding `capabilities/<id>/capability.json` and rebuild.
See also: [ADR-1244](../adr/1244-capability-ecosystem.md) —
[Capability manifest fields](#manifest-field-reference) —
[Trust model explanation](../explanation/capability-trust-model.md)
---
## Column definitions
| Column | Description |
|---|---|
| **id** | Canonical capability identifier; must be unique across first- and third-party capabilities. Reserved prefixes: `gsd-`, `gsd-core-`, `anthropic-`. |
| **role** | `feature` — extends what the loop does; `runtime` — adapts GSD to a specific AI runtime/IDE. |
| **tier** | `core` — always active; `standard` — active when the runtime supports it; `full` — opt-in or runtime-specific. |
| **version** | Semver version of the capability. Values shown are placeholders; the generator stamps exact per-capability versions from `capability.json` at release. |
| **engines.gsd** | Semver range expressing host-version compatibility. A hard gate at install and at load. |
| **extension points** | Loop extension points this capability registers into. See [the phase loop](../explanation/the-phase-loop.md) for the full ordered list. `see capability.json` means the generator would emit the precise set; only well-known registrations are listed here. |
| **hook kinds** | Subset of `step`, `contribution`, `gate` that the capability's hooks use. |
| **source** | `first-party` — ships with GSD Core; `third-party` — installed from an external source via `gsd capability install`. |
---
## Native (first-party) capabilities
First-party capabilities are implicitly trusted: they ship as part of the GSD
Core package and are stamped with the package version at release (per
ADR-1244 D6). They are not subject to the consent or integrity-pin flow
applied to third-party capabilities.
### Feature capabilities (role: feature)
Feature capabilities extend what the five-step loop does — contributing
research, planning, execution, verification, or ship artefacts.
| id | role | tier | version | engines.gsd | extension points | hook kinds | source |
|---|---|---|---|---|---|---|---|
| `research` | feature | standard | 1.6.0 | `>=1.6.0` | `discuss:pre`, `plan:pre` | step, contribution | first-party |
| `ui` | feature | standard | 1.6.0 | `>=1.6.0` | see capability.json | step | first-party |
| `ai-integration` | feature | standard | 1.6.0 | `>=1.6.0` | see capability.json | step, gate | first-party |
| `security` | feature | full | 1.6.0 | `>=1.6.0` | `execute:pre`, `verify:pre` | gate | first-party |
| `code-review` | feature | standard | 1.6.0 | `>=1.6.0` | `verify:pre`, `verify:post` | step, gate | first-party |
| `schema-gate` | feature | standard | 1.6.0 | `>=1.6.0` | `execute:pre` | gate | first-party |
| `pattern-mapper` | feature | standard | 1.6.0 | `>=1.6.0` | see capability.json | contribution | first-party |
| `nyquist` | feature | full | 1.6.0 | `>=1.6.0` | see capability.json | step, gate | first-party |
| `validation` | feature | standard | 1.6.0 | `>=1.6.0` | `verify:pre`, `verify:post` | step, gate | first-party |
| `graphify` | feature | full | 1.6.0 | `>=1.6.0` | see capability.json | step | first-party |
| `intel` | feature | standard | 1.6.0 | `>=1.6.0` | see capability.json | step, contribution | first-party |
| `audit` | feature | standard | 1.6.0 | `>=1.6.0` | see capability.json | step, gate | first-party |
> **Note:** version `1.6.0` is the placeholder the generator replaces with the
> actual per-capability `version` field from each `capability.json`. The 12
> loop extension points available to feature capabilities are, in order:
> `discuss:pre`, `discuss:post`, `plan:pre`, `plan:post`, `execute:pre`,
> `execute:wave:pre`, `execute:wave:post`, `execute:post`, `verify:pre`,
> `verify:post`, `ship:pre`, `ship:post`. A capability registers into the
> subset it needs; registration of all 12 is unusual.
### Runtime capabilities (role: runtime)
Runtime capabilities adapt GSD to a specific AI runtime or IDE — emitting
skills, agents, hooks configuration, and surface files appropriate for that
host environment.
| id | role | tier | version | engines.gsd | extension points | hook kinds | source |
|---|---|---|---|---|---|---|---|
| `claude` | runtime | core | 1.6.0 | `>=1.6.0` | see capability.json | step | first-party |
| `codex` | runtime | core | 1.6.0 | `>=1.6.0` | see capability.json | step | first-party |
| `gemini` | runtime | standard | 1.6.0 | `>=1.6.0` | see capability.json | step | first-party |
| `antigravity` | runtime | standard | 1.6.0 | `>=1.6.0` | see capability.json | step | first-party |
| `cline` | runtime | standard | 1.6.0 | `>=1.6.0` | see capability.json | step | first-party |
| `cursor` | runtime | standard | 1.6.0 | `>=1.6.0` | see capability.json | step | first-party |
| `opencode` | runtime | standard | 1.6.0 | `>=1.6.0` | see capability.json | step | first-party |
| `kilo` | runtime | standard | 1.6.0 | `>=1.6.0` | see capability.json | step | first-party |
| `copilot` | runtime | full | 1.6.0 | `>=1.6.0` | see capability.json | step | first-party |
| `augment` | runtime | standard | 1.6.0 | `>=1.6.0` | see capability.json | step | first-party |
| `trae` | runtime | standard | 1.6.0 | `>=1.6.0` | see capability.json | step | first-party |
| `qwen` | runtime | standard | 1.6.0 | `>=1.6.0` | see capability.json | step | first-party |
> **Note:** runtime capabilities typically do not register into the 12 loop
> extension points in the same way feature capabilities do — their primary
> responsibility is surface emission (skills, agents, config). Exact hook
> registrations, where they exist, are emitted by the generator into the
> `extension points` cell.
---
## Third-party capabilities
Once a user installs a third-party capability via `gsd capability install
<spec>`, it enters the **runtime registry overlay** (ADR-1244 D2) and appears
in this matrix on their machine alongside native capabilities. Third-party
rows use the same column schema as first-party rows.
### How a third-party row is produced
The generator reads the capability's `capability.json` from the per-scope
install root (`~/.gsd/capabilities/<id>/` for global installs;
`.gsd/capabilities/<id>/` for project-scoped installs), validates it against
the same conformance rules applied to native manifests, and emits a row
identical in shape to the native rows above. The only difference is the
`source` column, which shows `third-party`.
### Column values for third-party rows
| Column | Value |
|---|---|
| **id** | As declared in `capability.json`. Must not use reserved prefixes (`gsd-`, `gsd-core-`, `anthropic-`). |
| **role** | `feature` or `runtime`, as declared. |
| **tier** | `core`, `standard`, or `full`, as declared. |
| **version** | Semver from `capability.json`; the value recorded in the ledger at install time. |
| **engines.gsd** | Range from `capability.json`; verified at install and at each load. |
| **extension points** | As declared in `capability.json`. Validated against the known 12 extension-point identifiers. |
| **hook kinds** | `step`, `contribution`, and/or `gate` as declared. Disclosed in the consent summary at install. |
| **source** | `third-party` |
### Community registry
Whether GSD operates or advertises a central community registry of third-party
capabilities is **TBD/TBA** (see [PRD-1244 §8](../prd/1244-capability-ecosystem.md#8-open-questions--decisions-deferred)).
The matrix mechanic and all manifest fields ship in 1.6.0 regardless of that
decision. URL/git/npm/tarball import does not depend on a central registry.
---
## Manifest field reference
The fields below are defined in `capability.json` and govern how a capability
appears in this matrix. For the full schema, see [ADR-1244 D1](../adr/1244-capability-ecosystem.md#d1--versioned-capability-manifest).
| Field | Required | Type | Purpose |
|---|---|---|---|
| `version` | **Yes** | semver string | Capability version. The registry rejects manifests without this field. |
| `engines.gsd` | Recommended | semver range | Host-version compatibility gate. Enforced at install and load. |
| `compatVersions` | No | object: cap-version → min-gsd-version | Graceful downgrade table for sources that enumerate versions (git tags, registry, npm). |
| `integrity` | No | `sha512-<base64>` | SHA-512 digest of the capability bundle. Verified before extraction when present; mismatch aborts. |
| `provenance` | No | `{ sourceRepo, commit }` | Source provenance. SHOULD be present for first-party and curated capabilities; populated in CI. |
---
## Related documents
- [ADR-1244 — Capability Ecosystem](../adr/1244-capability-ecosystem.md)
- [Capability trust model](../explanation/capability-trust-model.md) — why the trust rules are structured the way they are
- [The phase loop](../explanation/the-phase-loop.md) — the 12 loop extension points in context
- [ADR-857](../adr/857-capability-system.md) — the original capability architecture; D7 and D8 extended by ADR-1244
- [ADR-894](../adr/894-capability-declaration-format.md) — capability declaration format
- [ADR-1016](../adr/1016-runtime-capability-descriptor.md) — runtime capability descriptor

View File

@@ -0,0 +1,256 @@
# `gsd capability` Command Reference
> **Slash form:** `gsd:capability` (surfaced as a slash command on slash-command runtimes)
> **CLI form:** `gsd capability`
> **Canonical ADR:** [ADR-1244](../adr/1244-capability-ecosystem.md)
> **See also:** [Capability Manifest Reference](capability-manifest.md) · [How to develop a capability](../how-to/develop-a-capability.md)
The `capability` family manages the installation, upgrade, removal, and inspection of GSD capabilities — both first-party and third-party overlays. A row for this command also appears in [docs/COMMANDS.md](../COMMANDS.md) (that file is not edited here).
---
## Subcommands
### `install`
**Synopsis**
```
gsd capability install <spec> [--integrity sha512-<hash>] [--scope global|project] [--yes]
```
**Arguments**
| Argument | Description |
|---|---|
| `<spec>` | Source specification (see [Source specifications](#source-specifications) below). |
**Flags**
| Flag | Type | Default | Description |
|---|---|---|---|
| `--integrity` | `sha512-<base64>` | — | SHA-512 bundle hash to verify before extraction. When supplied, a mismatch aborts the install. When the source registry or `capability.json` already carries an `integrity` field, both must agree. |
| `--scope` | `global` \| `project` | `global` | Installation root. `global` writes to `~/.gsd/capabilities/<id>/`; `project` writes to `.gsd/capabilities/<id>/` in the current working directory. |
| `--yes` | flag | off | Suppress the interactive consent prompt. The executable-surface disclosure is still printed; consent is taken as granted. |
**Behaviour**
Resolves `<spec>` to a versioned, staged capability bundle. The pipeline is: fetch → verify integrity or SHA pin → check `engines.gsd` against the installed GSD version → disclose executable surfaces (hooks, command modules) → obtain consent (unless `--yes`) → validate the incoming manifest against conformance invariants over the merged first-party ∪ existing-overlay ∪ new set → extract to the scope root → write the ledger entry atomically.
An overlay whose `id` collides with a first-party capability `id`, or that claims a skill or agent stem already owned, is rejected before extraction. Install never executes capability code; staging is copy-only.
The ledger file (`~/.claude/.gsd-capabilities.json` for the global scope on the Claude runtime, and analogues per runtime) records the installed version, source, integrity hash, owned files, and any fragments written into shared files (e.g. hook registrations in `settings.json`).
---
### `update`
**Synopsis**
```
gsd capability update [<id> | --all]
```
**Arguments**
| Argument | Description |
|---|---|
| `<id>` | Capability identifier to update. Omitting both `<id>` and `--all` is an error. |
**Flags**
| Flag | Description |
|---|---|
| `--all` | Update every installed overlay capability that has a newer version available from its original source. |
**Behaviour**
Fetches the latest version (or the newest version satisfying `engines.gsd`) from the capability's recorded source. Follows the atomic stage-then-swap pattern: the new bundle is fully staged, verified, and validated before the ledger write commits the swap. A crash during staging leaves the previous version intact. A crash after the ledger write leaves the new version intact; a reconciliation sweep on next run resolves any orphaned files.
When `--all` is used, update availability is source-dependent:
| Source kind | Update detection |
|---|---|
| `<name>@<registry>` | Registry catalogue query |
| git (`https://…/repo.git#<tag>`) | Remote tag fetch |
| npm (`npm:@org/pkg@<range>`) | `npm dist-tags` query |
| tarball (`https://…/cap-x.y.z.tgz`) | Not auto-detectable; requires manual `install` with the new URL |
| local (`./local/path`) | Not auto-detectable |
For third-party capabilities, auto-update is **off** by default. When auto-update is enabled, a version whose executable set (hooks, command modules) differs from the previously consented version triggers a re-prompt before the swap completes.
---
### `outdated`
**Synopsis**
```
gsd capability outdated
```
**Flags**
| Flag | Description |
|---|---|
| `--json` | Emit a JSON array instead of the default table. |
**Behaviour**
Queries the source of each installed overlay capability and reports those for which a newer version is available. Capabilities installed from tarball or local-path sources are listed as `"unknown"` for latest version.
**`--json` output shape**
```json
[
{
"id": "string",
"current": "semver",
"latest": "semver | \"unknown\"",
"source": "string",
"scope": "global | project"
}
]
```
---
### `remove`
**Synopsis**
```
gsd capability remove <id> [--purge-data]
```
**Arguments**
| Argument | Description |
|---|---|
| `<id>` | Identifier of the installed overlay capability to remove. |
**Flags**
| Flag | Description |
|---|---|
| `--purge-data` | Also remove any data files created by the capability at runtime (artefacts under the capability's declared paths that are not part of the install bundle itself). |
**Behaviour**
Reads the ledger entry for `<id>` and removes exactly: the owned files listed in `files`, and the fragments written into shared files listed in `sharedEdits` (e.g. hook registrations spliced into `settings.json`). Shared files are not deleted; only the capability's fragments are stripped. The ledger entry is removed atomically after all file operations complete.
First-party capabilities (shipped with GSD) cannot be removed via this subcommand; the entire product uninstall path (`gsd --uninstall`) handles first-party removal.
---
### `disable`
**Synopsis**
```
gsd capability disable <id>
```
**Arguments**
| Argument | Description |
|---|---|
| `<id>` | Identifier of an installed capability to disable. |
**Behaviour**
Marks the capability as disabled in the ledger. A disabled capability is present on disk but excluded from the runtime overlay; it is skipped by the registry loader and contributes no hooks, config keys, or loop extension registrations. The ledger entry is preserved; `enable` reverses the operation without re-fetching.
---
### `enable`
**Synopsis**
```
gsd capability enable <id>
```
**Arguments**
| Argument | Description |
|---|---|
| `<id>` | Identifier of a previously disabled capability to enable. |
**Behaviour**
Clears the disabled flag in the ledger entry for `<id>`. On the next GSD invocation, the capability is included in the runtime overlay subject to its `engines.gsd` range. If the GSD version has changed since the capability was disabled, the `engines.gsd` check is re-evaluated at load time; an incompatible capability is skipped with a warning.
---
### `list`
**Synopsis**
```
gsd capability list [--json]
```
**Flags**
| Flag | Description |
|---|---|
| `--json` | Emit a JSON array instead of the default table. |
**Behaviour**
Lists all capabilities visible to the current GSD session: first-party capabilities (shipped with GSD) and installed overlay capabilities in both global and project scopes. Disabled capabilities are included with a `disabled` status.
**`--json` output shape**
```json
[
{
"id": "string",
"role": "feature | runtime",
"version": "semver",
"tier": "core | standard | full",
"source": "first-party | string",
"scope": "first-party | global | project",
"status": "active | disabled | incompatible",
"title": "string"
}
]
```
`status` values:
| Value | Meaning |
|---|---|
| `active` | Loaded and contributing to the current session. |
| `disabled` | Present in the ledger but excluded via `gsd capability disable`. |
| `incompatible` | `engines.gsd` range does not satisfy the current GSD version; skipped with a warning at load time. |
---
## Source specifications
The `install` subcommand accepts the following source specification forms.
| Form | Example | Adapter |
|---|---|---|
| Registry name | `my-cap@gsd-registry` | Registry — fetches the capability bundle from the named registry; `integrity` is populated from the registry catalogue. |
| Git URL with tag | `https://github.com/org/repo.git#v1.2.0` | Git — clones/fetches at the specified tag; `#sha:<40-hex>` pins a specific commit. |
| npm package | `npm:@org/gsd-capability-foo@^1.0.0` | npm — resolves via `npm dist-tags` / semver range; installs with `--ignore-scripts`. |
| Tarball URL | `https://host/path/cap-x.y.z.tgz` | Tarball — fetches over HTTPS, verifies SHA-512 when `--integrity` is supplied. |
| Local path | `./local/path` | Local — copies from the filesystem path relative to the current working directory. Auto-update and `outdated` detection are not available for this form. |
All forms pass through the same pipeline: fetch → verify integrity or SHA pin → check `engines.gsd` → obtain consent → validate → extract → record ledger.
---
## Install layout
Installed overlay capabilities are written to one of two roots, depending on `--scope`:
| Scope | Root path | Ledger file |
|---|---|---|
| `global` | `~/.gsd/capabilities/<id>/` | Per-runtime, e.g. `~/.claude/.gsd-capabilities.json` |
| `project` | `.gsd/capabilities/<id>/` (CWD) | Per-runtime, adjacent to project root |
The ledger is the commit point for installs and upgrades. Its entries record the installed version, original source URL, integrity hash, owned files, and shared-file edits. A reconciliation sweep on the next GSD run resolves crash orphans (files on disk without a ledger entry, or ledger entries with missing files).

View File

@@ -0,0 +1,219 @@
# Build Your First Capability
In this tutorial you will build a tiny, fully declarative GSD capability from scratch and watch it act inside your project's loop. By the end you will have a working capability installed, visible in `gsd capability list`, and firing at the `plan:pre` extension point.
No code is required. Declarative capabilities — those that own only prompt fragments and hook declarations, with no executable hook scripts or MCP servers — require no trust prompt at install time.
We will build a capability called `hello-note`. It registers a `step` at the `plan:pre` extension point that injects a short greeting fragment into the planner's context and declares that it produces a file called `HELLO.md`.
---
## Before you begin
You need:
- GSD 1.6.0 or later (`gsd --version`).
- A throwaway project directory. Create one now:
```bash
mkdir ~/hello-demo && cd ~/hello-demo
gsd init
```
You will work inside `~/hello-demo` for the rest of this tutorial.
---
## Step 1 — Scaffold the capability folder
Capabilities live in a `capabilities/<id>/` folder. Create the folder structure:
```bash
mkdir -p capabilities/hello-note/fragments
```
Your project tree now looks like this:
```text
~/hello-demo/
.gsd/
capabilities/
hello-note/
fragments/ ← prompt fragments live here
```
---
## Step 2 — Write the prompt fragment
The fragment is a short Markdown file that will be injected into the planner's context when the `plan:pre` hook fires. Create it:
```bash
cat > capabilities/hello-note/fragments/plan-pre.md << 'EOF'
## Hello from hello-note
This planning session was started with the hello-note capability active.
Record a brief note in HELLO.md summarising the plan goal in one sentence.
EOF
```
Notice that the fragment is plain prose. The capability system inlines it into the agent prompt at dispatch time.
---
## Step 3 — Write `capability.json`
Create the manifest at `capabilities/hello-note/capability.json`:
```json
{
"id": "hello-note",
"role": "feature",
"version": "0.1.0",
"title": "Hello Note",
"description": "Injects a greeting note step at plan:pre and produces HELLO.md.",
"tier": "standard",
"requires": [],
"engines": { "gsd": ">=1.6.0" },
"runtimeCompat": { "supported": ["*"], "unsupported": [] },
"skills": [],
"agents": [],
"config": {},
"steps": [
{
"point": "plan:pre",
"fragment": { "path": "fragments/plan-pre.md" },
"produces": ["HELLO.md"],
"consumes": [],
"onError": "skip"
}
],
"contributions": [],
"gates": []
}
```
A few things to notice:
- `version` is required in 1.6.0. Use semver.
- `engines.gsd` is a hard gate: GSD will refuse to install or load this capability on any version older than 1.6.0.
- `role: "feature"` means this capability adds optional behaviour to the loop — it is not a runtime descriptor.
- The single entry in `steps` attaches at `plan:pre`. `produces` tells the registry that this step writes `HELLO.md`, which lets the registry order hooks and detect unsatisfied dependencies in more complex setups.
- `onError: "skip"` means the loop continues even if this step fails. For a first capability that is the safe choice.
No `ref.agent` or `ref.skill` is declared here because this is a fragment-only step: the planner receives the fragment text inline and acts on it. This keeps the capability completely declarative.
---
## Step 4 — Install the capability into your project
Install from the local path with `--scope project` so it is scoped only to this demo project:
```bash
gsd capability install ./capabilities/hello-note --scope project
```
You will see output similar to:
```
Installing hello-note 0.1.0 …
Role : feature
Scope : project
Hooks : 1 (plan:pre step)
Executable surfaces : none
✔ hello-note installed.
```
Because `hello-note` declares no executable surfaces (no hook scripts, no MCP servers, no command modules) GSD copies the files to the project capability ledger without displaying a consent prompt. That is intentional — declarative capabilities are safe to install without reviewing runnable code.
---
## Step 5 — Confirm the installation
```bash
gsd capability list
```
You will see at least one row for `hello-note`:
```
id version role scope status
hello-note 0.1.0 feature project enabled
```
You can also query the active hook set for the `plan:pre` point:
```bash
gsd capability hooks plan:pre
```
Expected output (abbreviated):
```json
[
{
"capability": "hello-note",
"point": "plan:pre",
"kind": "step",
"produces": ["HELLO.md"],
"fragment": { "inline": "## Hello from hello-note\n…" }
}
]
```
Notice that `fragment.inline` now contains the materialised text from `fragments/plan-pre.md`. The capability system inlined it at install time.
---
## Step 6 — Trigger the loop step
Start a planning session. The planner will receive the `hello-note` fragment as part of its context:
```bash
gsd plan
```
Watch the planner output. You will see a line noting that `hello-note` contributed a `plan:pre` step. The planner will produce `HELLO.md` in your project's planning directory as directed by the fragment.
If you are running in an environment where the planner agent is not configured, you can inspect what the resolver would dispatch without running the full agent:
```bash
gsd loop render-hooks plan:pre --raw
```
The JSON output will include your `hello-note` step with its inlined fragment, confirming that the capability is wired into the loop.
---
## Step 7 — Disable the capability
When you want to stop the step from firing, disable the capability:
```bash
gsd capability disable hello-note
```
Run `gsd capability list` again. The `status` column will now show `disabled`. Run `gsd loop render-hooks plan:pre --raw` and you will see that `hello-note` is absent from the active hook set. Disabled capabilities are removed from the resolver output by construction — there is nothing feature-specific for the loop to run.
To re-enable it:
```bash
gsd capability enable hello-note
```
---
## You have built your first capability
You scaffolded a capability folder, wrote a manifest with a single `plan:pre` step, installed it into a project-scoped ledger without a trust prompt, confirmed it in the active hook set, watched it contribute to the planning loop, and disabled it cleanly.
The capability you built is fully declarative: it owns a prompt fragment and a hook declaration, and no executable code was involved at any point.
---
## Where next
- [Publish a capability](../how-to/publish-a-capability.md) — package and share your capability via a URL or registry.
- [Import a capability from a URL](../how-to/import-a-capability-from-a-url.md) — install a third-party capability from a git URL, tarball, or npm package.
- [Capability manifest reference](../reference/capability-manifest.md) — all fields, types, and validation rules for `capability.json`.
- [Capability trust model](../explanation/capability-trust-model.md) — why declarative capabilities need no consent prompt and how executable surfaces are disclosed.