From 29b81a3a8f8da3cc418ff169f644dafe09ce973d Mon Sep 17 00:00:00 2001 From: Mark Beacom Date: Tue, 8 Sep 2026 23:19:32 -0400 Subject: [PATCH 1/5] feat(agent-plugin): offer the bootstrap decision record as an offer, 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 --- .claude-plugin/marketplace.json | 4 +- AGENTS.md | 17 +- CHANGELOG.md | 19 ++ bun.lock | 2 +- ...-offer-rather-than-a-backfill-candidate.md | 172 ++++++++++++++++++ docs/reference-verification-agent-plugin.md | 30 +++ .../agent-plugin/.claude-plugin/plugin.json | 2 +- packages/adapters/agent-plugin/README.md | 25 +-- packages/adapters/agent-plugin/apm.yml | 2 +- .../agent-plugin/commands/adr-backfill.md | 12 +- packages/adapters/agent-plugin/package.json | 2 +- .../skills/decision-backfill/SKILL.md | 52 +++++- .../skills/decision-memory/SKILL.md | 8 +- .../adapters/agent-plugin/test/wiring.test.ts | 38 ++++ site/src/content/docs/backfill.mdx | 37 +++- site/src/content/docs/quickstart.mdx | 11 ++ 16 files changed, 404 insertions(+), 29 deletions(-) create mode 100644 docs/adr/0038-offer-the-bootstrap-decision-record-as-an-offer-rather-than-a-backfill-candidate.md diff --git a/.claude-plugin/marketplace.json b/.claude-plugin/marketplace.json index 850f4b7..3a5aa1d 100644 --- a/.claude-plugin/marketplace.json +++ b/.claude-plugin/marketplace.json @@ -6,14 +6,14 @@ }, "metadata": { "description": "Decision memory for human- and agent-authored plans.", - "version": "0.2.0" + "version": "0.3.0" }, "plugins": [ { "name": "adrkit", "source": "./packages/adapters/agent-plugin", "description": "Load the architecture decisions that govern a change, check work against them, and audit code, documentation, and history for decisions that were never recorded. Ships decision-memory and decision-backfill skills, a read-only decision-checker agent, and five slash commands, all driven by the adr CLI. Ships no MCP server: adrkit's MCP server is configured per project, and the skills use its tools when they are connected.", - "version": "0.2.0", + "version": "0.3.0", "author": { "name": "Mark Beacom", "url": "https://github.com/mbeacom" diff --git a/AGENTS.md b/AGENTS.md index cb41714..d0c40c6 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -224,8 +224,10 @@ validation. Scope and limitations: That functional evidence covers the v0.1.0 context/check/draft/queue baseline. The v0.2.0 backfill skill and command are contract- and static-host-validated. A fresh Copilot synthetic-consumer run produced the expected covered/history/new -classification and a complete handoff without changing the worktree. No -persistent reference-repository or external run exists. +classification and a complete handoff without changing the worktree. The v0.3.0 +bootstrap-record offer is contract- and static-host-validated only, with no +functional run of any kind. No persistent reference-repository or external run +exists. Things that are load-bearing and easy to break — each measured against the real hosts rather than read off their docs, so a change that "looks more correct" @@ -266,7 +268,16 @@ will usually be a regression: treats source text as untrusted data, stays inside the worktree, enforces explicit scan caps, and requires confirmation before running a CLI resolved inside an inherited repository. -- `copilot plugin install` prints only a skill count. Version 0.2.0 should report +- **The bootstrap record is an offer, not a candidate.** A repository with no + corpus is missing the process decision (keep decisions in git) and the tooling + decision (enforce them with adrkit). Nothing proves a human ratified either, + so it stays out of the candidates table and out of every `backfillHandoff` — + it governs the corpus directory, a glob, and could never supply concrete + `candidatePaths` — and routes to plain `/adr-draft`. Adopting adrkit is never + a supersession of the decision to record decisions; `supersedes` is reserved + for a prior *tooling* record, and a MADR corpus is migrated, not superseded + ([ADR-0038](./docs/adr/0038-offer-the-bootstrap-decision-record-as-an-offer-rather-than-a-backfill-candidate.md)). +- `copilot plugin install` prints only a skill count. Version 0.3.0 should report two skills; that does not inventory the agent or commands — verify them in a fresh session. diff --git a/CHANGELOG.md b/CHANGELOG.md index b34bbf8..5aba615 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,25 @@ Until `1.0.0`, minor releases may include breaking changes ## [Unreleased] +### Added + +- **The agent plugin offers the bootstrap decision record (`adrkit` 0.3.0).** A + repository with no ADR corpus, or one whose corpus never recorded why it keeps + decisions, is now offered the process decision (keep decisions in git) and the + tooling decision (enforce them with adrkit). It is an **offer, not a + candidate**: no source span proves a human ratified either choice, so it fails + backfill's evidence rule and is kept out of the candidates table and out of + every `backfillHandoff` — whose concrete `candidatePaths` it could never supply, + since the path it governs is the corpus directory. It routes to plain + `/adr-draft` instead. Adopting adrkit is never a supersession of the decision + to record decisions: `supersedes` is reserved for a prior *tooling* record, + a MADR corpus is migrated rather than superseded, and the governing process + record is detected through `adr check`'s `governing` bucket rather than by + hand-parsing frontmatter. Contract- and static-host-validated only; no + functional run + ([ADR-0038](docs/adr/0038-offer-the-bootstrap-decision-record-as-an-offer-rather-than-a-backfill-candidate.md)). + + ## [0.13.0] - 2026-08-30 ### Added diff --git a/bun.lock b/bun.lock index 0b73eaa..2072782 100644 --- a/bun.lock +++ b/bun.lock @@ -12,7 +12,7 @@ }, "packages/adapters/agent-plugin": { "name": "@adrkit/agent-plugin", - "version": "0.2.0", + "version": "0.3.0", }, "packages/adapters/catalog-backstage": { "name": "@adrkit/catalog-backstage", diff --git a/docs/adr/0038-offer-the-bootstrap-decision-record-as-an-offer-rather-than-a-backfill-candidate.md b/docs/adr/0038-offer-the-bootstrap-decision-record-as-an-offer-rather-than-a-backfill-candidate.md new file mode 100644 index 0000000..75bac24 --- /dev/null +++ b/docs/adr/0038-offer-the-bootstrap-decision-record-as-an-offer-rather-than-a-backfill-candidate.md @@ -0,0 +1,172 @@ +--- +schemaVersion: 0.1.0 +id: "0038" +title: "Offer the bootstrap decision record as an offer rather than a backfill candidate" +status: proposed +date: 2026-09-08 +deciders: + - "@mbeacom" +tags: + - agent-plugin + - backfill + - governance +scope: component +reversibility: two-way-door +blastRadius: component +relatesTo: + - "0001" + - "0016" + - "0028" + - "0034" +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" +provenance: + authoredBy: agent-drafted +review: + tier: auto + tierReason: Guidance-only change to one adapter; sole decider. +reviewBy: 2027-09-08 +--- + +# ADR-0038: Offer the bootstrap decision record as an offer rather than a backfill candidate + +## Context + +`/adr-backfill` audits a repository for decisions that were made but never +recorded. Run against a repository with no corpus at all, it finds the decisions +*in* the code and says nothing about the one decision the caller is in the +middle of making: whether to keep decision records here, and whether adrkit is +how they will be enforced. + +That record is the one adrkit itself keeps as +[ADR-0001](./0001-record-architecture-decisions-in-git.md). Its absence in a +consumer repository is not cosmetic — it is the record that explains `docs/adr/` +to whoever finds the directory in a year, and the one that carries the rejected +alternatives (a database-backed tool, a wiki) that otherwise get re-proposed. + +Two forces make this awkward rather than obvious: + +- **It is not archaeology.** Every other backfill candidate is admitted on + evidence: a source span, a commit, a plan. Nothing in a repository proves a + human chose to keep ADRs, because at the moment backfill runs, nobody has. The + skill's own admission rule — *code proves what exists, not why it was chosen* — + excludes it, correctly. +- **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 itself, which is a glob and frequently does not exist yet. There are + no paths to snapshot. + +There is also a live way to get the relationship backwards. "Adopt adrkit" is a +*tooling* decision that depends on the *process* decision to record decisions at +all. An agent that treats the two as one thing will propose superseding a +consumer's existing process ADR — reversing a decision the consumer never asked +to reverse, and reading as tooling imperialism — when the honest edge is +`relatesTo`. + +## Decision + +Backfill **offers** the bootstrap record and never mines it. The offer is +reported under existing corpus state and named in the recommended next action, +explicitly marked as an offer rather than a candidate. It stays out of the +candidates table and out of every `backfillHandoff`, and is routed to plain +`/adr-draft` — the non-backfill path, where the caller supplies the authority +the evidence cannot. + +The edge is read off the corpus rather than assumed: + +| Corpus state | Offer | Edge | +| --- | --- | --- | +| No corpus | Process and tooling decision | `relatesTo` between them when split | +| Records exist, none govern the corpus directory | Process and tooling decision | `relatesTo` between them when split | +| A process record governs the corpus directory | Tooling decision only | `relatesTo` that record | +| A prior tooling record governs it | Tooling decision | `supersedes` that prior tooling record | + +Adopting adrkit is never a supersession of the decision to record decisions. +`supersedes` is reserved for a prior *tooling* record (`adr-tools`, +`log4brains`, a bespoke MADR script). An existing MADR corpus is migrated by +`adr migrate --from madr`, not superseded. + +Detection runs through the CLI — `adr check` over one record already inside the +corpus, reading the `governing` bucket — because this skill forbids hand-parsing +frontmatter, and an invalid record drops out of the parsed corpus, so a grep for +a meta tag can be confidently wrong. + +## Options considered + +### Option A: Offer it outside the candidate handoff (chosen) + +| Dimension | Assessment | +|---|---| +| Handoff contract | Untouched — no exemption, no synthetic paths | +| Honesty | The record is labeled as what it is: a current decision | +| Cost | Guidance only; no CLI or schema change | +| Reach | Only where the plugin is installed | + +### Option B: Admit it as a candidate with an exempted handoff + +Model the bootstrap record like any other candidate and carve out an exception +to the `candidatePaths`-never-globs rule. + +Rejected. The rule exists so `/adr-draft` can re-verify a candidate against the +corpus immediately before writing and refuse a stale one. A candidate exempt +from that check is a candidate nobody re-verifies, and the exemption would be +available to every future candidate that finds concrete paths inconvenient. It +also asserts evidence that does not exist. + +### Option C: A CLI affordance (`adr init`, or `adr new --bootstrap`) + +Rejected for now, and partly moot: `createAdr` already creates the corpus +directory and allocates `0001`, so the mechanism exists and the gap is the +prompt and the content. Baking one opinionated record body into `@adrkit/core` +puts prose in the hardest place to revise, and makes the tool write an opinion +rather than offer one. Reconsider if the offer proves valuable to people who do +not install the plugin. + +### Option D: Leave it to the documentation + +`site/src/content/docs/quickstart.mdx` already calls `adr new` the bootstrap +step. Rejected as insufficient alone — the documentation is read by the person +setting adrkit up, while the agent auditing the repository is the one holding +the empty-corpus finding. The quickstart is updated as well, not instead. + +## Trade-offs + +The offer reaches only repositories that install the plugin; a consumer driving +the CLI directly still gets nothing. Backfill's report grows a section that is +noise for the common case of a repository that already has a healthy corpus and +a process record. And the record being offered is worth writing only if the +caller can name a real rejected alternative — an ADR that says "we decided to +use ADRs" and nothing else is ceremony that `lint` will nag about forever, so +the guidance offers a decision to make rather than a template to accept. + +## Consequences + +- Easier: an agent auditing an empty repository names the missing process and + tooling decisions instead of silently reporting no candidates; the + `relatesTo`-vs-`supersedes` distinction is stated once, in a place both skills + can be tested against. +- Harder: the backfill report has one more conditional section, and the edge + table has to stay correct as adrkit's own relationship vocabulary evolves. +- **How we would know this was wrong:** a consumer's bootstrap record lands with + no rejected alternative and no `affects` matcher, or an agent proposes + superseding a consumer's existing process ADR. Either means the offer is + producing ceremony or reversing decisions, and the guidance is wrong. +- Revisit if: a CLI-level affordance is requested by someone not using the + plugin, or a functional run shows hosts do not surface the offer on an empty + corpus. + +## Action items + +1. [x] Add the bootstrap section to `decision-backfill`, with the edge table and + CLI-based detection. +2. [x] Add the matching no-corpus clause to `decision-memory`. +3. [x] Add a wiring test, observed failing first per ADR-0016. +4. [ ] Exercise the offer in a functional Copilot run against an empty-corpus + consumer, and record the result in + `docs/reference-verification-agent-plugin.md`. diff --git a/docs/reference-verification-agent-plugin.md b/docs/reference-verification-agent-plugin.md index 8678b45..7909d78 100644 --- a/docs/reference-verification-agent-plugin.md +++ b/docs/reference-verification-agent-plugin.md @@ -249,6 +249,36 @@ because they are release-policy decisions rather than defects: state. Pinning to a cut ref, or protecting the directory behind a single reviewed release commit, are both real options with different costs. +## v0.3.0 bootstrap-record guidance (2026-09-08) + +`decision-backfill` gained one section: a repository with no corpus, or one +whose corpus never recorded why it keeps decisions, is offered the process and +tooling decisions as an **offer rather than a candidate**, routed to plain +`/adr-draft` and excluded from every `backfillHandoff`. `decision-memory` gained +a matching clause on its no-corpus branch. + +**This addition is contract- and static-host-validated only.** It has no +functional run of any kind — no Copilot synthetic-consumer exercise, no +reference repository, no external adopter. + +| Check | Command | Observed | +|---|---|---| +| Contract | `bun test packages/adapters/agent-plugin/` | 40 pass, 0 fail | +| Claude Code | `claude plugin validate packages/adapters/agent-plugin` | PASS | +| Claude Code | `claude plugin validate .claude-plugin/marketplace.json` | PASS | + +The new wiring test was observed failing before the guidance was written, per +[ADR-0016](./adr/0016-require-every-check-to-be-observed-failing-before-it-counts-as-coverage.md). +It asserts the two properties that are easy to regress: the bootstrap record +stays out of the candidates table and out of every `backfillHandoff`, and +adopting adrkit carries a `relatesTo` edge to a process record while reserving +`supersedes` for a *prior tooling* record. + +What is therefore unverified: whether a host actually surfaces the offer on an +empty corpus, and whether the `governing`-bucket detection reads correctly +against a real third-party MADR or `adr-tools` corpus. Both need a functional +run before this section moves past a contract claim. + ## Verdict The plugin's six components load on Copilot CLI and function correctly against a diff --git a/packages/adapters/agent-plugin/.claude-plugin/plugin.json b/packages/adapters/agent-plugin/.claude-plugin/plugin.json index f96ce01..68d8542 100644 --- a/packages/adapters/agent-plugin/.claude-plugin/plugin.json +++ b/packages/adapters/agent-plugin/.claude-plugin/plugin.json @@ -2,7 +2,7 @@ "$schema": "https://json.schemastore.org/claude-code-plugin-manifest.json", "name": "adrkit", "displayName": "adrkit — decision memory", - "version": "0.2.0", + "version": "0.3.0", "description": "Load the architecture decisions governing a change, check plans against them, audit existing code and documentation for missing decisions, and draft new records deliberately — driven by the adr CLI.", "author": { "name": "Mark Beacom", diff --git a/packages/adapters/agent-plugin/README.md b/packages/adapters/agent-plugin/README.md index 8318a66..2d34f1e 100644 --- a/packages/adapters/agent-plugin/README.md +++ b/packages/adapters/agent-plugin/README.md @@ -48,8 +48,9 @@ confirmation before executing a CLI resolved inside that worktree. ### Updating -Version 0.2.0 adds the second skill and fifth command. Existing installations -must refresh and start a new host session: +Version 0.3.0 adds the bootstrap-record offer to backfill; 0.2.0 added the +second skill and fifth command. Existing installations must refresh and start a +new host session: ```bash copilot plugin update adrkit@adrkit @@ -181,7 +182,7 @@ Each of these was measured against the real hosts, not inferred from their docs. stated in the agent body instead. `apm install --target opencode` reports this class of error, which is how it was found. -- **`copilot plugin install` prints only a skill count.** Version 0.2.0 should +- **`copilot plugin install` prints only a skill count.** Version 0.3.0 should report two skills; that still does not inventory the agent or five commands. Verify those in a fresh session, not from the install output. @@ -241,14 +242,16 @@ validation (rung 3). The full scope, including what these runs do *not* establish, is in the [evidence index](../../../docs/reference-verification-agent-plugin.md). -The v0.2.0 backfill addition has contract coverage, passes Claude Code's plugin -and marketplace validators, loads through Copilot CLI's `--plugin-dir`, and was -deployed by APM into isolated `claude`, `copilot`, and `opencode` targets with -five commands and two skills discovered. A fresh Copilot 1.0.80 synthetic -consumer run resolved one accepted decision, retained one rejected decision as -history, emitted one evidence-backed `backfillHandoff`, and left the worktree -fingerprint and ADR count unchanged. It remains rung 1: there is no persistent -reference repository, no Claude/APM functional run, and no external validation. +The v0.3.0 bootstrap-record offer is contract- and static-host-validated only, +with no functional run. The v0.2.0 backfill addition has contract coverage, +passes Claude Code's plugin and marketplace validators, loads through Copilot +CLI's `--plugin-dir`, and was deployed by APM into isolated `claude`, `copilot`, +and `opencode` targets with five commands and two skills discovered. A fresh +Copilot 1.0.80 synthetic consumer run resolved one accepted decision, retained +one rejected decision as history, emitted one evidence-backed `backfillHandoff`, +and left the worktree fingerprint and ADR count unchanged. It remains rung 1: +there is no persistent reference repository, no Claude/APM functional run, and +no external validation. Authorized by [ADR-0028](../../../docs/adr/0028-ship-decision-memory-as-a-portable-agent-plugin-and-omit-the-mcp-wiring-hosts-cannot-honor.md) diff --git a/packages/adapters/agent-plugin/apm.yml b/packages/adapters/agent-plugin/apm.yml index 2e6c69d..1c36e89 100644 --- a/packages/adapters/agent-plugin/apm.yml +++ b/packages/adapters/agent-plugin/apm.yml @@ -15,7 +15,7 @@ # `description` is deliberately the shorter, CLI-listing variant of the # plugin.json description. That is the one intended difference between them. name: adrkit -version: 0.2.0 +version: 0.3.0 description: Load governing decisions, check plans, and audit code or documentation for missing ADR candidates. author: Mark Beacom license: Apache-2.0 diff --git a/packages/adapters/agent-plugin/commands/adr-backfill.md b/packages/adapters/agent-plugin/commands/adr-backfill.md index 0433902..919d6ac 100644 --- a/packages/adapters/agent-plugin/commands/adr-backfill.md +++ b/packages/adapters/agent-plugin/commands/adr-backfill.md @@ -121,13 +121,21 @@ Audit `$ARGUMENTS` for durable decisions that were made but never recorded. 7. **Return the report.** - Scope and coverage ledger. - - Existing corpus state and relevant history. + - Existing corpus state and relevant history. When no corpus exists, or + when no record governs the corpus directory, say so and offer the + bootstrap record — the process decision to keep decisions in git and + the tooling decision to enforce them with adrkit. It is an offer, not + a candidate: keep it out of the candidate table and out of every + `backfillHandoff`, and name plain `/adr-draft`. Adopting adrkit is + never a supersession of the decision to record decisions; reserve + `supersedes` for a prior tooling record. - Candidate table: key, decision, confidence, evidence, likely `affects`, blast radius, and reconciliation. - One evidence card per candidate: context, apparent choice, alternatives, consequences, citations, gaps, and status treatment. - Excluded observations and why they failed admission. - - A short prioritized review list. + - A short prioritized review list, naming a missing bootstrap record as an + offer rather than a candidate. - For each selectable candidate, include this copy-ready block: ```yaml diff --git a/packages/adapters/agent-plugin/package.json b/packages/adapters/agent-plugin/package.json index e7c719b..7c45562 100644 --- a/packages/adapters/agent-plugin/package.json +++ b/packages/adapters/agent-plugin/package.json @@ -1,6 +1,6 @@ { "name": "@adrkit/agent-plugin", - "version": "0.2.0", + "version": "0.3.0", "description": "Portable agent plugin exposing adrkit decision memory and evidence-backed ADR backfill to GitHub Copilot CLI, Claude Code, APM, and opencode.", "type": "module", "private": true, diff --git a/packages/adapters/agent-plugin/skills/decision-backfill/SKILL.md b/packages/adapters/agent-plugin/skills/decision-backfill/SKILL.md index 559a64b..3cfeb80 100644 --- a/packages/adapters/agent-plugin/skills/decision-backfill/SKILL.md +++ b/packages/adapters/agent-plugin/skills/decision-backfill/SKILL.md @@ -5,7 +5,7 @@ license: Apache-2.0 compatibility: "Requires repository read access and git for history-backed evidence. Existing ADR reconciliation uses the `adr` CLI (@adrkit/cli), resolved from $ADRKIT_CLI, then ./node_modules/.bin/adr, then PATH. The optional adrkit MCP server may replace read-only corpus retrieval, but is not bundled." metadata: author: Mark Beacom - version: "0.2.0" + version: "0.3.0" homepage: https://adrkit.dev/backfill/ --- @@ -153,6 +153,50 @@ Never hand-parse frontmatter as a substitute for `adr lint` or `adr check`. Invalid records drop out of the parsed corpus, so a hand-read "nothing governs this" answer can be confidently wrong. +### The bootstrap record is an offer, not a candidate + +Two repositories are missing the same record: one with no corpus at all, and one +whose corpus never recorded why it keeps decisions. That record is really two +decisions — the process decision to keep architecture decisions in git, and the +tooling decision to enforce them with adrkit. + +Offer it; do not mine it. No source span proves a human ratified either choice, +because the caller is making it now. It is a current decision rather than +archaeology, so it fails the admission rule above on evidence alone. Keep it out +of the candidates table and out of every `backfillHandoff`: the path it governs +is the corpus directory, which is a glob and may not exist yet, so it can never +supply the concrete `candidatePaths` a handoff requires. Report it under +existing corpus state and name plain `/adr-draft`, the non-backfill path, where +the caller supplies the authority the evidence cannot. + +Read the edge off the corpus instead of assuming one. Adopting adrkit is +never a supersession of the decision to record decisions — the tooling choice +depends on the process choice and cannot replace it. + +| Corpus state | Offer | Edge | +| --- | --- | --- | +| No corpus | Both the process and the tooling decision | `relatesTo` between the two when they are split into separate records | +| Records exist, none govern the corpus directory | Both the process and the tooling decision | `relatesTo` between the two when they are split into separate records | +| A process record governs the corpus directory | The tooling decision only | `relatesTo` that record | +| A prior tooling record governs it (`adr-tools`, `log4brains`, a bespoke MADR script) | The tooling decision | `supersedes` that prior tooling record | + +Detect the governing process record through the CLI, never by reading +frontmatter. Run `adr check` over one record already inside the corpus and read +its `governing` bucket: a meta record binds itself with an `affects` matcher +whose `type` is `path` and whose `pattern` covers the corpus directory, so it +resolves there like any other governing decision. + +```bash +adr check --dir "$ADR_DIR" --json -- "$ADR_DIR/.md" +``` + +An empty `governing` bucket for a path inside the corpus means no process record +exists yet. An exit of `1` means corpus findings are outstanding; treat that +absence as unverified until they are repaired. + +An existing MADR corpus needs no supersession here. `adr migrate --from madr` +preserves those records deterministically, so adopting adrkit reverses nothing. + ## Candidate report contract Return these sections: @@ -160,7 +204,8 @@ Return these sections: 1. **Scope and coverage** — sources reviewed, exclusions, history window, and blind spots. 2. **Existing corpus state** — whether adrkit or MADR exists, lint status, open - proposals, and rejected/superseded records relevant to the scope. + proposals, and rejected/superseded records relevant to the scope. Say here + whether the bootstrap record is missing, and which edge it would carry. 3. **Candidates** — ordered by confidence and blast radius. | Key | Candidate decision | Confidence | Evidence | Likely `affects` | Reconciliation | @@ -182,7 +227,8 @@ Return these sections: 5. **Excluded observations** — notable patterns that did not meet the admission rule, with the reason. 6. **Recommended next action** — name at most the first few candidates worth - human review. Do not write them. + human review. Do not write them. Name a missing bootstrap record here too, + marked as an offer rather than a candidate. For a selected machine-assisted candidate, invoke `/adr-draft ` while the complete handoff remains in context. That diff --git a/packages/adapters/agent-plugin/skills/decision-memory/SKILL.md b/packages/adapters/agent-plugin/skills/decision-memory/SKILL.md index 0b7818b..014d3fc 100644 --- a/packages/adapters/agent-plugin/skills/decision-memory/SKILL.md +++ b/packages/adapters/agent-plugin/skills/decision-memory/SKILL.md @@ -5,7 +5,7 @@ license: Apache-2.0 compatibility: "Requires the `adr` CLI (@adrkit/cli), resolved from $ADRKIT_CLI, then ./node_modules/.bin/adr, then PATH. The adrkit MCP server is optional and is NOT bundled with this plugin — when a project has connected it separately, its tools provide the same retrieval; otherwise the CLI is the only path, and the only one that can write." metadata: author: Mark Beacom - version: "0.2.0" + version: "0.3.0" homepage: https://adrkit.dev --- @@ -92,6 +92,12 @@ Read three things out of the result, and carry all three forward: If no corpus exists, say so rather than reporting "nothing governs this." Those are different answers and only one of them is true. +A repository with no corpus is also missing the decision to keep one. That +record — keep decisions in git, enforce them with adrkit — is an offer, not a +finding: `/adr-backfill` reports it under existing corpus state, and only +`/adr-draft` writes it. Adopting the tool never supersedes the decision to +record decisions; it depends on it. + ## After you plan, before you implement Run the same check over the plan's target paths and reconcile every governing diff --git a/packages/adapters/agent-plugin/test/wiring.test.ts b/packages/adapters/agent-plugin/test/wiring.test.ts index eaf9ace..ab174d2 100644 --- a/packages/adapters/agent-plugin/test/wiring.test.ts +++ b/packages/adapters/agent-plugin/test/wiring.test.ts @@ -403,6 +403,44 @@ describe('guidance that must not regress', () => { } }); + test('backfill offers the bootstrap record outside the candidate handoff', () => { + // A repository with no corpus is missing two decisions at once: to record + // decisions at all, and to enforce them with adrkit. Neither is archaeology + // — no source span proves a human ratified either, so both fail this + // skill's own admission rule and cannot become evidence-backed candidates. + // They also cannot travel through a `backfillHandoff`: the governed path is + // the corpus directory, a glob that may not exist yet, so there are no + // concrete `candidatePaths` to snapshot. Routing them to plain `/adr-draft` + // is what keeps the handoff contract intact. + const body = readFileSync(join(packageRoot, 'skills', 'decision-backfill', 'SKILL.md'), 'utf8'); + + expect({ offered: /bootstrap record/i.test(body) }).toEqual({ offered: true }); + expect({ + excluded: /out\s+of\s+the\s+candidates\s+table\s+and\s+out\s+of\s+every\s+`backfillHandoff`/i.test(body), + }).toEqual({ excluded: true }); + expect({ routed: /name\s+plain\s+`\/adr-draft`/i.test(body) }).toEqual({ routed: true }); + + // The edge is the part that is easy to get backwards. Adopting the tool + // depends on the decision to keep records; it can never replace it. The + // only legitimate supersession target is a *prior tooling* record. + expect({ + neverSupersedesProcess: /never\s+a\s+supersession\s+of\s+the\s+decision\s+to\s+record\s+decisions/i.test(body), + }).toEqual({ neverSupersedesProcess: true }); + expect({ + supersedesPriorTooling: /prior tooling record[\s\S]{0,200}`supersedes`/i.test(body), + }).toEqual({ supersedesPriorTooling: true }); + + // Detection has to come from the CLI: this skill forbids hand-parsing + // frontmatter, and an invalid record drops out of the parsed corpus, so a + // grep for a meta tag can be confidently wrong. + expect({ detects: /`governing`\s+bucket/i.test(body) }).toEqual({ detects: true }); + + // A MADR corpus is migrated, not superseded. + expect({ madrKept: /migrate --from madr[\s\S]{0,200}reverses nothing/i.test(body) }).toEqual({ + madrKept: true, + }); + }); + test('draft consumes a complete backfill handoff without adding a writer', () => { const body = readFileSync(join(packageRoot, 'commands', 'adr-draft.md'), 'utf8'); for (const field of [ diff --git a/site/src/content/docs/backfill.mdx b/site/src/content/docs/backfill.mdx index 74b6802..1512b85 100644 --- a/site/src/content/docs/backfill.mdx +++ b/site/src/content/docs/backfill.mdx @@ -36,14 +36,14 @@ claude plugin update adrkit@adrkit # restart Claude Code after updating apm update --yes --target copilot,claude ``` -Version 0.2.0 exposes two skills (`decision-memory`, `decision-backfill`), one +Version 0.3.0 exposes two skills (`decision-memory`, `decision-backfill`), one agent, and five commands. Start a fresh host session after installing or updating. Copilot's install summary reports only skills, so the expected `Installed 2 skills` message does not inventory the agent or commands. ```sh -copilot plugin list # expect adrkit 0.2.0 -claude plugin details adrkit@adrkit # expect 0.2.0 and the component inventory +copilot plugin list # expect adrkit 0.3.0 +claude plugin details adrkit@adrkit # expect 0.3.0 and the component inventory apm audit --ci # verify the APM lock and deployed files ``` @@ -173,6 +173,37 @@ npx @adrkit/cli queue --dir "$ADR_DIR" Human ratification is separate. The backfill command discovers and reconciles; it never approves, accepts, or bulk-writes records. +## No corpus yet — the bootstrap record + +Run against a repository with no `docs/adr/`, backfill reports the empty corpus +and offers the record that repository is missing: the **process** decision to +keep architecture decisions in git, and the **tooling** decision to enforce them +with adrkit. + +That offer is deliberately not a candidate. Every other candidate is admitted on +evidence — a source span, a commit, a plan — and nothing in a repository proves +a human chose to keep ADRs, because at that moment nobody has. So it carries no +`backfillHandoff`, stays out of the candidate table, and is written through plain +`/adr-draft`, where you supply the authority the evidence cannot. + +The relationship matters when a corpus already exists: + +| Corpus state | Offered | Edge | +| --- | --- | --- | +| No corpus | Process and tooling decision | `relatesTo` between them when split | +| Records exist, none govern the corpus directory | Process and tooling decision | `relatesTo` between them when split | +| A process record governs the corpus directory | Tooling decision only | `relatesTo` that record | +| A prior tooling record governs it | Tooling decision | `supersedes` that prior tooling record | + +**Adopting adrkit never supersedes the decision to record decisions.** The +tooling choice depends on the process choice and cannot replace it. `supersedes` +is reserved for a prior *tooling* record — `adr-tools`, `log4brains`, a bespoke +MADR script. An existing MADR corpus is migrated, not superseded; see below. + +The record is worth writing only if you can name a real alternative you rejected. +An ADR that says "we decided to use ADRs" and nothing else is ceremony that +`adr lint` will nag about forever. + ## Existing MADR corpus Do not use model-assisted backfill for files adrkit can migrate diff --git a/site/src/content/docs/quickstart.mdx b/site/src/content/docs/quickstart.mdx index c6adf0b..f8ece38 100644 --- a/site/src/content/docs/quickstart.mdx +++ b/site/src/content/docs/quickstart.mdx @@ -80,6 +80,17 @@ writes the first record, so it is the bootstrap step as well as the everyday one Running `adr lint` first on a repository with no corpus exits `2` with `Corpus directory not found: 'docs/adr'`. + +