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 |
Context
Section titled “Context”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.
Decision
Section titled “Decision”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:
- Spike 008’s
no-goverdict, 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. - The defect is located in the spike’s own verdict procedure — SC-007’s
no-gotrigger appliedMutationBaselinebyte-identity to lifecycle actions that necessarily write — not in Spec Kit, not in the hook mechanism, and not in adrkit. - 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.
- The extension inherits the spike’s verified constraints as production
requirements, not as suggestions:
speckit_versionpinned 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.0on 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.draftwrites, 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.
- 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:
specify extension add --devcopies the extension directory verbatim and does not skipnode_modules. Bun’s isolated linker had created one here for a single@types/bundevDependency, and the workspace symlink inside it aborted the install partway through with ashutil.Error. The package now declares no dependencies at all.- The install was depositing our test suite and
tsconfig.jsoninto 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.
Options considered
Section titled “Options considered”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.
Trade-offs
Section titled “Trade-offs”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.
Consequences
Section titled “Consequences”- 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.
Action items
Section titled “Action items”- Build the extension at
packages/adapters/spec-kit/under the constraints above - Enforce the read-only hook boundary with a test observed failing first
- Re-verify the
speckit_versionpin 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 the1.0line’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. - Decide whether to publish
@adrkit/spec-kitto npm, or install it from the repository — both channels, see the addendum below - Submit the catalog entry to
github/spec-kitonce 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-09catalog.community.jsonlists 0.1.4 with>=0.13.0,<1.1.0, whichspecify extension search adrkiton 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). - Remove
@adrkit/spec-kitfromBOOTSTRAP_PACKAGESafter 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. TheNPM_BOOTSTRAP_TOKENsecret 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 ingithub/spec-kit’scatalog.community.json. This is how a Spec Kit user will actually find and install it, and it is why the manifest now declarescategoryandeffect. - 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--devinstall has generated since at least1.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 creditsauthor: Mark Beacom (@mbeacom)fromextension.author, where1.0.4emittedauthor: 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.