Skip to content

feat(agent-plugin): offer the bootstrap decision record as an offer, not a candidate - #199

Open
mbeacom wants to merge 4 commits into
mainfrom
feat/backfill-bootstrap-record
Open

feat(agent-plugin): offer the bootstrap decision record as an offer, not a candidate#199
mbeacom wants to merge 4 commits into
mainfrom
feat/backfill-bootstrap-record

Conversation

@mbeacom

@mbeacom mbeacom commented Sep 9, 2026

Copy link
Copy Markdown
Owner

What and why

Run /adr-backfill against a repository with no ADR corpus and it audits the code for decisions that were never recorded — while saying nothing about the one decision the caller is in the middle of making: whether to keep decision records here at all, and whether adrkit enforces them.

That record cannot be a backfill candidate, for two independent reasons:

  • It fails the skill's own evidence rule. Every other candidate is admitted on a source span, a commit, or a plan. Nothing in a repository proves a human chose to keep ADRs, because at the moment backfill runs, nobody has.
  • It cannot travel through the handoff. A backfillHandoff carries concrete candidatePaths, never globs, and /adr-draft re-runs adr check over exactly those paths before writing. The bootstrap record governs the corpus directory — a glob that frequently does not exist yet.

So it is offered, not mined: reported under existing corpus state, routed to plain /adr-draft, kept out of the candidates table and every handoff. The handoff contract is untouched and no exemption was carved into it.

The edge is read off the corpus rather than assumed. Adopting adrkit is never a supersession of the decision to record decisions — the tooling choice depends on the process choice. supersedes is reserved for a prior tooling record (adr-tools, log4brains); a MADR corpus is migrated, not superseded.

The defect worth reading

Measuring detection instead of reasoning about it found a real bug in the first version of this guidance. Against an unmigrated MADR corpus, adr check returns an empty governing bucket — identical to "no process record exists" — because no record parses the frontmatter fence, including the record that is the process decision. Following the original wording, an agent would offer the process decision to a repository that already had one: precisely the failure ADR-0038 names as proof the design is wrong.

Fixed by making the exit code a precondition rather than an afterthought. Only on exit 0 does an empty bucket mean absence; on exit 1 the corpus did not parse and the offer is unverified until migrated.

Evidence

Detection measured against five corpus shapes with a CLI built from this branch reporting 0.13.0:

