Skip to content

ADR-0019: Ship the Spec Kit extension, treating the spike's no-go as a measurement artifact

Field Value
Status accepted
Date 2026-08-01
Review by 2027-08-01
Schema version 0.1.0
Reversibility two-way-door
Blast radius org
Scope org
Tags strategy, integration, distribution, evidence, governance
Deciders @mbeacom
Authored by agent-drafted
Ratified by @mbeacom
Review tier arb
Review reason Realizes ADR-0003’s distribution strategy by creating the first package under packages/adapters/**, and overrides the recorded outcome of a completed, independently audited spike. Both the packaging boundary and the precedent for when a recorded verdict may be set aside are expensive to reverse informally.
External refs Spec Kit extension API reference at the frozen v0.13.0 commit
Relates to 0003, 0007, 0010, 0014, 0016
Affects path:packages/adapters/spec-kit/**
Source docs/adr/0019-ship-the-spec-kit-extension-treating-the-spike-no-go-as-a-measurement-artifact.md

ADR-0003 commits this project to two distribution surfaces: a standalone CLI, which ships, and a Spec Kit extension, which never has. Its action item 1 said to spike the extension hooks before committing to a production package.

That spike ran (specs/008-spec-kit-hook-viability/). It executed, was remediated post-merge, and was independently audited across nine fresh-context passes. Its recorded verdict is no-go, whose contractual meaning is “no production Spec Kit integration is recommended at this time.”

On the merits, the spike found the opposite. Every mechanism the extension needs was verified working:

Verified Result
after_plan hook fires in a live agent session yes, with genuine plan context
Fixture-local scripts/*.sh references survive rendering yes (an execution-time hypothesis upstream does not document)
Shelling out to adrkit’s built CLI, offline yes, under a kernel-enforced network namespace
Repository mutation during hook-fire none
Disable, re-enable, remove clean and complete
Rendering across two independent upstream agents both
Honest failure on absent context / absent CLI non-zero, specific message, no crash

The no-go fired on one axis only: MutationBaseline rows 0 and 3, install and remove. Those are lifecycle operations whose entire purpose is to write files, measured against a literal byte-identical git status --porcelain=v1 bar. The spike said so itself, in its own Limitations section: the verdict procedure has “no carve-out for a lifecycle action whose entire purpose is to write files. This tension is reported honestly rather than smoothed over.”

So the record contains a no-go that no evidence in the bundle supports, produced by a contract that could not have returned anything else. Two failure modes are available here, and both are bad. Quietly building anyway makes the recorded verdict decorative — which, in a project whose entire thesis is that recorded decisions bind, is self-refuting. Treating the verdict as load-bearing forever means a measurement defect permanently blocks a committed strategy.

The way out is neither: name the defect, and record the override as a decision that is itself reviewable.

Ship the production Spec Kit extension at packages/adapters/spec-kit/, and record spike 008’s no-go as a measurement artifact rather than a finding.

Concretely:

  1. Spike 008’s no-go verdict, its evidence bundle, and its audit history stand unmodified. Nothing is retro-edited, no checkbox is flipped, no verdict is rewritten. It remains the honest record of what that contract returned.
  2. The defect is located in the spike’s own verdict procedure — SC-007’s no-go trigger applied MutationBaseline byte-identity to lifecycle actions that necessarily write — not in Spec Kit, not in the hook mechanism, and not in adrkit.
  3. That procedure’s finding is therefore not evidence against a production integration, and this decision overrides it for the purpose of authorizing one. Every other spike finding continues to bind, including the ones that constrain the design below.
  4. The extension inherits the spike’s verified constraints as production requirements, not as suggestions:
    • speckit_version pinned to a bounded range whose upper edge is a verification boundary. Widening it is a re-verification, not a version bump. (Originally <0.14.0, the one minor line the spike verified; widened to <0.16.0 on 2026-08-01 — see the addendum above.)
    • Exactly one hook, after_plan, optional: true. Never a mandatory hook.
    • Hooks may only target commands that do not write. speckit.adrkit.draft writes, and is therefore reachable only by explicit human invocation.
    • The honest-failure contract (non-zero exit, a message naming the missing dependency, no fabricated success) applies to every command.
  5. These constraints are enforced by tests, not by convention. Each was observed failing under a deliberately introduced defect before being trusted (ADR-0016).

Per ADR-0014, this lands the extension on rung 1 only — unit and contract evidence. It is not reference-verified and not externally validated. Neither this decision nor the package claims otherwise.

Addendum, 2026-08-01: pin re-verification (action item 3)

Section titled “Addendum, 2026-08-01: pin re-verification (action item 3)”

Action item 3 came due immediately: Spec Kit had already released v0.14.x and v0.15.1 by the time this record was written, so the <0.14.0 bound shipped unable to install on current upstream. Re-verified rather than widened on inference:

Evidence Result
extensions/EXTENSION-API-REFERENCE.md, frozen 9a30db48 vs v0.15.1 byte-identical, 858 lines, empty diff
templates/commands/plan.md (renders the hook) one added py: script line; hook rendering unchanged
src/specify_cli/extensions/__init__.py changed, but additively — a new optional events section, and “must provide at least one command or hook” relaxed to “command, hook, or event”. A manifest providing commands and hooks still satisfies it.
Install + render on v0.14.4 clean; all three commands registered
Install + render on v0.15.1 clean; commands rendered to .github/agents/ and .github/prompts/, hook registered in .specify/extensions.yml as optional: true
Installed script run end-to-end against the real built CLI and a real corpus correct governing decisions returned, exit 0

The bound is therefore widened to <0.16.0, verified at 0.13.0, 0.14.4, and 0.15.1. Past 0.16 remains a re-verification, not a bump.

Two packaging defects surfaced only because the extension was installed for real rather than reasoned about, and both are fixed:

  1. specify extension add --dev copies the extension directory verbatim and does not skip node_modules. Bun’s isolated linker had created one here for a single @types/bun devDependency, and the workspace symlink inside it aborted the install partway through with a shutil.Error. The package now declares no dependencies at all.
  2. The install was depositing our test suite and tsconfig.json into the consuming project. Now excluded via .extensionignore, which upstream supports across the whole pinned range.

This addendum’s evidence is stronger than pure unit/contract work — it is real upstream, really installed, really executed. It is deliberately not claimed as ADR-0014 rung 2: the scratch projects were session-scoped and are gone, there is no tracked reference repository, no evidence index, and no independent review. The rung-1 scoping above stands unchanged.

Superseded 2026-08-02 by the rung-2 addendum below. The paragraph above described this record’s state before reference verification existed. Rung 2 has since been met; the caveats it names have been answered rather than restated.

Addendum, 2026-08-02: rung-2 reference verification

Section titled “Addendum, 2026-08-02: rung-2 reference verification”

The extension is now landed / reference-verified on ADR-0014 rungs 1–2.

Rung 2 is met in mbeacom/adrkit-t018-dogfood (PR #9, merge b559d32c48b0098510e0dae8d4fb994afd6f8053) — the same maintainer-owned isolated repository that carries the Phase 6 evidence. Its workflow reinstalls this extension from the pinned adrkit commit 61f5910 into a real Spec Kit project, across all three declared upstream versions, on every push and weekly: 41 self-verifying, fail-closed assertions per version.

The self-verifying criterion was demonstrated rather than asserted. A deliberate divergence run repointed the pin at 07658e4, this extension’s first commit, and all three legs went red — catching the defects that version really had (INS-2: the then-<0.14.0 pin genuinely rejects 0.14.4 and 0.15.1; PKG-*: it shipped development files into the consumer’s project). A gate only ever seen green is a gate nobody has watched work.

Full evidence index, including hashes, run links, expected-vs-observed rows, and named limitations: docs/reference-verification-spec-kit-extension.md.

Rung 3 — external / community validation — remains absent. Nobody but the maintainer has run this extension in their own repository.

Option A: Ship, and record the override as a decision (chosen)

Section titled “Option A: Ship, and record the override as a decision (chosen)”

Costs one ADR and states plainly that a recorded verdict was set aside, why, and on what evidence. The override is reviewable, and the reasoning survives for the next person who finds a no-go in the history and wonders why a package exists anyway.

Option B: Treat the no-go as binding and leave ADR-0003 unrealized

Section titled “Option B: Treat the no-go as binding and leave ADR-0003 unrealized”

Maximally deferential to the record. But it lets a defect in a spike’s own measurement procedure permanently veto a committed strategy, on an axis the spike itself disclosed as spurious. It also teaches the wrong lesson: that a contract’s literal output outranks its own author’s disclosure of why that output is wrong.

Option C: Re-run the spike under a corrected verdict procedure

Section titled “Option C: Re-run the spike under a corrected verdict procedure”

The most rigorous option, and the one to take if the mechanism evidence were thin. It is not. The re-run would exercise the same hooks against the same frozen commit and, by construction, return go — paying a full spike cycle to relabel findings already in the bundle. Rejected as ceremony, and recorded here so the choice is visible rather than skipped.

Option D: Ship manual commands only, no hooks

Section titled “Option D: Ship manual commands only, no hooks”

The narrow reading of manual-command-only. But the hook is the whole point: governance an agent must remember to ask for is governance that gets skipped. The hook evidence is also the strongest part of the bundle — a live fire with real plan context and zero mutation. Scoping it out would discard the best-verified capability to respect a verdict that capability did not trigger.

Overriding a recorded verdict is a precedent, and precedents get reused with less care than they were set. The mitigation is that this ADR is narrow and explicit: it overrides one named trigger on one named axis of one named spike, on the strength of that spike’s own disclosure. It does not establish that verdicts are advisory, and it is not authority to set aside a finding that is merely inconvenient.

The pin means the extension breaks on the first unverified upstream minor rather than degrading quietly. (As accepted, that bound was a single upstream minor; it has since been re-verified and widened twice, and now spans 0.13 through the 1.0 line — see the addenda. The mechanism is what this paragraph is about, and it is unchanged: the bound moves only behind evidence.) Under ADR-0007 that is the intended behavior for an adapter — an adapter’s semver contract is with its upstream — but it does mean real maintenance rather than a floating range that appears to work until it does not.

  • Easier: ADR-0003’s second surface exists; packages/adapters/** has its first real inhabitant and ADR-0007’s boundary is now exercised rather than theoretical; ADR-0003 action item 1 can close.
  • Harder: an upstream to track, and a documented precedent for overriding a recorded verdict that future decisions must be careful not to over-read.
  • Revisit if: Spec Kit’s extension or hook API changes shape; the pin needs widening (which requires re-verification, not a bump); or reference/external validation is attempted, at which point ADR-0014 rungs 2 and 3 apply and this record’s rung-1 scoping should be restated, not quietly outgrown.
  1. Build the extension at packages/adapters/spec-kit/ under the constraints above
  2. Enforce the read-only hook boundary with a test observed failing first
  3. Re-verify the speckit_version pin against the next Spec Kit minor before widening it — done 2026-08-01, see the addendum above; widened to <0.16.0, verified at 0.13.0, 0.14.4, 0.15.1. Superseded three times since, and this item records the practice rather than a one-time task: re-verified 2026-09-09 and widened to <1.1.0, then extended 2026-09-12 to close the 1.0 line’s upper edge, then re-verified 2026-10-09 and widened to <1.2.0. The current bound and its evidence live in the addenda below, not on this line.
  4. Decide whether to publish @adrkit/spec-kit to npm, or install it from the repository — both channels, see the addendum below
  5. Submit the catalog entry to github/spec-kit once a release asset exists — submitted 2026-08-03 as issue github/spec-kit#3942 and applied the same day by a maintainer in #3947, listing version 0.1.2. (An earlier revision of this line said “landed 2026-08-25 via #3947”; both the issue and the pull request are dated 2026-08-03, and the pull request was the maintainer’s step rather than ours.) This is a standing per-release obligation, not a one-time task, and treating it as one-time cost two releases: the entry stayed pinned at 0.1.2 through 0.1.3 and 0.1.4, still advertising >=0.13.0,<0.16.0 — a bound that by then refused to install on any current Spec Kit. An update request for 0.1.4 was submitted 2026-09-13 as github/spec-kit#4571 and applied when a maintainer closed it on 2026-09-15: on 2026-10-09 catalog.community.json lists 0.1.4 with >=0.13.0,<1.1.0, which specify extension search adrkit on 1.1.x also reports. Filing is not refreshing, so record each refresh when the catalog change actually lands, not when the issue is filed. 0.1.5 owes the same submission once its asset resolves. The release runbook carries the step (docs/RELEASING.md).
  6. Remove @adrkit/spec-kit from BOOTSTRAP_PACKAGES after its first publish, once Trusted Publishing is configured for the name — done 2026-08-03; the set is now empty, which is its correct steady state. The NPM_BOOTSTRAP_TOKEN secret can be deleted.

Addendum, 2026-08-02: distribution channels (action item 4)

Section titled “Addendum, 2026-08-02: distribution channels (action item 4)”

Two channels, because they serve different consumers:

  • Spec Kit community catalog — the idiomatic path. Extensions are installed with specify extension add, and the ~40 community extensions distribute via a GitHub release asset plus an entry in github/spec-kit’s catalog.community.json. This is how a Spec Kit user will actually find and install it, and it is why the manifest now declares category and effect.
  • npm — for programmatic and pinned installs alongside the rest of the scope.

npm required extending the release pipeline rather than bending a decision. scripts/release-pack.ts asserted versions.size === 1 across every release package, and that every package publish a dist. Both were true of a repository whose only published packages were one lockstep Node surface. Neither is true of an adapter, and forcing them would have contradicted ADR-0007’s “adapters … are versioned independently. Their semver contract is with their upstream, not with our core.”

So the pipeline now distinguishes the two:

Field Meaning
versioning: 'lockstep' Moves with the release tag. Core, evaluator, CLI, MCP — one API surface, pinned together.
versioning: 'independent' Carries its own version. The release tag names the lockstep version and says nothing about it.
shipsNodeArtifact: false No dist and no Node engine constraint required — and dist is now rejected, since a package of manifests, markdown, and shell scripts that ships one is misdeclared.

Publishing was already idempotent per artifact, so an unchanged adapter is skipped rather than republished on the next core release. A first publish of a new npm name cannot use Trusted Publishing, which requires the name to exist, so @adrkit/spec-kit is temporarily in BOOTSTRAP_PACKAGES (action item 6).

Addendum, 2026-09-09: pin re-verification across the 0.16 and 1.0 lines

Section titled “Addendum, 2026-09-09: pin re-verification across the 0.16 and 1.0 lines”

Spec Kit released 0.16.0–0.16.5 and its first majors 1.0.0–1.0.5 after the 2026-08-01 widening; the <0.16.0 bound fails loud on current upstream exactly as designed. Re-verified rather than widened on inference, against the same standard as the first widening:

Evidence Result
extensions/EXTENSION-API-REFERENCE.md: frozen 9a30db48 (0.13.0) → v0.16.5 → v1.0.4 additive only; v0.16.5 vs v1.0.4 byte-identical (896 lines, empty diff). The additions are provides.templates/provides.scripts and a relaxed “at least one of commands/templates/scripts/hooks/events” rule that a manifest providing commands and hooks already satisfies.
src/specify_cli/extensions/__init__.py: v0.15.1 → v0.16.5 → v1.0.4 additive or refactor; the only removed lines relocate an error string and harden registry/config reads. .extensionignore (gitignore semantics) and SpecifierSet version parsing are unchanged.
Install + render on 0.16.5 (PyPI), 1.0.0 (git tag v1.0.0), 1.0.4 (PyPI) clean on all three; after_plan registered optional: true; the installed tree carries only LICENSE, NOTICE, README.md, commands/, extension.yml, scripts/ — .extensionignore still excludes the test suite and tsconfig.json
Negative control: previous pin <0.16.0 on 1.0.4 rejected with a compatibility error naming both specifiers, exit 1 — the fail-loud contract observed working

One rendering change is recorded, not repaired: on 1.0.x with the Copilot integration, extension commands render as agent skills under .github/skills/speckit-adrkit-* (the same surface upstream’s own commands use) rather than as .github/agents/ and .github/prompts/ files. All three commands register; invocation follows upstream’s skills-mode convention.

The bound is widened to >=0.13.0,<1.1.0, verified at 0.13.0, 0.14.4, 0.15.1 (the standing dogfood matrix), 0.16.5, 1.0.0, and 1.0.4 (this addendum’s install runs). Past 1.0 remains a re-verification, not a bump.

Limitations, stated honestly: this re-verification ran as a maintainer session (install, render, structural checks) rather than as tracked legs of the dogfood reference workflow, whose matrix still exercises 0.13.0/0.14.4/0.15.1. Adding 0.16.5/1.0.0/1.0.4 legs there is the follow-up that restores weekly self-verifying coverage across the widened range. The @adrkit/spec-kit version moves to 0.1.4 with this widening.

Addendum, 2026-09-12: closing the 1.0 line’s upper edge, and the sampling rule

Section titled “Addendum, 2026-09-12: closing the 1.0 line’s upper edge, and the sampling rule”

The 2026-09-09 widening to <1.1.0 was correct in mechanism but left a real hole in its evidence: upstream’s newest release at the time was 1.0.5, and the install runs reached only 1.0.4. A bound whose whole justification is “this is a verification boundary, not a guess” was therefore admitting a released, unverified patch. 1.0.6 shipped shortly after. That gap is now closed, on the same maintainer-session terms as the addendum above.

Evidence Result
extensions/EXTENSION-API-REFERENCE.md: v1.0.4 vs v1.0.5 vs v1.0.6 byte-identical at all three tags (896 lines, SHA-256 cb037d69fe62c7d8…). The extension-facing contract does not move inside the 1.0 line.
src/specify_cli/extensions/__init__.py: v1.0.4 → v1.0.5 a single hunk widening the __SPECKIT_COMMAND_*__ placeholder pattern from [A-Z][A-Z0-9_]* to [A-Z][A-Z0-9_-]* — strictly more permissive; nothing that matched before stops matching.
src/specify_cli/extensions/__init__.py: v1.0.5 → v1.0.6 three call sites pass author=manifest.data["extension"].get("author") into skill generation. .get(), so a manifest without an author is unaffected. .extensionignore handling and SpecifierSet parsing are untouched by both diffs.
Install + render on 1.0.5 (PyPI) and 1.0.6 (PyPI), Python 3.12 exit 0 on both; three commands and three agent skills registered; after_plan recorded optional: true; installed tree still excludes test/, tsconfig.json, package.json, and node_modules/

Two clarifications the earlier addendum’s phrasing invites, both recorded rather than repaired:

  • Its “the installed tree carries only …” list omitted .specify-dev/, which a --dev install has generated since at least 1.0.4. It is the loader’s own staging directory for the skills it registers — upstream output, not development files defeating .extensionignore.
  • On 1.0.6, generated skill metadata credits author: Mark Beacom (@mbeacom) from extension.author, where 1.0.4 emitted author: github-spec-kit. This is the observable effect of the loader change above. It is an improvement, it required no adrkit change, and it is noted so a future reader does not mistake it for drift.

The sampling rule this decision now commits to, because leaving it implicit is what produced the hole: the bound is declared and verified at minor granularity — the endpoints and samples of each admitted line, not every patch. <1.1.0 asserts “verified through the 1.0 line”. A subsequent 1.0.x patch does not invalidate that claim and does not require a new addendum; 1.1.0 is where the fail-loud gate fires and re-verification is owed. Upstream patch releases are the case semver exists to describe, and a boundary that chased them would have to move on a release adrkit has no say over.

Limitations: unchanged from the addendum above. These are maintainer-session installs, not tracked dogfood legs; the reference workflow’s matrix still exercises 0.13.0/0.14.4/0.15.1, and bringing 0.16.5, 1.0.0, and 1.0.4–1.0.6 under the weekly self-verifying gate remains the open follow-up. No manifest change accompanies this addendum — the pin already reads >=0.13.0,<1.1.0; only its evidence moved. @adrkit/spec-kit 0.1.4 is the release that carries it.

Addendum, 2026-10-09: widening to the 1.1 line

Section titled “Addendum, 2026-10-09: widening to the 1.1 line”

Spec Kit 1.1.0 shipped on 2026-10-02, and the <1.1.0 gate fired as this decision intends: 0.1.4 refuses 1.1.0 and 1.1.3 from both its release archive and --dev, with `Compatibility Error: Extension requires spec-kit

=0.13.0,<1.1.0, but 1.1.3 is installed.` and exit 1. This addendum records the re-verification on the same maintainer-session terms as the two above.

Evidence Result
extensions/EXTENSION-API-REFERENCE.md, v1.0.13 → v1.1.3 byte-identical through v1.1.2 (SHA-256 cb037d69fe62c7d8…); v1.1.3 adds only the bundled bug extension’s hook events. Additive.
Loader, v1.0.13 → v1.1.3 generic-integration registration (#4785), exact catalog release selection (#4726), external agent adapters (#4862), and install rollback that unregisters hooks on failure. SpecifierSet parsing and .extensionignore are untouched; the one new refusal applies only to catalog installs.
Install of 0.1.5 on 1.1.0 (git tag; never on PyPI) and 1.1.3 (PyPI), Copilot and Claude integrations, by --dev, by --from its packed archive, and by specify init --extension exit 0 on all twelve; three speckit-adrkit-* skills registered; after_plan recorded optional: true; the plan skill still maps dots to hyphens; no development files deposited. 1.1.1 and 1.1.2 sampled under Copilot with the same results.
context.sh and check.sh from the installed location, @adrkit/cli 0.18.0 exit 0 on every run, with the expected governing records

Nothing in commands/ or scripts/ needed to change. The bound is widened to >=0.13.0,<1.2.0: <1.2.0 asserts “verified through the 1.1 line”, and 1.2.0 is where re-verification is next owed. @adrkit/spec-kit moves to 0.1.5 with this widening, which also ships the README install line corrected after 0.1.4. The full matrix is in docs/reference-verification-spec-kit-extension.md.

Limitations: unchanged. These are maintainer-session installs, not tracked dogfood legs, and no live agent session watched the hook offer render on 1.1.x.