Skip to content

Remove what the installer wrote, recognised by shape rather than by name - #328

Merged
MongLong0214 merged 2 commits into
devfrom
feat-issue-272-v2
Jul 31, 2026
Merged

Remove what the installer wrote, recognised by shape rather than by name#328
MongLong0214 merged 2 commits into
devfrom
feat-issue-272-v2

Conversation

@MongLong0214

Copy link
Copy Markdown
Owner

Closes #272 (T-1123). Supersedes draft #278.

Why #278 could not be rebased

It removes the entry whose key is commitloredelete mcp[key] — with no check on what that entry points at. command, args and wrapper appear nowhere in its src/commands/uninstall.ts.

That is the failure this ticket names in its own text: "Matching any entry whose command merely contains commitlore would also match an unrelated server a user happened to name that way, which is the 'never remove an entry the installer did not write' rule failing in the one case nobody would notice." Key-matching is weaker still. It also removes the other install's entry on a machine carrying two, and neither loss is reported.

It is also 71 commits behind with dist/ committed from that point.

Recognised by shape, and by which install

isCommitloreEntry(format, entry, wrapperPath) matches the shape and the wrapper it points at. Ten refusal cases are asserted, including our own shape pointing at a different install.

The shape is not one shape:

Format Agents Entry
json-mcpServers gemini-cli, cursor, windsurf {command: <wrapper>, args: ["mcp"]}
toml-mcp_servers codex [mcp_servers.commitlore] + command, args
json-mcp opencode {type: "local", command: [<wrapper>, "mcp"], enabled: true}

The ticket's text names only the first. A recogniser built from it alone leaves opencode's entry behind — and a removal that finds nothing looks exactly like a removal with nothing to do.

The bidirectional table found a gap in the ticket

The ticket's measured inventory lists four agent configs. Both installers write five: ~/.codeium/windsurf/mcp_config.json is wired by wire_windsurf in install.sh and Wire-McpServersJson 'windsurf' in install.ps1, and appears in neither the ticket's table nor its count.

test/agent-configs.test.ts asserts in both directions — every row is found in both installers, and every config path either installer writes is in the table. The second direction is the one that is easy to omit, because a table nobody contradicts looks complete. It is the direction that caught this.

Also re-measured at this head: a checkout is 1366 files, not the 1206 the ticket recorded at 6e1d46d. Nothing asserts that number, but anything that did would be stale.

LIVE cycle (required by the ticket)

Real install.sh into a scratch HOME with unrelated entries planted first, then the installed wrapper's own uninstall:

installed: cursor=['commitlore', 'other-server']  codex_blocks=2

dry-run:   would remove: <wrapper> / <checkout> / the commitlore entry in .codex/config.toml
                         / the commitlore entry in .cursor/mcp.json
           unchanged by dry-run: yes

real:      removed all four, then:
             wrapper:  removed
             checkout: removed
             cursor servers: ['other-server']        <- somebody else's server survived
             unrelated key:  {'keep': 'me'}          <- unrelated top-level key survived
             codex: [mcp_servers.someone-else] intact, byte for byte

The surviving TOML block is why the codex path edits by line instead of parsing and re-emitting: a round-trip normalises quoting, whitespace and key order across the whole file, which is the "never reformat beyond the one entry removed" this ticket forbids.

Restraint, asserted

  • a wrapper without the marker is kept, with the reason named
  • an entry carrying our name but another program is kept
  • a config that fails to parse is left byte-identical and reported
  • a second run removes nothing and exits 0
  • neither the report nor --json contains any other entry's name, command or key
  • per-repository state points at hooks uninstall and inject uninstall-claude-hook; the plugin points at /plugin uninstall. None is touched
  • XDG_DATA_HOME is honoured, because the installer honours it

Verification

  • test/agent-configs.test.ts + test/uninstall.test.ts34 passed
  • npm run typecheck clean; dist/ rebuilt and committed
  • Full suite — 78 files, 1914 passed, 1 skipped
  • Unverified: Windows. install.ps1 writes the same five configs, but no uninstall has been executed there.

`commitlore uninstall` takes back a wrapper, a pinned checkout and one MCP entry
per agent config. Everything interesting about it is what it declines to touch.

An entry is matched on its shape and on the wrapper it points at, never on the
key it sits under. Removing by key would take a server a user happened to name
`commitlore`, and would take the other install's entry on a machine carrying two
-- and in both cases the config just loses a server with nothing to report it.
That is the rule failing in the one place nobody would look.

The shape is not one shape. Both installers write `{command, args:["mcp"]}` into
`mcpServers` for three agents, and opencode gets `{type, command:[wrapper,"mcp"],
enabled}` with the command as an array. A recogniser written for the first shape
alone leaves opencode's entry behind, and a removal that finds nothing looks
exactly like a removal with nothing to do.

The path table found something the ticket's own measured inventory missed. It
lists four agent configs; both installers write five. Windsurf, under Codeium's
directory, is wired by `wire_windsurf` and `Wire-McpServersJson` and appears in
neither the ticket's table nor its count. The assertion that caught it reads the
installers rather than trusting a list, which is the whole reason it is
bidirectional -- and the direction that caught this is the one that is easy to
leave out, because a table nobody contradicts looks complete.

TOML is edited by line rather than parsed and re-emitted. A round-trip normalises
quoting, whitespace and key order across the file, and the live cycle shows a
neighbouring `[mcp_servers.someone-else]` block surviving byte for byte, which a
re-emit would not have left alone.

A config that cannot be parsed is left exactly as it was and reported. Rewriting
a file we could not read is how an uninstall destroys a config it was only meant
to remove one key from.

Record-Id: r-uninstall1123
Limit: 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
Limit: opencode's entry is shaped differently from the other three -- the command is an array -- so one recogniser cannot serve all of them
Limit: a checkout is 1366 files at this head, not the 1206 the ticket measured at 6e1d46d; any assertion bound to that count is stale
Ruled-out: Removing the entry whose key is `commitlore` | it takes a server the user named that way and the other install's entry on a two-install machine, and neither loss is reported
Ruled-out: Parsing the TOML config and writing it back | the round-trip reformats the whole file, which is the "never reformat beyond the one entry removed" this ticket forbids
Ruled-out: Rewriting a config that failed to parse | the failure mode is losing a config we were only supposed to edit one key of
Ruled-out: Removing the Claude Code plugin cache | it is thousands of files this command did not write, keyed by plugin version; naming the step is the honest boundary
Certainty: firm
Blast: local
Undo: easy
Verified: LIVE install-then-uninstall in a scratch HOME with unrelated entries planted first -- dry-run reported four removals and changed nothing, the real run removed the wrapper, the checkout and both entries, and an unrelated server, an unrelated top-level key and a neighbouring TOML block all survived; 34 focused assertions including ten refusal cases; typecheck clean; dist rebuilt; full suite 78 files, 1914 passed, 1 skipped
Unverified: Windows, where install.ps1 writes the same five configs but no uninstall has been executed; the count of 1366 files is this head's, and will move again
@github-actions

