Skip to content

Release v0.4.0 - #255

Merged
MongLong0214 merged 78 commits into
mainfrom
dev
Jul 30, 2026
Merged

Release v0.4.0#255
MongLong0214 merged 78 commits into
mainfrom
dev

Conversation

@MongLong0214

Copy link
Copy Markdown
Owner

Promotes dev to main for the v0.4.0 tag. 78 commits since v0.3.0.

Why this release matters to a user

Recording a decision becomes something the tool does rather than something you remember to ask for.

  • commitlore capture runs prepare, verify and stage as one command. You never type trailer syntax. Most commits still carry nothing, and a verification failure produces no record without failing the command.
  • A record can no longer attach to the wrong commit. Five conditions must hold before a staged record is applied — unchanged HEAD, unchanged staged diff, staged, unexpired, unconsumed, unchanged policy — and if any fails the commit proceeds untouched. A post-commit finaliser then consumes it exactly once, bound to the commit that actually resulted.
  • commitlore demo shows the product in a temporary repository with no network and no model, and removes what it created even on failure.
  • commitlore init reports readiness instead of internal step names, with the old output behind --verbose.
  • Three write-side MCP tools give an agent the same capture contract, writing only into .git/commitlore/pending/ and never Git history. There is deliberately no write_record tool.

What it does not claim

Nothing here measures whether an agent behaves differently for having received a decision. The fresh-agent recovery protocol is registered and unrun, and the changelog says so.

Guard is now labelled an experimental advisory everywhere it is exposed — CLI help and output, the MCP tool description, the README's known limitations — with its measured precision 44.8% (95% Wilson 32.7%–57.5%) and recall 22.0%. The MCP description no longer tells a caller that an empty result is a verdict rather than an absence, because at that recall it usually is an absence.

Windows and musl remain unsupported. Capture's write-side cost is still reported as not instrumented rather than as a number.

Release gate

check result
version agreement package.json, .claude-plugin/plugin.json, binary --version all 0.4.0
README install pin all four language files pin v0.4.0 — caught by test when they did not
record lint over the range validate --range origin/main..dev — 0 violations, 0 secrets
answers or says it cannot unborn repository reports history: "empty"
both typechecks tsc -p tsconfig.json and tsc -p bench/tsconfig.json exit 0
committed dist/ matches src/ by fresh rebuild
issues every issue in the repository is closed; all milestones at 0 open

Pushing the tag is what triggers the four-platform binary build, so that is the step after this merges.

MongLong0214 and others added 30 commits July 30, 2026 10:41
ADR-0020, ADR-0021, ADR-0022 and PRD-F9 all cite a "CTO acceptance matrix"
(rows P0-1 through P0-8, P1-5) as an authority they depend on or are accepted
against. No such file was ever committed. An authority reference that points
at nothing is exactly what A-001's own acceptance criterion (from the source
review these documents trace to) rules out.

docs/GATE-A-ACCEPTANCE.md reconstructs each row from where its label was
actually cited, not from a recovered original — the file states this and
records the one grep that would disprove it (git log -p --all for any prior
text). Two rows had no ticket:

P0-4 (the P0-3 architecture proven end-to-end, not only per-ticket unit AC)
had no closing ticket. T-1023 adds one: real CLI/MCP invocation, real Git
commit, real linked worktree, not mocked modules.

P0-7 has no prior citation anywhere in this repository. It is not a recovered
row; it is named from an operational gap found by reading PRD-F9's 14
requirements end to end: none of them mention removing a pending-transaction
file, and T-1002's prepare step writes one on every capture attempt including
ones that verify empty. Nothing ever deletes it. T-1019 adds recovery and GC.
The numbering gap (T-1018 to T-1020, P0-6 to P0-8) is circumstantial and is
labelled as such in the document — it is not evidence of a lost definition.

Separately, T-1007, T-1008, T-1009 and T-1020 all edit src/mcp/server.ts.
T-1009 already depended on T-1007 and T-1008, but T-1007/T-1008 had no
ordering between each other, and T-1020 depended only on the already-accepted
ADR-0020 — so all four were eligible to start in parallel against the same
file. Each ticket now states the merge order explicitly.

Ruled-out: rewriting ADR-0020/0021/0022 or the existing #193-213 issue bodies | they already meet or exceed the source review's own requirements (exact ownership, forbidden scope, RED/GREEN, AC-to-test, four-tier verification, evidence invalidation, stop conditions); rewriting working documents to match a stale review would be pure churn
Ruled-out: inventing content for a hypothetical P0-7 original definition | none exists in history (verified); asserting one would be the exact failure this document exists to prevent
Limit: nothing enforces the src/mcp/server.ts merge-sequencing rule automatically — it is a process constraint the next PR author has to read and follow
Blast: local
Undo: easy
Certainty: firm
Record-Id: r-gateaacc
Land the Gate A acceptance matrix and its two missing tickets
T-1010's RED test and AC table asserted active.lifecycle, superseded.lifecycle
and proposal.lifecycle directly on static fixture objects. Two independent
problems, both verified against current source before writing this fix:

Lifecycle (src/core/types.ts:151) is 'active' | 'superseded' | 'expired' --
'proposal' is not a member, so that assertion could never pass. And
Record.lifecycle (types.ts:162) is documented "Computed by the stale engine",
never a property a hand-authored CommitMessage carries -- it comes from
folding Supersedes/Expires through real git history (src/core/stale.ts).

T-1010's own Forbidden scope already rules out filesystem operations for this
ticket ("the fixture is pure data, not a temp repo creator"), so it cannot
fold history and cannot know a lifecycle value. T-1011, which the same
document already has creating real commits and running the real query,
already asserts the outcome correctly via its own AC row ("Output shows
lifecycle filtering... stdout includes the active record, excludes the
superseded one") -- that ticket needed no change.

The fixture now exports raw commit-message text (predecessor, successor,
proposal) plus the two expected record ids, and the AC table checks structural
properties a static fixture can actually have: schema validity, the Supersedes
link, and record-id agreement. No object in the file carries a .lifecycle
property.

Ruled-out: adding a stale engine call inside T-1010 to compute lifecycle there | contradicts the ticket's own forbidden scope, and duplicates work T-1011 already does correctly
Limit: this repairs the two tickets that referenced the broken shape (T-1010 directly, T-1011 needed no change); it does not add a general check that a future ticket cannot reintroduce a static .lifecycle assumption
Blast: local
Undo: easy
Certainty: firm
Record-Id: r-fixdemofx
Correct T-1010's fixture shape: lifecycle is computed, not authored
…-path

T-1001 never specified how src/core/pending.ts resolves .git/commitlore/pending/,
and ADR-0021 claims that directory "is per-worktree and per-clone" as a property
of the design. That property only holds if the path is resolved correctly.

A linked worktree's .git is a file containing a gitdir pointer, not a directory;
path.join(cwd, '.git', 'commitlore', 'pending') resolves against the wrong
location there. This codebase already has the fix as an established pattern --
src/core/index-db.ts:325 states outright "--git-path is what makes this
correct", and src/hooks/prepare-commit-msg.ts uses git rev-parse --git-path
twice already. The ticket for the new transaction store simply never pointed
at that precedent.

Ruled-out: leaving this to the implementer's judgment | the precedent already exists in this codebase and the failure mode (silently wrong location in a worktree) would not surface as a test failure unless someone specifically writes a worktree test -- worth pinning explicitly rather than hoping it's noticed
Blast: local
Undo: easy
Certainty: firm
Record-Id: r-pendpath
Pin T-1001's pending-directory path resolution to git rev-parse --git-path
Four of the eight Gate A rows had been reconstructed from where their labels
happened to be cited rather than from the source review, and the reconstruction
put a different subject in each. P0-4 had become an end-to-end proof
requirement, P0-7 pending-transaction cleanup, and P0-8 capture-pipeline guard
integration. P0-1 and P0-5 had been narrowed to one sub-item each.

P0-4 is the MCP capture write-side, ticketed to close by T-1007/T-1008/T-1009.
P0-7 is result-oriented init output, ticketed to close by T-1012/T-1013. P0-8 is
one commitlore_before_change tool, and it now has T-1024 (#219) rather than
sitting unscheduled.

Two tickets had been created to close the fabricated P0-4 and P0-7. Both name a
real gap and both survive as independent audit findings claiming no row, each
reduced to one responsibility: T-1019 drops status and discard and keeps
garbage collection; T-1023 narrows from every Gate A ticket to the capture
pipeline it actually exercises, and its test path follows the flat convention
this repository uses.

Every row's ticket column is now "ticketed to close" because every one of those
issues is OPEN. A row records that a recommendation was accepted and ticketed,
never that it is done -- the previous wording read as completion.

Ruled-out: keeping T-1019 and T-1023 attached to P0-7 and P0-4 with corrected wording | the rows they claimed do not mean what those tickets do, so the attachment was the defect rather than the phrasing
Ruled-out: leaving P0-8 unscheduled with the confidence-separation question open | the review asks for it in Gate A, and T-1024 answers the question structurally instead: guard_confidence qualifies possible_revival_matches and the context fields never inherit it, so no second label is invented
Limit: nothing enforces that a later editor re-derives a row from the review rather than from its citations, which is exactly how these four drifted
Blast: local
Undo: easy
Certainty: firm
Record-Id: r-m5align730
An independent exact-head review caught the last completion claim in the set.
T-1024's opening line said it "closes acceptance row P0-8" while #219 is OPEN,
which is the same defect the rest of this branch removed: a row's ticket column
records that a recommendation was accepted and ticketed, never that it is done.

The status legend at the top of the acceptance matrix already says so, and one
line contradicting it is worse than no legend, because a reader who trusts the
legend then reads a completion claim has no way to tell which is current.

Ruled-out: leaving it and noting the legend covers it | a legend that one line contradicts stops being a rule and becomes a preference
Limit: nothing mechanically prevents the next editor writing "closes" again; the phrasing rule lives in the legend and in review, not in a check
Blast: local
Undo: easy
Certainty: firm
Record-Id: r-m5align730b
The preamble still described the table as reconstructed from wherever each row
label happened to be cited, and counted the damage as four wrong subjects while
naming three. Both statements were left over from the version this branch
replaced, and a reader following them would derive the next row the same wrong
way. The source review is the primary authority for a row; the ADRs, PRDs and
tickets are cross-checks, and a disagreement is a drift to fix rather than a
competing definition.

Four mappings were wrong in ways a reader could not detect from the row alone.
P0-3 asserts a pipeline through consume but listed only T-1001-T-1009, omitting
the ticket that owns consumption. P0-6 asserted a generic hero-and-order change
where the review specifies an explicit product-first section order, so the row
could be satisfied without moving the measurement below the product. T-1023
called itself the last Gate A ticket, which stopped being true when T-1024 was
added. And ADR-0020 governs T-1024 without naming it, so the link ran one way.

ADR-0020 now lists the four tickets it governs and states the
confidence-separation constraint as a binding acceptance criterion rather than
leaving it in the ticket alone: guard_confidence qualifies
possible_revival_matches, the context fields never inherit it, and no second
label is invented to make the asymmetry visible.

Ruled-out: keeping the provenance paragraph and correcting four to three | it duplicated the rewritten preamble, and two accounts of the same history is how the first one drifted
Ruled-out: recording the confidence-separation rule only in T-1024 | the constraint outlives the ticket, and an ADR a ticket governs itself by must be readable from the ADR
Limit: nothing mechanically checks that a row's assertion still matches the review; the ordering rule is stated here and enforced in review only
Blast: local
Undo: easy
Certainty: firm
Record-Id: r-m5align730c
The preamble says the source review is primary for every row, and three rows
did not name it: P0-2, P0-3 and P0-6 cited only their ADR. A row whose stated
authority is an ADR can be re-derived from that ADR, which is the drift the
preamble exists to prevent, so each now names the review section first and the
ADR as the cross-check.

Two Verification cells proved less than their row asserted. P0-1 asserts the
release is closed end to end but pointed only at the README install sub-item, so
a reader could satisfy the row while the tag, the assets and the version
agreement went unchecked; it now names T-1030's diagnostic AC and T-901's
release-process evidence. P0-6 asserts an explicit product-first section order
and pointed only at a merge-ordering note about a different ticket, so the row
could be closed without the measurement ever moving below the product; it now
names T-1014's and T-1015's AC tables.

Ruled-out: leaving the ADR as sole authority on rows whose ADR happens to be correct | correctness today is not the property being protected -- the next editor re-deriving from an ADR is
Ruled-out: pointing P0-1 at the release PR instead of at tickets | a PR is not a checkable acceptance criterion once merged, and the tickets carry the AC tables
Limit: T-901 predates this milestone and its evidence lives in the release workflow rather than in an AC table, so P0-1's release-process leg is verified by artifacts rather than by a test
Blast: local
Undo: easy
Certainty: firm
Record-Id: r-m5align730d
Align the M5 acceptance rows with the source review
Expiry moves from creation to stage success. A record in prepared or verified
phase now carries expires_at: null, and stage stamps staged_at together with
expires_at = staged_at + 5 minutes. The old wording would have expired a capture
that was still being verified, and the staleness it was guarding against is
already caught by the staged-diff-hash gate.

Two headings carried a prerequisite and a merge-ordering constraint in one
field. T-1020 and T-1021 depend only on ADR-0020, and T-1024 only on T-1020;
the src/mcp/server.ts and README orderings are file contention and now say so.
Read literally, the old wording pushed T-1020 from the first wave to the sixth,
T-1021 to the third and T-1024 to the seventh.

The trust boundary on the write path is now explicit. A caller-supplied nonce is
validated against ^[0-9a-f]{32}$ before it reaches any path resolution, the
capture CLI forwards the nonce and nothing else, and every binding a staged
record carries is recomputed server-side rather than accepted from the caller.

T-1015's five abstract section labels are pinned to concrete headings, because
an implementer cannot reorder sections against labels that never name them.
T-1016 is a Node-stdlib animated SVG checked byte-exactly, with no recorder
dependency and no manifest fallback. T-1030 is diagnostic honesty only and may
not close #192 alone.

Ruled-out: keeping expiry anchored to creation with a longer window | a longer window hides the same defect and makes it rarer rather than absent
Ruled-out: a frame manifest for T-1016 | a check that tolerates drift stops detecting the drift it exists to detect
Ruled-out: leaving verification_gaps to be derived at implementation time | it was the second invented concept this milestone, and naming the closed ordered set now is what stops a third
Limit: nothing mechanically enforces that a future heading keeps prerequisites and ordering in separate fields; the separation is stated and reviewed, not checked
Blast: local
Undo: easy
Certainty: firm
Record-Id: r-gate2amend
The default install one-liner referenced the mutable dev branch, meaning the
bytes a user executes are not the bytes any release was reviewed at. The pinned
example still said v0.2.0 while package.json is 0.3.0. Both are corrected
across all four language READMEs.

Replacements in README.md, README.ko.md, README.ja.md, README.zh-CN.md:
- Line 26: /dev/install.sh -> /v0.3.0/install.sh
- Line 71: /v0.2.0/install.sh -> /v0.3.0/install.sh
- Line 72: sh install.sh v0.2.0 -> sh install.sh v0.3.0
- Line 75: version=0.2.0 -> version=0.3.0

Ruled-out: referencing main instead of a tag | main is also mutable and does not name a reviewed release
Ruled-out: leaving the pin at v0.2.0 until the next release | v0.3.0 is tagged and published; the mismatch with package.json is a documentation defect now, not a timing issue
Limit: install.sh must already support tag-based download for the one-liner to work; verified that the URL resolves to a tagged tree
Blast: local
Undo: easy
Certainty: firm
Record-Id: r-pin030readme
The hook-runtime diagnostic unconditionally claimed "git's PATH carries no
node" regardless of what stderr contained. When the hook's node process ran
and threw (e.g., a stack trace from dist/mcp/server.js:49), the message
fabricated a cause the evidence did not support.

The probe now distinguishes three cases by inspecting exit code and the first
line of stderr:
- exit 127 or stderr matching "node: not found" / ENOENT: reports that the
  hook cannot find a node interpreter.
- stderr containing a stack frame or .js line reference: reports that node ran
  but threw, quoting the first line without asserting a cause.
- anything else: says "cause unclear" rather than guessing.

The ok-path message is unchanged. The intermittent failure at
dist/mcp/server.js:49 remains unexplained — that is #221, not this commit.

Ruled-out: 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
Ruled-out: 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
Limit: 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
Blast: local
Undo: easy
Certainty: firm
Record-Id: r-t1030diag
Define the static data module for the demo command: one predecessor decision
(will be computed as superseded once the successor lands in history), one
successor that retires it via Supersedes trailer (computed as active), and a
proposal text that re-proposes the reversed approach.

The fixture exports raw commit-message strings and identifiers only. Lifecycle
is never stated — it is computed by foldLifecycle from the Supersedes trailer
across chronologically ordered records (binding constraint from audit).

Closes #202

Limit: fixture validation requires git in PATH for parseCommitMessage
Blast: local
Undo: easy
Certainty: firm
Record-Id: r-t1010demo
Provenance: authored
CommitLore-Version: 2.0.0
Replace the verbose [1/4]…[4/4] default format with a concise
result summary (≤6 lines for a clean run). Internal command
names (interpret-trailers, notes refspec, index --rebuild) are
absent from the default output. Every failure and warning is
still named — never folded into a cheerful success message
(#63, #67 contract preserved).

The verbose format is preserved as an exported function for
T-1013 (--verbose flag, separate ticket).

Limit: --verbose flag not wired yet (T-1013)
Blast: local
Certainty: firm
Undo: easy
Record-Id: r-initresult204
fix(readme): pin install one-liner to v0.3.0 tag (#211)
feat(demo): add scenario fixture for demo command (#202)
fix(doctor): report what the hook probe knows instead of guessing (#192)
feat(init): result-oriented default output
The guard tool description told callers that an empty matched array is a
verdict, not an absence. At 22% recall an empty result is a miss roughly
four times in five — the sentence was false in the common case. The
description now states the experimental-advisory classification and the
measured precision (44.8%) and recall (22.0%) from the 417-decision corpus,
and replaces the overclaiming sentence with an honest disclosure that an
empty result does not guarantee safety.

The first two sentences are preserved. The annotations (readOnlyHint: true)
are unchanged.

Ruled-out: removing the description entirely | the first two sentences carry useful contract information a caller needs
Ruled-out: stating only precision without recall | a caller needs both to judge the signal; recall is the more dangerous gap
Limit: the test asserts on the exact precision and recall figures; a future re-measurement changes both the description and the test
Blast: local
Undo: easy
Certainty: firm
Record-Id: r-t1020desc
Limit: 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
Implements the monotonic prepare/verify/stage/apply/consume lifecycle from
ADR-0021. Every mutation is an atomic rename so no concurrent reader can
observe a partial file. Path resolution uses git rev-parse --git-path so
linked worktrees resolve correctly.

The nonce trust boundary validates ^[0-9a-f]{32}$ before any path join, so
no caller-supplied string reaches the filesystem unsanitised. expires_at is
null while the record is in prepared or verified phase and is stamped only
when stage succeeds, as staged_at + 5 minutes.

readPending returns null for absent files and throws a typed PendingFormatError
for corrupt or unknown-version content, distinguishing the two failure modes
that the hook must treat differently.

Ruled-out: setting expires_at at creation time with a longer window | a longer window hides staleness rather than preventing it, and the staged-diff-hash gate already catches real drift
Ruled-out: a shared atomicWriteJson utility exported from a utils module | three files already duplicate the pattern and the ticket scope does not include a cross-cutting refactor
Ruled-out: allowing stagePending on a prepared record when records are empty | skipping verification breaks the trust chain that prevents an MCP client from injecting arbitrary trailers
Limit: the test proves monotonic transitions but cannot prove absence of TOCTOU between read and rename on a loaded filesystem; atomic rename is the kernel-level guarantee
Blast: local
Undo: easy
Certainty: firm
Record-Id: r-t1001pend
Limit: 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
T-1020: disclose measured limits in MCP guard tool description
feat(core): add pending transaction store (#193)
Prepare is the half of capture that decides what a later verification is allowed
to check against. It records the HEAD, the staged diff hash and the evidence
hashes at the moment the agent is asked, so a draft cannot later be verified
against a tree that has moved underneath it.

Expiry is deliberately absent here. A prepared record carries expires_at: null,
because a capture that is still being verified must not expire while the person
is reading it. The clock starts when stage succeeds.

Ruled-out: stamping an expiry at prepare with a generous window | a longer window makes the same defect rarer instead of absent, and the staleness it guards against is already caught by the diff-hash gate
Ruled-out: letting the caller supply the nonce | the nonce is the key a later phase resolves a path from, so it is generated here and validated by the store against a strict pattern before any path resolution
Limit: the prompt contract is a string this phase emits; nothing verifies the agent honoured it until verify runs
Blast: local
Undo: easy
Certainty: firm
Record-Id: r-t1002prep
The product's value is a scene: a decision was reversed, similarity retrieval
returns the old one, lifecycle returns the current one. Until now a newcomer had
to build that scene themselves in their own repository before they could see it.

demo builds it in a temporary repository, shows the contrast using the existing
projection rather than new lifecycle logic, and removes what it created even when
it fails. It needs no network and no model, so it works on a plane and cannot
bill anyone.

Two of its properties are safety rather than features and are tested as such: the
temporary directory is gone after a crash, and the user's own repository is never
written to.

Ruled-out: 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
Ruled-out: 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
Limit: the scene is one fixed pair of decisions, so it demonstrates the mechanism rather than measuring how often it matters
Blast: local
Undo: easy
Certainty: firm
Record-Id: r-t1011demo
Known limitations named Windows, musl, unbuilt features and that M4 never tested
a guard effect, but not guard's own quality. A reader could therefore install
this, run guard, see nothing, and conclude their proposal was clear.

At recall 22.0% an empty result is the common case for a genuine revival, not
evidence of one's absence. That now appears in all four languages with its
interval, next to the precision figure, so the number cannot be quoted without
the uncertainty attached.

Ruled-out: stating precision alone | precision without recall reads as a quality score and hides the failure mode that matters, which is silence
Ruled-out: publishing the point estimates without the Wilson interval | a figure without its interval is the defect this project removed from the README twice already
Limit: the figures are measured against one archived 417-decision corpus, which is deliberately hard and is not deployment prevalence
Blast: local
Undo: easy
Certainty: firm
Record-Id: r-t1021known
Implements T-1024: a single read-only MCP tool that returns path-scoped
context alongside an optional experimental guard result, with confidence
levels kept structurally separate per ADR-0020.

The response carries exactly five fields and no more. guard_confidence
qualifies possible_revival_matches only. verification_gaps is a closed
ordered set of three known checks. cache_key structurally separates
context-only from proposal-bearing calls.

Record-Id: r-t1024bc
Limit: 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
Verified: 14 focused tests pass, 49 test files / 1491 tests pass full suite, both tsc invocations exit 0, mutation oracles confirm test sensitivity
MongLong0214 and others added 28 commits July 30, 2026 22:19
prepare, verify and stage are the right internal decomposition and the wrong
thing to ask a person to remember. capture runs all three and reports what
happened, so the user never types trailer syntax and never learns the phase
names.

It composes the three core modules and adds no logic of its own. It also passes
the nonce and nothing else to stage: every binding a staged record carries is
recomputed server-side, so the command cannot widen the trust boundary by
forwarding something a caller handed it.

A verification failure is a normal outcome. The command reports that no record
was produced and exits successfully, because most commits should carry nothing.

Ruled-out: 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
Ruled-out: 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
Limit: 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
Blast: local
Undo: easy
Certainty: firm
Record-Id: r-t1006cli
The README needed to show the demo without asking a reader to run it. A recording
is only trustworthy if it can be regenerated, so this one is an animated SVG
rendered from Node's standard library with a fixed environment, viewport and
frame timing, and a --check mode that re-renders and compares bytes.

The comparison is exact. A single differing byte fails, which is the only version
of this check worth having: a recording that may drift from the demo it depicts
stops being evidence and becomes decoration.

Ruled-out: a terminal recorder dependency | the artifact would then depend on a tool's output format, and reproducing it would mean reproducing that tool's version
Ruled-out: a frame manifest with tolerance instead of byte comparison | a check that tolerates drift stops detecting the drift it exists to detect
Limit: byte-exactness is verified on this platform; a different platform's Node could in principle render differently, and nothing here proves it does not
Blast: local
Undo: easy
Certainty: firm
Record-Id: r-t1016svg
Record the demo as bytes a check can compare (#212)
Give capture one command instead of three phases (#198)
The hook decides whether a pending record belongs to the commit being written.
Five conditions have to hold together: HEAD is what prepare recorded, the staged
diff still hashes the same, the record has been staged and has not expired, it
has not already been consumed, and the policy identity is unchanged.

The important half is what happens when one of them fails. The record is left
alone and the commit proceeds without it. A capture that cannot be proven to
belong here produces silence, never an error and never a record attached to the
wrong commit. Consumption happens exactly once, so a retried commit cannot pick
up a record a previous commit already took.

Ruled-out: 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
Ruled-out: 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
Limit: policy identity is compared as a hash, so a policy edit that produces the same hash is indistinguishable from no edit
Blast: local
Undo: easy
Certainty: firm
Record-Id: r-t1005gates
stage_capture completes the MCP write side. Its input schema accepts one field,
a nonce, and the server computes everything the staged record commits to: the
base HEAD, the staged diff hash, the policy identity, the staged timestamp and
the expiry derived from it. An agent cannot assert what a record is bound to; it
can only ask for the record it prepared to be staged.

Expiry starts here and nowhere earlier. A prepared or verified record carries a
null expires_at, so a capture being verified slowly cannot expire while someone
is still reading it.

Ruled-out: 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
Ruled-out: 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
Limit: 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
Blast: local
Undo: easy
Certainty: firm
Record-Id: r-t1009stage
test/mcp.test.ts asserted the exact tool list under a title that said "the three
tools of the ticket". The list had already grown past three, and each tool added
since has had to edit an assertion whose name no longer described it. Adding
stage_capture broke it again.

The assertion is worth keeping — an unexpected tool appearing on the MCP surface
should fail a test — so it now lists what the server exposes and the title says
that instead of a number that was wrong four tools ago.

Ruled-out: relaxing the assertion to a subset check | an unexpected tool on the write surface is exactly what this test should catch, and a subset check would not catch it
Limit: the list is still maintained by hand, so it will need editing for every future tool; that is the cost of catching an unintended one
Blast: local
Undo: easy
Certainty: firm
Record-Id: r-t1009list
Refuse to attach a record the commit no longer matches (#197)
Let an agent stage a capture without handing it the bindings (#201)
Implement the post-commit hook that finalises pending capture consumption
after a successful commit. The hook inspects applied, unconsumed pending
transactions and consumes exactly the one whose base_head matches the new
commit's first parent, staged_tree_oid matches the committed tree,
applied_record_hash matches the canonical trailer block, and every
Record-Id is present in the commit message.

Also registers the internal post-commit CLI command and adds idempotent
post-commit hook installation to commitlore init alongside the existing
hooks step. Foreign hooks are preserved per the containment policy.

Limit: consumption happens after commit succeeds, exactly once; consuming earlier loses the record on failed commits, consuming twice lets one record attach to two commits
Record-Id: r-0ll5sxk0
feat(hooks): post-commit consumption finaliser (#213)
Pending files written by prepare accumulate without bound because no
pipeline step removes them. This adds gcPending(cwd) which removes
files when now > expires_at and phase is not staged/applied, removes
consumed files past a 24h retention window, and skips any file whose
age or phase cannot be determined. Wired as commitlore capture gc.

Limit: 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
Record-Id: r-c44a1edb
feat(capture): pending-transaction garbage collection (#215)
Independent audit finding — does not close any acceptance-matrix row.
Proves the capture pipeline end-to-end against real temporary Git
repositories and the real CLI binary across six scenarios:

1. CLI capture full chain (prepare → commit → post-commit → queryable)
2. MCP three-tool sequence over one nonce then commit
3. HEAD moves between prepare and commit (no record attaches)
4. Fabricated/foreign nonce fails closed
5. Concurrent capture in linked worktree (independent pending dirs)
6. Aborted commit (pending record stays retriable)
test: capture pipeline E2E integration (#216)
When invoked without --transcript or --diff, the command now emits the
static contract (rules, vocabulary, output format) with placeholder text
for the TRANSCRIPT and DIFF sections that says '(provided at harvest
time)'. Previously it refused with a skip message, which inverted the
order of use: the contract is what a session needs before it has produced
a transcript.

The exit code remains 0, which is now correct: the command succeeded and
produced the requested output on stdout. A caller distinguishes success
from the old silent skip by checking stdout content.

Fixes #229

Record-Id: r-lb0xl89a
Ruled-out: parameterising the contract with empty strings | prints a template with empty holes that teaches the session nothing about placeholder handling
Limit: the static contract uses explicit placeholder text for TRANSCRIPT and DIFF rather than omitting those sections, because the prompt text references them by name
The density harness reported one record-bearing rate against all
commits without naming the population. Merge commits, generated by
policy with --no-ff, carry no record by design; including them in the
denominator deflated the rate by over 20 points relative to the
authored-only population.

Now measureDensity emits both populations (all commits and authored
non-merge commits), each labelled in the rendered table. The Linux
OOM-Killer comparison names its denominator and restates the
unit mismatch (rationale sentences vs record-bearing messages).

Measured at this head: 71.7% all-commits vs 98.1% authored-only
(26.3-point gap across 375 commits, 117 of which are merges).

Closes #183

Limit: density row must name both denominators; a rate without a named population is the defect class "check must state what it could not see"
Record-Id: r-density183denom
checkInjectRuntime reported 'fail' with needsAttention=true when the
configured PreToolUse hook executable was not resolvable from PATH
(ENOENT). This caused init's final doctor step to exit 1 in any CI
environment that had not installed commitlore system-wide, making tests
that assert a clean exit intermittently flaky.

The fix: checkInjectRuntime now overrides needsAttention to false for
the ENOENT case after evaluateInjectRun returns, preserving the 'fail'
status (so standalone doctor still surfaces the finding for
actionability) while preventing init from treating a missing system
binary as a blocking finding. This is analogous to how checkRefspec
overrides needsAttention for 'no remote': an incomplete environment,
not a misconfiguration.

Closes #221, closes #192.

Limit: checkInjectRuntime ENOENT does not block init
Record-Id: r-flake221fix
Ruled-out: downgrade evaluateInjectRun to warn | doctor.test.ts integration test asserts status=fail for ENOENT
Ruled-out: remove trailers from init.test.ts repoWithRemote | weakens inject-probe test coverage without addressing the semantic defect
Blast: local
The demo-lint workflow only triggered on pull_request events, so it linted
only a PR's own commits. Two commits duplicating a Record-Id from separate
PRs appeared together only in the full origin/main..dev range, which nothing
checked until a promotion PR was opened. The r-readme729 collision sat
green for a day because of this blind spot.

Add a promotion-range-lint job that fires on push to dev and validates the
full origin/main..HEAD range. The existing PR lint job is unchanged; it
still validates only the PR's own commits via the action's base-ref logic.

Record-Id: r-rangelint186
Blast: system
Undo: easy
Certainty: firm
Evidence: .github/workflows/demo-lint.yml
fix(bench): name both denominators in rationale_density row (#183)
fix(harvest): --prompt-only prints the contract with no other input
fix(doctor): do not block init on unresolvable inject executable
fix: lint full promotion range on dev push (#186)
Real-usage testing found `commitlore capture gc` could not run at all. The parent
`capture` command declared `--transcript` with requiredOption, and commander
enforces a parent's required options even when a subcommand is invoked, so the
subcommand failed during parsing before its own action ran. Seventeen unit tests
for gcPending passed while the command exposing it was unreachable.

The requirement has not been dropped; it moved into the capture action, where it
applies to the capture flow and not to a sibling that needs no transcript.

A second defect surfaced immediately after: `--json` is declared on both
`capture` and `capture gc`, and commander binds a flag declared on both to the
parent, so the subcommand's own opts never saw it and the flag was silently
ignored. gc now reads it from either level.

Both were invisible to the unit tests because the tests called gcPending
directly and never invoked the command.

Ruled-out: 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
Ruled-out: 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
Limit: the guard against this class is four CLI-level tests; nothing structurally prevents a future subcommand from colliding with a parent option again
Blast: local
Undo: easy
Certainty: firm
Record-Id: r-gcwiring
Make capture gc reachable, and let it honour --json
0.4.0 is the release where recording a decision becomes something the tool does
rather than something the user remembers to ask for: capture runs as one command,
a staged record can only attach to the commit it was prepared against, and it is
consumed exactly once after that commit succeeds.

The notes lead with upgrade reasons rather than a ticket list, because a reader
deciding whether to upgrade needs to know what changes for them. Guard gets its
own section, and it is not good news: precision 44.8% and recall 22.0% are stated
in the notes for the same reason they are now stated in the CLI, the MCP tool
description and the README — a reader who sees no guard warning must not conclude
their proposal is clear.

The known-limitations section carries what this release does not do, including
that nothing here measures whether an agent behaves differently for having
received a decision.

Ruled-out: describing 0.4.0 as making agents safer | no study here measures agent behaviour, and the recovery protocol is registered and unrun; the release delivers a loop, not an outcome
Ruled-out: omitting guard's figures from the release notes because they are unflattering | the notes are read by people deciding whether to trust the tool, and a quality limit hidden at release time is discovered in use
Limit: 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
Blast: local
Undo: easy
Certainty: firm
Record-Id: r-rel040notes
Bumping package.json without the README pins would have published an install
command pointing at the previous release while the package claimed the new one.
The assertion added with the pin correction caught it in CI on all four language
files, which is the first time that test has earned its place.

All four READMEs now pin v0.4.0: the one-liner, the reviewed-install example, and
the release-asset verification snippet.

Ruled-out: pinning after the tag exists so the reference is never briefly dangling | the notes, the version and the pin have to land together or the release is internally inconsistent at the moment it is cut, and the tag follows within the same promotion
Limit: 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
Blast: local
Undo: easy
Certainty: firm
Record-Id: r-rel040pins
Write the 0.4.0 notes before tagging
@github-actions

Copy link
Copy Markdown

CommitLore — record lint

Trailers: clean — 78 commits in origin/main..13291f21c1070e2ec33ab16d22c38528dd9bdcf0
Active constraints: 123 limits · 211 ruled-out · 85 warnings — from 175 records over 119 changed paths

Active constraints for the paths this PR touches

Limits (123)

  • 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-density183denom 62ee7a4 — density row must name both denominators; a rate without a named population is the defect class "check must state what it could not see"
  • 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-t1009list 778638c — the list is still maintained by hand, so it will need editing for every future tool; that is the cost of catching an unintended one
  • 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-778acc00 fa9cf8d — expires_at anchor is always stage success, never prepare or verify
  • 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-t1002prep 3e2c8c1 — the prompt contract is a string this phase emits; nothing verifies the agent honoured it until verify runs
  • r-t1001pend 0f5ef32 — the test proves monotonic transitions but cannot prove absence of TOCTOU between read and rename on a loaded filesystem; atomic rename is the kernel-level guarantee
  • r-t1001pend 0f5ef32 — 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-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-t1010demo 9820c21 — fixture validation requires git in PATH for parseCommitMessage
  • 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-gate2amend a4bdc42 — nothing mechanically enforces that a future heading keeps prerequisites and ordering in separate fields; the separation is stated and reviewed, not checked
  • r-m5align730d 5fd9565 — T-901 predates this milestone and its evidence lives in the release workflow rather than in an AC table, so P0-1's release-process leg is verified by artifacts rather than by a test
  • r-m5align730c 4a790c1 — nothing mechanically checks that a row's assertion still matches the review; the ordering rule is stated here and enforced in review only
  • r-m5align730b 6d98064 — nothing mechanically prevents the next editor writing "closes" again; the phrasing rule lives in the legend and in review, not in a check
  • r-m5align730 ad59b5b — nothing enforces that a later editor re-derives a row from the review rather than from its citations, which is exactly how these four drifted
  • r-fixdemofx fd5089b — this repairs the two tickets that referenced the broken shape (T-1010 directly, T-1011 needed no change); it does not add a general check that a future ticket cannot reintroduce a static .lifecycle assumption
  • r-gateaacc 45a8f2b — nothing enforces the src/mcp/server.ts merge-sequencing rule automatically — it is a process constraint the next PR author has to read and follow
  • 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-bench161 b9b788e — digest fallback proves identical harness code at HEAD, not existence of the recorded commit
  • r-corpus157 ae60ce5 — the 44.8% precision estimates this deliberately hard archived decision corpus, not deployment prevalence
  • r-econ127a 40e197d — no task in the current 8-task pool passes Make benchmark task qualification two-sided (4–5 of 6) #109 qualification
  • 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-densitymerge135 490b2bb — the density-only path must not contend with a wall-clock benchmark
  • r-densityci135 e81cb1f — a published density result must be recomputed from its recorded source history in CI
  • r-cpaa134 f6f6d03 — the floor excludes the model's generation cost, so a reader comparing this against a hosted service's bill is comparing a part against a whole
  • 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-f61curve f05a3fc — the frozen 30-label corpus gives the archived 3/8 precision a 95% Wilson interval of 13.7%–69.4%
  • 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-squash132 c19160e — GitHub's server-side squash button runs no local hook, so that path still loses records
  • r-be140cost 8c01bd5 — no per-turn provider token ledger or observed avoided-work cost exists yet
  • r-noisefix142 bf83027 — each active fixture record must touch the target path
  • r-noise142 aa3d1f1 — exposure uses a fixed-seed corpus of two applicable records and realistic unrelated records at five sizes
  • 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-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-seabin39 9e9cd0e — doctor's PreToolUse hook runtime check still shells to scripts/commitlore-run.sh via bash for its own probe; a binary install with the Claude Code plugin hook already wired reports a plain ENOENT-style fail there rather than trying the binary directly -- not one of B-09 · Single static binary — remove the Node runtime dependency #39's six required commands, not fixed here
  • 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-issue59a1 21dd96c — three score bands over 8 firings is still a small-sample report; issue Guard fires wrong 5 times out of 8 — precision 37.5% at the shipped threshold #61 item 5 tracks corpus growth as a separate concern from this wording fix
  • 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-det58p 1edccdf — deterministic results must identify a committed harness, corpus, binary, run, and machine
  • 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-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-5a29f7 7727944 — guard's false-alarm rate on real agent output is 5 in 25 on diffs; an earlier pass reported 0 in 10 on curated proposals, which was the optimistic case rather than a regression since
  • 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-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-4e9c72 a7a7e26 — the index is derived, so nothing about its state can make the tool give a wrong answer -- only a slower one
  • 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-7e5f02 e5f5e00 — npm installs through an engine mismatch, so the ecosystem's own signal cannot be relied on to stop anything
  • 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-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 (211)

  • r-rel040pins b76c40b — pinning after the tag exists so the reference is never briefly dangling | the notes, the version and the pin have to land together or the release is internally inconsistent at the moment it is cut, and the tag follows within the same promotion
  • r-rel040notes 5d57a72 — describing 0.4.0 as making agents safer | no study here measures agent behaviour, and the recovery protocol is registered and unrun; the release delivers a loop, not an outcome
  • r-rel040notes 5d57a72 — omitting guard's figures from the release notes because they are unflattering | the notes are read by people deciding whether to trust the tool, and a quality limit hidden at release time is discovered in use
  • 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-t1009list 778638c — relaxing the assertion to a subset check | an unexpected tool on the write surface is exactly what this test should catch, and a subset check would not catch it
  • 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-t1016svg 321c6f1 — a terminal recorder dependency | the artifact would then depend on a tool's output format, and reproducing it would mean reproducing that tool's version
  • r-t1016svg 321c6f1 — a frame manifest with tolerance instead of byte comparison | a check that tolerates drift stops detecting the drift it exists to detect
  • 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-t1021known 8dfffc1 — stating precision alone | precision without recall reads as a quality score and hides the failure mode that matters, which is silence
  • r-t1021known 8dfffc1 — publishing the point estimates without the Wilson interval | a figure without its interval is the defect this project removed from the README twice already
  • 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-t1002prep 3e2c8c1 — stamping an expiry at prepare with a generous window | a longer window makes the same defect rarer instead of absent, and the staleness it guards against is already caught by the diff-hash gate
  • r-t1002prep 3e2c8c1 — letting the caller supply the nonce | the nonce is the key a later phase resolves a path from, so it is generated here and validated by the store against a strict pattern before any path resolution
  • r-t1001pend 0f5ef32 — setting expires_at at creation time with a longer window | a longer window hides staleness rather than preventing it, and the staged-diff-hash gate already catches real drift
  • r-t1001pend 0f5ef32 — a shared atomicWriteJson utility exported from a utils module | three files already duplicate the pattern and the ticket scope does not include a cross-cutting refactor
  • r-t1001pend 0f5ef32 — allowing stagePending on a prepared record when records are empty | skipping verification breaks the trust chain that prevents an MCP client from injecting arbitrary trailers
  • 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-pin030readme 504b54e — referencing main instead of a tag | main is also mutable and does not name a reviewed release
  • r-pin030readme 504b54e — leaving the pin at v0.2.0 until the next release | v0.3.0 is tagged and published; the mismatch with package.json is a documentation defect now, not a timing issue
  • r-gate2amend a4bdc42 — keeping expiry anchored to creation with a longer window | a longer window hides the same defect and makes it rarer rather than absent
  • r-gate2amend a4bdc42 — a frame manifest for T-1016 | a check that tolerates drift stops detecting the drift it exists to detect
  • r-gate2amend a4bdc42 — leaving verification_gaps to be derived at implementation time | it was the second invented concept this milestone, and naming the closed ordered set now is what stops a third
  • r-m5align730d 5fd9565 — leaving the ADR as sole authority on rows whose ADR happens to be correct | correctness today is not the property being protected -- the next editor re-deriving from an ADR is
  • r-m5align730d 5fd9565 — pointing P0-1 at the release PR instead of at tickets | a PR is not a checkable acceptance criterion once merged, and the tickets carry the AC tables
  • r-m5align730c 4a790c1 — keeping the provenance paragraph and correcting four to three | it duplicated the rewritten preamble, and two accounts of the same history is how the first one drifted
  • r-m5align730c 4a790c1 — recording the confidence-separation rule only in T-1024 | the constraint outlives the ticket, and an ADR a ticket governs itself by must be readable from the ADR
  • r-m5align730b 6d98064 — leaving it and noting the legend covers it | a legend that one line contradicts stops being a rule and becomes a preference
  • r-m5align730 ad59b5b — keeping T-1019 and T-1023 attached to P0-7 and P0-4 with corrected wording | the rows they claimed do not mean what those tickets do, so the attachment was the defect rather than the phrasing
  • r-m5align730 ad59b5b — leaving P0-8 unscheduled with the confidence-separation question open | the review asks for it in Gate A, and T-1024 answers the question structurally instead: guard_confidence qualifies possible_revival_matches and the context fields never inherit it, so no second label is invented
  • r-pendpath 233a891 — leaving this to the implementer's judgment | the precedent already exists in this codebase and the failure mode (silently wrong location in a worktree) would not surface as a test failure unless someone specifically writes a worktree test -- worth pinning explicitly rather than hoping it's noticed
  • r-fixdemofx fd5089b — adding a stale engine call inside T-1010 to compute lifecycle there | contradicts the ticket's own forbidden scope, and duplicates work T-1011 already does correctly
  • r-gateaacc 45a8f2b — rewriting ADR-0020/0021/0022 or the existing T-1001 · Pending transaction store #193-213 issue bodies | they already meet or exceed the source review's own requirements (exact ownership, forbidden scope, RED/GREEN, AC-to-test, four-tier verification, evidence invalidation, stop conditions); rewriting working documents to match a stale review would be pure churn
  • r-gateaacc 45a8f2b — inventing content for a hypothetical P0-7 original definition | none exists in history (verified); asserting one would be the exact failure this document exists to prevent
  • 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-notes030 a289ca5 — editing the dated benchmark artifact to restate its denominator | it is a provenance-stamped snapshot of one run, and rewriting a past measurement to read better today is the failure this project exists to prevent
  • r-notes030 a289ca5 — tagging first and correcting the notes after | the tag is what users read
  • r-bench161 b9b788e — run deterministic benchmarks only after merging to dev | reviewers must be able to examine a measurement beside the code that produced it
  • r-corpus157 ae60ce5 — change the scorer with the corpus | moving the labels and scoring composition together would make both unverifiable
  • r-econ127a 40e197d — computed break-even ratio | the avoided-work denominator has not been observed
  • 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-cpaa134 f6f6d03 — reporting one capture cost | the marginal-token figure and the cache-inclusive figure differ by more than an order of magnitude, and publishing whichever is smaller would make every other number here less trustworthy
  • 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-f61curve f05a3fc — change the shipped threshold | the frozen 30-decision curve has no precision-safe cutoff: 0.55 removes every true positive while retaining false positives
  • 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-squash132 c19160e — reporting one combined survival rate | history retention and path reachability measure different outcomes
  • r-be140cost 8c01bd5 — retaining a modelled break-even figure | a ratio inherits its weakest input, and the denominator is assumed rather than observed
  • r-noise142 aa3d1f1 — simulate pointer or pull delivery | the current CLI exposes neither route
  • 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-readme129 ab5f210 — writing that CommitLore saves tokens | the cost is measured and the threshold is arithmetic, but nothing here shows the guard clears it, and that gap is the whole reason the section states three things instead of one
  • 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-m4basis 5e2d2cb — keeping the non-delivery wording as a hedged claim | it asserts something I never established, and a verdict that overstates its own evidence is the defect this repository exists to catch
  • r-m4withdraw e5f9b73 — retracting the dataset or calling M4 invalid | the data is valid and its provenance is clean; what it measured was not the treatment, and those are different words
  • r-readmeux1 b664205 — describing harvest as automatic record creation | it drafts from a transcript and a human still commits, and claiming otherwise is the overreach this project keeps closing issues about
  • r-rel021a a79e350 — retagging v0.2.0 | the tag is already on the remote and moving a published tag breaks anyone who fetched it
  • 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-m4docs1 32d5bf1 — keeping the withdrawal notice and only landing the verdict document | bench/report.ts already draws this line -- a provenanced dataset that still shows a withdrawal is a hard failure in check-readme-numbers.mjs (checked here), not a style choice left open
  • r-rel0200b 5870b35 — leaving "21 defects" and fixing only the GitHub Release body | CHANGELOG.md is the permanent record; a release-notes-only fix would leave the canonical file quoting a number two independent checks had already disproven
  • r-rel0200b 5870b35 — re-deriving a fresh defect count from scratch instead of verifying the given one | gh issue list --label bug,security undercounts (misses no-label and epic-labeled defect issues like doctor probed dist/cli.js while distribution ships dist/commitlore.mjs; CI red #40-56), so a bottom-up recount would have been less reliable than confirming the specific claim (Guard fires wrong 5 times out of 8 — precision 37.5% at the shipped threshold #61/commitlore-query skill teaches agents an invocation whose documented property is false #69 reopened) against the issue tracker directly, which matched
  • r-rel0200a a074754 — bumping ci.yml's "v0.1.0 was published with zero attached assets" comments | those describe a historical fact about the actual v0.1.0 release, not a version this project declares; the check they document (releases/latest/download/SHA256SUMS returning 200) is written to start exercising the real path automatically the day any release ships assets, v0.2.0 included, with no workflow edit
  • r-rel0200a a074754 — touching docs/adr/ADR-0001-scope-v010.md, docs/tickets/release.md, bench/VERDICT-M1.md, HANDOFF.md, bench/ROUTE-GAP.md | planning and historical-record prose that names v0.1.0 as a past decision or measurement subject, not a live version carrier
  • r-rel0200a a074754 — changing test/mcp.test.ts's CommitLore-Version: 0.1.0 fixture trailer | that's protocol-version content inside a synthetic seed commit (what an old commit's trailer looked like), unrelated to and never asserted against package.json's version
  • r-instci99a 318b9a2 — pre-installing curl/jq/tar/git in the containers before running install.sh | would hide exactly the gap this work exists to find — Debian ships neither curl nor wget by default, and asserting that failure cleanly is the point of the bare debian:stable-slim step, not an obstacle to route around
  • r-instci99a 318b9a2 — publishing a -musl release target so Alpine could install successfully | a release.yml/build-matrix change, not an install.sh or CI-verification fix; out of scope here (DO NOT: no Docker in the release build matrix) and orthogonal to making the existing failure clear instead of a raw crash
  • r-instci99a 318b9a2 — heuristically detecting musl before download (checking for /lib/ld-musl-*.so.1) | executing the freshly extracted, already checksum-verified binary directly is a stronger, more general check — it catches any reason the binary cannot run on this machine, not only the one musl signature this repository happens to know the path for
  • r-instci99a 318b9a2 — a matrix job splitting debian/alpine | the two images need different assertions (debian: bare failure, then success once curl is added; alpine: one clean failure, nothing more to add) and share one staged release + HTTP server; one job with named, ordered steps keeps that shared setup and reads linearly in the log
  • r-rdme96a 9c9371c — dropping the git-clone / source-build paths from the top entirely | install.sh is the fast path, not a universal one (no Windows binary yet, per ADR-0015) — the detailed section has to stay reachable, just not first
  • 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-fix93pkg 9c4a396 — repointing bin at dist/commitlore.mjs instead of removing it | that entry exists only to serve a package-manager install ADR-0011 already replaced with a git clone; repointing it keeps maintaining a path to an install mode that will never happen
  • r-fix93pkg 9c4a396 — also removing files now | the issue's own "After B-09 · Single static binary — remove the Node runtime dependency #39" note groups bin/files/dependencies as one cleanup once the static binary lands; files alone is inert without a registry rather than actively broken, so doing it now means doing it twice
  • r-relinstall c6e1d04 — guessing the current version to build the asset URL directly | would need either the GitHub API (rate-limited, needs no-auth headers handled correctly) or trusting a redirect's final Location header parsing. Downloading the fixed-URL SHA256SUMS first and reading the real asset name back out of it needs neither and is what the checksum step has to fetch anyway.
  • r-relinstall c6e1d04local for scoping — not POSIX per se, but supported by dash, bash, and every shell this script is realistically piped into (verified directly, see Verified) | not used in the end; the script has few enough variables that scoping was not needed, only noted here because it was considered.
  • r-seabin39 9e9cd0e — mainFormat: "module" (an ESM SEA main) | verified to fail both blob generation and runtime on this Node line, not merely documented as unsupported (see above)
  • r-seabin39 9e9cd0e — pkg / nexe | third-party bundlers embedding a separate, forked Node runtime this project does not control the patch cadence of; pkg is archived upstream. Trades the Node runtime dependency this ticket removes for a different, less-maintained one
  • r-seabin39 9e9cd0e — Deno compile / Bun compile | a different runtime. node:sqlite, the TypeScript, and NodeNext resolution are all Node-specific; retargeting them is a second runtime port, not a build step, and issue B-09 · Single static binary — remove the Node runtime dependency #39's own first option ("Node SEA -- no source rewrite") needs none
  • r-seabin39 9e9cd0e — reimplement in Go/Rust | issue B-09 · Single static binary — remove the Node runtime dependency #39's own second option, and a real one via spec/fixtures + spec/contract-cases, but an order of magnitude more work than this ticket and not needed to solve either problem (latency, no-Node-on-PATH) this ticket opens with
  • r-seabin39 9e9cd0e — committing dist/commitlore next to dist/commitlore.mjs | breaks ADR-0011's committed-dist/-matches-src/ invariant at ~115 MiB per platform/arch, and a pushed blob that size is not removable from git history again
  • r-seabin39 9e9cd0e — Windows (commitlore.exe) in this PR | Node's docs describe a signtool path this repository has no CI runner to verify; shipping an unverified platform claim is what this project's numbers-or-silence discipline exists to refuse. classifyBinTarget and the resolution order are written so it is a small additive follow-up, not a redesign
  • r-distrace88 d118a73 — consolidating the four files' redundant tsc rebuilds into one vitest globalSetup | would remove the race for every file at once and is the more complete root-cause fix, but touches five files' bootstrap plus global config, and mcp.test.ts's beforeAll already tolerates unrelated in-flight tsc errors elsewhere in src/ (OWNED_SOURCES filtering) in a way a single shared build step cannot preserve without its own redesign — bigger blast radius than this issue asked for.
  • r-distrace88 d118a73 — raising bench-ablation's timeout, globally or per-test | proven not to be a timeout problem at all: execFileSync blocks synchronously, so vitest's timeout mechanism cannot even engage against it (verified with a probe test); the actual failure is the runner subprocess legitimately exiting 1.
  • 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-plugman51 db8e2a1 — PreToolUse hook fires twice per edit | Claude Code's plugin loader dedups a manifest.hooks path that resolves (via realpath) to the already-loaded hooks/hooks.json before merging it, so the hooks array is never doubled; the redeclaration instead surfaced as a hook-load-failed PluginError under the loader's default strict mode
  • r-issue59a1 21dd96c — choosing band edges from this run's own score distribution | issue Guard fires wrong 5 times out of 8 — precision 37.5% at the shipped threshold #61 already rejected sweeping the threshold over the same 8 firings the number would then describe -- picking bin edges from the same data is the same act with a different name, and PREREGISTRATION §4 forbids that shape for the behavior benchmark for the same reason
  • r-issue59a1 21dd96c — folding the adversarial corpus into the pattern-authored TP/FP count | that would let a corpus scored by its own authors absorb an independently written one, exactly the conflation issue Injection scanner misses base64 and non-English payloads: 4 of 6 on an adversarial set, against 21/21 on the authored corpus #70 exists to separate
  • 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-det058 695cdf6 — add the 100k history suite to CI | CI budget and deterministic local measurement are separate decisions
  • 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-3b57e2 30f2d5f — converting the three translated READMEs for consistency | they are the product, not the record, and two checks exist specifically to keep them
  • 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-6f92c4 3cebb89 — restating the withdrawn numbers as prose ("we measured a reduction") | it is the same claim with the evidence removed
  • r-6f92c4 3cebb89 — leading with the protocol's features and putting the measurement record near the bottom | that is the arrangement of someone hoping it is not read
  • r-2b58d4 4842356 — exempting datasets written before the fields existed | it is one line and it deletes the guarantee
  • 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-6b83f2 4c1a503 — deleting both sentences | the clone-runs-without-installing claim is true for validate, context, guard and the MCP server, and dropping it would understate what a clone gives you as badly as the old text overstated it
  • 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-c53d19 110be8c — leaving the claim and letting T-706 · Bundle the CLI as a single file — run from a clone alone #38 make it true later | the README is what someone reads while deciding to adopt this, and a claim that is false today does not become honest because it is scheduled
  • r-9c07e2 9c4d25a — invoking npx on every Edit | it puts a registry round trip on the hot path of every tool call, which is how a hook earns being uninstalled
  • r-9c07e2 9c4d25a — committing dist/ so the plugin is self-contained from a git clone | it puts build output in review diffs forever to save one background install
  • r-0d4b81 8005227 — a longer quickstart that demonstrates context, limits, ruled-out, warnings and stale | an agent calls those itself once the MCP server is registered, so listing them teaches the human a workflow that is not theirs
  • r-5a29f7 7727944 — re-running the same design at 400-800 per arm | it buys precision on a lever there is now direct evidence is weak, at 20-40 hours, while the route the claim actually rests on stays unmeasured
  • r-5a29f7 7727944 — swapping guard in as the delivery route without measuring false alarms | one blocked edit in five that should not have been blocked is the rate at which a PreToolUse hook gets uninstalled
  • r-7f31c9 750ab17 — reporting both datasets from one source list | readSources groups by condition and cannot separate repositories, so any second dataset with a commitlore-on arm silently corrupts the headline test
  • r-9c2f74 d653153 — resume the pilot into the same file | a new process would load the edited code and create the mixing that had not happened
  • r-9c2f74 d653153 — run the ablation arms as they stand | three nulls from comparing identical inputs read as "these guarantees do not matter"
  • r-9c2f74 d653153 — keep the tasks that showed an effect and rewrite only the rest | the property is the criterion, not the direction of the result
  • r-4a8e15 49e12c7 — make the parser accept a bare block | it would disagree with git, and delegating to git is the one thing SPEC section 2 requires
  • r-6e1a72 5e09846 — leave the banner until release | it understates for weeks and readers leave rather than build from source
  • r-6e1a72 5e09846 — update English only and translate later | the lag is itself a wrong answer for whoever reads the other three
  • 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-4e9c72 a7a7e26 — fail when the index is missing or stale | it would be the first thing users learn to ignore, and then a real failure is quiet too
  • 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-7e5f02 e5f5e00 — hold Node 20 and downgrade both dependencies | paying maintenance cost to keep supporting an unpatched runtime
  • r-7e5f02 e5f5e00 — floor at 24 | excludes Node 22 users today for no benefit, and 22 outlives v0.1.0
  • r-7e5f02 e5f5e00 — drop engines entirely | the failure moves from install time to runtime, where it is someone else's confusing bug
  • 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
  • r-9d31b7 4ac6e30 — fix the values and move on | the same drift already happened once through a rename, and prose review did not catch it either time
  • r-9d31b7 4ac6e30 — parse the README at runtime in the CLI | the check belongs in the conformance suite, not in shipped code
  • r-c0f4e2 3d249cd — GitLore published as git-lore | the binary and search results still collide with the existing gitlore tool
  • r-c0f4e2 3d249cd — keep Annals | the sound problem does not decay, and with code near zero this is the cheapest moment the project will ever have
  • r-c0f4e2 3d249cd — rename code and spec first, documents later | the drift window makes every artifact written in it wrong
  • r-b2e7f1 00d348d — line-matching Key: prefixes | prose containing a colon line parses as a record and feeds agents false context (verified B3)
  • r-b2e7f1 00d348d — content-hash Record-Id | must survive rebase and squash, which a hash of the commit does not
  • r-a8f3c1 ef48843 — keep name, change vocabulary only | vocabulary is the protocol, so half the change leaves the substance untouched
  • r-a8f3c1 ef48843 — drop Certainty as a dead field | a real route exists -- stale sweep prioritizes guess-level records for review

Warnings (85)

  • r-hero172a bc0d971 (claim) — BM25 recall is 0/2 beyond zero distractors; do not state it is 1/2 throughout
  • r-bench161 b9b788e (claim) — an unresolvable commit with an absent or mismatched harness digest must remain a hard failure
  • r-corpus157 ae60ce5 (claim) — future scoring changes must keep bench/fixtures/guard-quality.json fixed and be judged against it
  • r-release030 510d70e (claim) — Tagging and publishing remain separate release steps.
  • r-econ127a 40e197d (claim) — publish a value only after reproposed counts correct behaviour as failure — measure rejected work, not rejected mentions #141 counters run on a qualified task pool
  • 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-densitymerge135 490b2bb (claim) — generated density artifacts are recomputed from their recorded source history in CI
  • r-densityci135 e81cb1f (claim) — empty commit messages count as examined commits even though they contribute no body lines
  • r-epipe2026 d9ee9ff (claim) — preserve a non-null status before interpreting an EPIPE input-pipe race as a spawn failure
  • r-f61curve f05a3fc (claim) — the fixed 0.05 sweep supports no retune at 0.35; it is not an F1 selection
  • r-init107 f485f07 (claim) — a configured but unreachable remote remains an actionable warning and exits 1
  • r-be140cost 8c01bd5 (claim) — do not restore a break-even figure without both measurements
  • r-noisefix142 bf83027 (claim) — latency measurement was deferred because another deterministic benchmark is running
  • r-noise142 aa3d1f1 (claim) — the deterministic runner refuses wall-clock measurement while another bench/deterministic process is running
  • 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-rel0200a a074754 (claim) — scripts/commitlore-bootstrap.sh is orphaned -- no hooks.json entry invokes it, and its npm-install strategy contradicts ADR-0011. It still carries a live version default, now bumped for consistency, but nothing exercises it. Worth a follow-up issue: either wire it up correctly or delete it.

Truncated: 74 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.

@MongLong0214
MongLong0214 merged commit 7a07d17 into main Jul 30, 2026
18 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