Repository navigation
Releases: rvdbreemen/adr-kit
Release list
adr-kit v0.61.0
A minor release that brings ADR Kit natively to OMP (oh-my-pi), makes skills
answer in the user's language, and fixes a doubled OpenCode plugin. The
headline: an omp plugin install of ADR Kit now gets the same lifecycle
context, pull-request guard and judge host as the other clients, with no
manual configuration. ADR-050 accepts OMP as the fourth certified client; the
certification itself (registry entry, installer, doctor and native evidence)
follows in v0.62.0, and until then OMP is documented as native with
certification in progress.
Upgrading. Claude Code, Codex, Copilot CLI and OpenCode users need no
action. OMP users: omp plugin marketplace update rvdbreemen-adr-kit, then
omp plugin upgrade adr-kit@rvdbreemen-adr-kit, and start a new session so OMP
loads the extension. A hand-set ADR_KIT_ROOT is no longer needed and is
respected if kept.
Added
- Native OMP (oh-my-pi) support (ADR-050, TASK-239). OMP installs the plugin
from the existing marketplace (omp plugin install adr-kit@rvdbreemen-adr-kit) and loadsomp/extension.ts, declared in
package.jsonunderomp.extensions. The extension maps OMP's session,
prompt, edit, shell, subagent and compaction events onto
hooks/adr-hook.py, blocks a shell call only on the pull-request guard's
explicit deny, and setsADR_KIT_ROOTso the skills find the engine, which
OMP's missing${CLAUDE_PLUGIN_ROOT}previously broke.omp-cli
(omp -p --no-extensions --no-session --no-tools) joins the judge host
clients, and an OMP session records it as the host. ADR-050 (Accepted)
makes OMP the fourth certified client, amending ADR-010's client list; its
registry entry, installer and doctor support and native evidence follow in
v0.62.0, and until then OMP is documented as native with certification in
progress (docs/clients/omp.md). - README: a Supported clients table separates the certified clients, the
native OpenCode package, OMP's in-progress certification, and the portable
MCP/Agent Skills fallback.
Changed
- Skills and the ADR generator agent now reply in the language the user writes
in, defaulting to English. Command output, identifiers, paths and ADR file
content stay as they are: an ADR keeps the project's documentation language.
A language set in the host client (for example Claude Code'slanguage
setting) still takes precedence (TASK-238).
Fixed
- OpenCode: a global npm entry plus a project entry for a checkout loaded two
plugin copies, since OpenCode dedupes by npm package name only. Each spawned
its own hook per event, the skills path and guide were listed twice, and the
global copy owned the MCP server and/adr-kit-*commands, so the checkout
was not the code that ran. Now one copy per project is active, the
last-loaded, which is the project's (TASK-235).
adr-kit v0.60.0
A minor release with two behaviour changes and the fixes of the 2026-10-11
defect sweep. The headline: the LLM layers now actually run on a default
install, on the coding assistant the plugin is installed in, and the user is
asked once whether to keep them. Before this release the common install route
(/plugin install) never recorded a host client, so the judge's LLM pass, the
adr-suggest nudge and the guardian's LLM tier silently did nothing.
What you will notice. The first session in a project records the client it
runs in as the judge host, and the next prompt asks, per layer, whether to keep
it on (recommended) or turn it off for this machine. The question repeats at
most once a day until it is answered. From that first session on, a commit's
diff goes to that client's own model: the same assistant that already sees the
code, with no API key and no pinned model. An "off" answer is written to the
gitignored .adr-kit.local.json and stays on your machine; it no longer
switches a layer off for the whole team. ADR-035 and ADR-049, which record
these defaults, are Accepted.
Upgrade notes.
- To keep every diff away from any model, export
ADR_KIT_NO_LLM=1. It now
stops every model call, including the suggestion pass, which it did not
before. If you relied on the nudge while it was set, the nudge is off too. - The pre-commit hook is a copy in each project. Re-run
/adr-kit:install-hooksand choosereplaceto get the fixes that make a
refused commit print its report and find the engine where Copilot CLI and
OpenCode install it. The old hook is kept as a timestamped backup. - Run
/adr-kit:upgradeto replace the project-scoped guardian entry, which
no longer needs GNUsort -V. An entry added by an older
/adr-kit:install-hookshas no version stamp: remove it with
/adr-kit:install-hooksand add it again. - Codex runs a plugin's hooks only after you review them in
/hooks;
adr-doctor --deepnow says so when they are unreviewed. - On macOS and Linux, OpenCode now finds a Python 3.10 or newer itself. If it
reported the MCP server as failed, it should start after this upgrade.
Added
- The LLM layers are on by default on every install route, on the coding
assistant the plugin runs in, and the user confirms them (ADR-049). The
layers - the per-commit judge pass, theadr-suggestnudge and the
guardian's LLM tier - only run where a judge host client is recorded, and
the common install route (/plugin install) never recorded one, so they
did nothing. The first session in a project now records the client it runs
in (never replacing a recorded choice), and the next prompt asks once whether
to keep each layer on or turn it off for this machine, until the answer is
recorded inllm.confirmed./adr-kit:initand/adr-kit:upgradeask the
same, one question per layer, on every run. - OpenCode runs the LLM layers on its own model (
opencode run --pure). Its
plugin borrowed Claude Code's hook shape and would have recordedclaude -p
as an OpenCode user's judge; it now names its own host. guardian.llm_enabled(defaulttrue) switches the guardian's LLM tier off;
adr-substancethen calls no model and the tier is never due.adr-settings --set <switch>=<value> --localwrites one of the four LLM
switches for this machine only.- ADR-043 restores the two rules that guarded the host-model-only judge: no
entry point may pin a vendor model, and none may carry its own default
command vector. ADR-017 enforced them until ADR-036 superseded it without an
Enforcement section, and since then no commit was checked against them. The
scope now also coversbin/adr-substance, which calls a model and was never
inside it; a gate test (adr-no-pinned-model-v1) fails when a new
model-calling entry point is left outside the rule. - ADR-044 to ADR-047, from the 2026-10-11 defect sweep, are Accepted.
- ADR-044 decides that every entry point finds Python and the engine through
one resolver. Its Enforcement block applies from this release: a skill may
not name a client's plugin-cache path or usesort -V. The skills comply
(see Fixed); the resolver itself lands in a later release. - ADR-045 has the judge acquire its own diff and gives every diff shape an
explicit verdict. ADR-046 has every reader ofpath_globmatch in the
judge's dialect. Both are implemented in a later release. - ADR-047 gives the query engine's cap on supporting ADRs an Accepted record
again, as an amendment of ADR-036: at most two per response, filling only
the slots under the limit. The engine already behaves this way. The cap was
decided in ADR-014, which has been superseded since 2026-08-01.
- ADR-044 decides that every entry point finds Python and the engine through
- ADR-048 is Accepted. It amends ADR-010: a native client is certified per
platform, from a real run on that platform, and the documents claim only
what a current record shows. Windows is no longer the only certifiable
platform, and certification stays optional for a release. The validator,
the support matrix and the new records land in a later release.
Changed
- An LLM switch in the gitignored
.adr-kit.local.jsonnow wins over
.adr-kit.jsonforjudge.llm_enabled,suggest.enabled,
guardian.llm_enabledandguardian.llm_autorun, so one developer's "off"
no longer has to switch a layer off for the whole team. Only these four
booleans cross over. ADR_KIT_NO_LLM=1now stops every model call, not only the judge's. The
suggestion pass (adr-suggest, on by default) and the substance check
(adr-substance) resolved their backend without looking at it, so a user who
exported it to keep diffs off any model still hadadr-suggestcall one on
every commit. The switch is now checked in the shared resolver and outranks
--llm-cmdandADR_KIT_LLM_CMD. If you set it and relied on the nudge, the
nudge is now off too.- The
adr-judgeandadr-index-checkworkflow templates, and the readiness
examples in the README and the grilling guide, pin the action to a release
instead of@main, like the readiness template already did. They are
registered version sites, so each release moves them.
Fixed
-
adr-readinessno longer suggests/adr-kit:grill --revalidatefor an
ADR that is consistently superseded, so finished records stop reading as
open work. Thesupersession-requiredlabel stays (ADR-011 fixes the
classes); the command is now suggested only where status and successor
disagree (TASK-234). -
On Windows, a host CLI installed through npm (Codex, Copilot CLI, OpenCode)
is a.cmdshim that the LLM pass could not start (FileNotFoundError), so
the pass degraded silently. The command is now resolved the way a shell
would. -
The guardian skill no longer says its LLM tier runs a pinned
claude-sonnet-4-6; it runs on the recorded host's own model. -
build-client-adapters.pyandsync-agent-plugins.pyno longer fail with
ModuleNotFoundErrorin an installed plugin.client_certification.py
importsclient_support_matrix.py, which the release allowlist did not ship;
a test now checks that every shipped script's sibling imports ship too. -
The project guide installed as
.claude/adr-kit-guide.mddescribes the edit
tier as it works: the plugin's hook injects the governing ADRs before an edit
and repeats the lookup after it, withinject.enabled/watch.enabledas
the switches. It namedbin/adr-watch, which no hook runs, and listed three
of the seven MCP tools. -
adr-suggest --helpand the config schema give the suggestion pass's real
default timeout, 30 s; both said 120. -
adr-doctor --deepdescribes its probes as native registration, MCP and hook
latency; it no longer mentions a model probe that was retired with ADR-036,
and neither do the README and TROUBLESHOOTING.md. -
The CLI latency corpus no longer budgets the retired
adr-embedand
adr-context-vector, budgetsadr-watchinstead of excluding it as a
long-running watcher (every mode exits), and a test now rejects a budget
for a name that is not inbin/. -
clients/capabilities.jsonlists the three hook registration files as
generated, which they are, instead of hand-authored. -
The guardian's SessionStart entry (
templates/cc-settings/guardian-hook-entry.json)
no longer depends on GNUsort -Vto pick the newest installed plugin. Where
sorthas no version mode the pipeline yielded nothing, and the entry's
|| truechain then skipped the guardian check on every session start
without a word. The pick now runs on the Python the entry already finds.
The same class of defect as #155; existing projects pick it up through
/adr-kit:upgrade, which replaces the entry. -
The ten skills that run a bundled tool find the kit wherever it is
installed. In Claude Code that includes a--plugin-dircheckout, and the
fix also covers OpenCode, which loads the same skills. They looked only in
Claude's marketplace cache, through a pipeline that needs GNUsort -V. Now
they takeADR_KIT_ROOTwhen it is set, which the OpenCode plugin does for
its shell, and otherwise the plugin root that Claude Code substitutes into
the skill (ADR-044). When neither yields a root, the skill stops with one
line saying so. -
/adr-kit:install-hooksadds the project-scoped guardian hook from
templates/cc-settings/guardian-hook-entry.jsoninstead of an inline copy.
The copy still usedsort -V, which the template no longer does (see the
entry above), and it had no_wrapper_versionstamp, so the guardian
reported it as present but never as stale and/adr-kit:upgradeleft it in
place. To replace an entry added with the copy, remove the guardian hook with
/adr-kit:install-hooksand add it again. -
A commit the judge refuses prints the violation report. Wherever
flock
exists, the hook's concurrency lock sent its stderr to/dev/nullfor the
rest of the hook, so th...
adr-kit v0.59.1
A patch release for the pre-commit hook, prompted by an external bug report
(#155, thank you @Simwawa). Both fixes make the hook choose the right tools on
machines that do not look like the maintainer's: several Pythons side by side,
or a sort without GNU extensions such as the one macOS ships.
Upgrade note. Nothing needs to change on a supported setup (a Python 3.10 or
newer on PATH). On a machine whose only Python is older than 3.10, the hook no
longer runs adr-judge on that interpreter. It prints its existing
"Python 3 not found ... Skipping ADR check for this commit." message and lets
the commit through. Install Python 3.10+ to get the check back.
The hook is a copy in each project, so existing projects pick the fix up by
re-running /adr-kit:install-hooks and choosing replace when it reports
that .githooks/pre-commit differs from the template. The old hook is kept as
a timestamped backup.
Fixed
- The pre-commit hook selects a Python that can run
adr-judge. Its probe
took the first command reporting any Python 3, so withpython3on 3.9 and
a newerpythonorpyinstalled it picked 3.9 and the gate failed. It now
asks each candidate for its version and skips one below 3.10. Reported in
#155. - The pre-commit hook no longer depends on GNU
sort -Vto pick the newest
installed plugin. Underset -easortwithout version mode aborted the
whole hook; the comparison now runs on the Python the hook already requires.
Reported in #155.
adr-kit v0.59.0
A follow-up to 0.58.0 that finishes the same theme: tools that measured the
developer's machine instead of the project, and tools that quietly rewrote
files they should only have read.
Upgrade notes. Nothing needs to change, but two results can differ:
adr-retirenow reads the files git versions (tracked, plus untracked
files that are not ignored). A technology used only in git-ignored output
no longer counts as in use, so an ADR whose only remaining trace was build
output may now appear as a retirement candidate. That is the intended
reading; check the candidate before retiring it./adr-kit:lintnow runsbin/adr-lint, so the first run may ask your
client for permission to use the shell.
Fixed
adr-retirescans what git would version, not what lies around. It walked
every directory, so git-ignored output such asgraphify-out/(1174 files,
67 MB on one machine) took a run from under a second to 3.3 s, and CI,
where that folder does not exist, never saw it. Inside a git repository it
now readsgit ls-files --cached --others --exclude-standard; outside one,
its walk skips junctions and directory symlinks and stops after 100,000
entries or 10 seconds, asadr-lint's scan does since 0.58.0 (TASK-212).scripts/release.pycan be re-run afterprepare, as its own message
says. preflight refused the uncommitted version bump that prepare leaves
behind on purpose; it now accepts a tree whose only changes are declared
version sites and the generated client trees, once the version is
written, and still names any other changed file (TASK-213).adr-doctorno longer regenerates client adapters when it runs from a git
checkout; it reports them stale with the build command instead. Repair
mode rewrote this repository'scodex/andcopilot/from every test
that ran doctor without--check. Installed payloads keep the repair
(TASK-213).bin/adrlifecycle commands keep a CRLF file CRLF. They wrote every
record back with LF, so one answered question showed as a whole-file
change on a Windows checkout (TASK-213)./adr-kit:lintrunsbin/adr-lintand explains its findings. It
described the CLI's output without being allowed to run it (TASK-213).- This repository's own
.adr-kit/ADR-guide.mdand.githooks/pre-commit
are refreshed from their sources, the guide's stamp is a declared version
site, and a test keeps both copies equal to their source (TASK-213).
adr-kit v0.58.0
This release is about tools that could run for a very long time and checks
that could stay green while checking nothing. adr-lint no longer walks into
Windows junctions without bound, and the judge's LLM pass now has a ceiling a
host CLI cannot hold open. Two new advisory checks look for what every gate
missed: Enforcement rules whose scope reaches no file, and ADR sections that
are written but say nothing.
Upgrade notes. Nothing needs to change for an existing project. Three
behaviours are new and visible:
judge.llm_pass_timeout_seconds(default 600) is a new, optional key in
docs/adr/.adr-kit.json. A pass that reaches it leaves the remaining ADRs
without a verdict and marks the attestation degraded; the commit still
passes on the declarative checks.adr-judge-precommitexits 2 after 900 s (ADR_KIT_JUDGE_TIMEOUT_S)
where it used to wait without limit.adr-lintreports a gate it could not reach within its scan budget as
could not be verifiedrather than as missing. Pass--repo-rootthe
project root if you see it.
Added
adr-substanceasks the host model, in the guardian's LLM tier, whether
each written section of a Proposed ADR actually answers its heading. It
reports "TBD", "see above" and generic prose, which every deterministic
check reads as written, and quotes the author's own words. A quote the
model cannot locate in the section is dropped. One isolated call per
Proposed ADR;--estimatestates the count before anything is spent, and
the guardian skill adds it to its cost prompt. Advisory only: the exit code
is 0, andadr-readiness, the MCP server andadr-lintare untouched
(TASK-203).adr-judge --check-scopenames every Enforcement rule of an Accepted ADR
whosepath_globmatches no tracked file. Such a rule is checked against
nothing, so every gate that asks whether it was violated answers green for
as long as the glob and the code disagree about where the code lives.
adr-audit --whole-codebasecarries the result as advisory, without moving
its exit code. The review skill gains the half no tool can check: whether
every case an ADR enumerates and every target it names reaches an
implementation (TASK-204).
Fixed
adr-migratenames a Related Decisions section it had to add. The
conversion writes- None.into it, which every detector rightly reads as
content, but the author never saw the section. It is now listed as
review: ## Related Decisions, apart from the placeholder holes, and a
hand-written- None.is still left alone (TASK-202).adr-migrate --to-profileno longer blames a conversion for holes the
source already had. It compared section titles before and after, and two
profiles name the same section differently, so a nygard## Contextthat
already held a TODO was reported as a new## Context and Problem Statementhole on the way to madr. It now compares roles, each side read
in its own profile (TASK-201).- The judge's LLM pass now has a ceiling it keeps. A host CLI call used to
return only when every process holding its output pipe had exited, so a
helper it started could hold a 120 s call open indefinitely: measured at
60 s for a 2 s timeout. Output now goes to temporary files and a timeout
stops the CLI's whole process tree. The pass as a whole stops after
judge.llm_pass_timeout_seconds(default 600); ADRs not reached get no
verdict and the attestation names them, as for any unusable call
(ADR-038).adr-judge-precommitgives up after 900 s, configurable with
ADR_KIT_JUDGE_TIMEOUT_S, and exits 2 with a hint (TASK-210). adr-lintcan no longer walk without end while looking for an Accepted
binding ADR'sgate. The scan descended into Windows junctions, which
os.walk(followlinks=False)does not treat as links, and its 5000-file cap
counted only files it read. A junction into a large tree took 160 s where it
now takes 0.5 s. The scan now skips directory links andgraphify-out,
skips files over 1 MB, and stops after 100,000 entries or 10 seconds. When a
budget runs out the finding says the gatecould not be verifiedand names
the budget, instead of reporting the gate as missing (TASK-209).bin/adr acceptno longer waits forever on the quality and lint runs it
starts: they time out after 120 s and report the timeout as an error. The
gitcalls inadr-lintno longer inherit the caller's stdin.- The guardian's Proposed-ADR queue ranks by age again. Every record showed
age 0 days, becauseadr-readinessdropped the date the ADR catalog had
already parsed. Each readiness item now carriesdate: the latest status
change, or the frontmatter date when there is no history. The report stays
deterministic, since the date comes from the file and not from the clock
(TASK-200). - An Open Question that wraps onto more lines, or carries nested bullets, is
one question again.bin/adr answerused to mark only its first line, so
the answer landed mid-question;--question <text>searched only that
line; each nested bullet counted as a question; and a continuation line
ending in?became an unresolved question thatanswercould not reach,
which keptacceptblocked after everything was answered. All three
parsers now share one item grouping. A multi-line question gets its answer
on a new line after its last line, and the confirmation no longer cuts the
question mid-word (TASK-194). - The ADR guide that setup installs as
.adr-kit/ADR-guide.mdcarried the
stampv0.35.0in every project set up since v0.36.0. Its source is now a
declared version site and moves with each release (TASK-192). - The test suite no longer renames a developer's live Copilot plugin
directory while testing the installer (TASK-191), and the certification
checks no longer fail on the calendar 30 days after their fixture date
(TASK-211).
adr-kit v0.57.0
This release makes two gates stricter, and both can turn a pipeline that was
green on 0.56.0 red on upgrade. adr-lint --strict and bin/adr accept now
fail a required section that is present but empty, reporting
missing sections: ['References (present but empty)']; fill the section, and
note that a migration placeholder still passes deliberately, so an imported
record is never blocked on arrival. The lifecycle commands now exit 2 rather
than 0 when an ADR's Status line records a transition they would otherwise
discard; write the ## Status History block and re-run.
Everything else is a release the project can now run on itself. One command
drives it end to end, the tag is created from the merge rather than typed, and
adr-readiness says when a record holds a placeholder instead of an answer
rather than reporting it ready.
Added
-
One command now drives a release.
python scripts/release.py X.Y.Zruns every
step ofdocs/RELEASING.mdin order and is safe to re-run: each phase asks the
repository whether its work is already done, so an interrupted release is
resumed rather than restarted.--statusreports what is left without changing
anything,--onlyruns a single phase, and--skip-testsleaves the suite to
CI.docs/RELEASING.mdremains the specification the driver implements; the
driver is now the sanctioned path (ADR-042). -
An installation smoke workflow. It exercises the
.pre-commit-hooks.yaml
install path throughpre-commit try-repoagainst a fixture repository, which
nothing tested before, and checks that every@vX.Y.Zaction pin shipped in
the README and the templates names a tag that exists and still defines that
action. Its job summary states what it does not cover: the three vendor CLIs
are not installable on a hosted runner and stay certified from retained
Windows evidence (ADR-010). -
A gate refusing a CHANGELOG entry appended to a section that has already
shipped.release-publish.ymlpublishes a## [X.Y.Z]section verbatim as
its GitHub Release body and section membership is positional, so a bullet
written under a released heading is absent from the next release's notes and
claimed by a release that does not contain it. That reacheddevtwice before
a human sweep found it, because the diff carries### Fixedas unchanged
context and reads like a correct addition. The check compares the number of
top-level bullets against the same section at its tag rather than the text:
measured over the 70 sections that can be compared, several differ from their
tag today and every one is a correction, which a byte-identity rule would
fail. Both tag spellings this repository ships are resolved,v0.56.0and the
olderadr-kit--v0.1.0, so only six sections cannot be compared at all, and
those are reported as skipped rather than passed. It runs invalidate, the
only required check ondev.
Changed
- The driver recognises an unwritten release section by its shape rather than by
the wordTODOappearing anywhere in it.bump-version.pyinserts a
- TODO: describe this release.list item and the driver must refuse to
publish that as a Release body, but the substring test also refused this very
release, whose notes describe placeholder detection and therefore quote
- TODO:markers five times. A gate that will not let a release describe its
own subject leaves the author no move except worse notes. A leftover TODO list
item is still caught, which comparing against the exact scaffold string would
have missed. - The driver now exits non-zero when npm serves a different version than the one
released.npmsetslatestto the version published last rather than the
highest, so approving staged versions out of order silently points
npm installat an older release; that left 0.55.1 released whilelatest
read 0.54.0 for a week with nothing reporting it. A registry that does not
answer is reported as its own state rather than guessed at, and also exits
non-zero, so a release is never reported as finished on the strength of a
failed lookup. - The release no longer asks anyone to type a tag.
release-publish.ymlcreates
it from the merged commit, and the driver verifies the tag resolves to
origin/mainand refuses to move one that does not. Typing the tag by hand is
what cost v0.55.0. - The local install step reads each client's version back instead of trusting the
installer's exit code, which has reported success while leaving clients on the
previous version.
Fixed
- Lifecycle commands refuse to discard a transition the Status line still
carries. On an ADR that predates the## Status Historyconvention,accept,
reject,propose,supersedeanddocumentseed one recovered entry from
the Status line before replacing it, but the reader took only the leading
status and the first date. A line such asSuperseded by ADR-088, 2026-08-07. Originally Accepted, 2026-05-08.yielded exactly the transition asupersede
repair was about to write, the equality check returned early, and the line was
overwritten with exit 0: the history then asserted the record's first-ever
transition was Superseded. The command now exits 2, naming the file, the line,
what it kept and what it would lose, and changes nothing; write the
## Status Historyblock first, then re-run. The recovered entry'sreason
also carries the Status line verbatim, so aDecision Maker:attribution or
any other prose the parser has no field for survives instead of being dropped.
templates/adr-kit-guide.mddocuments both refusal conditions.
(#120) - A required section that is present but empty no longer counts as complete. The
completeness gate tested whether the heading matched somewhere in the text and
never looked at the body, so## Referenceswith nothing under it passed.
bin/adr acceptruns that gate with--strict, which made an empty heading
enough to walk a record carrying no verifiable reference into an immutable
Accepted state. The finding now separates the two cases,Referencesagainst
References (present but empty), because an author needs to know whether the
section is missing or hollow. The rule stops at empty, deliberately: a
migration placeholder still counts as content, because an imported record must
not fail a blocking gate on arrival. The same hole existed inadr-quality,
whose section loop scored presence while three checks beside it already
measured emptiness for Decision, Alternatives and Consequences; References and
Related Decisions fell through that gap and
adr accept --quality-thresholdreads that score. adr-migratesays which sections it could not fill. Converting a profile
appends- TODO: ...into any required heading it has to add, which is honest
at write time but counts as content by decision, so a migrated record passes
completeness and nothing said it was unfinished. Each such section is now
reported asneeds content: ## <heading>, counting only what that run left
unfilled so a hole the author already had is not blamed on the migration. That
report is the whole signal, and deliberately not a refusal: the gates stay open
on a placeholder, which is what keeps a migrating team from disabling them.
Measured on a real record whose## Referencesholds only
- TODO: add verifiable references., the acceptance gate set still passes it
(adr-qualityscores it 0.87, grade A), while the same record with an empty
## Referencesis blocked with
missing sections: ['References (present but empty)'].adr-readinesssays when a required section holds a migration placeholder
rather than an answer. A Proposed record whose## Referencescontained only
the linebin/adr-migratewrites classifiedready-for-confirmationand
carried no finding at all, which is a lie about a record
nobody has finished writing; nothing between the migration andadr accept
ever said otherwise, because the migrator prints its report once, to whoever
ran the command. The newSECTION_PLACEHOLDER_ONLYfinding names each
section and moves the record toneeds-human-input, which gives it a
next_commandof/adr-kit:grill. Readiness cannot block and deliberately
gains no exit-1 path: the completeness gate still passes such a record on
arrival, because a team that hits a wall on import disables the gate.
Empty sections are not reported here, sinceadr-lintalready fails
completeness on those. Measured across 212 real records (this repository's 43
and the 169-record OTGW corpus): zero new findings, so the signal fires only
on records that carry a placeholder.- The guardian queue no longer drops a record for being honest about itself.
Enrollment is gated onsignals = (linked, shipped, ready, open_questions, below_threshold), and for exactly this population -- unlinked, unshipped,
quality above the threshold, no open questions --readywas the only true
signal. Reporting the placeholder moves the classification off
ready-for-confirmation, which measured as 0 candidates: the honest report
would have made the record less visible than saying nothing. Aneeds_human
signal now carries it, with its reason placed before the unconditional
age N daysentry so it survives thereasons[:2]cut the SessionStart
block applies. unfilled_required_sectionsrecognises both placeholder spellings. It matched
the- TODO:list itembin/adr-migratewrites but not the
<!-- TODO: ... -->comment the/adr-kit:migrateskill writes, while
bin/adr-linthas always stripped comments before counting and
tests/test_adr_policy.pypins the two as equivalent. A skill-migrated record
was therefore reported as finished. The helper also splits the two holes it
used to conflate:placeholder_required_sections...
adr-kit v0.56.0
The release now tags itself. release-publish.yml reads the CHANGELOG version
on a push to main and creates the tag on the commit that carries it, so a tag
can no longer name a commit whose version sites disagree with it. That is the
failure that burned v0.55.0. Nothing to do on upgrade.
Two lifecycle defects are fixed that both ended in a decision silently not
reaching your agents, with exit code 0 throughout.
Added
release-publish.ymlcreates the release tag from the mergedmaincommit
and publishes in the same run, and its job summary now carries the npm
approval steps, the staged-packages URL, and the warning that npm sets
latestto the version published last. The instruction used to live only in
docs/RELEASING.md, which is why three staged versions sat unapproved for a
week. Recorded in
ADR-042,
which amends ADR-012's automation boundary.scripts/check-release-version.py --print-canonicalprints the canonical
CHANGELOG version and nothing else, so the workflow derives the tag from the
same registry entry the gate compares against.tests/test_docs_claims.pyholds four documentation claims that nothing read
before: the counts the README advertises, no version literal in
SECURITY.md's supported-versions section, no current-version assertion in
ROADMAP.md's status section, and every action pin in a file users copy must
be a declared version site.adr_format.section_span()returns the offsetssection_text()already
computed, so a writer can place content inside the bounds the reader parses.
Fixed
- Lifecycle commands no longer write
status_historyentries outside the
## Status Historysection.append_status_historylocated its insertion
point withbody.find("```")over the whole remaining document, so on a
record whose history block carries no fence - the shape the shipped agent
template emits - the entry landed in the next fenced block, in this project
the## EnforcementJSON. The frontmatter readAcceptedwhile
ADR-INDEX.jsonwent on reportingProposed, and only Accepted ADRs are
injected, so a decision the maintainer had just signed stopped reaching any
agent. A second shape, no later fence at all, appended a second
## Status Historysection and inverted chronology. Affectsaccept,
reject,propose,supersedeanddocument, which share one mutation
path. (#119) - Frontmatter inference reads the status instead of defaulting to
Proposed.
It recognised exactly one Status shape and guessed on every other, including
any line starting with**,[or-.adr-migratewrote that guess and
exited 0. Measured over a real 169-record corpus, 79 records were affected:
60 readProposedwhile the body saidAccepted, 12 while it said
Superseded, oneAmended. The reader that handles those shapes already
existed and called itself the single cross-tool status reader; the function
that writes the field never used it. It now does, and an unreadable status
stays undetermined rather than becoming a confident wrong answer - both
callers already had an honest path for that and neither was being used.
(#118) superseded_bysurvives the link-wrapped form real bodies use,
Superseded by [ADR-124](ADR-124-title.md), and the date and successor are
now recovered from the same line the status word was read from rather than
from a## Statussection the record may not have.- Lifecycle commands no longer feed the generated
ADR-INDEX.mdto the
frontmatter loader.glob("ADR-*.md")matches it, which was only ever
harmless because inference invented a status for it. SECURITY.mdno longer names a supported version line that goes stale; it
states the policy and points at the latest release. It had claimedv0.33.x
was current, twenty-two minor versions behind, and contradicted the sentence
directly above it.- The README described the MCP server as five-tool in five places while saying
"Seven tools" further down, claimed 15 workflows against 17 and six bounded
hooks against eight, and pointed OpenCode users at a superseded package
version.ROADMAP.mdasserted a version number and omitted the OpenCode
distribution. templates/github-workflows/adr-readiness.ymlpinned its action eighteen
minor versions back because the pin was declared in no registry. It and the
README OpenCode package pin are now sites in
packaging/version-sites.json, soscripts/bump-version.pywrites them.
Changed
- The Claude Code marketplace listing no longer advertises "v0.33 local
governance tools" as if that set were new. - The reviewed migration baseline for the bundled corpus moves from 81
deterministic and 88 guided to 79 and 90, and the metadata dry run from 154
changed and 15 failed to 152 and 17. Two records now need human judgement
instead of receiving an invented status, and two more report an actionable
issue instead of being rewritten with one. The retrieval probe for PS=1
summary parsing now expects ADR-046 rather than its superseded predecessor
ADR-045, which ranked as the governing answer only because its status was
misread.
adr-kit v0.55.1
The version number skips 0.55.0. That tag was pushed at the dev tip before
the version bump was committed, so every version site at that commit still
read 0.54.0 and the release gate correctly refused to publish it. The tag is
left in place rather than moved, because a pushed tag is a public ref that
consumers may register a marketplace from. Nothing was ever published as
0.55.0.
ADR-029 is finally carried out: the native hook binary is gone and Python is
the only hook host on every platform. Nothing to do on upgrade unless you set
ADR_KIT_NATIVE_HOOK, which no longer exists.
Removed
- The committed
adr-hook.exeartefact, its Rust sources, the dispatcher
branch that preferred it, and theADR_KIT_NATIVE_HOOKopt-in, from all
three generated client trees. ADR-029 has been Accepted andbinding: true
since 2026-08-04 and its Must reads "Remove the native host, its committed
artefact, and the dispatcher branch that prefers it"; none of it had
happened (TASK-187). - Three parity-from-source tests in
test_adr_hook_result_limit.pythat read
the Rust source to compare it against the Python host. Maintaining that
comparison is the second implementation ADR-029 rejects.
Fixed
-
adr-mcprejects malformed JSON-RPC frames instead of answering them
(TASK-186). Four deviations, all reproduced against v0.54.0:"id": nullwas answered with a full result and"id": null, so the
reply itself failed to validate againstJSONRPCResultResponse. The base
protocol says the ID MUST NOT be null; only key presence was checked."id": 1.5and"id": {"a":1}were echoed verbatim.RequestIdis
string | number.- The
jsonrpcmember was never checked, so a frame with no version, or
with"1.0", was served normally and got"2.0"back. io.modelcontextprotocol/clientCapabilitieswas tested for presence only,
so the string"nonsense"passed andserver/discoversucceeded. A
schema-invalid params now returns-32602.
None of these breaks a conforming client. They are worth fixing because a
spec deviation in an outgoing frame is the one category the other side
cannot correct, and a non-conforming client previously received a
valid-looking answer instead of a usable error. -
ADR-029's gate no longer certifies the option the decision rejects. The
anchor flipped on by TASK-127 asserted that the native host ran only under
ADR_KIT_NATIVE_HOOK=1, which is the opt-in state ADR-029's Decision
Outcome rules out, soadr-lintreported compliance for a decision nobody
had implemented. The Verification section is corrected in place, dated, and
carries a matchingstatus_historyentry;verified_innow names the three
tests that hold the assertions. -
bin/adr_doctor_checks.pyno longer requiresadr-hook.exe, which would
have made every healthy install report "failed" once the artefact was gone.
Changed
docs/hook-performance.mddescribes Python as the only host rather than a
fallback, and drops the edit-tier budget that ADR-030 had already replaced.- The
ADR-INDEX.jsonsize budget intests/test_adr_index.pyallows 2.5 KB
per ADR instead of 2 KB. At 41 records the old bound cleared by 4 bytes, so
it had stopped measuring growth and fired on whichever ADR was edited next.
The ratio assertion that bounds the graph at 25% of the ADR markdown is
unchanged and remains the meaningful limit.
adr-kit v0.54.0
Automatic interactive ADR grilling is now enabled by default for user-visible
sessions. No breaking changes; existing projects retain an explicit opt-out.
Added
- Automatic Proposed-ADR handoff (ADR-041): the next user-visible prompt
can emit one boundedAUTO_GRILL_PENDINGinstruction for the highest-priority
unfinished Proposed ADR. The handoff remains deterministic, local, fail-open,
and human-gated. - Settings visibility:
grill.auto_startis available through
bin/adr-settings, with provenance showing whether the default or project
configuration supplied the value.
Changed
- Upgrade default materialization:
/adr-kit:upgradewrites
grill.auto_start: truewhen an existing project has no explicit value. An
existinggrill.auto_start: falsesetting is preserved. - Native client handoff: Claude, Codex, Copilot, and OpenCode receive the
exact client-native grill command without starting interviews from CI,
pre-commit, background, or unattended lifecycle events.
Upgrade Notes
- Run
/adr-kit:upgradeafter installing this release to materialize the new
project setting. The runtime default is already enabled when the setting is
absent. - Set
grill.auto_start: falsefor a project-wide advisory-only opt-out, or
useADR_KIT_AUTO_GRILL_DISABLE=1for one run.
adr-kit v0.53.0
The MCP server grows from five to seven tools, completing the prescribed
agent development cycle for MCP-only clients. No breaking changes; existing
installations need no migration.
Added
adr_lintMCP tool: lint the ADR set against the deterministic
verification gates over MCP, with an optionalstrictmode. Wraps
bin/adr-lint --format jsonand reportsverdict: okor
verdict: findingsalongside the full JSON report, so a shell-less agent
can run the mandatory pre-completion lint step.adr_relatedMCP tool: the dependency graph (inbound and outbound
edges, supersession links, dangling references) for one ADR over MCP,
wrappingbin/adr-related --format json. Agents can now follow graph
links afteradr_contextwithout raw file reads.- ADR-040 (Accepted): records the admission criterion for the MCP tool
surface — read-only, deterministic and key-free, agent-query-shaped, and
covering a development-cycle step no exposed tool already covers. Future
tool proposals are evaluated against these four recorded tests.
Changed
-
Agent guide rewritten for autonomous operation
(.adr-kit/ADR-guide.md): a tool-surface table mapping each MCP tool to
its CLI equivalent with mutation flags, per-phase steps with expected
outcomes and agent reactions, and an explicit human-gated list (accept,
supersede, reject, signer). Projects pick this up on the next
/adr-kit:setupor/adr-kit:upgrade. -
Doctor deep probe and installer smoke test now verify the complete
seven-tool MCP contract. -
OpenCode npm publication flow now runs from the canonical
release-publish.ymlworkflow. It stages the package through Trusted
Publishing after the GitHub Release, while maintainer 2FA approval remains
required before the package becomes public. -
OpenCode npm publication flow now runs from the canonical
release-publish.ymlworkflow. It stages the package through Trusted
Publishing after the GitHub Release, while maintainer 2FA approval remains
required before the package becomes public.