github-actions Bot commented Jul 31, 2026

Copy link
Copy Markdown

CommitLore — record lint

Trailers: clean — 2 commits in origin/dev..a218c8c5c41b46b008e43f39458137cf57ce1de2
Active constraints: 75 limits · 192 ruled-out · 54 warnings — from 110 records over 15 changed paths

Active constraints for the paths this PR touches

Limits (75)

  • r-inventory1123 a218c8c — a measured number in a ticket ages against the code it measured, so a contract bound to one has to name the head it was taken at
  • 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-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-winfix1127 bd6a7fb — the fix has to reconcile the writer in hooks.ts with the reader in the stub; normalising either side alone relocates the mismatch rather than removing it
  • r-winfix1127 bd6a7fb — doctor's mirror is path.win32 and the stub is MSYS sh, so a stub-only fix leaves the mirror green while the hook is dead
  • 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-f14owner281 f149ff9 — the shipped install.sh still fetches SHA256SUMS at this base, so no document may describe the installer as Node-only until T-1120 changes both together
  • 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-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-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-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-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-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-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-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-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-f61a2c 9114cf0 — the matcher remains deterministic and lexical; no embedding or semantic service is available to distinguish paraphrases
  • 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-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-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-8a2f61 1a5adec — hooks and CI branch on the exit code, so two failures sharing one code is a wrong answer, not an inconvenience
  • r-3f7a29 49817dc — reconstruction reads text written before the protocol existed, so the evidence is thinner than a harvest and the discard rate is expected to be high
  • r-2b8f45 0adcaf5 — a matcher that flags real work gets uninstalled, so the false positive rate is the binding constraint, not detection
  • r-8c4a17 f2ab0c2 — Record-Id is single-valued, so a merge that inherits several records has no well-formed way to declare them in the message
  • r-8c4a17 f2ab0c2 — a verifier that accepts near-miss citations verifies nothing, so normalisation cannot grow past whitespace
  • r-0b7c44 d2b2ce3 — a command is only real once --help names it, because that is where users look before they read source
  • 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-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

Ruled out (192)

  • r-inventory1123 a218c8c — Leaving the inventory as it was and noting the gap only in the pull request | the ticket is the authority a later reader consults, and a pull request is not where a corrected measurement survives
  • r-inventory1123 a218c8c — Binding the implementation to the corrected file count | it will move again at the next merge; the path table is derived from the installers instead, which does not
  • r-uninstall1123 4ddac0d — Removing the entry whose key is commitlore | it takes a server the user named that way and the other install's entry on a two-install machine, and neither loss is reported
  • r-uninstall1123 4ddac0d — Parsing the TOML config and writing it back | the round-trip reformats the whole file, which is the "never reformat beyond the one entry removed" this ticket forbids
  • r-uninstall1123 4ddac0d — Rewriting a config that failed to parse | the failure mode is losing a config we were only supposed to edit one key of
  • r-uninstall1123 4ddac0d — Removing the Claude Code plugin cache | it is thousands of files this command did not write, keyed by plugin version; naming the step is the honest boundary
  • r-winsupported1124 6333251 — Flipping the cell on the throwaway verification head | the ticket requires the attacks to pass in a required job in this ticket's own pull request, and a head that exists to be discarded is not that
  • r-winsupported1124 6333251 — Leaving the assertion pinned and editing only the document | the two contradict each other, so the suite would fail; a guard that blocks the change it was guarding for is a defect in the guard
  • r-winsupported1124 6333251 — Widening this ticket to the hook fix so it could be self-contained | the stop condition sent that to T-1127 for good reason, and this ticket's job was to measure and then say the word once measurement allowed it
  • r-winpath1127 bdf4ac0 — Normalising only the recorded root at write time | it would fix new installs and leave every existing repository comparing a stored win32 string against pwd -P output
  • r-winpath1127 bdf4ac0 — Loosening the containment match so the legitimate bundle runs | that trades Security: commitlore.bin and COMMITLORE_BIN are executed, and the env path lacks the guard the config path has #71's property for a working hook, which is exchanging one defect for another and is what this ticket's forbidden scope names
  • r-winpath1127 bdf4ac0 — Renaming the containment helper to match its new signature | T-1125's invariant asserts the old identifier as its proxy for the property surviving, and that test belongs to another ticket
  • r-winpath1127 bdf4ac0 — Fixing the stub and leaving doctor's mirror alone | the mirror reports no problem in precisely the state where the hook is dead, so the check that should warn would keep saying healthy
  • r-winfix1127 bd6a7fb — Amending T-1124 to own the three source files | its stop condition exists for this fork, and voiding it to let a measurement ticket absorb a platform fix teaches that stop conditions are optional when the failure is interesting
  • r-winfix1127 bd6a7fb — Merging Execute #71's containment on Windows, with the baseline that gives it meaning #320's measurement step alone to keep CI green | it preserves the observation and discards the executable definition of done, and the hang would remain
  • r-winfix1127 bd6a7fb — Marking the containment assertions skipped until the fix lands | a skip looks like coverage and encodes surrender; the honest place for a red assertion is a draft pull request, not the mainline
  • r-winfix1127 bd6a7fb — Treating "new installs work" as the fix | a wedged repository cannot commit, so the recovery path is the part that matters to anyone already affected
  • r-muslbullet1126 04ac181 — Replacing the bullet with "musl is supported" | T-1122 owns the compatibility statement, and a second summary in the README is the duplication the ownership map exists to prevent
  • r-muslbullet1126 04ac181 — Removing the Windows bullet beside it | it is still true, test/readme.test.ts uses its exact wording as a mutation oracle, and T-1124 owns that claim
  • r-muslbullet1126 04ac181 — Folding this into T-1122's pull request | that ticket's forbidden scope is one pointer line per README and nothing else; widening it to fix an adjacent owner's debris is how single-writer discipline decays
  • r-muslbullet1126 04ac181 — Merging T-1122 first and cleaning up afterwards | two ordinary merges are not transactional, and the window where dev contradicts itself has no upper bound
  • r-nobin284 ab2f08f — Keeping the binary classification arm for the installer's wrapper | the wrapper is a shell script that execs node, so trusting it by name would re-admit any extensionless executable called commitlore while the bundle it runs is the thing with an interpreter to check
  • r-nobin284 ab2f08f — Relaxing the dogfood Evidence check to allow a missing path | it exists to catch a citation nobody can follow, and dropping the check to accommodate a deletion would retire the guarantee instead of correcting the question it asked
  • r-nobin284 ab2f08f — Leaving src/core/paths.ts alone because ADR-0026's inventory did not name it | it was the only remaining code that read files out of a compiled artifact, so leaving it would have left the invariant false while the test asserting it passed
  • r-nobin284 ab2f08f — Keeping /dist/commitlore in gitignore in case a binary returns | an ignore rule for an artifact nothing produces is a claim that something might, and the ADR says otherwise
  • r-pending311 4985a3a — Extending capture gc to also list what it would remove | gc answers what is collectable, and the transaction a caller cares about is usually the one gc will never touch
  • r-pending311 4985a3a — Storing stale in the transaction file | it is a fact about HEAD now, so a stored copy is wrong the moment HEAD moves and right only when it is redundant
  • r-pending311 4985a3a — Changing expires_at so verified transactions expire | it silently deletes work a caller may still stage, and this issue asked to see the state rather than to change the retention rule
  • r-pending311 4985a3a — Resolving an ambiguous prefix to the newest match | it guesses at the one moment the caller has proved they do not know which transaction they mean
  • r-query307 3cf3c58 — Testing the working tree with stat | a deleted file has real history and real records, so stat would report a missing path for one of the cases the diagnostic exists to distinguish
  • r-query307 3cf3c58 — A distinct exit code for a path that is not in history | context is on the hook path, where a nonzero exit is a failure to fail open around, and the caller who needs to branch has a diagnostic to read
  • r-query307 3cf3c58 — Emitting the diagnostic unconditionally from runQuery | the PreToolUse path queries a file that is often being created, so it would fire on every new file and break the hook's silence contract
  • r-query307 3cf3c58 — Searching for the nearest rename alias rather than the nearest ancestor | the alias walk is what already ran and found nothing, while the ancestor probe is bounded at four parents and answers the question that actually bit the reporter
  • r-capture309 5a9655d — Printing the reasons without unifying the two draft paths | the envelope path produced no reasons to print, so the output would still have been silent for the reported input
  • r-capture309 5a9655d — Keeping the envelope path unchecked and letting the verifier speak for it | the verifier checks whether a citation holds, not whether a field exists, so an unknown field is invisible to it by design
  • r-capture309 5a9655d — Defaulting the diff to the empty string and documenting that --diff is required | it makes the ordinary invocation useless and contradicts prepare, which needs no --diff to bind the same bytes
  • r-capture309 5a9655d — Comparing hashes leniently so an empty diff passes | the binding exists so a draft cannot be verified against sources other than the ones prepared, and loosening it removes the guarantee rather than the defect
  • r-harvest310 c055467 — Making the plain harvest message conditional on whether a diff exists | it would make the prescription accurate by narrowing it, leaving the contract still unreachable in the case that matters
  • r-harvest310 c055467 — Emitting the contract only when the transcript is also absent | that is what harvest --prompt-only refuses to print the contract without a transcript and a diff #229 landed and what this issue reports as insufficient, since a caller usually has a transcript before they have a diff
  • r-harvest310 c055467 — Keeping the old assertion and adding a new one beside it | two assertions requiring opposite outputs for one input is not a test suite, it is a coin toss
  • r-hooks296 b701559 — Making hooks install re-validate only when the hook file is unchanged | recordBinPath already runs every time, so the short-circuit was never the cause and gating on it would have left the wrong value being rewritten
  • r-hooks296 b701559 — Falling through to commitlore.node in the stub | it changes the file that carries install-root containment, and a stub already written in an affected repository is not re-read, so it would repair nobody who has the problem today
  • r-hooks296 b701559 — Having doctor --fix repoint the config itself | once the prescribed command works, the prescription is true; a second repair path would leave two places able to write the same key
  • r-hooks296 b701559 — Recording resolve(argv[1]) with a warning when it does not exist | the hook cannot act on a warning it never sees, and a recorded dead path is what stops resolution at the first step
  • r-wrapfallback 5a78bcc — Adding a node version check to the wrapper now | requirement 7 makes it thin deliberately and the cost lands on the hook hot path, so that trade belongs to an approved ticket rather than a documentation pass
  • r-wrapfallback 5a78bcc — Recording that an older Node breaks the bundle | none was available to run it, and asserting a failure nobody observed is the overclaim this project refuses
  • r-wrapfallback 5a78bcc — Leaving the fallback undocumented because it works | it works by using an unchecked interpreter, and the check it silently drops is one the installer advertises
  • r-idxhelp303 cd2ac79 — Deleting the better-sqlite3 mentions in index-db.ts as well | those comments explain why node:sqlite was chosen, and removing the reason leaves a future reader wondering whether the native option was considered
  • r-idxhelp303 cd2ac79 — Asserting the package name appears nowhere in src at all | that forbids the explanation along with the defect, so the check reads only lines that reach a user
  • r-idxhelp303 cd2ac79 — Naming node:sqlite in the message | the failure a user hits is that the binding is unavailable, and the module name adds nothing they can act on
  • r-t1122measured 1267579 — Marking musl supported because the platform gate is gone | removing an obstacle is not the same as running the thing, and claiming a platform without executing it is the specific error the distribution ADRs record
  • r-t1122measured 1267579 — Listing Node once as a prerequisite for both install paths | only one of them checks it, and a document that implies otherwise tells a user their setup was validated when it was not
  • r-t1122measured 1267579 — Fixing the stale musl bullet here | it is a residual compiled-binary reference, which the ownership map assigns to T-1125, and two tickets editing one region is the defect this file was corrected for
  • r-t1123inv 7472bc1 — Matching config entries by the substring commitlore | it would also match an unrelated server a user named commitlore-something, and removing someone else's entry is the failure this ticket exists to prevent
  • r-t1123inv 7472bc1 — Having uninstall remove the Claude Code plugin cache too | 6948 files keyed by plugin version belong to the CLI that wrote them, and reaching into another tool's cache is not this command's job
  • r-t1123inv 7472bc1 — Leaving the counts out and describing the split in prose | the split is only obviously right once the numbers are beside each other
  • r-containevid 3cc3f22 — Leaving both clauses as written and relying on the issue comments | a ticket read at implementation time is the contract, and a comment on an issue is not where a forbidden-scope clause belongs
  • r-containevid 3cc3f22 — Concluding from this that Windows containment is already safe | the measurement is macOS only, which is the whole reason T-1124 exists, and inferring the platform is the specific error the ADR forbids
  • r-f14postt1120 e427225 — Deleting the install.sh inventory row outright | a struck-through row that says which ticket completed it and what remains is a record; a deleted row leaves a later reader unable to tell whether it was done or forgotten
  • r-f14postt1120 e427225 — Re-anchoring every 8b0c9fa reference to the merged head | some of them are RED justifications about what was true before T-1120 shipped, and rewriting those would make the tickets claim their tests fail against a state that no longer exists
  • r-f14postt1120 e427225 — Leaving T-1121 to re-derive T-1120's contract at implementation time | it is a fact now, and an implementer inferring it can infer it differently, which is how two installers stop implementing one contract
  • r-f14owner281 f149ff9 — Leaving the README to T-1122 and letting requirement 29 slide | the requirement exists because a README that describes an installer it does not ship beside is the failure this project treats as most serious, and deferring it guarantees a window in which that is exactly what ships
  • r-f14owner281 f149ff9 — Moving requirement 29 out of the removal section into T-1120's own section | the requirement text is cited by number elsewhere, and renumbering to tidy placement would break those citations for a cosmetic gain
  • r-f14owner281 f149ff9 — Splitting the README region between T-1120 and T-1122 by language or by block | two owners on one region is the defect being corrected, and any split reintroduces it in a smaller form
  • r-f14owner281 f149ff9 — Declaring one owner per file and calling ci.yml an exception without enumerating it | an exception a reader has to interpret is the kind that rots; the four jobs are listed so a crude scan and a human reach the same answer
  • r-mcpdraft291 d4444d5 — Changing the harvest contract to emit a bare array instead | the contract text, harvest's validator and the CLI all implement the object shape, so moving the contract would break two working surfaces to accommodate one broken one
  • r-mcpdraft291 d4444d5 — Accepting only the object shape and dropping bare-array support | the earlier tool description advertised an array, so a caller may exist that sends one; refusing it now would trade this defect for a different one
  • r-mcpdraft291 d4444d5 — Leaving the mismatch and documenting the array shape in the prompt | the prompt is generated by the same code the CLI uses, so the two consumers would then disagree about what the same generated text means
  • r-mcpadv273 0166937 — Omitting guard_advisory when it has no matches | absence on the wire reads as no ruled-out alternative applies, which is exactly the claim ADR-0020 forbids; an empty matches array with the disclosure attached says the true thing
  • r-mcpadv273 0166937 — Leaving the MCP response as the seven binding fields and treating the advisory as CLI-only | MCP is the first-class surface for every agent that is not using the Claude Code plugin, so a CLI-only advisory is an advisory most callers never see
  • r-t1110policy 9e7b37a — Hashing the parsed policy object instead of the file contents | ADR-0021 fixed the input as the contents, and it is the stronger choice: a reformat that changes nothing semantically still changes the identity, and reporting that the file changed is true
  • r-t1110policy 9e7b37a — Supporting a user-global policy file alongside the repository-local one | two locations need a precedence rule, and the user story this answers is repository-scoped, so one location removes the ambiguity rather than documenting it
  • r-t1110policy 9e7b37a — Ignoring an unknown key in the policy file | a silently dropped key lets a user believe a setting applied; rejecting it names the key and the allowed set
  • r-t1110policy 9e7b37a — Falling back to the defaults silently when the file cannot be parsed | the identity hash would then describe a policy the user never asked for, with nothing said about it
  • r-t1110policy 9e7b37a — Bumping the pending format version for the new field | ADR-0021 fixed the migration so no version change is needed, and producing one would falsify that ADR
  • r-gateb4replan 5a35062 — Writing the Gate B acceptance rows for these tickets now | a row with no approved ticket behind it is exactly the dangling authority the Gate A matrix was written to stop, so each row lands with its ticket
  • r-gateb4replan 5a35062 — Shipping the compatibility statement before the installers | the document would describe an installer that does not exist, which is the failure mode this project treats as most serious
  • r-gateb4replan 5a35062 — Removing the compiled-binary code in the same wave as the new installers | it would open a window in which neither install path works
  • r-gateb4replan 5a35062 — Treating Windows as supported once install.ps1 lands | reachability through an installer is not a verified containment property, and conflating them would ship a platform where the property is unestablished
  • r-gateb4replan 5a35062 — Reusing the F12 ticket numbers for this work | those numbers name withdrawn tickets whose issues are closed as not planned, and reusing them would make the history unreadable
  • r-t1109adv 4398856 — Emit raw GuardMatch objects in advisory | renderGuardMatch is the trust-grading boundary that withholds blocked content from model-visible surfaces
  • r-t1109adv 4398856 — Add a policy key to control advisory behavior | ADR-0020 classifies guard as non-blocking with no configuration escape
  • r-t1109adv 4398856 — Bump PendingRecord.version to 2 | ADR-0021 already fixed migration so no version bump is needed
  • r-gcwiring f21f28e — dropping --json from the gc subcommand and relying on the parent's | the subcommand would then document a flag it does not declare, and a reader of capture gc --help would not see it
  • r-gcwiring f21f28e — keeping requiredOption and giving gc its own top-level command | gc belongs to capture's surface, and moving it would make the fix a rename rather than a fix
  • r-flake221fix 2b21ed9 — downgrade evaluateInjectRun to warn | doctor.test.ts integration test asserts status=fail for ENOENT
  • r-flake221fix 2b21ed9 — remove trailers from init.test.ts repoWithRemote | weakens inject-probe test coverage without addressing the semantic defect
  • r-lb0xl89a 236229e — parameterising the contract with empty strings | prints a template with empty holes that teaches the session nothing about placeholder handling
  • r-t1009stage b5fcf4e — accepting base_head or a diff hash from the caller for efficiency | the caller is the party the transaction exists to constrain, so taking its word for the binding removes the point of the binding
  • r-t1009stage b5fcf4e — a single write_record tool that skips the transaction | PRD-F9 forbids it, and it would let a draft reach Git without ever being verified
  • r-t1005gates 15421c0 — blocking the commit when a gate fails | the commit is the user's work and the record is ours; failing their commit to protect our bookkeeping inverts whose job this is
  • r-t1005gates 15421c0 — treating a null expires_at as unexpired | null means the record was never staged, so it fails the staged requirement before expiry is consulted; reading it as "no expiry" would apply an unstaged record
  • r-t1006cli d22580b — exposing prepare, verify and stage as user-facing subcommands | the phases exist for the transaction, not for the user, and three commands is the problem this ticket removes
  • r-t1006cli d22580b — exiting non-zero when verification produces nothing | silence is the expected result for most commits, and a non-zero exit would train people to ignore it
  • r-t1007mcp b6ef112 — readOnlyHint: true | tool persists state under .git/commitlore/pending/ so marking it read-only would be dishonest
  • r-t1022sig e0c641d — keeping the score in text output behind a debug flag | a second flag for a number nobody should act on adds surface without adding information
  • r-t1022sig e0c641d — dropping the score from --json as well | a consumer that has already decided to parse structured output is not the reader being misled
  • r-t1011demo 1c0fc0c — seeding the demo into the user's repository behind a confirmation | a demo that can modify the thing it is explaining is not a demo, and a confirmation prompt is not a substitute for being unable to
  • r-t1011demo 1c0fc0c — recomputing lifecycle inside the demo to keep it self-contained | a second implementation of the rule would drift from the one under test, and the demo would stop being evidence
  • r-t1020desc dd12b42 — removing the description entirely | the first two sentences carry useful contract information a caller needs
  • r-t1020desc dd12b42 — stating only precision without recall | a caller needs both to judge the signal; recall is the more dangerous gap
  • r-t1030diag 344ada0 — a single generic message that quotes stderr without case distinction | it would still let the old "carries no node" wording survive on the exit-127 path, and would not communicate to the reader whether the probe learned anything
  • r-t1030diag 344ada0 — parsing more than the first line of stderr to classify the cause | false-uncertain is acceptable, false-confident is not; one line is enough to distinguish the obvious cases
  • r-fix191amb cb94448 — adding a guard clause only in checkReferences | leaves the predicate willing to say "resolved" about an unresolvable group; any future third caller would silently inherit the same defect
  • r-fix191amb cb94448 — removing the post-filter entirely | the original fix for cross-commit succession is correct and the release gate depends on it
  • r-fix187val 40f2436 — passing tip-scoped records directly to findIdCollisions | chronological ordering by committedAt breaks on same-second commits from git-log's newest-first output; the post-filter avoids reordering entirely
  • r-fix187val 40f2436 — duplicating hasDeclaredSuccession into validate.ts | the drift between two copies of the same predicate was the bug; a second copy would reproduce it
  • r-dupsucceed 6f77fcf — adjusting the four expectations to match the new grades | they encode that trust does not depend on the order records appear in the log, which is a property of the protocol rather than of this implementation
  • r-valdup145 bcb9563 — adding a duplicate-id scan to validate on its own | it would have to decide block boundaries again, and a second answer to that question is what let parse and validate disagree in the first place
  • r-convtrail150 57e89d2 — excluding Fixes and Closes with the rest | they carry decision context an agent can use, and discarding them would trade a projection full of attribution for one missing the issue a change answers
  • r-epipe2026 d9ee9ff — error-first spawn result handling | a completed git process can retain a real status after the input-pipe EPIPE race
  • r-doctorepipe 0420f5c — the executable being absent from PATH | the failing run's own message names EPIPE, not ENOENT — the ENOENT branch produces different text than what CI printed, and the executable resolves and runs every time
  • r-init107 f485f07 — treating every doctor warn as actionable | no remote is expected before a repository begins sharing records, while configured remote failures still need action
  • r-init107 f485f07 — promising rename following for several paths | git log --follow accepts one pathspec and the CLI already reports that limit
  • r-survsplit e73aed5 — keeping a single conservation rate with a footnote | the two failures need different fixes and one of them is not a defect in this project at all, so a shared number misdirects the reader on both
  • r-probepath 51f6446 — prepending the build output to PATH inside the check | it would make CI green by giving the probe an executable no user has, which is the reverse of testing what is configured
  • r-doctorprobe ed94491 — changing what init writes into settings.json | the installed hook was correct in every reported case; the check was reading a different world from the one it was auditing
  • r-f61a2c 9114cf0 — raising the guard threshold | the frozen corpus puts every true positive in the middle band, so a higher cutoff removes signal without repairing score order
  • r-f61a2c 9114cf0 — dropping keyword evidence | long proposals and diffs dilute Jaccard, while one-token alternatives such as Redis still need lexical containment
  • r-init96a 913c7e3 — running doctor --fix first, hooks/index after (the old README recipe's order) | doctor's report would open with findings the next two steps are about to fix, which reads as init shipping with a problem it does not have
  • r-init96a 913c7e3 — matching doctor's exit code exactly (0 unless a check is fail) | a warn init cannot resolve itself (no remote, PreToolUse hook) would then sit under a "completed cleanly" summary line — the same silent-success shape doctor's own fix breaks git fetch and git pull, then reports ok #63 and inject --hook-input is byte-identical on malformed input and on no-records: silent fail-open #67 were
  • r-fix92dupid 7f41a6e — flagging every duplicate sha in a group regardless of payload, matching parse's labelRecordBlocks exactly | validate's own collision-check array pairs repositoryRecords (already carrying the message's last block once) with a per-block candidate; an unconditional duplicate-sha rule would flag the message's own single last block against its own re-derived copy, a false positive fixed instead by building ownRecords once per message rather than loosening the predicate past correctness
  • r-fix92dupid 7f41a6e — reverting core/query.ts's collectRows dedup key back to sha+source+seq once findIdCollisions was fixed | that key was already wrong on its own terms (it does not match the trailers table's unique index), and leaving it in place would keep silently dropping a commit's second record block from any scoped-path context query, collision or not
  • r-fix90shape 7f5766d — keeping the parent-count signal and also computing it for --message-file via .git/MERGE_HEAD | that only exists while a merge is in progress (the commit-msg hook's own scenario), not when re-validating an already-made merge commit's extracted message -- which is the issue's own reproduction and stays unreconciled under that approach
  • r-parsemulti 6d39d25 — expanding findIdCollisions/index-db to flag a same-message duplicate id generally | reaches into query.ts's lifecycle fold and every consumer route (context, guard, stale) for a case bug-issue-89 only asked parse to surface; the smaller, requested fix is in parse alone.
  • r-multirec01 92aeb24 — minting a fresh Record-Id for an inherited record that never declared one | no other code path in this project invents identity; Record-Id is always author-declared, and doing it here would be new scope beyond what the issue asked for
  • r-multirec01 92aeb24 — a CI step comparing a PR's commits against its post-merge squash commit, as the primary mechanism for finding 1 | needs a GitHub API dependency this tool takes nowhere else, and can only run after the squash is already pushed; doctor catches the same mistake locally, before push, when the source branch is still in refs/heads
  • r-83d43117 8a49ddc — an install-root check for COMMITLORE_BIN | its only reason to exist is aiming the hook at a build outside the install root -- a test harness, a monorepo's local bin. Restricting its location would remove the one thing it is for; doctor's existing COMMITLORE_BIN visibility is the right amount of scrutiny for a channel that is supposed to point anywhere.
  • r-83d43117 8a49ddc — resolving commitlore.bin with an external realpath/readlink -f binary | neither is guaranteed on every machine a hook runs on, the same reasoning 1fd0d53 gave for not shelling out to shasum. cd ... && pwd -P is a POSIX-specified builtin and covers the same physical-resolution need without a new dependency.
  • r-exit065 e545dee — leaving guard's inversion in place and only documenting it | guard and inject are the two commands wired into hooks and CI, so a script written against guard today is already trusting the number; a documented inversion is still an inversion, and it gets load-bearing the longer it sits. The surface is one boolean per command right now — cheaper to fix once than to explain forever.
  • r-fix70a1 d707fc7 — exhaustive per-language phrase enumeration | unbounded phrase lists cannot provide semantic coverage, so this fix documents a bounded lexical policy and independent corpus
  • r-shwt66 5efa206 — checking --git-dir/shallow | linked worktrees keep the shallow marker in the common Git directory
  • r-fix760 fb8ba45 — testing commits with more than one parent in dogfood scope | their platform-generated merge messages carry no authored decision, so requiring a record would require one nobody wrote
  • r-refint74 572f573 — allowing a note to extend a commit record under the same Record-Id | notes are remote-reachable, so divergent content would inherit a human-approved identity
  • r-warn75 24c7cc8 — accepting run-on or indented trailers | Git treats them as prose and accepting them would make records ambiguous
  • r-doctor72 996bcde — choosing either parent doctor list | each drops a runtime check required by the other incident
  • r-fix067 a915af0 — treat an outside-repository file_path as a legitimate no-record result | only an in-repository query can truthfully establish that no records apply; silence would hide a broken matcher
  • r-fix063h f0bb995 — leave hook runtime as a separate contradictory check | a fix-bearing installation check must not report ok or warn when the installed hook demonstrably fails
  • r-fix063 0b8c496 — add the explicit refspec only after confirming the remote ref exists | setup would depend on today’s remote state and require another doctor run after the first notes push
  • r-fix053 ecc4b90 — add a public mirrored boolean | the per-record sources array already models contributing channels, so a second representation would create drift
  • r-fix055 43b40f8 — infer whether a quote supports Verified | deterministic text matching can prove presence, not that a check ran
  • r-fix055 43b40f8 — downgrade harvested Verified to reconstructed | it preserves a citation-bearing assertion the verifier cannot substantiate
  • r-fix054 664d4e2 — write X-Inherited-From into commit messages too | transport metadata would lengthen every preserved user-facing message merely to restore symmetry
  • r-fix054 664d4e2 — stop squash-preserve from writing notes | it discards the mirror instead of fixing the query seam
  • r-fix056 55cb8bc — reuse the injection omission list for blocked withholding | it includes prose-bearing Evidence and Expires, so it is not a safety boundary
  • r-7b26f1 ec070ec — retrying the read inside the transaction | a write lock held across a subprocess is a worse failure than the one being fixed
  • r-1e58d3 1fd0d53 — removing the recorded-path branch entirely | a clone is on no PATH and in no node_modules (ADR-0011), and that branch is the only thing that finds the CLI there
  • r-1e58d3 1fd0d53 — hashing the recorded binary at install time | shasum is not guaranteed on every machine a hook runs on, and a check that silently no-ops is worse than the one being replaced
  • r-9c74b3 68340e4 — withholding only in --json | a shell agent reads stdout, and the text form is what it reads
  • r-4b17f8 7efba5c — retrying the read inside the transaction | a transaction holding a write lock while it shells out to git is a lock held across a subprocess
  • r-7a48c3 b85d847 — a CONFLICTED lifecycle state for divergent declarations | see above — it would block work on records that have a correct answer
  • r-8d51a6 27f73b0 — filtering blocked matches out of the result | the caller needs to know something matched; withholding is a rendering decision, made once
  • r-8d51a6 27f73b0 — reusing exit 1 for "could not check" | 1 already means a broken invocation, and a hook that cannot tell a bad flag from an unreadable repository will treat both as noise
  • r-2f7d94 a7673d0 — an allow-list of free-text keys | it is the shape of the original bug, and a new key would be unguarded until someone remembered
  • r-2f7d94 a7673d0 — leaving the wording generic ("a trailer") | an operator needs to know which line to edit, and the key was available two frames up
  • r-4e29b7 66829bb — folding this into the existing notes field | they are independent axes and can co-occur; one enum would have to enumerate the product
  • r-4e29b7 66829bb — throwing on an unreadable repository | context runs from a hook on every edit, and an exception there is a broken editor rather than a refusal
  • r-1c47e9 0e9930b — dropping the check | the two installation failures it exists for are real and were both invisible to configuration reads
  • r-1c47e9 0e9930b — probing whichever file is newer | "which artifact is this installation" is a fact about the layout, not about timestamps
  • r-3d92a8 f85101a — keeping the searches first and fixing the shim | the shim belongs to npm, not to us, and the version-skew problem survives the fix
  • r-3d92a8 f85101a — a config-only hook check | it was written, it reported ok, and the hook failed on the next commit
  • r-7c05e2 218ea28 — fetching notes automatically when the ref is missing | a query is a read, and silently reaching the network on a read is a surprise that belongs to git fetch
  • r-7c05e2 218ea28 — leaving it to doctor | doctor is run by a person once, and the answer that misleads is the one an agent gets on every task
  • r-7c05e2 218ea28 — a diagnostic string alone | the field it qualifies is records: [], and prose is not something a consumer can branch on
  • r-9b31c7 e8d45fb — keeping the placeholder until author trust was configurable | the placeholder was the permissive direction, so waiting meant shipping the hole
  • r-9b31c7 e8d45fb — withholding blocked payloads from the CLI too | a person reading a terminal can disbelieve a sentence; a tool result is retrieved fact
  • r-9b31c7 e8d45fb — dropping blocked records from the MCP answer entirely | an agent that silently receives less than there is cannot notice, and cannot audit
  • r-6c48b2 aaadedf — matching the whole file at edit time | the file contains everything the agent did not write, and GUARD-CANNOT-BLOCK measured prose surfaces producing false alarms specifically on compliant agents
  • r-6c48b2 aaadedf — blocking on a match | the score bands overlap, measured
  • r-6c48b2 aaadedf — running T-705 · guard route benchmark — measure the path SPEC §5 assigned to Ruled-out #37 without this pre-check | an arm that fires zero times measures nothing, and 120 runs is an expensive way to learn that
  • r-5b9e37 010782c — baking the resolved path into the hook stub | hooks status is a byte comparison against commitMsgStub(), so every hook installed from a different checkout would report outdated forever
  • r-5b9e37 010782c — an npx fallback | the existing comment is right — npx --no still queries the registry when the package is absent, putting a network call on every commit and breaking offline commits
  • r-2f9c40 07f47ca — wiring guard into the plugin as a blocking hook | true and false positives occupy the same score band on real agent output, so the only precision-safe threshold catches 1 of 5 and every useful threshold blocks four compliant edits in twenty-five
  • r-2f9c40 07f47ca — raising RECORD_ID_WEIGHT's threshold instead of gating the signal | the false alarms scored 1.0000, so no threshold below the maximum excludes them and the maximum excludes everything
  • r-2f9c40 07f47ca — semantic matching to separate the populations | ADR-0002 keeps the core LLM-free and zero-cost, and B-04 · Optional embedding-search tier #31 registers embeddings as opt-in — nothing measured here justifies moving that into the core
  • r-7a3e91 cf859e4 — inlining spec/SPEC.md and the schema into the bundle | SPEC.md would need a codegen step that itself needs a drift guard, and the package-root walk removes the reason to want it
  • r-7a3e91 cf859e4 — replacing the tsc output with the bundle | test/cli.test.ts, test/hooks.test.ts and test/mcp.test.ts import dist internals by path
  • r-8a2f61 1a5adec — leave commander's default and document it | the code is the interface a hook sees, and documentation does not reach it
  • r-8a2f61 1a5adec — give guard a distinct code for matches | 2 is the contract its ticket specified and callers already treat it as a warning
  • r-3f7a29 49817dc — repair a draft that fails verification | backfill's source material is weak enough that a repair loop would mostly be inventing
  • r-3f7a29 49817dc — write reconstructed records into commit messages | history rewriting is irreversible and reaches every existing clone
  • r-3f7a29 49817dc — post a fresh comment per push | it turns the signal into noise and the check gets muted
  • r-2b8f45 0adcaf5 — raise the threshold until the false positives clear | it takes the weakest true positive with them
  • r-2b8f45 0adcaf5 — exclude the observed false positives by hand | it fixes the ten measured and nothing else
  • r-2b8f45 0adcaf5 — return an error from the MCP guard tool when nothing matches | an agent cannot tell that from "not ruled out"
  • r-8c4a17 f2ab0c2 — pick the first or last value for a single-valued key on conflict | ordering is not judgement, and the value feeds an approval gate
  • r-8c4a17 f2ab0c2 — fuzzy or token-similarity matching for evidence | a fabricated quote that shares most of its words is exactly the case this exists to catch
  • r-8c4a17 f2ab0c2 — mint a fresh Record-Id for the merge | it would claim authorship of decisions made on the branch
  • r-9a5e17 6d68703 — let each command edit src/cli.ts | guaranteed conflict, and the conflict surfaces only after every worker has finished
  • r-9a5e17 6d68703 — npx fallback in the hook stub | a network call on every commit, and offline commits start failing
  • r-5a8c04 c46a577 — regex trailer parsing | B3 prose false-positives are the failure mode this protocol exists to prevent
  • r-5a8c04 c46a577 — a custom GitError subclass | the convention here is new Error with structured context, and callers read GitResult

Warnings (54)

  • r-nobin284 ab2f08f (claim) — an extensionless COMMITLORE_BIN is now ignored rather than honoured -- the hook falls through to the recorded install and to PATH, so a caller who pointed it at the wrapper still gets a working hook, by a different route
  • r-pending311 4985a3a (claim) — gc_eligible mirrors gcPending's rule rather than sharing its code -- a change to the collection rule has to be reflected in both, and the test that pins never-collected is what will catch a drift
  • r-query307 3cf3c58 (claim) — the probe costs one git log per ancestor, capped at four -- it runs only for a single path whose own history is empty, so an ordinary query with records pays nothing
  • r-capture309 5a9655d (claim) — prepare and verify must be given the same diff bytes -- prepare derives them from git diff --cached, so any caller that resolves a diff separately has to resolve it identically or the transaction is refused
  • r-hooks296 b701559 (claim) — nothing is recorded when the entry cannot be resolved -- the stub then uses COMMITLORE_BIN, PATH or a node_modules walk, which is the intended fallback rather than a failure
  • r-wrapfallback 5a78bcc (claim) — the wrapper enforces no Node version at run time -- the installer's Node 22 check holds only until the recorded interpreter path changes
  • r-f14owner281 f149ff9 (claim) — T-1120 must land the installer and all four README shell-install regions in one commit -- a two-commit split puts a false README on the branch, which is the window this correction closes
  • r-t1110policy 9e7b37a (claim) — the key order in POLICY_DEFAULTS is the hash input -- reordering those three lines changes the digest every pending file in flight was written with, which the pinned test exists to catch
  • r-gateb4replan 5a35062 (claim) — T-1125 must preserve install-root containment for the wrapper case -- a diff that deletes the check together with the compiled-binary arm removes a verified security property
  • r-t1109adv 4398856 (claim) — Advisory text reaches a model; only RenderedGuardMatch (never raw GuardMatch) may be written to the pending file
  • r-dupsuccorder f46c02d (claim) — preserve chronological ordering when changing duplicate detection
  • r-dupsucc729 5a6b238 (claim) — a successor must remain later than the duplicate declaration
  • r-epipe2026 d9ee9ff (claim) — preserve a non-null status before interpreting an EPIPE input-pipe race as a spawn failure
  • r-init107 f485f07 (claim) — a configured but unreachable remote remains an actionable warning and exits 1
  • r-f61a2c 9114cf0 (claim) — keyword-strength is IDF-weighted keyword mass multiplied by unweighted distinctive-token coverage; do not restore the old identity label
  • r-fix90shape 7f5766d (claim) — a message whose first line happens to start with one of these templates on a non-merge, non-CommitLore commit would also be excused if its trailer-shaped final paragraph were entirely unknown-key -- the same shape of risk bug-issue-76's original parent-count gate carried for genuine merge commits, now traded for one that is at least computable the same way everywhere
  • r-parsemulti 6d39d25 (claim) — context and validate still do not catch two commit-sourced blocks in the current message sharing one Record-Id unless a notes mirror also disagrees (findIdCollisions requires a notes-sourced record in the group) — parse's check is intentionally independent, not a call into that function, so fixing this in context/validate is separate work.
  • r-multirec01 92aeb24 (claim) — index-db.ts SCHEMA_VERSION is now 2 for the added block column — bump it again, not the shape in place, the next time the trailers table changes.
  • r-83d43117 8a49ddc (claim) — the containment check resolves the recorded path's directory physically and separately rejects a symlink at the final component, but does not protect a relative commitlore.bin with no path separator (a bare filename), which resolves against the hook's cwd and could coincide with the install root in a repository that installs commitlore against itself. That residual case sits in the same accepted-risk category as the severity note above: an attacker who can write that config key already has an equivalent, direct route.
  • r-exit065 e545dee (claim) — guard's swap is a breaking change for anything scripted against the old exit 2 for "matched" — see CHANGELOG Unreleased/Breaking
  • r-fix70a1 d707fc7 (claim) — add malicious and benign fixtures together when extending scanner patterns; false positives can make the defence unusable
  • r-merge66 40e7987 (claim) — test/hooks.test.ts must keep both worktree git-path resolution and reference-integrity assertions
  • r-fix760 fb8ba45 (claim) — unknown-only final paragraphs are treated as prose only when the source is a multi-parent commit
  • r-refint74 572f573 (claim) — exact commit and note mirrors remain one logical record; only divergent note payloads collide
  • r-warn75 24c7cc8 (claim) — query several paths one at a time when rename history matters
  • r-shallow66 60a8659 (claim) — shallow history remains advisory; query and guard exit-code semantics are unchanged
  • r-doctor72 996bcde (claim) — keep commit-msg health bound to hook-runtime and keep inject-runtime as a separate known-good payload probe
  • r-fix067 a915af0 (claim) — the accepted tool set is the union of the plugin and settings matchers; aligning those matchers remains Shallow clone: answers from 1 commit of history without saying history is truncated #66
  • r-fix063 0b8c496 (claim) — the wildcard fetches every ref under refs/notes, including notes owned by other tools
  • r-fix055 43b40f8 (claim) — Verified remains valid protocol vocabulary for facts recorded from actual command or test execution; only harvest refuses it
  • r-7b26f1 ec070ec (claim) — the concurrency test is deterministic rather than sleep-based, so it proves the transaction boundary and not the absence of every race
  • r-1e58d3 1fd0d53 (claim)COMMITLORE_BIN still accepts any executable, deliberately — a harness must be able to aim the hook at a specific build. It is now reported rather than restricted
  • r-9c74b3 68340e4 (claim)context now prints [blocked] beside a record whose payload is gone, which is more visually alarming than the old silent leak. That is the intended direction — a withheld record should be conspicuous — but it changes what a clean repository's output looks like the first time someone commits a Warn: that trips a pattern by accident
  • r-4b17f8 7efba5c (claim)deleteNoteRows opens its own transaction inside the new outer one. better-sqlite3 nests these as savepoints; node:sqlite has neither, so ADR-0012's migration must flatten this rather than assume it works
  • r-7a48c3 b85d847 (claim) — these two changes were developed concurrently in one worktree and share a built dist/. Splitting them would leave one commit whose dist/ did not match its src/, so they land together and are described together
  • r-5c92e0 73b1285 (claim) — the delegate reported "943 passed" for a suite whose baseline is 1108. It ran while another task was writing to the same worktree and collected a partial set. The real count, verified here on a quiet tree, is 1109 across 31 files — but a delegated test count is now a claim to check, not a result to accept
  • r-8d51a6 27f73b0 (claim) — guard stays advisory. Nothing here makes it block, and GUARD-CANNOT-BLOCK still holds — the point is that it no longer lies about what it saw
  • r-2f7d94 a7673d0 (claim)Evidence: and Expires: are now scanned. Both usually hold paths and dates, so a false positive there withholds a legitimate record. No case is known; a legitimate record carrying a path, a URL and a date was checked and passes
  • r-4e29b7 66829bb (claim)historyAvailability spends two git invocations per query. Both are metadata reads, but this is a hot path and nothing measures it yet
  • r-1c47e9 0e9930b (claim) — this is the second defect in three days from assuming the development checkout is the deployment. The first was exec node in the run script
  • r-3d92a8 f85101a (claim)hook-runtime executes the hook on every doctor run. The probe message is valid so nothing is written, but it is no longer a read-only command
  • r-3d92a8 f85101a (claim) — the check pins PATH to /usr/bin:/bin, which assumes git is there. On a system where it is not, this reports a hook failure that is really a probe failure
  • r-7c05e2 218ea28 (claim)notesAvailability runs git rev-parse and up to two git config reads on every query. Config-only, no network, but it is not free on a hot path
  • r-9b31c7 e8d45fb (claim) — the default is now fail-closed on every route — with no --trusted-author, every Warn: grades claim. That is SPEC §7 and it is what inject already did, but a user who saw [directive] yesterday will see [claim] today
  • r-6c48b2 aaadedf (claim) — recall here is against four re-proposals. It is a go/no-go signal for whether an arm has anything to measure, not an effect size
  • r-5b9e37 010782c (claim)commitlore.bin and commitlore.node are local config, so they do not survive a fresh clone of a repository whose hook was installed elsewhere — re-run hooks install there, which is what the failure message now says
  • r-2f9c40 07f47ca (claim) — --require-content changes precision, not recall; it removes a false-alarm class and catches nothing new
  • r-7a3e91 cf859e4 (claim) — hardcoding ../ counts back to the package root is what broke this — new code reads assets through installedPath(), never through import.meta.url
  • r-8a2f61 1a5adec (claim) — exitOverride on the program alone silently misses subcommands -- the test matrix includes a bad flag on two different subcommands for exactly that reason
  • r-3f7a29 49817dc (claim) — every backfilled record is Provenance: reconstructed, which the trust model always renders as a claim -- do not add a path that lets a draft override that field
  • r-2b8f45 0adcaf5 (claim) — the matcher is tuned against this repository's rejection corpus, where those common words come from -- re-measure the margin on a corpus with different vocabulary before trusting the threshold there
  • r-8c4a17 f2ab0c2 (claim) — squash-preserve writes nothing without --message-file or --target; the bare form is a plan, and scripting it means passing one
  • r-9a5e17 6d68703 (claim) — commands are advertised in --help only once they work -- test/cli.test.ts holds the landed and unlanded lists, and moving a name between them belongs in the commit that wires it
  • r-5a8c04 c46a577 (claim) — do not add --grep anywhere under src/ -- test/source-guards enforces its absence and will fail the build

git log --follow accepts exactly one pathspec, so renames are not followed for 15 paths; query one path at a time to follow its rename chain

withheld the content of 2 record(s) graded blocked: a Ruled-out, Verified trailers matching an injection pattern is reported, never quoted (SPEC §7)

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

…agent

The inventory said four agent configs and bound the contract to that number.
Both installers write five: Windsurf, under Codeium's directory, is wired by
`wire_windsurf` and `Wire-McpServersJson 'windsurf'` and appears in neither the
list nor the count. The checkout figure has moved too -- 1206 files at `6e1d46d`,
1366 re-measured at `51abef8`.

The entry-shape sentence was short by two. It names
`{command, args:["mcp"]}` as *the* shape; that covers the three `mcpServers`
agents, and codex spells the same pair as TOML keys, and opencode's command is an
array with two further keys. An implementation built from that sentence alone
leaves opencode's entry behind -- silently, because a removal that finds nothing
is indistinguishable from a removal with nothing to do.

Both corrections come from executing the shipped installer rather than from
reading it, and both were surfaced by the bidirectional assertion the ticket
itself asks for. That is worth recording where the requirement lives: the
argument for reading the installers instead of trusting a list is that the list
was wrong, in the document making the argument.

Record-Id: r-inventory1123
Limit: a measured number in a ticket ages against the code it measured, so a contract bound to one has to name the head it was taken at
Ruled-out: Leaving the inventory as it was and noting the gap only in the pull request | the ticket is the authority a later reader consults, and a pull request is not where a corrected measurement survives
Ruled-out: Binding the implementation to the corrected file count | it will move again at the next merge; the path table is derived from the installers instead, which does not
Certainty: firm
Blast: local
Undo: easy
Verified: install.sh wires five agents and install.ps1 writes the same five paths; a scratch-HOME install at this head produced 1366 checkout files; the focused suites still pass at 34 assertions
Unverified: nothing new -- this commit changes documentation only
@MongLong0214
MongLong0214 marked this pull request as ready for review July 31, 2026 16:42
@MongLong0214
MongLong0214 merged commit 2c1eeaa into dev Jul 31, 2026
8 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.

T-1123 Uninstall: name the plugin path, remove what the shell/Node installer wrote

1 participant