Release 0.5.1 - #348
Merged
Merged
Conversation
`.serena/` is written by an MCP server into whatever directory it was pointed at, and it records that directory's name. A copy committed from a worktree therefore ships that worktree's label: v0.5.0 carries `project_name: "t1124"`, which is meaningless to anyone who clones it and wrong for anyone who uses the same tool. It entered at `51abef8` -- the T-1124 merge -- because the commit before it staged with `git add -A` in a worktree where the server had left the directory untracked. Nothing failed, because nothing looks: the manifest test asserts that every *declared* file parses in a clean clone, and a file nobody declared is invisible to that. Distribution here is a clone (ADR-0011), so an undeclared file ships as surely as a declared one. Removed and ignored. Ignoring it is the part that lasts: the file will be recreated in every worktree the server is pointed at, so the next `-A` would otherwise put it back. Not a functional defect, and not worth a patch release on its own -- the file is inert. It is recorded here rather than quietly dropped because the release that carried it is tagged, and a later reader comparing the v0.5.0 tree against this one should find the reason rather than a silent deletion. Record-Id: r-strayserena Limit: a clean clone is the distribution, so an undeclared file ships; the manifest test checks declared files parse and cannot see a file nobody declared Ruled-out: Cutting 0.5.1 for this | the file is inert -- it changes no behaviour and breaks no install -- and a release exists to move something a user needs Ruled-out: Deleting it without a record | the tag that carries it is immutable, and a later reader comparing the two trees deserves the reason rather than an unexplained absence Ruled-out: Adding a stray-file assertion in this commit | test/manifest.test.ts belongs to another ticket's scope, and the guard is worth its own issue rather than a drive-by edit here Certainty: firm Blast: local Undo: easy Verified: the manifest and install-script suites pass at 34 with the directory gone; typecheck clean; the tree no longer carries .serena and .gitignore now excludes it Unverified: nothing executable changed
Remove a tool's local config that a `git add -A` swept into the release
A draft that was prose rather than the contract's JSON object came back as `missing --transcript`. The transcript was not the problem, and the reporter spent two invocations finding that out: the first sent them after a file they did not need yet, for a draft that was never going to parse. The three options were required together, and the draft was only read afterwards. It is resolved first now -- present, readable, and actually a draft -- and the other two are required behind it. The ordering matters more here than orderings usually do. A session that emits prose believes it staged records, and nothing contradicts that belief until somebody verifies. Fifteen files had been staged that way in the report. The sooner the message says "this is not a draft", the sooner the staging error is visible at all. `harvest --draft` was checked and does not share the shape: it routes to draft mode before touching any other input, and executing it against the same prose produces the same message. That is measured, not read. Record-Id: r-draftfirst329 Limit: a usage error that names the wrong input costs an invocation and points the reader away from the fault; ordering is part of the message Ruled-out: Reporting all missing options at once | it would say the draft is unparseable and the transcript missing together, and the second is not true until the first is fixed Ruled-out: Changing harvest --draft to match | it already resolves the draft first, verified by running it against the same prose rather than by reading the dispatch Certainty: firm Blast: local Undo: easy Verified: `harvest-verify --draft <prose>` with no other options now reports `draft is not valid JSON` at exit 2, where it reported `missing --transcript` before; a well-formed draft with no transcript still reports `missing --transcript`; an unreadable draft reports `cannot read --draft`; harvest-verify and harvest suites pass at 89; typecheck clean Unverified: nothing else -- the change is an ordering, and both orderings are asserted
Say the draft is not a draft before asking for a transcript
`.serena/project.yml` reached v0.5.0. A tool writes it into whatever directory it is pointed at, a `git add -A` in a worktree swept it in, and the released tree carried that worktree's name as somebody's project label. Nothing failed, because nothing looks. The manifest suite asserts that every *declared* file parses in a clean clone, and a file nobody declared is invisible to it -- it is not on the list being checked. Distribution here is a clone (ADR-0011), so an undeclared file ships as surely as a declared one. The guard is deliberately narrow. "No unexpected files" is unimplementable here: the tree legitimately holds src, test, bench, spec, docs, dist, skills and more, and a whitelist of everything would fail on every honest addition and teach the next author to widen it rather than read it. What is checkable is that **no tool-local state ships** -- state keyed to the machine or the directory that produced it, and therefore wrong for every clone by construction. That is what separates it from editor settings a repository may ship on purpose, which this does not forbid. The list is short and names what has actually appeared or plausibly would. It is not a claim to be exhaustive; a guard that pretends to catch everything is the same false comfort as a check that catches nothing. Record-Id: r-toolstate334 Limit: a check that only inspects a declared list cannot see an undeclared file, and in a clone-is-the-distribution model the undeclared file ships anyway Ruled-out: Asserting the clone contains no unexpected path | the legitimate tree is large and grows, so the whitelist would fail on every honest addition and be widened without being read Ruled-out: Forbidding every dotted directory | some are deliberate -- .github and .claude-plugin are the product -- and a rule that cannot tell those apart is not a rule Ruled-out: Asserting only against .serena | it names the instance rather than the class, and the next one will have a different name Certainty: firm Blast: local Undo: easy Verified: the guard passes on the current tree, and fails on a tree where .serena was committed -- proved by committing the defect, running the suite, and resetting; the path construction is checked against files that are genuinely present, so a pass cannot come from looking in the wrong place; typecheck clean Unverified: the list's completeness, which is not claimed
Notice a file that ships but was never declared
On a repository with zero records, `index` populated 106 rows by accepting any RFC-822-shaped `key: value` line -- conventional-commit prefixes (`ax:`, `fix:`, `docs:`), a Homebrew digest (`sha256:`), arbitrary body fields. `doctor` called that healthy. `context` is wired into the PreToolUse hook, so what an agent was handed before editing was a commit subject presented as a recorded decision. `stale` reads git and said 0, so two commands in one tool disagreed about whether the repository had records at all. There was already a filter here, and it was not wrong -- it was answering a different question. `CONVENTIONAL_TRAILER_KEYS` decides which trailers to keep *inside* a record, and its comment argues for a denylist so a project's own `Ticket:` survives beside a `Limit:`. That reasoning holds, and SPEC agrees: an implementation must preserve keys it cannot interpret. What nothing asked was the prior question -- **is this a record?** -- and a denylist cannot answer it, because the set of keys nobody has claimed is unbounded. So the prior question is asked separately. `isCommitLoreKey` is SPEC §3's fifteen keys plus `X-<Name>:`, which §3 gives a slot of its own; a block with none of them is dropped rather than indexed. The denylist keeps its job unchanged, and a `Ticket:` inside a real record is still preserved. One case cannot be decided and is written down instead of guessed at. `Verified:` is vocabulary, so a release note using it as a field is indistinguishable from a record using it for what it means. Reading context to tell them apart is how a tool starts discarding real records, so the block is a record and both keys are kept. The test says so, rather than leaving it to be rediscovered as a bug. Record-Id: r-recordgate335 Limit: a denylist cannot decide whether something is a record, because the keys nobody has claimed are unbounded; that question needs the vocabulary, and the two must not be answered by one filter Limit: `Verified:` in a release note is indistinguishable from `Verified:` in a record, and no context signal separates them without risking real records Ruled-out: Adding the observed noise keys to the denylist | `ax`, `sha256` and the rest are one repository's accident, and the next repository invents different ones Ruled-out: Requiring `Record-Id:` to make a block a record | `stale` needs it for identity, but a commit carrying only `Limit:` is a record SPEC recognises and dropping it would lose real context Ruled-out: Inferring intent from position or neighbouring lines | that is guessing, and the failure mode is discarding a record somebody wrote on purpose Certainty: firm Blast: local Undo: easy Verified: the issue's own repro executed against the build -- a repository of conventional commits plus a `sha256:` body now reports 0 trailers from doctor and 0 records from stale, and `context` returns nothing where it previously served a commit subject; index-db suite at 49 including four assertions that failed before the change; full suite 78 files, 1922 passed, 1 skipped Unverified: the reporter's own repository, where the 106 rows were observed
A block with no CommitLore key is not a record
…ot again `docs/COMPATIBILITY.md` has called Windows supported since T-1124 established #71's containment there by execution. Every README went on saying the opposite five sections later, in four languages, while pointing at that document as the authority. A Windows user read the plugin install path, then `supported`, then `unsupported`. This is the second time. `Alpine and other musl Linux hosts are unsupported` outlived the executed install that made it false and was removed the same way. Both sat in a section the compatibility work was forbidden to edit, and both were found by reading rather than by anything failing -- which is the part worth fixing. So the missing direction is asserted now: for every host the table calls `supported`, no README declares it unsupported. Proved by restoring the bullet and watching it fail, in the language the reader would see it in. The oracle that anchored on that bullet moves with it. `readme.test.ts` mutated `Windows is unsupported` to show an unrelated change does not disturb the guard assertions -- and a `replace` whose needle is gone is a no-op, so it would have kept passing while testing nothing. It anchors on a bullet that exists, and asserts the needle is present before relying on it. `uninstall` also had no documentation anywhere. It shipped in 0.5.0 and the four READMEs never mentioned it. The section states what it removes, and what it deliberately leaves to the commands that own it. Record-Id: r-readmefinal Limit: a mutation oracle anchored on a claim that can become false will silently stop testing when the claim is removed; the needle has to be asserted present Ruled-out: Rewriting the bullet to say Windows is supported | the compatibility document owns the support statement, and a second copy in four READMEs is the duplication the ownership map exists to prevent Ruled-out: Checking only the English README | the contradiction shipped in four languages, and a reader sees one of them Ruled-out: Asserting every host row against every README line | only the `supported` direction is decidable from the table; a host the table calls undecided may honestly be described either way Certainty: firm Blast: local Undo: easy Verified: restoring the Windows bullet fails the new assertion with the offending line quoted, and removing it passes; readme, readme-order, readme-numbers, compatibility-matrix and install-script suites pass at 92; check-readme-numbers exits 0 Unverified: nothing executable changed
The README argued the product well and demonstrated it only on its own corpus. A field report from a ~768-commit Swift MCP server gives the thing an argument cannot: a specific moment where a path-scoped query changed what an engineer was about to build. The query returned a merged pull request from two weeks earlier that had already removed eight of the sites he was cataloguing. What it changed was not his task list but his model of the problem -- the surviving sites were not "the coordinate problem", they were the residual after a shipped removal campaign, which is a different engineering problem with a different risk assessment. The line worth quoting is his: none of it was in any chat history; it was in the repository, and he got it by naming a file path. That is the whole product claim, stated by somebody who did not write it. Attributed as one engineer's report, not presented as a benchmark. The numbers that travel with it -- one command, 7.4 seconds for 768 commits -- are his measurements on his machine and are labelled as such. Overstating here would cost the one property this repository has that most tools in the category do not, and which the same report singles out: it prints its own weak numbers next to the feature they belong to. The three properties in the closing list are his framing too: reviewable, owned by the repository, travels with a clone. They are the reason the authority is Git rather than a service, and they read better as consequences of a real session than as bullet points arriving unprompted. Record-Id: r-fieldreport Limit: this section reports one engineer's day on one repository; it is evidence that the mechanism works there, not a measured effect size, and the wording has to keep those apart Ruled-out: Presenting the 7.4s index and 768 commits as product benchmarks | they are one machine's numbers on one corpus, and bench/ is where measured claims live under ADR-0018's provenance rules Ruled-out: Paraphrasing his account in the product's voice | the credibility is that somebody outside the project said it, and paraphrase throws exactly that away Ruled-out: Leading the README with this | the opening claim is the product's own and should stay first; this belongs where a reader who is already interested asks what it looks like in practice Certainty: firm Blast: local Undo: easy Verified: readme, readme-order, readme-numbers, compatibility-matrix and install-script suites pass at 92 across all four languages; check-readme-numbers.mjs exits 0, so the generated block and the stray-statistic rule are both intact Unverified: nothing executable changed
The README argued the product accurately and asked a first-time visitor to read a long way before finding out what it prevents. A conversion review put the gap plainly: this document explains how CommitLore was built, not why a team using coding agents already has the problem. So the hero states the failure first -- an agent proposing a fix the team rejected six months ago, and somebody spending the review explaining a decision that was already made. The precise claim stays, one line down, in bold: an agent must not revive a decision the repository already reversed. Order changed; accuracy did not. Then the scene that makes it concrete. A session reuses `calculatePrice` for admin quotes because the inputs look alike, and the team gains a flag, a wrapper and a compatibility branch guarding a use case the function never owned. With the record present the agent shares the calculation primitives and leaves the policy entrypoint alone. That failure is recognisable to anyone who has reviewed agent-written code; the installer's musl story, which the field report section tells, is true but specific to this project. Four sentences replace three bullets for the comparison, because "which decisions still apply to this code path" only reads as different from RAG when the two sit in the same table. Four use cases replace none: a module boundary, a rejected workaround, temporary compatibility code, a verification gap -- each a sentence a diff cannot carry and a reviewer would otherwise say twice. Two recommendations were not taken. `Keep coding normally` after the install block would be the third time this repository shipped a document ahead of its code. The skill that ships to Claude Code still triggers on explicit CommitLore requests and still teaches the manual harvest path (#340). The sentence becomes true when that lands, not before. Testimonials from three external teams would need three external teams. One engineer's field report exists and is quoted as his; inventing the other two is the one thing that would cost this project the property it has and most tools in the category do not. Record-Id: r-convertreadme Limit: a README claim about the default workflow is only true if the shipped skill performs it, and the skill currently requires the user to name CommitLore first Ruled-out: Adding "Keep coding normally" now | the commit skill still triggers on explicit requests and teaches harvest; shipping the sentence first repeats the doc-ahead-of-code failure this session fixed twice Ruled-out: Writing testimonials to fill the social-proof section | one real field report exists, and fabricated or padded quotes would cost the claim honesty that is this project's strongest asset Ruled-out: Moving the benchmark block and protocol detail out of the README now | the generated block is byte-compared by check-readme-numbers and the guard figures are asserted inside the limitations slice; the move is worth doing and is worth its own change Ruled-out: Dropping "decision authority" everywhere | it is the right term in the architecture sections; it is only the wrong term for a first screen Certainty: firm Blast: local Undo: easy Verified: readme, readme-order, readme-numbers, compatibility-matrix, install-script and manifest suites pass at 116 across four languages; check-readme-numbers exits 0, so the generated block and the stray-statistic rule are intact; the order test's product anchor moves with the heading it names Unverified: whether any of this converts a visitor, which is not measurable from here
Stop the README saying Windows is unsupported, and check that it cannot again
"Stop re-reviewing the same bad idea" claimed something this tool does not do. It does not decide whether an idea is bad. It decides whether a decision still applies -- and an approach rejected last quarter can be the right one now that the constraint behind the rejection is gone. That distinction is the product; the headline erased it. Worse, it read as a promise about `guard`, which this project measures at precision 44.8% and recall 22.0% and labels an experimental advisory whose empty result is not a safety verdict. A headline leaning on the least-proven part, in a repository whose strongest asset is that it prints its own weak numbers next to the feature they belong to. It was also generic. A review bot, an ADR search, an architecture linter could all have carried that line, which means it identified no mechanism at all. The replacement names what actually crosses the boundary. Code survives a session; the constraints, the rejected alternatives, the warnings and the verification gaps do not, and `inherit` is the one word that covers sessions, agents and people at once. It claims preservation and delivery -- both of which are built and tested -- and claims no judgement about any proposal. The old line keeps a job. As pain copy above the before/after scene it is accurate, because that scene is a case where the decision genuinely still stands. It was only wrong as the thing the product is. Record-Id: r-heroinherit Limit: a headline that implies detection commits the product to guard's numbers, and guard is an advisory measured at 22% recall Ruled-out: Keeping "Stop re-reviewing the same bad idea" as the hero | it names a verdict on ideas rather than a lifecycle on decisions, and any review tool could carry it Ruled-out: "Never let agents make the same mistake twice" | guard cannot support a never, and the sentence sells detection this product does not perform Ruled-out: Leading with "decision authority" alone | precise in the architecture sections, and not a phrase a first-time reader converts on Certainty: firm Blast: local Undo: easy Verified: readme, readme-order, readme-numbers, compatibility-matrix, install-script and manifest suites pass at 116 across four languages; check-readme-numbers exits 0; typecheck clean; the install commands and the compatibility pointer survive the rewrite in all four files Unverified: whether the line converts, which is not measurable here
Sell what the product does, not a verdict it never makes
Patch. No new command, nothing breaking -- one defect that changed what an agent was handed, and two smaller corrections beside it. The one that matters: on a repository with no records, the index accepted any RFC-822-shaped `key: value` line as a trailer. Conventional-commit prefixes, a Homebrew digest, arbitrary body fields. One report had 106 rows where git had nothing. `context` is wired into the pre-edit hook, so a commit subject was reaching an agent as a recorded decision while `doctor` called the state healthy and `stale`, which reads git, correctly reported zero. Two commands in one tool disagreeing about whether a repository had any records at all. That is the product's own claim inverted -- the thing it exists to do is hand an agent accurate decision context -- so it does not wait for the next minor. `Verified:` is left alone deliberately, and the notes say so. It is protocol vocabulary, a release note using it as a field is indistinguishable from a record using it for what it means, and reading surrounding context to separate them is how a tool starts discarding records somebody wrote on purpose. The Windows repair path from 0.5.0 is restated rather than assumed inherited. A new release invites "this one fixes everything", and a repository whose hook predates 0.5.0 still needs `commitlore hooks install` re-run. Record-Id: r-release051 Limit: the hook is written at install time, so no release repairs a repository that already has one; every release touching hook behaviour has to restate what does Ruled-out: Waiting for 0.5.2 to carry #335 | false decision context reaching an agent is the inversion of the product claim, and a stable release should not hold it Ruled-out: 0.6.0 | no command is added and nothing breaks; calling it minor would hide a correctness fix behind a feature number Ruled-out: Filtering `Verified:` because a report listed it as noise | it is vocabulary, and separating a release-note use from a record use needs a guess that can discard real records Certainty: firm Blast: local Undo: easy Verified: check-release-version.mjs reports tag, package.json and `commitlore --version` all at 0.5.1; check-readme-numbers.mjs exits 0; typecheck clean; full suite 78 files, 1931 passed, 1 skipped Unverified: the release workflow, which runs only on a pushed tag
Write the 0.5.1 notes before tagging
CommitLore — record lintTrailers: clean — 16 commits in Active constraints for the paths this PR touchesLimits (115)
Ruled out (269)
Truncated: 150 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. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Promotes
devtomainfor 0.5.1. Same path 0.5.0 took.Merging this releases nothing.
release.ymltriggers on a pushedv*tag and nothing else. After this merge:The defect this release exists to ship
On a repository with no records, the index accepted any RFC-822-shaped
key: valueline as a trailer — conventional-commit prefixes, a Homebrew digest, arbitrary body fields. One report had 106 rows where git had zero.contextis wired into the pre-edit hook, so a commit subject reached an agent as a recorded decision, whiledoctorcalled the state healthy andstale— which reads git — correctly reported nothing. Two commands in one tool disagreeing about whether the repository had any records at all.That is the product claim inverted: the thing it exists to do is hand an agent accurate decision context (#335).
Also in
harvest-verifyreports an unparseable draft before demanding a transcript (harvest-verify: check the draft parses before requiring --transcript #329).serena/, a tool config agit add -Aswept into 0.5.0, removed — plus a guard so an undeclared file cannot ship unnoticed (Nothing notices a file that ships but was never declared #334)guardcannot perform at 22% recall (Sell what the product does, not a verdict it never makes #347)Deliberately unchanged
Verified:is protocol vocabulary. A release note using it as a field is indistinguishable from a record using it for what it means, and guessing from context is how a tool starts discarding records somebody wrote on purpose.The Windows repair path from 0.5.0 is restated, not assumed inherited: a repository whose hook predates 0.5.0 still needs
commitlore hooks installre-run.doctorreports such a repository asoutdated.Verification at this head
check-release-version.mjs v0.5.1— tag,package.json,commitlore --versionall0.5.1check-readme-numbers.mjs— exit 0;npm run typecheckcleandevpost-merge CI green