Corpus Exit governing Reading
docs/adr/ absent 2 Nothing to detect; offer both
present but empty 0 [] No process record; offer both
records binding src/** only 0 [] No process record; offer both
plus a record binding docs/adr/** 0 ["0002"] Detected by matcher, not id or title
unmigrated MADR 1 [] + rule: frontmatter-fence Trap — parse failure, not absence

Host surfacing behavior is unverified. Whether any host actually presents the offer on an empty corpus needs a functional run and is tracked as an open action item on ADR-0038, not claimed here.

Checklist

  • Commits are DCO signed off
  • ADR added — ADR-0038, proposed, awaiting ratification
  • Schema unchanged — N/A
  • packages/ci/src and @adrkit/core unchanged — N/A (see note below)
  • Tests observed failing before passing, per ADR-0016
  • typecheck, build, test, lint pass (2813 pass / 0 fail), plus check:changelog, check:site-grammar, check:doc-pins, check:deps, check:clean-clone, check:freeze-hashes, check:clause8, check:no-spike-heuristics, both claude plugin validate targets, adr lint, and the site build

Notes for reviewers

  • Version bumped to 0.3.0 across all eight surfaces (plugin.json, apm.yml, package.json, bun.lock, both marketplace entries, both skills' metadata.version) — Claude Code keys its plugin cache on it.
  • packages/ci/dist is deliberately untouched. Running bun run build to check a version rebuilt those bundles off-pin (local Bun 1.4.0 vs the pinned 1.3.14), producing ~4,400 lines of codegen drift. Reverted — the template's own warning about Mac-built bundles applies.
  • Guidance lives on both surfaces. /adr-backfill loads the command, so commands/adr-backfill.md carries the clause too; an earlier revision edited only the skill and the offer was unreachable from the entry point. The wiring test now asserts on both.
  • The ceremony risk is real and deliberately unmitigated in code. A bootstrap ADR that says only "we decided to use ADRs" is noise lint will nag about forever. The guidance offers a decision to make, not a template to accept, and says so.

https://claude.ai/code/session_018qNk7AF5cPWkqUkWFvGBqF

…not a candidate

Backfill run against a repository with no ADR corpus found decisions in the
code but said nothing about the one the caller was making: whether to keep
decision records at all, and whether adrkit enforces them.

That record cannot be a backfill candidate. No source span proves a human
ratified either choice, so it fails the skill's own evidence rule, and it
governs the corpus directory -- a glob that often does not exist yet -- so it
can never supply the concrete candidatePaths a backfillHandoff requires and
/adr-draft re-verifies before writing. Modeling it as a candidate would have
meant exempting it from that check.

So it is offered rather than mined: reported under existing corpus state,
routed to plain /adr-draft, and kept out of the candidates table and every
handoff. The handoff contract is untouched.

The edge is read off the corpus rather than assumed. Adopting adrkit is never
a supersession of the decision to record decisions -- the tooling choice
depends on the process choice. supersedes is reserved for a prior tooling
record; a MADR corpus is migrated, not superseded. Detection runs through
adr check's governing bucket, never by hand-parsing frontmatter.

The wiring test was observed failing before the guidance existed (ADR-0016).
Evidence is contract and static-host only -- no functional run -- and
docs/reference-verification-agent-plugin.md says so explicitly.

Claude-Session: https://claude.ai/code/session_018qNk7AF5cPWkqUkWFvGBqF
Signed-off-by: Mark Beacom <m@beacom.dev>
Measuring the detection mechanic against synthetic corpora found a real defect
in the guidance shipped by the previous commit.

An unmigrated MADR corpus returns an empty `governing` bucket -- the same
answer as a corpus with no process record -- because no record parses the
frontmatter fence, including the record that IS the process decision. adr check
reports frontmatter-fence errors and exits 1, so the signal exists, but the
guidance stated the conclusion first and qualified it afterwards. An agent
skimming it would offer the process decision to a repository that already has
one, which is the exact failure ADR-0038 names as proof the design is wrong.

The precondition now comes first: only on exit 0 does an empty bucket mean no
process record exists. The MADR case is named explicitly, since it is the
common real-world instance.

Also merged two edge-table rows that became identical after the row-2
correction, and noted that with no corpus there is nothing to detect -- adr
check against a missing corpus directory exits 2, measured.

Four corpus shapes are now recorded as measured evidence rather than reasoned
about: missing, source-only, process-record-present, and unmigrated MADR. Host
surfacing behavior remains unverified and stays an open action item.

The two new assertions were confirmed failing against the previous commit's
text before the fix was written (ADR-0016).

Claude-Session: https://claude.ai/code/session_018qNk7AF5cPWkqUkWFvGBqF
Signed-off-by: Mark Beacom <m@beacom.dev>
… surfaces

Three defects found by re-checking claims against what was actually measured.

The measurements were taken with the main checkout's stale dist, which reports
0.5.0, while the evidence table claimed 0.13.0. Rebuilt from this worktree and
re-ran every case at a CLI that genuinely reports 0.13.0. The results held, but
the label was wrong, and one row was wrong for a different reason: the harness
created docs/adr with mkdir -p, so the "no corpus" row actually measured an
empty corpus. Separating them found a fifth state -- a corpus directory that
exists but holds no record exits 0 with an empty bucket, where a missing one
exits 2. Both read the same way here, and the skill now says so.

The JSON finding key is `rule`, not `code`; the evidence table said the latter.

CHANGELOG, README, and AGENTS.md still carried "no functional run of any kind"
after detection had been measured. All three now state the same split the
evidence document does: detection measured against synthetic corpora, host
surfacing unverified. The CHANGELOG also gained the exit-code precondition,
which is the rule most likely to be regressed and was absent from the entry a
reader sees first.

The wiring test read only SKILL.md, so the reachability defect fixed earlier
could silently recur. It now asserts the offer, the not-a-candidate framing, and
the never-supersedes rule on both SKILL.md and commands/adr-backfill.md. All
three assertions were confirmed failing against the pre-clause command file.

ADR-0038 now relates to ADR-0008, since MADR migration is a precondition of
detection rather than an aside.

Claude-Session: https://claude.ai/code/session_018qNk7AF5cPWkqUkWFvGBqF
Signed-off-by: Mark Beacom <m@beacom.dev>
Copilot AI lite review requested due to automatic review settings September 9, 2026 03:44

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

It introduces a few correctness issues in newly added documentation/ADR metadata (stored inline), which should be fixed before approval.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Pull request overview

This PR updates the portable agent plugin’s backfill guidance so that, when run against a repo with no ADR corpus (or no governing process record), it offers the bootstrap “keep ADRs + enforce with adrkit” decision(s) as an offer (routed to /adr-draft) rather than treating it as an evidence-backed backfill candidate. It also bumps the agent-plugin surface version to 0.3.0 and documents/measures the MADR parse “empty governing bucket” trap.

Changes:

  • Add bootstrap-record offer guidance across the backfill skill, command docs, and site docs, plus a wiring test to prevent regression.
  • Add ADR-0038 (proposed) and extend reference-verification documentation for the measured detection behavior.
  • Bump agent-plugin version to 0.3.0 across manifests/metadata (including marketplace entry) and record in CHANGELOG/AGENTS.
File summaries
File Description
site/src/content/docs/quickstart.mdx Quickstart tip guiding the first record and linking to the plugin offer behavior.
site/src/content/docs/backfill.mdx Docs describing the “no corpus yet” bootstrap offer and relationship rules.
packages/adapters/agent-plugin/test/wiring.test.ts Adds regression assertions ensuring guidance exists on both skill + command surfaces.
packages/adapters/agent-plugin/skills/decision-memory/SKILL.md Version bump + no-corpus clause aligning with bootstrap-offer guidance.
packages/adapters/agent-plugin/skills/decision-backfill/SKILL.md Main new bootstrap-offer guidance, including MADR exit-code-before-bucket rule.
packages/adapters/agent-plugin/README.md Updates narrative to include 0.3.0 behavior and validation status.
packages/adapters/agent-plugin/package.json Bumps agent-plugin surface version to 0.3.0.
packages/adapters/agent-plugin/commands/adr-backfill.md Ensures the command surface includes the bootstrap-offer guidance.
packages/adapters/agent-plugin/apm.yml Bumps APM version to 0.3.0.
packages/adapters/agent-plugin/.claude-plugin/plugin.json Bumps Claude plugin manifest version to 0.3.0.
docs/reference-verification-agent-plugin.md Adds v0.3.0 guidance verification notes and measured detection table.
docs/adr/0038-offer-the-bootstrap-decision-record-as-an-offer-rather-than-a-backfill-candidate.md New proposed ADR recording the design/measurement and open functional-run item.
CHANGELOG.md Notes the new bootstrap-offer guidance under Unreleased.
bun.lock Updates workspace package version for @adrkit/agent-plugin to 0.3.0.
AGENTS.md Updates the agent-plugin “load-bearing” section to include the new guidance.
.claude-plugin/marketplace.json Bumps marketplace entry metadata/plugin version to 0.3.0.
Review details
  • Files reviewed: 15/16 changed files
  • Comments generated: 3
  • Review effort level: Lite

Comment on lines +22 to +28
affects:
- type: path
pattern: "packages/adapters/agent-plugin/skills/**"
- type: path
pattern: "site/src/content/docs/quickstart.mdx"
- type: path
pattern: "docs/reference-verification-agent-plugin.md"

| Corpus state | Offered | Edge |
| --- | --- | --- |
| No corpus, or records exist but none govern the corpus directory | Process and tooling decision | `relatesTo` between them when split |
Comment on lines +86 to +88
It governs `docs/adr/**`, so it is the record that explains the directory to
everyone who finds it later. Write it with `adr new "Record architecture
decisions as versioned markdown in git"` — it then takes `0001`, and the
MANIFEST.md is generated by `bun run emit:manifest` and CI diffs it under
network denial. Adding ADR-0038 made it stale, so clean-clone-builds failed on
the record count and the missing table row.

Found by CI rather than locally: the emit:* script was not in the set of gates
run before pushing, only the check:* ones. The remaining clean-clone steps --
schema:emit diff, the committed Action bundle diff, and release:pack -- were
verified locally this time and are clean.

Claude-Session: https://claude.ai/code/session_018qNk7AF5cPWkqUkWFvGBqF
Signed-off-by: Mark Beacom <m@beacom.dev>
@github-actions

github-actions Bot commented Sep 9, 2026

Copy link
Copy Markdown

Decisions governing this change

  • 0001 — Record architecture decisions as versioned markdown in git
    • via path: docs/adr/**
  • 0007 — Isolate integrations as optional adapters and build only against public surfaces
    • via path: packages/adapters/**
  • 0011 — Host the canonical JSON Schema at its $id on adrkit.dev
    • via path: site/**
  • 0016 — Require every check to be observed failing before it counts as coverage
    • via path: packages/adapters/*/test/**
  • 0028 — Ship decision memory as a portable agent plugin, and omit the MCP wiring hosts cannot honor
    • via path: .claude-plugin/**
    • via path: packages/adapters/agent-plugin/**
  • 0030 — Keep extension surfaces that carry a dependency tree outside this repository
    • via path: packages/adapters/**
  • 0033 — Select interactive graph presentation at the CLI boundary while preserving piped DOT
    • via path: site/src/content/docs/**
  • 0034 — Extend the portable agent plugin with decision backfill
    • via path: .claude-plugin/**
    • via path: docs/reference-verification-agent-plugin.md
    • via path: packages/adapters/agent-plugin/**
    • via path: site/src/content/docs/backfill.mdx

Active proposals touching this change

These are not yet ratified and do not bind this change:

  • 0037 — Treat generated knowledge systems as downstream read models, not decision authorities (proposed)
    • via path: site/src/content/docs/**
  • 0038 — Offer the bootstrap decision record as an offer rather than a backfill candidate (proposed)
    • via path: docs/reference-verification-agent-plugin.md
    • via path: packages/adapters/agent-plugin/skills/**
    • via path: site/src/content/docs/quickstart.mdx

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants