Skip to content

Release 0.8.1 - #575

Merged
MongLong0214 merged 26 commits into
mainfrom
release-081
Aug 12, 2026
Merged

Release 0.8.1#575
MongLong0214 merged 26 commits into
mainfrom
release-081

Conversation

@MongLong0214

Copy link
Copy Markdown
Owner

0.8.0 shipped a protocol whose server described half of itself.

The gap

The MCP server's instructions covered the read half — read the context resource before editing, how to read the trust tiers — and said nothing about when to record. Four of the seven hosts install.sh wires receive an mcpServers entry and no skills:

host receives could capture before
Claude Code plugin (skills + MCP + hook) yes
Codex MCP + plugin (skills) yes
Hermes MCP + skill bundle yes
Gemini, Cursor, Windsurf, opencode MCP only no

Those four held the capture tools with nothing telling them what the tools were for. AGENTS.md was the only channel carrying the missing half — which is why init wrote it into repositories whether or not they use the convention.

The fix

The server describes both halves. It ships to every host by definition; a file in somebody's repository does not.

Proven with the plugin disabled (no skill loaded) and no AGENTS.md anywhere:

tool invocations: prepare_capture 2, verify_capture 2, stage_capture 1
Record-Id: r-csvscan1   Provenance: drafted

That is the skill-less path — the one those four hosts are on.

Writing AGENTS.md is now --agents-md, off by default.

Also

A registration naming a path that does not exist is reported instead of counted as healthy. Four hosts on this machine pointed at /tmp/fresh256…/bin/commitlore, a temp directory deleted long ago, and every reinstall said they were fine. The file is still never rewritten.

Verification

Version consistent across all three manifests, both lockfile fields and the built CLI at v0.8.1; 30 pins updated across four READMEs and both installers. 199 pass across readme, manifest, check-release-version, init, mcp and both installer suites.

0.8.0 shipped a protocol whose server described half of itself. The read half
was there -- read the context resource before editing, how to read the trust
tiers -- and nothing said when to record anything. Four of the seven hosts
`install.sh` wires receive an `mcpServers` entry and no skills, so they held
the capture tools with nothing telling them what the tools were for, and
`AGENTS.md` was the only channel carrying the missing half.

That is why `init` wrote the file into repositories whether or not they use the
convention. The server carries both halves now, and writing AGENTS.md is
`--agents-md`, off by default.

Also: a registration naming a path that does not exist is reported instead of
counted as healthy. Four hosts on the author's machine pointed at a temp
directory deleted long ago, and every reinstall said they were fine.

Limit: the capture half reaches a host that surfaces MCP `instructions`; one that ignores that field still needs `--agents-md`, and nothing detects which kind a host is
Blast: system
Undo: easy
Certainty: firm
Verified: version consistent across all three manifests, both lockfile fields and the built CLI at v0.8.1, with thirty pins updated across four READMEs and both installers; 199 pass across readme, manifest, check-release-version, init, mcp and both installer suites
Provenance: authored
Record-Id: r-release081
@github-actions

github-actions Bot commented Aug 12, 2026

Copy link
Copy Markdown

CommitLore — record lint

Trailers: clean — 26 commits in origin/main..8f5cf785acbbd5f4379d5b4efb3a938381f6005a
Active constraints: 268 limits · 547 ruled-out · 142 warnings — from 325 records over 76 changed paths

Active constraints for the paths this PR touches

Limits (268)

  • r-assertfile1 8a859c6 — this checks the report validate produced; it does not re-derive the range, so a range that silently covered nothing would pass here
  • r-answerown1 3547382warn distinguishes ours from not-ours by the command string, and does not execute anything -- a wrapper that really is a CommitLore server still reads as unverified, which is the safe direction but not a probe
  • r-pretag01 86e0153registers_commitlore reads the key, so a config that registers under a different key -- a host with its own naming -- still reads as unregistered and is wired again
  • r-e2epipe01 bd680a7 — the model's judgement -- deciding a change is worth recording at all -- is the one step no fixture stands in for, and stays a manual pre-release matrix
  • r-dogfoodref1 f605dbb — this asserts the two checks validate performs; a third class added later is not required by name until somebody adds it here
  • r-mslquote1 5b23c44 — this is the second quoting layer in this step; a third -- a value with a double quote in it -- would need a different mechanism than more escaping
  • r-engfloor01 fe83524 — the parser covers the range shapes npm packages actually publish -- comparators like >=22 <23, and pre-release identifiers, are read by their first version and not by their bounds
  • r-readyhosts1 9db3c4d — the new jobs establish that an install runs and answers on those hosts, not that every command behaves identically there
  • r-dropfake01 06961d3 — the runtime's presence proves this installer wrote the directory, not that its contents are unmodified since
  • r-secondcopy1 01ebee5 — the budget bounds the two scans, not the command -- process startup, path resolution and rendering still sit outside it
  • r-staleclaim1 59cb5d9 — withholding uses the same pattern table as every other route, so a payload that trips nothing still passes; this closes a route that had no grading at all, not the heuristic behind it
  • r-snapshome1 e339cba — this normalises the two spellings this report produces; a third form -- a symlinked home, a UNC path -- would need its own
  • r-nodefloor1 f4c924f — this bounds the version, not the feature -- a Node that ships node:sqlite behind a flag, or removes it, is not detected here
  • r-codexunver1 980d747 — presence is read from the marketplace name, so a Codex that reports neither a source nor a listing this can parse is treated as absent and one is added under a name that may already be taken
  • r-release081 ffe702a — the capture half reaches a host that surfaces MCP instructions; one that ignores that field still needs --agents-md, and nothing detects which kind a host is
  • r-mcpproc01 db1363d — this establishes that a host which surfaces MCP instructions can capture without a skill; a host that ignores that field still needs --agents-md, and nothing here detects which kind a host is
  • r-observed01 43cfa5e — existence is not identity -- a path that resolves to something other than this tool still reads as a working registration, which is doctor reports a registered MCP command as working without establishing its identity #572
  • r-ownsemver1 fcc6e4a — the evidence is a directory this installer wrote, so an install whose data directory was deleted is now refused rather than upgraded -- a refusal naming the file, against silently destroying it
  • r-ceiling01 543453b — this bounds the scan and not the command -- process startup, path resolution and rendering are outside it, so a repository whose single cheap pass is slower than the budget still exceeds it by that much
  • r-clocktest1 6ac2b44 — the injected clock proves the loop stops and resumes correctly; it does not measure that a real budget corresponds to any particular wall-clock pause, which stays a measurement rather than a test
  • r-saywhat01 56444db — entailment is still unchecked, and this narrows the claim rather than closing the gap -- the protection remains that no drafted record is ever delivered as a directive
  • r-boundverify1 9f2c12e — the phase check makes a refused store visible; it does not let a caller re-verify a transaction that has moved on, which stays a matter of preparing a new one
  • r-bynottname1 8beaa6d — an entry whose command is launchable but wrong still counts as a registration; the check establishes that a host could start something, not that what it starts is this tool
  • r-hookbudget1 e09014c — the budget bounds the wait, not the answer -- a repository large enough to trip it keeps getting a partial view until somebody runs init, and the notice is the only thing that makes that visible
  • r-winstall1 0b4e551 — this pins what the repository says about itself, and cannot check that the tag it names has been published -- the install gate does that, after the tag exists
  • r-scanall1 62a6fbf — the scanner remains a heuristic, so this closes the exemption and not the gap behind it -- a payload that trips no pattern still reaches the agent
  • r-wrongtag1 99667f3 — PowerShell cannot be run here, so this is reasoned from the shared design and proven against the shell twin; only the windows-latest job is evidence for install.ps1
  • r-vbind001 2c88d24 — this binds the requested tag to the runtime that answers, not the tag to its content -- a tag moved after publication installs whatever it now points at, which is a signing question rather than a version-binding one
  • r-structk1 a7bee10 — this closes the exemption, not the heuristic behind it -- a payload that trips no pattern still passes, and the scanner remains a speed bump rather than a boundary
  • r-ownfail01 8de1326doctor can say the setting is unreadable and cannot say what its author meant by it, so the repository is held to the stronger mode until a person decides
  • r-expwall01 e7ddd92 — the cache cannot notice an expiry that falls between two reads inside the same day -- a record expiring at noon is still delivered until the day rolls over, which is the granularity the determinism is bought with
  • r-distrace1 bc23b26 — the suites still share one repository checkout, one npm cache and the machine's temporary directory, so a test that writes outside its own fixture can still reach another
  • r-codexerr1 a6d0fab — the first line of Codex's output is not always the cause -- a wrapper that prints a banner before its error will have the banner reported instead, and the full output is still only visible by running the command directly
  • r-insttxn1 afb7bfb — this establishes that the installed tree is complete and its commands run on this machine at this moment -- not that the machine will still have a working node tomorrow, and not that any agent host will load what was installed
  • r-authdir01 ae2a66f — in the default mode a directive establishes that the commit's author string matched a configured one, and nothing about who produced the commit
  • r-authdir01 ae2a66f — in signature mode a verified signature establishes that a key the verifier trusts signed this commit -- not that its holder has authority over this repository, and not that the record's content is true or safe
  • r-mcpdir01 a9886b5 — neither route can tell a caller whether the trusted-author configuration reflects anyone's actual identity -- it reports what the repository decided, and the decision is a local git config value
  • r-codexreg1 5933aa4 — an entry can be correct when the installer reads it and wrong afterwards -- a later install, a moved data root or a hand edit all leave the name intact, and nothing revisits it until the installer runs again
  • r-codexplug e5fe95a — a plugin can put a skill in front of a session; it cannot make the session follow it, and nothing here reports whether one did
  • r-readme001 7f82d47 — the README still cannot tell a reader whether their particular host will follow a written procedure; only the hosts with a plugin or an installer have that answered by a mechanism rather than by hope
  • r-hermesx01 2eb8176hermes skills inspect resolves remote sources only in this Hermes version, so discovery was verified through hermes skills list --source all in a fresh isolated profile rather than from inside a live conversation; that a session then follows the procedure is not something an installer can establish
  • r-codexwire 955f290 — an instruction file is guidance, not enforcement -- a host may ignore it, summarise it away, or never read it, and nothing here can tell whether any session followed the procedure
  • r-initmcp1 e601ad3 — this registers for hosts that read a repository-scoped .mcp.json; a host keeping its configuration elsewhere still needs its own installation, and this cannot tell whether any host ever loaded the file
  • r-cdeb10reg 48bd5a8 — wrong-path viability, deterministic oracle feasibility, code disclosure, bounded implementation, and unproven ordinary or benchmark authorship cannot be decided from history and remain undecided for human review
  • r-mintid01 1e5f500 — deterministic minting can reserve only identities visible in this repository; independently diverged history can still introduce a collision
  • r-notes512a ce937c9 — the observation is as old as the last doctor --fix; a mirror pushed upstream after it is not visible here, and an empty answer will read as a true empty until the next probe
  • r-autotrue2 6cc5032 — a host may be registered and never call the tool, or be configured outside the repository entirely, so this distinguishes wired from unwired and never observed from unobserved
  • r-autotrue1 70b7e06 — a host integration may still be installed or selected outside the repository, so operators must ensure it supplies the session transcript before committing; the core cannot observe or enforce that host-side action
  • r-cdeb08an 60db89f — the paired bootstrap describes resampling stability within these five frozen repositories and thirty frozen tasks, and says nothing about any other repository, task or agent population
  • r-coldpath1 0412f81 — a genuinely cold fallback still reads the whole history once, because repository-wide lifecycle folding cannot be scoped to a path without changing what the answer means
  • r-coldpath1 0412f81 — nothing outside index and init builds the index now, so a repository whose derived file was deleted stays on the scan path until one of them is run
  • r-autoswitch b8497b8 — the prompt defaults to yes and a bare Enter takes it, so a reflexive Enter costs a team-wide consent — the file is committed, and every clone captures with nobody in the loop until someone runs auto off
  • r-unattshadow b7b532a — together the two features measure how often an unattended pipeline would have written, and remove the asking from the writing -- neither half can say whether what gets written is worth a reader's attention, so shadow's number for an unattended repository is a volume, not a value
  • r-unattended511 f6679e1 — with nobody in the loop, the pipeline decides on its own what is worth recording, and every record it keeps spends a future reader's attention without asking anyone first -- the switch is a repository consenting to that cost, and nothing in this change reduces it
  • r-retireserena c1171ef — the preregistration fixes claims before numbers exist, so what it says about the calibration cannot move to match later tree state
  • r-shadow511 d093bef — shadow measures commits whose transcripts are gone, so its numbers describe the substitution of a committed message and patch for a transcript -- they say nothing about what capture would record over a live session, and no shadow output may be read as a pipeline baseline
  • r-mcpexit506 f1b1fb0 — a process killed with SIGKILL still writes nothing, so the log shows a start with no exit -- that case is inferred from the absence of a line rather than reported, and stays the way MCP tools for commitlore vanish mid-session (ToolSearch returns zero results despite server reported connected) #424's original observation had to be made
  • r-demostory505 8016424 — the demo is one scenario, so it shows supersession and not expiry, path scope, or trust grading; a reader who wants those still has to read past the image
  • r-readmeorder 383f77d — the hook leads with the headline number, so a reader who stops there has the effect without the conditions on it; the section naming those conditions is now two screens up rather than at the end, which is a shorter path than before but still a path
  • r-pindigest c5a7cfa — pinning fixes the tree, not its behaviour -- a pinned action still runs with the job's token and can read this repository, and upgrading now requires deliberately resolving a new digest rather than inheriting one
  • r-leastpriv 7a27c82 — gate jobs still run mutable @v4 action references, so a changed action can read this repository's source and its own job's token -- what it can no longer do is create a release; pinning those actions by digest is a separate decision with its own upgrade cost
  • r-canonsha499 46ab656 — the window between the binding check and gh release create is not itself covered by a check -- the ruleset is what holds it closed, and a bypass actor added to that ruleset would reopen it silently
  • r-filters471 c7572f6 — a filtered run reports honestly about what it ran and says nothing about what it skipped, so a repository whose only failure sits outside the selection reads as healthy-for-the-selection with no hint that the unexamined rows exist
  • r-envelope469 0162b73installSource is declared and derived per surface only where a test asserts that surface; an unasserted surface reports unknown rather than a guess, so the field is honest and incomplete rather than complete and unverified
  • r-effects476 43eb4aaenv and the clock are injected but process.cwd and the filesystem are still reached for directly inside some checks, so a check that reads a path can be pointed at a fixture but not at a purely synthetic tree
  • r-headline470 55b810cinit keeps the checks-only renderer, so the headline a doctor run shows is absent from the install path where a first-time user is most likely to meet a finding
  • r-budget472 8ea15f1 — 13.2x headroom over the measured baseline is sized to survive a contended shared runner, so it catches an order-of-magnitude regression and would not notice doctor becoming three times slower
  • r-dsplit467 b24e371 — the split is by responsibility, not by dependency direction -- runner, registry and renderer all still reach into the model, which is correct for a shared vocabulary and would not catch a model that grew behaviour
  • r-pubprereq 8ffb31c — the gates qualify the commit and its CI, not the tag's authorship or timing -- anyone who can push a v* tag to a qualified commit on main can still publish, and expiring or re-pointing a tag after these jobs pass is outside what any of them observe
  • r-collapse466 d24a284 — only two edges are declared -- inject-version on inject-runtime, and the §2.2 checks on the capture chain -- so a dependency nobody wrote down still surfaces as an independent finding
  • r-m5sources b910dba — the seven shards are declared individually, so a shard added later is invisible to this block until someone lists it -- which is the property the declaration was built for and the cost that comes with it
  • r-evidence465 e1a3c92 — evidence answers what was observed, not whether the observation was the right one to make -- index ingests any key: value line as a trailer; doctor reports 106 records where git has 0, and context serves commit subjects to the agent #335's wrong count would now be visible in a field rather than prevented
  • r-queryroute 4ae1f6f — the sweep covered ?? against an options field in src/commands, which is the shape that produced both defects; an option whose default is consumed some other way would not have shown up
  • r-rel071 af8e0ab — 0.7.0 stays published with its notes amended to name the defect at the top; retracting a tag people may already have installed trades a known-bad version for an unknown one
  • r-rel070 d4a4d8b — the README's behaviour claim now rests on M5 while the generated numbers block beneath it still publishes M4, which is The README's generated numbers block still publishes M4; M5 measured the thing the README leads with #480 rather than a release-time edit
  • r-numgate b770054 — the README's behaviour claim and the generated block below it now describe different studies until The README's generated numbers block still publishes M4; M5 measured the thing the README leads with #480 lands
  • r-skipreason 85aa8d6 — the union has six members because ten sites needed six, and the next check that skips will need a seventh rather than one of these stretched to fit
  • r-readmem5 6d04c0b — the README now leads its behaviour claim with a [claim]-tier number while shipping a [directive] tier nobody has measured, and that gap will widen until something measures it
  • r-snapnode 2ef8745 — normalisation is a list of known-variable things, so the next machine-specific value to appear in a detail string will fail once before it is added
  • r-registry463 ddf5592 — the registry is data but nothing filters it yet, so the ordering guarantees are tested and unused until the --only ticket
  • r-checkmodel 9cbed57 — evidence is {} on every row until the ticket that populates it, so the field exists and proves nothing yet
  • r-doctorpend 458bcec — the check reads staleness, so a capture whose base commit is still HEAD reports ok even if it has been waiting long enough that nobody remembers preparing it
  • r-clog070 172fa3d — the entry stays under ## Unreleased and names no version, because the version bump belongs to the release commit and a changelog that pre-announces a number can be wrong about it
  • r-readmecold 08efdff — only README.md is reordered, so the ko, ja and zh-CN readers still meet the evidence first until the follow-up lands
  • r-selfaudit cd0068f — the page is maintained by hand, so an entry can go stale against the code it describes; the closing line says so and asks for an issue when it does
  • r-trust415 a030e93 — this changes what a fresh install delivers, so M1 and M5 remain measurements of [claim]-graded delivery and their numbers do not transfer to the directive path
  • r-cdebver01 ce7b278 — the schemas freeze protocol 1.2.0 constants -- thresholds, matrix size, category names -- so a protocol change is a schema change and CI notices
  • r-mcplife424 8cd3c6d — the tool registration that was lost belongs to the client, so nothing in this repository can detect the loss from inside a session or restore it
  • r-capmode30 40818c2stage cannot check consent, so auto records what is certainly true -- no prompt was shown -- instead of asserting what it cannot know
  • r-drafted30 b126176 — a commit message is immutable, so a drafted record is never upgraded in place -- promotion is a later record that Supersedes it, and that half is not in this change
  • r-plugupd1 bd4363b — the plugin cache belongs to the client, so nothing in this repository can update it or detect the version drift except by asking the running hook
  • r-amendid430 4c450ebcommit-msg gets no argument, environment variable or ref that distinguishes an amend from an ordinary commit
  • r-hookver433 a3b92d7 — nothing here can update anything -- the plugin cache is the client's, so the only move available is to say what is true
  • r-binx428 b9d1ea8 — the allowlist accepts .mjs/.js paths, which are exactly the files most likely to carry a shebang and no execute bit
  • r-recurse422 418734c — git runs pre-push on every push including one a pre-push hook makes, so any push from inside the hook must opt out of hooks explicitly
  • r-busy420 9555569 — a full rebuild on a large repository takes longer than any timeout a hook can afford to wait, so the scan fallback stays reachable by design
  • r-sync416 deb21d2 — git neither fetches nor pushes notes by default, so a mirror only moves when something configures or invokes it
  • r-mention408 ec314cd — paraphrase space is unbounded and this table cannot see semantic rewording, so blocked remains a speed bump and the grade remains the load-bearing control
  • r-schema406 22b2c35 — an index is derived state with no migration path, so a meaning change can only be handled by discarding the file
  • r-note409 0dff3e4 — the notes ref is an ordinary ref with no signature requirement, so authorship there is a claim about who wrote the text and not proof of it
  • r-backfillclosed 00de5fa — the guard reads the mirror state at invocation, so a fetch completing mid-run is not observed
  • r-initunfetched 889d191 — it reports the state as it was before init ran, so a mirror fetched between the capture and the report would be named wrongly; that window is the four steps of one command
  • r-mirrorunread 8e4bdc5 — the caveat can only fire where notesAvailability returns unfetched, so a refspec added after cloning and never fetched through still builds silently -- the distinction config alone cannot carry, recorded on that function under r-fetchowed
  • r-claimsmatch 506ada4 — this fixes the sentences an external reviewer found; no systematic pass was made over every claim in the four files against every published measurement
  • r-m5analysis 3450656 — the script enforces the row count, not the identity of the rows; a run that produced 1,160 rows under a changed harness would satisfy it, which is what harness_commit and dist_digest on each row are for
  • r-benchscope 67f4375 — nothing checks the shape of the eight metric-row files. This gate names them and steps over them, and bench/deterministic/types.ts is the only definition that family has -- there is no JSON schema for it, so drift on that side is still invisible
  • r-benchscope 67f4375 — the pre-provenance exemption reads started_at, which is data on the row rather than a fact about the file. A row that misreported it would be held to the shorter list of requirements; that is a deliberate falsification rather than the omission this fixes, and nothing here detects it
  • r-pinskew 007ccbf — the comparison reads the package.json above the recorded path rather than running it with --version, so a pin whose manifest and bundle disagree is reported by its manifest
  • r-priorart 507ae24 — the comparison is against Lore's README and its abstract; the full paper was not read, so a lifecycle described only in the PDF would have been missed
  • r-scaleproof 4c093f2 — the 100,000-commit figures come from a synthetic repository built by the deterministic harness, not from a real codebase of that size, so they describe the index's shape rather than any particular project
  • r-extbaseline 064daf6 — the band is four Python repositories chosen for having enough revert history to backfill from, so it is evidence about large long-lived Python projects rather than about repositories in general
  • r-3c9d52 dc9e769 — the sweep is two git log calls per path and the delivery phase runs git log --follow on every tracked path, so a full run over the four externals is hours rather than minutes on one machine
  • r-ledgerresult bc31c90 — both sides are byte-derived proxies under CHARS_PER_TOKEN=4 rather than a provider tokenizer, so the ratio cancels a uniform error and not a differential one between diff text and prose
  • r-ledgerresult bc31c90 — break-even in reads assumes reads land on the evaluation set the way the delivery run's per-path average describes, and real editing concentrates on a few files
  • r-surfacedeliv fae9e1e — every figure in the table is measured on this repository measuring itself, which is the weakest part of the evidence and is stated in the paragraph rather than left for a reader to discover
  • r-rel060 e999b9d — the install one-liner in all four READMEs now points at a tag that does not exist until this is tagged, so the window between merging to main and pushing v0.6.0 is one where the documented install is broken
  • r-pipesplit b4fa571 — test/dogfood.test.ts validates every record in this history, so a new violation class is only available if it rejects none of the 620 Ruled-out: values already written
  • r-gcunstageable 5cd6b8f — ADR-0021 fixes the pending format and stamps expires_at at stage only, so giving these phases an expiry earlier is a format change rather than a fix
  • r-gcunstageable 5cd6b8f — gc runs only when capture gc is invoked -- nothing schedules it, so a leaked file goes at the next run rather than at the 24-hour mark
  • r-gcunstageable 5cd6b8f — staleness is derived from base_head against HEAD; a transaction whose staged diff moved while HEAD did not is equally unstageable and is still kept, which is the conservative half of the same test
  • r-gcunstageable 5cd6b8f — a staged transaction that is never applied is still kept for ever -- the hook skips it once expires_at passes and gc protects the phase -- which is a separate leak this change deliberately does not touch
  • r-secondtie 998bf18 — committed_ts is %ct at one-second resolution and the index stores no ordinal that orders two commits inside one second, so a tie on that path can be made deterministic but never topological
  • r-dedupviol 18ad9c1 — the key includes line, so two detectors that locate one finding differently -- one with a line, one without -- would still print it twice; today both resolve the line through the same locateTrailerLines/lineForViolation path
  • r-readmesplit344 7314a03 — three checks bind content to a position in the README, so the complete record example, the protocol vocabulary table and the generated benchmark block could not move
  • r-owntmproot 6543870 — the demo still defaults to the shared tmpdir, so concurrent commitlore demo runs still create sibling directories there -- that is deliberate, and it is safe only because nothing now asserts over that namespace
  • r-diffdefault 4ac8163 — the test reads the option string out of the source rather than out of --help output, so a change to how commander renders descriptions would not be caught
  • r-shallowlast 0913821 — the spawn still happens once per validate that has a dangling ref, which is the case where the answer is actually needed
  • r-exitonemeans 89f7af8 — a shallow clone cannot tell a reference that resolves below the boundary from one that resolves nowhere, so neither verdict is available and the check can only name the question it could not answer
  • r-failopen abc54ea — with the gate installed and no CLI resolvable, commits are still refused -- that is the one hook holding a verdict back, and this change does not reach it
  • r-notereach 1e72a28 — reachability is decided against HEAD alone, so a record mirrored onto a live branch that is not checked out is not served until it is
  • r-heropolish f6144bc — README.ko.md still switches from 존댓말 to 해라체 below the hero; that split is older than this change and belongs to the restructure in README still carries the reference manual it should be linking to #344
  • r-pluginpath353 e364f3a — a plugin manifest has no way to add anything to PATH, so no plugin-side change can make the documented commands resolve
  • r-fetchowed 11f04b4 — config alone cannot separate a refspec that was fetched through from one that was only written, so the availability verdict cannot carry that distinction
  • r-guarddisclose 8a4d0c7 — a disclosure asserted by tool name covers the tool that is named, and the ADR's requirement is about every surface that exposes the behaviour
  • r-realoutput f9efea0 — a README block introduced as what the tool prints is a behavioural claim, and inventing its shape is the same defect as inventing a number
  • r-refspecfetch 936d206 — configuring a refspec is not fetching through it, and a state machine that conflates the two turns its own remedy into a way of hiding the problem
  • r-actionsleak a6fbb4b — a code path that no test and no first-party workflow exercises is the one an outside adopter takes by default, and its absence from CI is not evidence it is unused
  • r-actionsleak a6fbb4b — a trust label the caller must act on is worthless unless the value it describes is actually withheld at the point the data is built
  • r-release051 19810d2 — the hook is written at install time, so no release repairs a repository that already has one; every release touching hook behaviour has to restate what does
  • r-heroinherit 89b13ac — a headline that implies detection commits the product to guard's numbers, and guard is an advisory measured at 22% recall
  • r-convertreadme e12c816 — a README claim about the default workflow is only true if the shipped skill performs it, and the skill currently requires the user to name CommitLore first
  • r-fieldreport 753f4e7 — this section reports one engineer's day on one repository; it is evidence that the mechanism works there, not a measured effect size, and the wording has to keep those apart
  • r-readmefinal 40aeae0 — a mutation oracle anchored on a claim that can become false will silently stop testing when the claim is removed; the needle has to be asserted present
  • r-recordgate335 a83ebe3 — a denylist cannot decide whether something is a record, because the keys nobody has claimed are unbounded; that question needs the vocabulary, and the two must not be answered by one filter
  • r-recordgate335 a83ebe3Verified: in a release note is indistinguishable from Verified: in a record, and no context signal separates them without risking real records
  • r-draftfirst329 0506a5d — a usage error that names the wrong input costs an invocation and points the reader away from the fault; ordering is part of the message
  • r-release050 ad402c7 — the hook is written at install time, so a corrected release never reaches a repository that already has one; every release fixing hook behaviour has to say what repairs an existing install
  • r-uninstall1123 4ddac0d — the installers write five agent configs, not the four the ticket's measured inventory lists; the fifth is Windsurf at .codeium/windsurf/mcp_config.json
  • r-uninstall1123 4ddac0d — opencode's entry is shaped differently from the other three -- the command is an array -- so one recogniser cannot serve all of them
  • r-uninstall1123 4ddac0d — a checkout is 1366 files at this head, not the 1206 the ticket measured at 6e1d46d; any assertion bound to that count is stale
  • r-winsupported1124 6333251 — repositories that installed the hook before Windows: the commit-msg hook hangs instead of returning, and #71's containment can never match there #321 keep the old stub and must re-run commitlore hooks install; a corrected release does not reach them, and this row's claim is about a working install
  • r-winderive1124 282693b — a diagnostic that retypes the code it describes goes stale silently, because nothing fails when the two drift apart -- it has to be read out of the artifact that ships
  • r-winbound1124 156deed — an unbounded hang is not evidence; it is a job that dies at the runner timeout having printed nothing, so every hook-invoking commit here has to carry its own bound
  • r-winassert1124 616005d — an assertion whose only oracle is an absent side effect cannot distinguish "refused" from "the mechanism never worked", so each one needs a positive control that fires before the attack
  • r-winshell1124 aa68a9a — a GitHub Actions bash step is invoked as bash -eo pipefail, so set -uo pipefail inside the step does not clear -e and any bare command that fails ends the step at that line
  • r-winpath1127 bdf4ac0 — the stub is written to .git/hooks at install time, so a repository installed before this fix keeps the old text and must re-run commitlore hooks install; installing a corrected release is not enough
  • r-winpath1127 bdf4ac0${dir%/*} returns its input unchanged when no separator remains, so a loop that tests for emptiness never terminates at a drive root
  • r-winpath1127 bdf4ac0 — neither dirname nor ${var%/*} finds a parent in a backslash-separated path; both answer .
  • r-compat1122 e7d8516 — a non-empty guard does not detect deletion; each table's row keys have to be asserted as a set or the statement can silently shrink to one row
  • r-compat1122 e7d8516 — substring comparison hides a narrowing -- ./ is inside ../ and Edit|Write is inside Edit|Write|MultiEdit|NotebookEdit -- so cells are compared as their rendered form
  • r-compat1122 e7d8516 — a sentinel containing \0 makes git treat the file as binary, which costs it diff, blame and log -p permanently
  • r-compat1122 e7d8516 — the plugin path needs bash, because scripts/commitlore-run.sh carries a #!/bin/bash shebang, and no install script checks for it
  • r-muslbullet1126 04ac181 — this ticket owns four bullets and not the tests that read the section around them, so a check that breaks here means a region was taken that was not allocated
  • r-ps1scope282 72f23df — the hook-runtime probe spawns /bin/sh, so it cannot report on a Windows hook at all until T-1124 changes it
  • r-ps1stderr282 e37b4d3 — Windows PowerShell 5.1 turns a native command's stderr into a terminating error under $ErrorActionPreference = Stop, so no native call in this script may merge stderr into its output
  • r-ps1shell282 97735d6 — a step's shell key takes no expression, so a per-host matrix cannot select the interpreter
  • r-t1120nodeinst 14deeb4 — git and node are hard prerequisites now, so a host without them installs nothing and says which one is missing
  • r-t1110policy 9e7b37a — only a repository-local policy file is read -- PRD-F13 requirement 11 permits either one location or a stated precedence, and an ambiguous precedence is worse than a missing feature
  • r-gateb3rev a2e38b9 — the shipped install.sh downloads a platform asset, so no document may describe it as Node-only until the installer itself changes
  • r-rel041notes 71efe1f — 0.4.1 makes the installer honest about a verification it cannot complete rather than fixing the kill, so an upgrading user may still see the unverified message instead of a version
  • r-instverify256 3715677 — the root cause of the signal kill is unestablished; this makes the installer honest about it rather than fixing it, and Documented install exits 137 on upgrade: a killed verification turns a successful install into a failure #256 stays open for the cause
  • r-rel040pins b76c40b — the pin names a tag that does not exist until the tag is pushed; between this merge and that push the documented command refers forward
  • r-rel040notes 5d57a72 — the 26.3-point density gap quoted in the notes is measured at this head and will drift with merge volume; it is illustrative of the denominator problem rather than a stable figure
  • r-gcwiring f21f28e — the guard against this class is four CLI-level tests; nothing structurally prevents a future subcommand from colliding with a parent option again
  • r-flake221fix 2b21ed9 — checkInjectRuntime ENOENT does not block init
  • r-lb0xl89a 236229e — the static contract uses explicit placeholder text for TRANSCRIPT and DIFF rather than omitting those sections, because the prompt text references them by name
  • r-c44a1edb 71f5197 — src/core/pending-gc.ts -- gc must never remove a staged or applied file regardless of expiry; T-1018 post-commit may still finalise them
  • r-0ll5sxk0 2853a22 — consumption happens after commit succeeds, exactly once; consuming earlier loses the record on failed commits, consuming twice lets one record attach to two commits
  • r-t1009stage b5fcf4e — the nonce pattern check bounds what a caller can send, but a caller holding a valid nonce for its own repository can stage repeatedly until the record is consumed
  • r-t1005gates 15421c0 — policy identity is compared as a hash, so a policy edit that produces the same hash is indistinguishable from no edit
  • r-t1016svg 321c6f1 — byte-exactness is verified on this platform; a different platform's Node could in principle render differently, and nothing here proves it does not
  • r-t1006cli d22580b — the command composes the phases in one process, so a crash between verify and stage leaves a verified pending record that only garbage collection will clean up
  • r-t1008mcp ab00b54 — src/mcp/server.ts: readOnlyHint must be false for verify_capture — the tool writes verification results to the pending transaction
  • r-t1007mcp b6ef112 — commitlore_prepare_capture uses readOnlyHint: false because it writes a pending transaction
  • r-t1003verify7b 5e9f96b — Verification failure must never block a commit | the verify phase returns empty on any failure rather than throwing, because blocking a commit on an optional enrichment step is the feature people disable (ADR-0006)
  • r-t1013verbose205 294ec82 — --verbose only selects the formatter; it does not change runInit logic, step order, exit codes, or --json output
  • r-t1022sig e0c641d — the first pushed attempt asserted one header string in the test and built another in the formatter; CI caught the mismatch and the formatter was aligned to the asserted string, which is the one that states the measured figures on the output surface
  • r-t1022sig e0c641d — focused-test evidence for this change is CI's, not local; test/guard.test.ts reports zero tests and stalls on this machine at dev with no changes applied
  • r-t1024bc 023f6d9 — response shape is exactly five fields per CEO amendments and ADR-0020 confidence-separation constraint | adding a sixth field or letting context inherit guard_confidence violates the acceptance criteria
  • r-t1021known 8dfffc1 — the figures are measured against one archived 417-decision corpus, which is deliberately hard and is not deployment prevalence
  • r-t1011demo 1c0fc0c — the scene is one fixed pair of decisions, so it demonstrates the mechanism rather than measuring how often it matters
  • r-t1020desc dd12b42 — the test asserts on the exact precision and recall figures; a future re-measurement changes both the description and the test
  • r-t1020desc dd12b42 — the first attempt's Record-Id used hyphens, which the r-[a-z0-9]{6,} format rejects; both the lint action and the dogfood test caught it
  • r-initresult204 ea4a08e — --verbose flag not wired yet (T-1013)
  • r-t1030diag 344ada0 — the heuristic uses a regex on the first line of stderr; an error that prints no stack frame and no "not found" string will be reported as cause unclear even if a human could classify it
  • r-pin030readme 504b54e — install.sh must already support tag-based download for the one-liner to work; verified that the URL resolves to a tagged tree
  • r-fix191amb cb94448 — the same-message test still passes by accident of collectRecords returning one record per commit; the divergent-notes test is what exercises the actual suppression path
  • r-fix187val 40f2436 — the tip-scan adds one full-history git-log call per range invocation; acceptable for a lint-time check but visible in benchmarks at scale
  • r-notes030 a289ca5 — the density denominator is named here and in the handoff, not in the harness that emits it, so the next run reproduces the same ambiguity
  • r-hero172a bc0d971 — Stale-exposure benchmark is one corpus, one query, and one pinned embedding model at a fixed two-record budget
  • r-dupsucceed 6f77fcf — supersession is resolved within one repository's history, so a record superseded in a fork that was never merged still grades as current here
  • r-dupsuccorder f46c02d — a successor before a later duplicate cannot resolve that later collision
  • r-dupsucc729 5a6b238 — published dev history cannot be rewritten
  • r-valdup145 bcb9563 — the same-message check sees only the message, so two commits each declaring the same id separately are still caught by the reference check rather than here
  • r-convtrail150 57e89d2 — the denylist answers a different question from isRecordKey's allowlist, so a conventional trailer this protocol later claims would need removing from one and adding to the other
  • r-epipe2026 d9ee9ff — spawnSync may report EPIPE after git exits while its input pipe is being written
  • r-doctorepipe 0420f5c — the new deterministic tests exercise evaluateInjectRun with a synthetic spawnSync result rather than forcing the live race, because no payload this check sends is large enough to make the write block deterministically the way an artificially large one does in the reproduction above
  • r-init107 f485f07 — the generated dist artifacts are rebuilt from TypeScript source
  • r-survsplit e73aed5 — path-reachability is measured against git's rename detection, so the figure moves with git's similarity threshold rather than with anything here
  • r-be140cost 8c01bd5 — no per-turn provider token ledger or observed avoided-work cost exists yet
  • r-probepath 51f6446 — the probe still only runs a command it recognises, so a hand-edited but equivalent hook reports not-checked rather than a verdict
  • r-readme129 ab5f210 — the break-even rests on tokens estimated from bytes at the product's own four-characters-per-token constant, so it moves with that assumption
  • r-doctorprobe ed94491 — the probe runs only a command it recognises, so a user who hand-edits the hook into an equivalent but different form gets not-checked rather than a verdict
  • r-m4basis 5e2d2cb — the guard question stays unanswered until the exposure instrument is verified and M4 is rerun on it
  • r-m4withdraw e5f9b73 — the guard question is now unanswered rather than answered null
  • r-instpath119 9e1fce7 — a user who ignores the printed line still gets "not found" on the next command
  • r-readmeux1 b664205 — interactive record building does not exist, so the honest answer is still "an agent writes it or you do"
  • r-rel021a a79e350 — v0.2.0 remains on the remote with no release attached
  • r-expreadme1 9e69abe — bench/VERDICT-M4.md still cites the Fisher figure; the two disagree until the verdict records why the number was withdrawn from the README
  • r-expomerge1 d6ad014 — M4's existing rows have no exposure field and must read as unknown, not as not-exposed — backfilling by inference would erase the finding
  • r-f61a2c 9114cf0 — the matcher remains deterministic and lexical; no embedding or semantic service is available to distinguish paraphrases
  • r-rdme96a 9c9371c — scripts/check-readme-numbers.mjs's withdrawal-notice and stray-statistic checks constrain what can appear outside the (absent, here) generated benchmark block — re-checked after every edit, not just at the end
  • r-init96a 913c7e3 — doctor's own exit-code contract treats warn as non-fatal by design (SPEC §10, commitlore-setup skill) — init deliberately diverges from it for its own summary, and that divergence is the one thing most likely to look like a bug on a future read of this diff
  • r-fix92dupid 7f41a6e — cross-references between two blocks declared by the same commit (a Follows:/Supersedes: naming a sibling block's id) are still reported as dangling rather than resolved against the sibling -- unchanged from before this fix, and called out in validate.ts's own comment as future work
  • r-fix93pkg 9c4a396 — package.json remains a development artifact (build, typecheck, dependency floor) -- it is not read as a distribution manifest by anything in this repository
  • r-relinstall c6e1d04 — never tested against the real GitHub release infrastructure (no release exists yet — that is the owner's action) — verified against a locally built SEA binary, a hand-made SHA256SUMS, and a local HTTP server standing in for GitHub's release-asset redirects, which is everything this repository lets a change verify before a tag exists.
  • r-distrace88 d118a73 — the fix insulates bench-ablation.test.ts from the race; it does not remove the underlying design (four test files independently, redundantly rebuilding one shared dist/ in their own beforeAll). A fifth file doing the same thing, or a future check elsewhere that also depends on dist/'s mid-run stability, can still race the same way.
  • r-parsemulti 6d39d25parse has no git-commit context (no sha, no notes mirror) — its identityCollision check is local to the one message being parsed and cannot detect a Record-Id that collides with something already committed elsewhere in history the way context's fold does.
  • r-multirec01 92aeb24 — parseRecordBlocks only recognizes a non-final block by its declared Record-Id, so an unidentified inherited record beyond the first stays recoverable in the plan that computed it but not in a later re-parse of stored text; squash-preserve orders unidentified blocks last so the common case (at most one) is unaffected.
  • r-multirec01 92aeb24 — multi-block reference checking (Follows:/Supersedes:) does not resolve one block's reference against a sibling block declared by the same commit; each block is still checked against every earlier commit in history.
  • r-exit065 e545dee — any new command's exit codes must be drawn from SPEC §10, not invented locally
  • r-fix70a1 d707fc7 — one encoding layer and explicit lexical forms in the four published languages; semantic paraphrases, nested encodings, and split payloads remain outside coverage
  • r-shwt66 5efa206 — git rev-parse --git-path may return a repository-relative path, so resolve it against cwd
  • r-merge66 40e7987 — Generated dist files were resolved only by npm run build and npm run bundle
  • r-fix760 fb8ba45 — Git remains the authority on trailer recognition; diagnostics must not loosen the parser
  • r-refint74 572f573 — validate cannot perform conservation checks because it has no before state
  • r-warn75 24c7cc8 — exit-code semantics remain owned by guard's exit 2 means blocked; everywhere else in the same CLI exit 2 means bad usage #65
  • r-shallow66 60a8659 — a depth-1 clone can only inspect its reachable commit history
  • r-doctor72 996bcde — generated dist artifacts must come from npm run build and npm run bundle, not a hand merge
  • r-fix067 a915af0 — PreToolUse hook failures must always exit 0 and never change stdout's hookSpecificOutput contract
  • r-fix063 0b8c496 — doctor performs remote probes; an unreachable remote reports could not verify instead of ok
  • r-det058 695cdf6 — the suite must need no model, agent, network or uncommitted benchmark input
  • r-fix053 ecc4b90 — QueryResult.notes remains repository-level availability and is independent from whether one record was mirrored
  • r-fix055 43b40f8 — harvest-verify makes no model call, so semantic entailment is outside its contract
  • r-fix054 664d4e2 — notes-only metadata must survive folding; a mirror is one record, not two
  • r-fix056 55cb8bc — blocked output may retain only validated structural values that cannot carry prose
  • r-7a3e91 cf859e4 — better-sqlite3 stays external because it is native — the bundle degrades to --no-index without it, which only works because r-6f2a08 made that load lazy first
  • r-6f2a08 4c2d432 — esbuild cannot follow createRequire(import.meta.url)('x'), so any dependency reached that way stays external no matter what the bundle config says
  • r-9c07e2 9c4d25a — the plugin still needs Node for the CLI — the protocol does not, but guard, the index and the MCP server do (T-706 · Bundle the CLI as a single file — run from a clone alone #38)
  • r-3b8f52 1f8b4be — the figure is one machine, one run — it establishes the order of magnitude, not a regression baseline anyone should tune against
  • r-9c2f74 d653153 — the ablation arms cannot discriminate on these fixtures -- no-grade and no-lifecycle are byte-identical to the treatment in 9 of 10 tasks, because the seeds carry one reconstructed record and one task with a lifecycle trailer between them
  • r-9c2f74 d653153 — the harness assembles its own projection rather than calling the shipped injector, so what is measured is the harness's rendering of the records, not src/core/inject.ts (issue B-08 · Replace the benchmark harness injector with the actual src/core/inject.ts #36)
  • r-4a8e15 49e12c7 — git's grammar requires a subject before a trailer block, so a serialized block is not by itself a parseable message
  • r-6e1a72 5e09846npx commitlore is the first thing a reader will try, and it fails until the package is published
  • r-0c5d38 aeb54a6 — the suite runs against the source tree, so no test in it can observe what packaging drops
  • r-4e9c72 a7a7e26 — the index is derived, so nothing about its state can make the tool give a wrong answer -- only a slower one
  • r-6f2e58 ea9ae6d — a library test and a binary test cover different failures, and the packaging layer between them is exactly where a working module becomes a broken install
  • r-1b7d94 736ef92 — git reads ambient configuration, so a suite that does not neutralise it is testing the developer's machine as much as the code
  • r-7e5f02 e5f5e00 — npm installs through an engine mismatch, so the ecosystem's own signal cannot be relied on to stop anything
  • r-3a9d68 6a3fc3b — a test runner reports what ran, and nothing in its summary distinguishes "did not run" from "does not exist"
  • r-9a5e17 6d68703 — five workers on one repository share npm test and tsc, so file ownership alone does not prevent one worker from "fixing" another's half-written code -- verification scope had to be split too
  • r-8e2d51 ef93c0e — git is the parser, so a git version that folds or bounds trailers differently is a correctness problem for us, not a compatibility footnote
  • r-7f0e39 76f3f2d — literal substitution only catches the exact strings you list, so the same term written with a different separator survives
  • r-5a8c04 c46a577 — git owns the definition of a trailer block, so any behavior we cannot get from interpret-trailers is behavior we must not invent
  • r-9d31b7 4ac6e30 — the example lives in four translated files, so any fix that is not mechanically enforced will drift again on the next edit
  • r-c0f4e2 3d249cd — npm gitlore is held by an active same-domain CLI, so the owner's first-choice name was not available
  • r-b2e7f1 00d348d — Parsing must delegate to git interpret-trailers -- reimplementing the block rules would drift from the rest of the git ecosystem
  • r-a8f3c1 ef48843 — Rename must land before any code exists -- after 27 tickets it would touch spec, fixtures, index, hooks and every doc

Ruled out (547)

  • r-engfloor01 fe83524 — adding semver as a dependency to parse this | one regex over a handful of published shapes does not justify a runtime dependency in a check that runs before install
  • r-mcpproc01 db1363d — keeping AGENTS.md as the default carrier | it reaches only repositories that adopt the convention, and it puts a hundred lines of protocol into a file the repository owns and commits
  • r-ownsemver1 fcc6e4a — keeping the semver rule and warning instead | the failure is destructive and silent, and a warning printed after the file is gone is not a warning
  • r-hookbudget1 e09014c — building the index from the hook | that is the unbounded rebuild the recorded design gives to index and init, and doing it on the edit path would trade a bounded pause for an unbounded one
  • r-scanall1 62a6fbf — keeping validateRecord and bundling ajv into the hook path | the hot path should not grow a dependency to answer a question a regex already answers
  • r-vbind001 2c88d24 — deleting a checkout that fails the version check | a directory the installer cannot identify may not be its own, and refusing costs an operator one command while destroying it may cost them something unrecoverable
  • r-structk1 a7bee10 — validating whole records at read time and discarding those that fail | history is not editable, and a reader that drops malformed records would silently lose real decisions instead of grading them cautiously
  • r-ownfail01 8de1326 — treating a malformed value as an error that stops the command | grading runs on the edit path, and refusing to answer there costs more than answering under the stricter rule and saying so
  • r-expwall01 e7ddd92 — documenting that expiry follows repository time | it is honest and it abandons the property, and the property is the reason the field exists
  • r-insttxn1 afb7bfb — treating "verification could not run" as fatal | it fails a good install over a missing optional tool, which is the failure the non-fatal policy was introduced to stop
  • r-authdir01 ae2a66f — requiring signatures by default | it would demote every existing repository's records for a risk this project is not currently exposed to, and a silent capability removal on upgrade is its own kind of dishonesty
  • r-authdir01 ae2a66f — deleting the directive tier to make the claims true | the tier is how a record says it is a constraint, and removing it would resolve the wording by removing the feature
  • r-mcpdir01 a9886b5 — having runQuery read the trusted authors itself | it would fix these two call sites and silently change every other one, including tests that mean to grade without trust
  • r-relfix01 96102b4 — keeping "floor, measured" and footnoting it | the sentence is the claim a reader takes away, and a footnote that contradicts it is worse than either alone
  • r-codexreg1 5933aa4 — replacing any entry named commitlore | a user may run their own server under that name, and taking it because the name matched is the failure this fix is about, pointed the other way
  • r-readme001 7f82d47 — deleting the evidence and audit material to shorten the page | the willingness to publish unflattering results is the asset, and shortening by removing it would trade the strongest thing here for a faster read
  • r-hermesx01 2eb8176 — writing the bundle into the profile directory and regenerating its manifest | it would be undone by the next sync and disagree with the manifest until then
  • r-codexwire 955f290 — a Codex-specific integration | the instruction surface is shared, and writing one integration per host would leave the same gap open for the next five
  • r-initmcp1 e601ad3 — writing into a user's host configuration from init | a repository may describe itself, but reaching into the machine that opened it is not the same act and not one an init should take unasked
  • r-mintid01 1e5f500 — random or clock-based identities | a retried capture would give one decision a different identity
  • r-notes512a ce937c9 — probing the remote from the query path | context runs before every edit and an edit must not wait on a network round trip
  • r-notes512a ce937c9 — treating a covering refspec as evidence the remote was consulted | the refspec says what this clone would fetch, never what a remote has
  • r-autotrue1 70b7e06 — initiating capture from a Git hook with the staged diff | a diff cannot supply the host transcript or establish that a decision was made
  • r-cdeb08an 60db89f — discovering row files under the result directory | an unregistered file contaminates the matrix while leaving every stopping rule looking satisfied
  • r-cdeb08an 60db89f — filling or dropping unavailable usage | both change a token aggregate without evidence, one by inventing a number and one by redefining the population
  • r-coldpath1 0412f81 — applying --limit before the lifecycle fold | it would bound the cold work and silently change which records survive supersession
  • r-coldpath1 0412f81 — serving a stale index when catching it up is not possible | a fast wrong answer is worse here than a slow right one; the fallback stays fail-closed and git remains the authority
  • r-autoswitch b8497b8 — enabling by default where no terminal can answer | the file is committed, so a CI run would hand itself a team-wide flip nobody ever saw asked
  • r-autoswitch b8497b8 — rewriting a policy file whose effective setting already matches | the bytes would change while the policy did not, and the identity hash would report a policy change that never happened — the false positive the hash exists to avoid
  • r-autoswitch b8497b8 — asking the question when a policy file already exists | the answer cannot change anything, and a yes that does nothing reads as consent being taken rather than given
  • r-unattshadow b7b532a — keeping the unattended branch's inline prepare body | the shadow refactor exists so both entry points share one side-effect-free half, and two copies of the same hashing and policy logic would drift the first time either changed
  • r-unattshadow b7b532a — checking unattended consent in the live path only | the refusal for mode "off" already lives in the shared half of prepare, and a consent check that guards one door but not the other is no guard for the next entry point added
  • r-unattended511 f6679e1 — putting unattended into the default policy identity hash | the default is a fixed false, and hashing it would refuse every capture in flight across the upgrade in every repository that never opted in -- a policy change that never happened, the exact false positive the hash exists to avoid
  • r-unattended511 f6679e1 — checking consent at stage instead of prepare | stage receives a nonce and nothing else by design, and cannot observe whether a declaration was made; consent checked nowhere it can be observed is checked nowhere
  • r-unattended511 f6679e1 — ignoring "unattended": true outside auto mode | a consent the mode cannot honour would become a silent no-op, and a user who believes a setting applied is worse than one told it did not
  • r-retireserena c1171ef — rewriting the evidence documents to match the retirement | the transcripts and the preregistration report what the recorded runs saw, and a published claim the evidence does not support is the defect class docs/SELF-AUDIT.md exists to catalogue
  • r-retireserena c1171ef — keeping the ignore entry and the test guard as cheap insurance | the record that justified them said the directory comes back while the tool runs; it no longer runs, and a guard for a tool nobody uses guards nothing
  • r-retireserena c1171ef — editing r-strayserena in place | a record lives in the commit that declared it; retirement is a new record that names the old one
  • r-shadow511 d093bef — quoting the historical-run numbers | they measure a committed message substituted for a missing transcript, and a number from the wrong instrument becomes a baseline the moment anyone repeats it
  • r-shadow511 d093bef — backfilling records from shadow's output | the draft is an approximation no agent judgment ever stood behind, and publishing it as lore would launder a substitution into the thing lore exists to prevent
  • r-shadow511 d093bef — deleting the instrument because its first question failed | the failure belongs to history's missing transcript, not to the pipeline, and a live session supplies what the first run could not
  • r-mcpexit506 f1b1fb0 — adding reconnect logic | the client owns reconnection and stdio transports are documented as not auto-reconnected; this makes the ending legible rather than pretending to prevent it
  • r-mcpexit506 f1b1fb0 — writing the cause to stdout where a client would see it | that stream carries the protocol, and a diagnostic on it corrupts the thing being diagnosed
  • r-demostory505 8016424 — keeping the cache scenario and rewriting the README paragraph to match it | the pricing example is the one that names a cost a reader has paid, and the image should follow the argument rather than the argument follow the image
  • r-demostory505 8016424 — hand-editing the SVG to say pricing | the recording would then be a drawing of output the command does not produce
  • r-readmeorder 383f77d — cutting the positioning prose rather than moving it | it answers the second question a reader has, and a document that only shows the example leaves them without the frame for it
  • r-readmeorder 383f77d — keeping "Known limitations" as the heading to avoid touching the test | the heading is the reason the section went unread, and a test that pins a name is meant to keep the disclosure honest rather than to keep the name
  • r-pindigest c5a7cfa — upgrading to the latest major while pinning | two changes in one, and a version bump that arrives inside a security fix gets reviewed as a security fix
  • r-pindigest c5a7cfa — a dependency bot to keep digests current | the pin is what removes silent movement, and a bot that updates it automatically restores the property being removed
  • r-leastpriv 7a27c82 — pinning the actions to digests in this change | it is the right move and it is not this defect, and bundling it would put an unrelated upgrade burden inside a release blocker
  • r-leastpriv 7a27c82 — keeping id-token/attestations for the attestation step someone may add later | the step is not here, and a permission waiting for a future caller is available to every present one
  • r-canonsha499 46ab656 — anchoring the resolver on the live tag and relying on the ruleset alone | a ruleset is repository state that can be edited, and a gate that is correct only while a setting holds is a setting, not a gate
  • r-canonsha499 46ab656 — comparing the tag object sha returned by a single ls-remote pattern | every annotated release would be refused, and the first fix for that would likely have been to trust the name again
  • r-filters471 c7572f6 — running every check and filtering the report | the cost and the repository access would remain, and the two would be indistinguishable from outside
  • r-filters471 c7572f6 — reporting an empty run for an unknown selection | zero rows and exit 0 is a command claiming it looked and found nothing wrong
  • r-envelope469 0162b73 — exiting 3 for degraded | that code means "ran but could not see everything" for every other command, and a doctor-private meaning would make the number mean two things
  • r-envelope469 0162b73 — deriving status inside each check | the invariant would then hold only as long as every future check remembered it, which is how the defect this milestone exists to fix was written in the first place
  • r-effects476 43eb4aa — migrating the checks in batches across several changes | a partially injected registry is the state the ticket names as worse than not starting, and it would have been the shipping state between batches
  • r-effects476 43eb4aa — defaulting the effects inside each check rather than in one context | thirteen defaults is thirteen places for the real implementation to leak back in, and nothing would report it
  • r-headline470 55b810c — adding the header to init as well | init's result-line budget is a separate frozen contract, and widening it inside this ticket would move it without its own decision
  • r-headline470 55b810c — capping the fix plan at the first few entries | a cap hides findings, and the two mechanisms that shorten this report -- collapse and dedup -- both do it without dropping one
  • r-budget472 8ea15f1 — lowering the cap now that truncation is visible | the cap is a separate decision with its own evidence, and changing it inside the ticket that made it observable would spend that evidence before anyone read it
  • r-budget472 8ea15f1 — raising status to warn when the scan truncates | this is disclosure, not a new verdict; a warn here would report a finding about the checker rather than about the repository
  • r-dsplit467 b24e371 — splitting the checks by category into subdirectories | the registry is a flat ordered list and a nested tree would suggest a grouping the emission order does not have
  • r-dsplit467 b24e371 — deleting the shim and updating every import | the path is what callers and tests already reference, and a move that also rewrites its callers cannot be verified as a move
  • r-pubprereq 8ffb31c — expressing either check as an if: on publish | a condition in YAML has no test, and a release gate whose logic cannot fail in a suite is a claim rather than a control
  • r-pubprereq 8ffb31c — treating a missing required check as nothing to report | that is the empty-set inversion above, and it is exactly how a release with no CI at all would have published
  • r-collapse466 d24a284 — declaring commit-msg-hook on hook-runtime to complete the graph | that edge runs backwards against registry order, and a declared edge the emission order cannot satisfy is a lie in the structure the fix plan walks
  • r-collapse466 d24a284 — omitting a blocked row from the report and keeping it only in the JSON | the text report is what a user reads, and a row missing from it is indistinguishable from a check that was never run
  • r-m5sources b910dba — globbing bench/results for m5-*.jsonl | the file next to them is a withdrawn design log, and a glob is how the wrong dataset gets published without anyone deciding to
  • r-m5sources b910dba — reporting only the 1,160 and dropping the row count | the rows are on disk and a reader who counts them would find the block understating; naming both and the reason is what makes either checkable

Truncated: 629 lines omitted — the comment hit GitHub's 65000 character limit.

Trailer violations fail this check. Active constraints are informational — they are what the repository already decided, not a verdict on this PR.

`unverifiable` warned and carried on to `codex plugin add commitlore@commitlore`
— which installs whatever that marketplace serves. A marketplace somebody else
had configured under this name, on a Codex that does not report marketplace
sources, could therefore supply the plugin while the install reported
CommitLore.

Refusing every unverifiable case would have been the wrong repair: it also
breaks an older Codex where the marketplace is genuinely ours and only the
source field is missing. Presence is what decides. A marketplace this install
adds itself is ours by construction; one that already exists under our name and
cannot be identified is the case worth refusing, and those are distinguishable
even when the output is a table this cannot parse — the name is still in it.

So `unverifiable` splits. Present and unidentifiable is refused with the same
reasoning as `foreign`, and names the two ways out. Absent is noted and the
install proceeds to add ours, because the question of whose it is does not
arise.

Limit: presence is read from the marketplace name, so a Codex that reports neither a source nor a listing this can parse is treated as absent and one is added under a name that may already be taken
Blast: module
Undo: easy
Certainty: firm
Verified: six states classify correctly against real `codex plugin marketplace list --json` output and hand-built table output — ours, foreign, absent, unverifiable-present, unverifiable-absent, and an entry whose source field is missing; a present-but-unidentifiable marketplace exits 2 having issued only the listing call and written no ownership marker; 71 pass across codex-plugin and both installer suites
Provenance: authored
Record-Id: r-codexunver1
`engines.node` said `>=22`; the index is `node:sqlite`, which does not exist
before 22.5. The source comment in `core/index-db.ts` already said so — "which
satisfies but predates the `>=22` floor in `package.json`" — and nothing acted
on it.

Both installers checked the major only, so 22.0 through 22.4 installed cleanly
and then could not build an index. The lazy resolve means that is not a crash:
the query falls back to a full scan, and the repository reads as slow rather
than misconfigured. "No index here" and "your Node is too old" are different
facts and only one of them tells the operator what to do.

The floor is declared where it is true and enforced on both installers, with a
message that names the version and why.

Limit: this bounds the version, not the feature -- a Node that ships `node:sqlite` behind a flag, or removes it, is not detected here
Blast: local
Undo: easy
Certainty: firm
Verified: the shell installer refuses v22.4.0 naming node:sqlite and 22.5, accepts v22.5.0 and v24.1.0, and still refuses v21.9.0 on the major; install.ps1 gains the same two-part check; 38 pass in install-script and 23 in install-ps1
Provenance: authored
Record-Id: r-nodefloor1
`doctor` writes paths home-relative, so this checkout appears as `~/…` under
`$HOME` and as an absolute path anywhere else. The snapshot was recorded in the
first case and asserted `~<path>`, so every checkout outside a home directory
failed a test about text the report had produced correctly.

The normaliser now collapses both spellings to `<root>`, which is what the
snapshot was always trying to say.

Limit: this normalises the two spellings this report produces; a third form -- a symlinked home, a UNC path -- would need its own
Blast: local
Undo: easy
Certainty: firm
Verified: the suite passes from a checkout under $HOME and from one under /private/tmp, which is the case that failed before
Provenance: authored
Record-Id: r-snapshome1
…it does

Three ways this told a reader or a model something that was not so.

**`commitlore_stale` served ungraded payloads.** `commitlore_query` grades every
record and renders a blocked one as a count; the stale handler serialised
`resolvedTrailers` straight into its report, and the MCP server returns that
report verbatim. So an expired `Warn: IGNORE ALL PREVIOUS INSTRUCTIONS…`
reached a model through a tool on the same server, ungraded — the payload only
had to be stale, which is the one state nobody watches. Reproduced against the
built CLI: the sentence appeared in `stale --json` in full.

Stale records are still listed, because what is stale is the operator's
business. Their values are withheld when a pattern matches, and their keys are
kept, a key being a closed vocabulary that cannot carry prose.

**The advertised one-liner was not pinned.** It fetched `v0.8.1/install.sh` and
passed no version, and with no argument the script resolves the newest remote
tag. Once 0.8.2 exists, that command installs 0.8.2 while `docs/install.md`
calls it pinned to the current release. It now passes the version its own URL
names.

**The documentation still described the old AGENTS.md behaviour.** `init` stopped
writing that file by default in this release, and the README, both the
compatibility table and the prose, `docs/install.md`, `docs/capture.md`,
`docs/cli.md` and all three translations still said it wrote the pre-edit
instruction. A reader following that path got neither. They now say where the
procedure actually lives — the MCP server's instructions, on every connection —
and that `--agents-md` writes the file for a host that reads the convention
instead.

Limit: withholding uses the same pattern table as every other route, so a payload that trips nothing still passes; this closes a route that had no grading at all, not the heuristic behind it
Blast: system
Undo: easy
Certainty: firm
Verified: the payload appears in `stale --json` before the change and is withheld after, with the record still listed and its keys intact; removing the withholding fails the new case; 136 pass across stale, readme and mcp; no public surface still claims `init` writes AGENTS.md
Provenance: authored
Record-Id: r-staleclaim1
Two repairs from earlier today were written against one call site each, and the
review found the sibling in both cases.

**The dead-registration check missed opencode.** It has its own writer for a
different config shape, so the generic path learned to name a target that no
longer exists while opencode kept reporting one as fine — and the changelog
named opencode among the four hosts the fix covered. The rule is now one shared
function on both installers, because two copies of it is how the first one
drifted.

**The scan budget bounded only the commit pass.** `scanTrailers` always runs
the notes pass afterwards, unbudgeted, in batches of 1024, parsing every note
and resolving paths. A repository whose records live in notes could stall an
edit well past the advertised ceiling while `unreadCommits` reported 0, because
nothing counted what that pass had skipped. Both passes now read the same
deadline, and the cost record has a half for each — they stop independently, so
one number could not describe both.

The budget regressions had no notes fixture, which is why this survived. They do
now: forty notes, a clock already past the deadline, and an assertion that both
halves report what they skipped.

Limit: the budget bounds the two scans, not the command -- process startup, path resolution and rendering still sit outside it
Blast: module
Undo: easy
Certainty: firm
Verified: a deleted command in an opencode config is now named on both the shell and PowerShell installers, and a live one still reports as before; a budgeted scan over a repository with forty notes reports unread commits and unread notes separately, and reverting either fix fails its own case; 237 pass across query, index-db and both installer suites
Provenance: authored
Record-Id: r-secondcopy1
**Verification.** The earlier repair checked `persist`'s refusal on the accepted
path. Four early exits — transcript mismatch, diff mismatch, unfetched notes,
unavailable history — were each written as `persist(result); return result;` by
hand and kept discarding it. Replaying a nonce through any of them returned a
rejection to the caller while the first call's record stayed stored, and
staging reads what is stored.

Every exit now goes through one function that substitutes an unbound result when
the store refuses. The exception handler is the single exception, and says why:
it already returns `empty` with `incomplete: true`, so a failed store cannot
change its answer.

**Ownership.** A legacy wrapper was accepted for replacement whenever a
directory named for its version existed under the data root. A directory called
`v1.2.3` is something anyone can create, so the check asked about a name an
attacker controls: an unrelated executable printing `1.2.3` was destroyed by
creating one empty directory. Reproduced exactly that way.

The evidence is now `dist/commitlore.mjs` inside that directory — written by
this install and by nothing else. The regression fixture created an empty
directory and passed without establishing anything, which is how the hole
survived a test named for it; it now writes the runtime, and the name-only case
has a test of its own.

Limit: the runtime's presence proves this installer wrote the directory, not that its contents are unmodified since
Blast: module
Undo: easy
Certainty: firm
Verified: a replay through the transcript-mismatch exit returns empty and incomplete while the stored transaction keeps the first call's record, and reverting the helper fails two cases; an unrelated executable survives when only a directory of the right name exists and is still replaced when the runtime is there; 124 pass across capture-verify, capture, mcp and both installer suites
Provenance: authored
Record-Id: r-dropfake01
…we claim

**`init` said ready over a repository where nothing can start a capture.** A
failed MCP registration was reported at code 0, so every step got a checkmark
and the run finished `init: ready`. Capture is the product; a setup command that
could not wire it is not ready, whatever else worked.

It is still not fatal — hooks, index and delivery all install — so this is 1,
the code that already means "ran, and something needs you", never 2. And it now
prints the repair: the exact `.mcp.json` to write, and `commitlore doctor` to
confirm it. The operator is the only one who can decide what belongs in that
file, so telling them beats guessing for them.

**The compatibility matrix claimed executions CI did not perform.** It defines
"supported" as an install path reaching the host *and the result being executed
there*. macOS was marked supported while the row itself admitted the install ran
on Linux only, and the musl row named two architectures the workflow never used.

Rather than narrow the claims, CI now does what they say. `install-macos` runs
`install.sh` on `macos-latest`, checks the wrapper reports the requested
version, and then runs `init`, `doctor` and `context` in a repository it sets
up. `install-alpine` does the same inside `alpine:3.21` on `linux/amd64` and
`linux/arm64`, the second through QEMU.

Prose cannot be tested, but a job name can: a new case reads every job the
matrix cites as evidence and fails if CI does not define it. That is the
cheapest check that would have caught both rows.

Limit: the new jobs establish that an install runs and answers on those hosts, not that every command behaves identically there
Blast: system
Undo: easy
Certainty: firm
Verified: a repository whose .mcp.json cannot be registered now reports `2 step(s) need(s) attention — MCP registration`, exits 1, prints the file to write, and never prints `init: ready`; the workflow parses with the two new jobs present and passes the action lint; citing a job CI does not define fails the new case; 121 pass across init, compatibility-matrix, action-lint and doctor
Provenance: authored
Record-Id: r-readyhosts1
…cies need

Yesterday's floor change said `>=22.5`, and `check-engines.mjs` read that range
by scanning it for digits and taking the smallest — so it measured every
dependency against **Node 5**. Both required jobs failed at that step, and
typecheck, build, the whole suite, dogfooding, the performance gate and the
fresh-clone check never ran behind it. The check that exists to keep
`engines.node` honest is the thing that broke the release.

It survived because the parser was inline in a script with no test of its own.
It is now `scripts/engine-floor.mjs` with seventeen cases, including the exact
shape that failed: `>=22.5` must not read as 5, and `>=22.12.0` must not admit
22.5.0 because their majors match.

With the parser correct, the real mismatch surfaced: `commander` requires
`>=22.12.0` while the package promised `>=22.5`. `node:sqlite` needs 22.5 and
that dependency needs 22.12, so the floor is the higher of the two and every
place that states it now says 22.12 — package, both installers, their messages
and the version matrix in their tests.

The `install-alpine` job added with this release also failed, on `dubious
ownership`: it set `safe.directory` with `--global`, and the container runs as
root with a different HOME than the runner that owns the checkout, so git never
read the entry back. `--system` puts it where git looks.

Limit: the parser covers the range shapes npm packages actually publish -- comparators like `>=22 <23`, and pre-release identifiers, are read by their first version and not by their bounds
Ruled-out: adding semver as a dependency to parse this | one regex over a handful of published shapes does not justify a runtime dependency in a check that runs before install
Blast: system
Undo: easy
Certainty: firm
Verified: the engine check passes naming Node 22.12.0, seventeen parser cases pass including the two that encode this defect, both installers refuse 22.4 and 22.11 and accept 22.12, and version consistency holds across all three manifests, both lockfile fields and the built CLI
Provenance: authored
Record-Id: r-engfloor01
The container script is one single-quoted argument to `sh -c`, so a nested
single-quoted `'*'` closed the quote and git received one argument where it
wanted two -- `wrong number of arguments, should be 2`, exit 129, before any
install ran. Double quotes inside the outer single quotes reach git intact.

Limit: this is the second quoting layer in this step; a third -- a value with a double quote in it -- would need a different mechanism than more escaping
Blast: local
Undo: easy
Certainty: firm
Verified: the workflow parses and the step now carries both `safe.directory` arguments as separate words; the action lint passes
Provenance: authored
Record-Id: r-mslquote1
`validate` answers `reference: not-checked` when the notes mirror was never
fetched, and exits 0 anyway — the reference half is skipped, not failed. CI ran
it for its exit code and never fetched `refs/notes/commitlore`, so the gate that
proves this repository keeps its own protocol had been passing with half the
check unperformed (#542).

Both halves matter because both are authoritative here: commit trailers and the
notes mirror. Checking references against only one of them is not checking them.

CI now fetches the mirror and reads the JSON: `shape` and `reference` must each
report `ok`. `not-checked` fails, which is the whole point — a check that was
skipped is not a check that passed.

The fetch is `|| true`, because a fork without the ref should fail on the
assertion that says references were not checked, not on a network error that
says nothing about this repository.

Limit: this asserts the two checks `validate` performs; a third class added later is not required by name until somebody adds it here
Blast: local
Undo: easy
Certainty: firm
Verified: against a real repository the assertion prints `shape: ok` and `reference: ok` and exits 0, and the same report with the reference status set to `not-checked` exits 1 naming the reason; the workflow parses and the action lint passes
Provenance: authored
Record-Id: r-dogfoodref1
…ous pass

Every suite tested a part; nothing asserted the product's whole claim — that a
decision recorded through the tools an agent calls survives into the commit and
reaches whoever edits that path next. `test/capture-pipeline-e2e.test.ts` runs
the real `dist/commitlore.mjs mcp` as a child process against a real repository
with real hooks: prepare, verify, stage, commit, read back.

The persistent client is why this one works. An earlier attempt drove the
server with `spawnSync`, which writes every frame and closes stdin; the server
read that EOF as the client hanging up and exited before answering, and a
missing response is indistinguishable from a rejected draft. `test/mcp.test.ts`
already had the right client, so it is now `test/mcp-client.ts` and both use it.

Writing this caught the same class of error in the new suite. The
fabricated-evidence case passed — and for the wrong reason: `verify` was never
handed the diff `prepare` had hashed, so every call was refused as a
source-mismatch and nothing about fabricated quotes was being tested. It now
passes the staged diff, and asserts the *reason* is a quote nobody said. A
negative case that cannot say why it is negative is not a negative case.

Four cases: the decision reaching git and coming back, a fabricated quote
leaving no record, `initialize` carrying both halves of the protocol, and a
replay through an early exit reporting empty and incomplete rather than leaving
the earlier record stageable.

Limit: the model's judgement -- deciding a change is worth recording at all -- is the one step no fixture stands in for, and stays a manual pre-release matrix
Blast: module
Undo: easy
Certainty: firm
Verified: four cases pass against the built artifact; disabling the evidence-quote check in harvest-verify fails the fabricated case, so the negative is load-bearing; the extracted client leaves test/mcp.test.ts passing unchanged
Provenance: authored
Record-Id: r-e2epipe01
…host jobs

Five findings from the pre-tag review, four of them in fixes this same release
made.

**The stale redaction had a second channel.** `resolvedTrailers` was withheld
and `expiresAt` was not — it carries the `Expires:` value verbatim and is
serialised beside them, so a payload in `Expires:` still reached a model through
the same tool. Redacting one field of two is not redacting.

**A refused store left the earlier record stageable.** `settle` changed what was
returned and not what was stored, and `stage` reads what is stored. So a replay
told the caller "empty" while the commit would have carried the first record —
the exact invariant the previous fix claimed. The transaction is now discarded:
two verifications of one nonce have disagreed, and there is no reading of that
where either result should reach a commit. `prepare` is one call away.

**A missing transaction reported a confident empty.** A stale or mistyped nonce
answered `empty` with `incomplete: false` — "nothing survived", from a call that
found nothing to check. It is `incomplete: true` now, which is what not knowing
means.

**Both new host CI jobs proved nothing.** They ran `init --unattended
--no-agents-md`, and that flag stopped existing when this release flipped the
default to `--agents-md`. Commander rejected it, `|| true` swallowed the
rejection, and `doctor` and `context` then measured a repository `init` had
never touched — while the jobs stayed green and the compatibility matrix cited
them as evidence. They now require init's own summary line, because the exit
code cannot tell "a step needs attention" from "could not run at all".

**The installers took a mention for a registration.** A grep for `"commitlore"`
matched the word anywhere in an agent config — a note, an unrelated value — and
reported the host already wired while nothing was registered. Both installers
now ask whether a server is registered under that key.

Also: Node 22.12 is enforced but the lockfile, four READMEs, `docs/install.md`
and the compatibility table still published 22+. A user on 22.11 met the stated
prerequisite and was refused by the script.

Limit: `registers_commitlore` reads the key, so a config that registers under a different key -- a host with its own naming -- still reads as unregistered and is wired again
Blast: system
Undo: easy
Certainty: firm
Verified: an injection in `Expires:` is withheld in `stale --json` where it appeared verbatim before; a replay leaves no pending transaction and `stageCaptureRecord` returns null; a missing nonce reports incomplete; `init --unattended` prints the summary line both jobs now require, and `--no-agents-md` is rejected as the review said; a config that merely mentions the word is wired while a real registration is preserved; 106 pass across stale, capture-verify, capture, readme, compatibility-matrix and both installer suites
Provenance: authored
Record-Id: r-pretag01
…erified

The last two findings of the pre-tag review, both the same mistake: taking the
cheapest observable fact for the one that matters.

**Ownership was a file's existence.** The previous repair replaced "a directory
named for this version" with "a runtime file inside it", which is still
something anyone can create — the regression fixture wrote a file containing
one comment and passed. Reproduced: an unrelated executable printing `1.2.3`
was destroyed by placing a comment at that path.

The evidence is now that the runtime *answers*: it runs and reports the version
the wrapper claims. Forging that means installing a working CommitLore of that
version, which is not an attack. `install.ps1` gets the same probe.

**Doctor called any launchable command a capture server.** `{"command":
"false"}` reported `ok` — a registration that starts nothing, described as
readiness. Preserving an operator's entry is right; vouching for it is not, and
those had been the same branch.

They are separate now. The command `init` writes reports `ok`. Anything else is
`warn`, names the command, says plainly that whether it starts a capture server
is unverified, and is still left exactly where it was. The test that codified
arbitrary wrappers as healthy now asserts both halves: preserved, and not
claimed.

Limit: `warn` distinguishes ours from not-ours by the command string, and does not execute anything -- a wrapper that really is a CommitLore server still reads as unverified, which is the safe direction but not a probe
Blast: module
Undo: easy
Certainty: firm
Verified: an inert file at the runtime path leaves a foreign executable intact while a runtime that reports the version is still replaced; doctor answers warn for `"false"` and for a wrapper path, ok for the command init writes, and rewrites neither file; 113 pass across doctor and both installer suites; engine check, dist reproducibility and version consistency all clean
Provenance: authored
Record-Id: r-answerown1
The entry described three fixes. Three independent reviews then found
twenty-one blockers against this candidate, and most of the release is the
answer to those — including several defects in the fixes made earlier in the
same release. A changelog that named only the first three would be the same
kind of claim this release spent its time removing.

Grouped by what a reader needs: what reaches every host now, what `init` stops
doing to their repository, what is served and what is claimed, what installing
establishes, and what CI establishes that it did not before.

Blast: local
Undo: easy
Certainty: firm
Verified: readme, manifest and release-version suites pass against the rewritten entry
Provenance: authored
Record-Id: r-changelog81
A new reader learned that delivery and capture are different layers well down
the page, after the install command they had already run. The split is the
first thing that decides whether this tool does what they expect: delivery
happens on its own, capture happens when an agent judges a change worth
recording, and an ordinary `git commit` cannot start it because a hook has the
diff and a capture needs the session. That now sits above the fold, in all four
languages.

The host table had gone stale in a way this release caused. It ended with one
row for "other `AGENTS.md`-convention hosts", written when that file was the
only channel carrying the capture procedure. It is not any more: Gemini, Cursor,
Windsurf and opencode receive the MCP server from `install.sh` and get the
procedure in its `instructions`, which is what a host that loads no skills has
to work from. They are their own row now, named, with what they actually
receive — and the caveat that whether a host surfaces those instructions is the
host's choice and nothing here detects it.

The Node badge still read `>=22` while the package, both installers and every
prerequisite line say 22.12.

Blast: local
Undo: easy
Certainty: firm
Verified: readme, compatibility-matrix and manifest suites pass across all four READMEs; the badge, the prerequisite lines and package.json now state one floor
Provenance: authored
Record-Id: r-readme81
`validate` exits 1 when it finds violations, and under `set -e` that killed the
step before the assertion could read the report — so the gate that was supposed
to name which check failed reported only that something had. Nine minutes of
output, and nothing said what was wrong.

The exit code is not discarded; it is re-derived from the JSON, which is the
thing that can name a violation. Violations are now listed with their commit
and rule.

I briefly added a baseline of three commits guessed from warning lines in the
CI log, then removed it: validating one of them directly reported zero
violations, so the guess was wrong. An exception list nobody verified is the
kind of claim this release has spent its time removing.

Blast: local
Undo: easy
Certainty: firm
Verified: the workflow parses and the action lint passes; validating a single commit reports shape ok, reference ok and no violations, which is what showed the guessed baseline to be wrong
Provenance: authored
Record-Id: r-gatesays01
The assertion was a `node -e` heredoc inside a `run:` block, and a comment in it
contained backticks. The shell read those as command substitution and the step
died with `unexpected EOF while looking for matching \``, exit 2, before
anything was asserted — while the log filled with `validate`'s own warnings,
which is what a reader would have blamed. I did blame them, and spent a while
looking for violations that were never there.

It is `scripts/assert-dogfood.mjs` now. A file has no quoting layer, so nothing
in it can be reinterpreted on the way to Node, and the step is two lines.

The assertion itself is unchanged: violations, secrets, and both `shape` and
`reference` reporting `ok`. `not-checked` still fails, which is the state this
gate exists for.

Limit: this checks the report `validate` produced; it does not re-derive the range, so a range that silently covered nothing would pass here
Blast: local
Undo: easy
Certainty: firm
Verified: run against a real report it prints `violations: none`, `shape: ok`, `reference: ok` and exits 0; with the reference status set to `not-checked` it names the reason and exits 1; the workflow parses and the action lint passes
Provenance: authored
Record-Id: r-assertfile1
The dogfooding gate fetched the notes mirror for the first time and its
reference check ran for the first time with it. It found one violation, in
this repository's own history: 03b4bfe carries `Follows: r-8c31f7`, and
`r-8c31f7` was not written until 53d60c2, six minutes later, by somebody who
had noticed the omission — the commit subject says exactly that. The record
exists and is reachable from HEAD; the reference still points forward, which
is what the check reports. It lives in a commit message, so it cannot be
corrected without rewriting history.

Two ways to make the job green: widen the check, or record the case. Widening
it would retire the check that #542 asked for on the day it first did its job.
So the exception is a file — sha, rule, value, and why — and the assertion
subtracts only what that file names. Anything else still fails. The count of
carried violations is printed on every run, so the exception stays visible
rather than becoming the silence it replaced.

Verified: `node scripts/assert-dogfood.mjs` over the full adoption range
  exits 0 and prints `reference: ok` with the entry present, and exits 1 on a
  violation the file does not name
Evidence: scripts/dogfood-baseline.json
Evidence: scripts/assert-dogfood.mjs
Follows: r-9c07e2
Blast: module
Undo: easy
Certainty: firm
Record-Id: r-4e91ab
Provenance: authored
CommitLore-Version: 2.0.0
There was no way to report one privately. GitHub's private reporting was off,
so the only channels were a public issue — which publishes the vulnerability at
the moment it is disclosed — or an email address nobody had. Private reporting
is now enabled on the repository and SECURITY.md points at it, so a draft
advisory stays between reporter and maintainer until there is a fix.

The scope section is the part worth reading. CommitLore's security surface is
not the usual one: it serves recorded text to agents that act on it, so the
questions are whether a record can reach an agent as an instruction, whether a
trust grade can overstate what it knows, and whether capture can write a record
it was never handed evidence for. Those are named as reportable, along with the
install path, which writes host configuration. What is deliberately not
reportable is named too — a stale index is a derived cache (ADR-0003), and
`[claim]` content being wrong is the grade doing its job, not a bug.

Dependabot covers npm and the workflow actions, monthly and grouped. Weekly
per-package PRs get closed unread, and the failure this guards against is a
pinned action going stale until a gate stops meaning what it says. Security
updates ignore both the schedule and the grouping.

Verified: private vulnerability reporting reads back `{"enabled": true}`; a
  Dependabot-shaped trailerless commit validates with 0 violations and
  `shape: ok`, so these will not trip the dogfooding gate
Evidence: SECURITY.md
Evidence: .github/dependabot.yml
Follows: r-4e91ab
Blast: system
Undo: easy
Certainty: firm
Record-Id: r-7d20c5
Provenance: authored
CommitLore-Version: 2.0.0
…ed like one

The release gate matched required checks by name alone. A check run's name is
chosen by whoever creates it, and any GitHub App installed on the repository
can create one: name it `check (22)`, conclude it `success`, point it at the
release SHA. The gate read that as this repository's CI having passed and let
publication through. Closes #571.

The run carries its producer. `app.slug` is GitHub's identifier for the app
that opened the check run, set by GitHub rather than by the caller, so
requiring `github-actions` distinguishes a CI result from something merely
shaped like one. A run with no app attributed is refused for the same reason —
absent provenance is not proof of provenance.

A foreign run is reported rather than filtered away. Dropping it silently would
leave the gate saying "required check is absent", which is true but hides that
something posted under that check's name. Both facts are stated.

Verified: the four new cases fail against the previous name-only match — a
  look-alike is accepted as the genuine check, and a forged duplicate alongside
  a real success passes — and pass with the producer required; 19/19 in the file
Evidence: scripts/check-exact-head-ci.mjs
Evidence: test/release-publish-prerequisites.test.ts
Follows: r-7d20c5
Blast: system
Undo: easy
Certainty: firm
Record-Id: r-b6f3e8
Provenance: authored
CommitLore-Version: 2.0.0
`npm audit` reported ten vulnerabilities on every CI run — six moderate, three
high, one critical — and the job was green every time. Nobody could tell
"assessed and accepted" from "never looked at", because both look like a
warning nobody acts on. Closes #545.

Splitting the number answers it. What users receive is `dist/`, committed per
ADR-0011 and cloned by the installer without npm ever running, so an installed
CommitLore has no `node_modules` at all. The production audit is therefore the
count that can reach a user, and it is zero — all ten live in build and test
tooling. That is now the blocking half, at `--audit-level=low` so zero means
zero rather than "nothing above a threshold I picked". The full audit still
runs and still prints, without failing a build no installation depends on;
Dependabot is what moves those forward.

The split is only honest while the premise holds, so the premise is checked
too: `dist` and `spec` are copied somewhere with no `node_modules` and the
runtime is asked for its version. If the bundle ever stops being
self-contained, the production audit quietly stops covering what users run —
this fails first.

Verified: `npm audit --omit=dev --audit-level=low` exits 0 while the full audit
  reports 10; the copied tree has no node_modules and answers `0.8.1`. The
  self-contained claim was checked further by hand — validate, an MCP handshake,
  tools/list, and a prepare_capture call all run from a clone with no
  dependencies installed; the `from 'ajv'` strings in the bundle are JSDoc
  examples inside the vendored SDK, not imports
Evidence: .github/workflows/ci.yml
Follows: r-b6f3e8
Blast: system
Undo: easy
Certainty: firm
Record-Id: r-9f04d7
Provenance: authored
CommitLore-Version: 2.0.0
An installation missing `spec/` failed every commit with a raw ENOENT and a
usage line underneath. The two things on screen were a path the user never
chose and usage for a command they never typed, so the available reading was
"my trailers are wrong" — and the message had not been examined at all. The
hook is where people meet this, which is why it cost an hour of editing a
commit message that was already correct. Closes #533.

`readInstalledFile` now distinguishes a missing shipped file from any other
read failure and says three things: what is absent, that the message was not
examined, and the command that restores it. Nothing the caller could retype
fixes this, so no usage line is printed.

The exit code separates too: 3 for an operational failure, distinct from 2 for
usage. That is the first code from the taxonomy #543 asks for, taken here
because #533 is where the conflation actually reaches a user. The rest of that
taxonomy — NoRecord, RecordRejected, InternalError, shared across the CLI, the
hooks and the MCP server — is still open and is not attempted in a release cut.

The gate's producer check from the previous commit had a second call site.
`release-tag-binding` builds its own check-runs payload and was refused by the
stricter gate; CI caught what running one test file did not. Both payload
builders are now the only two, confirmed by search.

Verified: with `spec/` removed, a real `git commit` through the commit-msg hook
  prints only those three things, creates no commit, and validate exits 3; four
  cases assert it against a copied installation tree, two of which fail against
  the previous routing
Evidence: src/core/paths.ts
Evidence: src/commands/validate.ts
Evidence: test/validate.test.ts
Follows: r-9f04d7
Blast: module
Undo: easy
Certainty: firm
Record-Id: r-2c8e5a
Provenance: authored
CommitLore-Version: 2.0.0
The 0.8.1 entry stopped at the point the reviews had reached. Since then the
reference check ran for the first time and found something, the release gate
learned to ask who reported a check, the audit number was split into the two
questions it was answering, and a broken installation stopped reading as a bad
commit message. A reader deciding whether to upgrade needs those.

The dogfood baseline is stated as a carried violation with its reason rather
than left implicit. An exception nobody can see is the thing it replaced.

Verified: the entry names each change against the commit that made it
Evidence: CHANGELOG.md
Follows: r-2c8e5a
Blast: local
Undo: easy
Certainty: firm
Record-Id: r-5a71bc
Provenance: authored
CommitLore-Version: 2.0.0
The baseline subtracted the recorded violation from the list and left
`validate`'s own `reference: failed` standing, so the gate went red for the one
case that had been named and explained. CI said it plainly — `1 carried`,
`shape: ok`, `ERROR: reference is failed` — and it was right to.

I checked the baseline against a report generated earlier, which still said
`reference: ok`. That is the whole mistake: the fix was verified against a
stale artefact instead of the one the gate produces. The report CI builds is
the only one that answers the question.

A failed class is now tolerated when the subtraction is its entire
explanation — nothing unrecorded remains and something was in fact carried —
and the line saying so is printed. `not-checked` is never tolerated, whatever
the baseline holds: a sub-check that did not run is precisely what #542 was
about, and no recorded exception excuses one.

This logic had no tests, which is why the flaw shipped at all. It has nine
now, including the exact report CI produced.

Verified: removing the tolerance fails the two cases that model CI's report;
  widening it to cover `not-checked` fails the case guarding #542; restored,
  9/9 pass
Evidence: scripts/assert-dogfood.mjs
Evidence: test/assert-dogfood.test.ts
Follows: r-5a71bc
Blast: module
Undo: easy
Certainty: firm
Record-Id: r-8b3f16
Provenance: authored
CommitLore-Version: 2.0.0
…bered

This release added `audit`, `install-macos` and two `install-alpine` jobs to
CI. The release gate's required list was not touched, so it kept qualifying
releases on the six checks it already knew. Four jobs could have failed at a
tagged commit while the gate reported every required check green — the same
shape as #571 one layer up: the gate looked strict and was checking less than
it appeared to.

The list stays fixed rather than inferred. A list built from what reported at a
SHA lets a check that failed to report define itself out of the requirement,
which is the failure the gate exists for. What was missing is something that
notices when CI grows past it, so two cases compare the list against ci.yml's
jobs in both directions: a job with no entry fails, and an entry naming a job
that does not exist fails too — the second because a check that cannot run can
never be present, which would block every release.

`lint` is deliberately excluded. Its job is conditioned on
`github.event_name == 'pull_request'`, so it does not run on the push to main
that produces the checks at a release commit. Confirmed against the current
main commit, which carries exactly six check runs, all from `github-actions`.

Verified: dropping `install-macos` from the list fails the first case; adding a
  name ci.yml does not define fails the second; restored, 55/55 across both
  release-gate files. The count in the tag-binding assertion now derives from
  the list rather than repeating a literal six
Evidence: scripts/check-exact-head-ci.mjs
Evidence: test/release-publish-prerequisites.test.ts
Follows: r-8b3f16
Blast: system
Undo: easy
Certainty: firm
Record-Id: r-3f6d09
Provenance: authored
CommitLore-Version: 2.0.0
@MongLong0214
MongLong0214 merged commit 0dbe3f1 into main Aug 12, 2026
22 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant