Skip to content

feat(cli): add adr accept and a terminal view for adr queue (ADR-0044) - #250

Merged
mbeacom merged 5 commits into
mainfrom
feat/adr-accept-and-queue-terminal
Oct 1, 2026
Merged

mbeacom merged 5 commits into
mainfrom
feat/adr-accept-and-queue-terminal

Conversation

@mbeacom

@mbeacom mbeacom commented Sep 30, 2026

Copy link
Copy Markdown
Owner

What and why

adr queue shows what is waiting for a decision but gives no way to make one. Accepting a record today is a hand edit, and the hand edit reliably drops review.decidedAt: neither ADR-0043 (#247) nor ADR-0040 (#248) recorded when it was ratified. And in a terminal the queue's Markdown tables print as raw pipes and wrap unreadably.

ADR-0044 (proposed) records both changes. A record is needed because accept grows the public write surface from two commands to three, and the CLI is a semver commitment under ADR-0031.

adr accept <id> --by <identity>

  • It changes exactly three fields: status, provenance.ratifiedBy, and review.decidedAt (RFC 3339 with seconds, per ADR-0043). A yaml round trip reformats 39 of this corpus's 44 records, so acceptAdrSource (in @adrkit/core, pure) edits those lines in place. It then re-parses the file and refuses unless every other field is semantically unchanged.
  • The re-check has already paid for itself. During development it caught decidedAt landing under a newly added provenance: block.
  • It refuses, exits 1, and leaves the file untouched when the record:
    • is not proposed;
    • has an unresolved objection;
    • has fewer approvals than its review.quorum;
    • has lint errors, or would be invalid once accepted.
  • --by is required and never inferred, and it never counts as an approval.
  • No agent surface runs it. The agent plugin's wiring test fails if any command, skill, or agent mentions adr accept. That is a test-only change; no plugin content changes.

adr queue terminal view

  • --format defaults to auto, following ADR-0033. A TTY gets a list that fits the window: SLA state, days to deadline, tier, approvals against quorum, objections, route, item findings, and either next: adr accept <id> --by <identity> or the reason acceptance is blocked.
  • Anything that captures stdout still gets the Markdown report byte for byte: the queue Action, /adr-queue, and SC-001.
  • The display-width helpers move from graph.ts to terminal-text.ts, so graph and queue share one implementation.
1. 0001  overdue · due 2025-12-15 (24 days overdue)
   Comprehensive Overdue ARB
   arb · ARB human review
   approvals 1/2 · objections 1 · route @alice, @bob
   docs/adr/0001-overdue-arb.md
   blocked: 1 unresolved objection; 1 of 2 required approvals

Checklist

  • Commits are DCO signed off.
  • ADR-0044 is added as proposed, with the argument (Options A–D, trade-offs, falsifiers).
  • Schema: unchanged.
  • @adrkit/core changed, but the rebuilt packages/ci/dist under pinned Bun 1.3.14 is byte-identical. The Actions never import the new export. I will re-check this under linux/amd64 1.4.2 after build: move the pinned Bun toolchain from 1.3.14 to 1.4.2 #249.
  • Every new test was observed failing before it passed (ADR-0016):
    • the plugin guard, against an injected adr accept mention;
    • the pipe-compatibility tests, with auto mutated to always choose terminal (5 failures);
    • the unparseable-record exit code, without its file-name match.
  • bun run typecheck && bun run build && bun test && bun run lint pass (3,192 tests). The built CLI was smoke-tested under Node.

Notes for reviewers

  • Merge order: docs(adr): accept ADR-0040 #248 first, then this PR. Both edit MANIFEST.md and the same AGENTS.md section, so I will rebase and re-run emit:manifest after docs(adr): accept ADR-0040 #248 lands. build: move the pinned Bun toolchain from 1.3.14 to 1.4.2 #249 (the Bun bump) is independent, but after it lands I'll rebase once more so the dist gate is checked under 1.4.2.
  • adr accept has only run against scratch copies of fixture corpora, never the real one. Its first real use is ADR-0044's action item 6, run by the maintainer after merge. Item 7 lists the four (**proposed**) qualifiers to flip in that same PR, because the command only changes status.
  • ratifiedBy is now written on human-authored records too. The schema always allowed it. The trade-off is recorded in the ADR.
  • Refused on purpose: flow-style provenance: {…} / review: {…} blocks get a message saying to edit by hand, rather than being reformatted.

Copilot AI balanced review requested due to automatic review settings September 30, 2026 23:30
@github-actions

github-actions Bot commented Sep 30, 2026 •

Copy link
Copy Markdown

Decisions governing this change

  • 0001 — Record architecture decisions as versioned markdown in git
    • via path: docs/adr/**
  • 0003 — Ship as a Spec Kit extension plus a standalone CLI, not a competing harness
    • via path: packages/cli/**
  • 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/*/test/**
    • via path: packages/adapters/*/test/**
  • 0022 — Scan inbound markers in check and CI without giving them exit-code authority
    • via path: packages/cli/src/index.ts
  • 0024 — Report the measured scan extent, not the window constant
    • via path: packages/cli/src/index.ts
  • 0028 — Ship decision memory as a portable agent plugin, and omit the MCP wiring hosts cannot honor
    • via path: packages/adapters/agent-plugin/**
  • 0029 — Scope Backstage publication as a downstream consumer, tiered on the entity-ownership mapping
    • via path: packages/cli/src/index.ts
    • via path: packages/cli/src/queue.ts
    • via path: packages/core/src/index.ts
  • 0030 — Keep extension surfaces that carry a dependency tree outside this repository
    • via path: packages/adapters/**
  • 0031 — Publish a narrow consumer SDK as the contract, and document the CLI JSON as its sibling
    • via path: packages/cli/src/index.ts
    • via path: packages/cli/src/queue.ts
    • via path: packages/core/src/index.ts
  • 0033 — Select interactive graph presentation at the CLI boundary while preserving piped DOT
    • via path: packages/cli/src/command-registry.ts
    • via path: packages/cli/src/graph.ts
    • via path: packages/cli/src/index.ts
    • via path: site/src/content/docs/**
  • 0034 — Extend the portable agent plugin with decision backfill
    • via path: packages/adapters/agent-plugin/**
  • 0035 — Execute the gates that certify a pull request from the default branch
    • via path: scripts/**
  • 0037 — Treat generated knowledge systems as downstream read models, not decision authorities
    • via path: site/src/content/docs/**
  • 0038 — Offer the bootstrap decision record as an offer rather than a backfill candidate
    • via path: packages/adapters/agent-plugin/**
  • 0039 — Derive a valid-time window from date and supersession, and resolve a git ref at the CLI boundary
    • via path: packages/cli/src/index.ts
    • via path: site/src/content/docs/commands.mdx
  • 0040 — Keep derived surfaces in lockstep with three mechanisms matched to three classes of drift
    • via path: AGENTS.md
    • via path: MANIFEST.md
    • via path: scripts/emit-manifest.ts

Active proposals touching this change

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

  • 0044 — Ratify a proposed record with adr accept and present the queue for terminals (proposed)
    • via path: packages/cli/src/accept.ts
    • via path: packages/cli/src/command-registry.ts
    • via path: packages/cli/src/queue-terminal.ts
    • via path: packages/cli/src/queue.ts
    • via path: packages/cli/src/terminal-text.ts
    • via path: packages/core/src/transition/**

Historical records that once covered this change

These no longer bind this change, and are listed for context only:

  • 0021 — Resolve inbound source annotations without changing the schema (superseded) — superseded by 0022
    • via path: packages/cli/src/index.ts

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.

Copilot review overview

🟡 Changes recommended

The terminal queue can recommend an acceptance that must fail, and successful acceptance prints unsanitized corpus-controlled terminal text.

Review effort: Balanced
Findings: 1 High severity · 1 Medium severity

Open (2)
What changed in this PR

Adds human-driven ADR acceptance and a TTY-aware queue view under proposed ADR-0044.

Changes:

  • Adds the pure acceptAdrSource transition and adr accept CLI command.
  • Adds terminal queue rendering while preserving piped Markdown output.
  • Updates tests, documentation, command registration, and ADR inventory.

Decision-context MCP tooling was unavailable, so governance applicability beyond supplied context was not independently verified.

File Description
AGENTS.md Documents queue and acceptance behavior.
CHANGELOG.md Records the new CLI features.
MANIFEST.md Adds ADR-0044 to generated inventory.
docs/​adr/​0044-...md Proposes the governing decision.
site/​src/​content/​docs/​commands.mdx Documents public CLI usage.
scripts/​emit-manifest.ts Updates write-surface commentary.
packages/​core/​src/​index.ts Exports the transition API.
packages/​core/​src/​transition/​accept.ts Implements splice-and-verify acceptance.
packages/​core/​test/​surface.test.ts Verifies the public export.
packages/​core/​test/​transition-accept.test.ts Tests transition behavior and refusals.
packages/​cli/​src/​accept.ts Implements adr accept.
packages/​cli/​src/​command-registry.ts Registers command and format choices.
packages/​cli/​src/​graph.ts Uses shared terminal helpers.
packages/​cli/​src/​index.ts Dispatches the new command.
packages/​cli/​src/​queue-terminal.ts Renders the terminal queue.
packages/​cli/​src/​queue.ts Adds automatic format selection.
packages/​cli/​src/​terminal-text.ts Centralizes terminal-safe text utilities.
packages/​cli/​test/​accept.test.ts Tests acceptance end to end.
packages/​cli/​test/​mistake-recovery.test.ts Updates format validation expectations.
packages/​cli/​test/​queue-terminal.test.ts Tests terminal rendering and compatibility.
packages/​adapters/​agent-plugin/​test/​wiring.test.ts Prevents agent-driven acceptance.

Comment thread packages/cli/src/accept.ts Outdated
Comment on lines +83 to +85
`${style.status('accepted')} ADR-${result.id}: ${result.title}`,
` ${style.label('ratified by')} ${result.ratifiedBy} ${style.note(`at ${result.decidedAt}`)}`,
` ${style.path(result.path)}`,

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Fixed in 9eeecdd. The success output and the refusal messages (which quote corpus ids, paths, and finding text) now go through cleanText, the same helper the queue view uses. A new test writes a title containing ESC[2J, an OSC title sequence, and BEL, then asserts none of them reach stdout. It failed before the fix with the raw sequences in stdout. JSON output was already escaped and is unchanged.

Comment on lines +110 to +114
const blockers = acceptBlockers(item);
if (blockers.length > 0) {
for (const line of wrapText(`blocked: ${blockers.join('; ')}`, available)) lines.push(`${indent}${style.yellow(line)}`);
} else {
const command = `adr accept ${id} --by <identity>`;

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Fixed in 9eeecdd, with a different mechanism than the one suggested. I didn't carry more accepted-state data into QueueItem, because QueueReport v1 is a public JSON contract that badges and the queue Action read. Instead, queue.ts dry-runs the same pure acceptAdrSource transition for each item, in the terminal view only, and shows the hint only when it would succeed. Otherwise it shows that refusal. This covers the empty-deciders case and any other refusal (layout, a future invariant) without a second rule set to keep in sync. The comprehensive fixture's 0010 now renders blocked: ADR-0010 would not be a valid accepted record: an accepted decision must name at least one decider, unless it was imported. The new assertion failed before the fix. Markdown and JSON output are unchanged.

@mbeacom mbeacom self-assigned this Sep 30, 2026
@mbeacom mbeacom added the gate-change-acknowledged A maintainer has seen and accepted this PR's change to the CI gate surface (ADR-0035) label Sep 30, 2026
@github-actions github-actions Bot removed the gate-change-acknowledged A maintainer has seen and accepted this PR's change to the CI gate surface (ADR-0035) label Sep 30, 2026
ADR-0044 (proposed) records both changes. It is needed because `accept`
grows the public write surface from two commands to three, which is a
semver commitment under ADR-0031.

adr accept <id> --by <identity> ratifies a proposed record. It sets
status, provenance.ratifiedBy, and review.decidedAt (RFC 3339 with
seconds, per ADR-0043) and changes no other line. A yaml round trip
reformats 39 of this corpus's 44 records, so acceptAdrSource in
@adrkit/core splices lines instead. It then re-parses the result and
refuses unless every other field is semantically unchanged. That
re-check caught a real bug during development: when provenance was
absent and review was the last block, decidedAt landed under the new
provenance block. The transition is pure; the CLI reads the clock.

It refuses, with exit 1 and the file untouched, a record that is not
proposed, has an unresolved objection, is short of its review.quorum,
has lint errors, or would not be a valid accepted record. --by is
required, is never inferred, and never counts as an approval. The agent
plugin's wiring test now fails if any command, skill, or agent mentions
adr accept. That test was observed failing against an injected mention
(ADR-0016).

adr queue --format now defaults to auto, following ADR-0033: a TTY gets
a width-aware list with SLA state, days to deadline, approvals against
quorum, objections, and either the accept command or the reason it is
blocked. Piped output is the Markdown report byte for byte. The existing
queue tests fail when auto is mutated to always choose terminal.
Display-width helpers move from graph.ts to terminal-text.ts so both
views share one implementation.

The committed Action bundles are byte-identical when rebuilt with the
pinned Bun 1.3.14.

Signed-off-by: Mark Beacom <m@beacom.dev>
…cord as invalid

ADR-0044 gains action item 7: flip its four public proposed-state
qualifiers when it is accepted, the lesson ADR-0040's item 6 records.
Its trade-offs now say why a durable commitment is still two-way-door
before 1.0.0.

adr accept now finds a record whose YAML does not parse by its file
name. Such a record's findings carry a path and no id, so it used to
exit 2 as an unknown id instead of 1 as existing but invalid. The new
test fails without the file-name match.

Signed-off-by: Mark Beacom <m@beacom.dev>
The edit that named accept as the third writing command left one
overlong line in AGENTS.md; this rewraps it to the file's width.

Signed-off-by: Mark Beacom <m@beacom.dev>
@mbeacom
mbeacom force-pushed the feat/adr-accept-and-queue-terminal branch from e58c0f3 to ffa448a Compare October 1, 2026 01:11
@mbeacom mbeacom added the gate-change-acknowledged A maintainer has seen and accepted this PR's change to the CI gate surface (ADR-0035) label Oct 1, 2026
@github-actions github-actions Bot removed the gate-change-acknowledged A maintainer has seen and accepted this PR's change to the CI gate surface (ADR-0035) label Oct 1, 2026
@mbeacom mbeacom added the gate-change-acknowledged A maintainer has seen and accepted this PR's change to the CI gate surface (ADR-0035) label Oct 1, 2026
…succeeds

Two review findings on #250, both reproduced by a test that failed first:

- adr accept printed the record's title and path to the terminal
  unsanitized. A title with ANSI/OSC sequences (YAML "\u001b" escapes)
  reached stdout intact. The success output and the refusal messages,
  which quote corpus ids, paths, and finding text, now pass through the
  same cleanText helper the queue view uses.

- The queue's terminal view offered `adr accept 0010` for a proposed
  record with no deciders. That record is valid as proposed but not as
  accepted, so the command would refuse. Review state alone cannot see
  this. queue.ts now dry-runs the same pure acceptAdrSource transition
  for each item (terminal view only) and shows its refusal instead of
  the hint. A record that cannot be re-read gets no hint. QueueReport v1
  is unchanged; Markdown and JSON output are untouched.

ADR-0044 and AGENTS.md now say the hint comes from that dry run.

Signed-off-by: Mark Beacom <m@beacom.dev>
@github-actions github-actions Bot removed the gate-change-acknowledged A maintainer has seen and accepted this PR's change to the CI gate surface (ADR-0035) label Oct 1, 2026
@mbeacom mbeacom added the gate-change-acknowledged A maintainer has seen and accepted this PR's change to the CI gate surface (ADR-0035) label Oct 1, 2026
@mbeacom
mbeacom merged commit 22a62b1 into main Oct 1, 2026
22 of 23 checks passed
@mbeacom
mbeacom deleted the feat/adr-accept-and-queue-terminal branch October 1, 2026 01:58
mbeacom added a commit that referenced this pull request Oct 1, 2026
* chore(release): prepare v0.16.0

Moves the lockstep surface to 0.16.0: the four public packages,
CLI_VERSION, SERVER_INFO and server.json, bun.lock's workspace entries,
and every documented pin (README, ci.mdx, badges.mdx, quickstart, the
site hero, RELEASING.md, the bug-report template).

The CHANGELOG's Unreleased section becomes 0.16.0, with what it was
missing:
- Security: the Action bundles' undici 6.29.0 (GHSA-rfgv-xxqx-mfg5,
  #251). It is the main reason to release now, since published v0
  consumers still run 6.27.0.
- Added: acceptAdrSource, a new @adrkit/core runtime export, which the
  release policy requires calling out.
- Changed: adr queue --format defaults to auto (only a terminal sees a
  difference), and the Bun 1.4.2 toolchain move, which shrinks the
  Action bundles by dropping unused zod locales and helpers.

The docs also catch up with adr accept, which adrkit.dev has described
since #250 merged but npm doesn't ship yet: the AGENTS.md status line
and the CLI README command lists name it, the CLI README and root README
gain a short queue-and-accept section with the terminal view, and the
container section lists accept among the commands that write.

The Action bundles rebuild byte-identical under linux/amd64 Bun 1.4.2,
and release:pack prepares all five packages for v0.16.0.

Signed-off-by: Mark Beacom <m@beacom.dev>

* docs(readme): name adr accept in the project status table

The queue row now says the shipped workflow includes ratifying from
the queue, not only reporting it.

Signed-off-by: Mark Beacom <m@beacom.dev>

* docs(changelog): don't claim the bundles' other dependencies are unchanged

The 0.16.0 Changed entry said every other bundled dependency was
unchanged, which contradicts the undici update under Security in the
same release. It now says that, apart from zod's dropped locales and
helpers and that undici update, the bundles contain the same modules as
0.15.0. I checked that against both bundles at v0.15.0 and on main.

Signed-off-by: Mark Beacom <m@beacom.dev>

---------

Signed-off-by: Mark Beacom <m@beacom.dev>
@mbeacom mbeacom added the enhancement New feature or request label Oct 10, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request gate-change-acknowledged A maintainer has seen and accepted this PR's change to the CI gate surface (ADR-0035)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants