Skip to content

Five from the backlog: the tool says where its own answers come from - #38

Merged
illodev merged 2 commits into
mainfrom
fix/backlog-doctor-and-guards
Aug 7, 2026
Merged

Five from the backlog: the tool says where its own answers come from#38
illodev merged 2 commits into
mainfrom
fix/backlog-doctor-and-guards

Conversation

@illodev

@illodev illodev commented Aug 7, 2026

Copy link
Copy Markdown
Owner

Five backlog cards with one theme: the tool saying where its own answers come from. Three of them had a premise that did not survive being run, and those corrections are on the cards.

T-0160 — --root answered from a directory that was not a workspace

doctor --root packages/workfile reported six issues, exited 0, and indexed that package's docs/ as the workspace's documents. The failure is silent and plausible: a mistyped or stale --root one directory too deep produces a clean, empty, believable answer, and nothing distinguishes it from a board that is genuinely empty.

The marker check the discovery path always had now applies to an explicit root — strictly, not as a walk, because --root is an assertion by the caller and resolving it upward would be a second surprise rather than a fix. --allow-new is the way through, and it reaches that branch now: it only ever reached the path that already refused one, which is how this existed at all.

T-0218 — a finding never said which module produced it

Every finding carries its reporter now, and the CLI groups by module/code, so integration:<id>/<code> is unmistakable — which is the one case that matters, since a diagnostic returned by a repository's own healthCheck is the only source that is not ours.

The card's premise was wrong. It says every module returns { module, issues }; only the integration registry does. Stamping report.module produced undefined on 1396 of 1397 findings in a real workspace — caught by running it against one. The module is named where runDoctor calls each reporter, which is the only place that knows which one it is calling.

code is untouched deliberately: namespacing it would read better and would make every baseline accepted with --accept-baseline stale at once. A test fails if issueIdentity grows the field.

T-0223 — only cards noticed a retitled record's filename

Memory records, managed documents and unreleased changelog fragments derive their filenames from their titles identically and had neither the rule nor the repair. Written once now, in the layer that holds every kind — the same argument duplicate-record-id already makes two functions away — and doctor --fix repairs all four kinds off the index rather than through four loaders.

On Fube it finds 53 findings that were invisible (35 docs, 18 memory). What it will not touch is stated where the rule is: an indexed README is not ours to rename, a released fragment is published history — and the protocol already refuses to retitle one, which I checked rather than assumed — and a release is named after its version.

It then caught my own hand-rename of LRN-0033, one commit after I made it: I wrote a-cards-finding and the slugifier produces a-card-s-finding. Fixed by the tool, which is the canonical derivation.

The four per-kind length caps stay different (50/60/70/70) and a test pins that. Unifying them would rename every existing record whose title crosses the new bound, in one sweep, on the next --fix.

T-0219 — two agents sharing an --actor looked like one process

A board entry carries the session behind its claim, so the guard stops having to recover one from an actor's tail that an explicit --actor does not have. Driven through the real hook, four cases: the holding session stays silent, two agents with different sessions and one actor now prompt, two session-free terminals with one actor stay silent because unproven is not a verdict, and different actors prompt.

Two things the existing tests caught on the way. Building the board before writing the session file made a session ask about its own card — the exact failure the guard exists not to have. And the one-separation-rule test was modelling the claim as sessionless, so it agreed with the guard by coincidence on the one case where the board had learned something the model had not; it now compares the rule the guard actually applies.

The hot path gained one string operation and no I/O; the relative latency budget test passes.

T-0147 — the byte ceiling overwrote a tool's own flag

Reproduced before being fixed, as the card insisted. With the ceiling lowered, project_agent_context returned truncated: {"records":1} where its own outputSchema declares a boolean. The transport's marker is resultTruncated now — declared in every tool's schema rather than merely allowed, so a caller reads it from the schema instead of meeting it the first time a payload gets large.

Not a regression

The strict ratchet went 488 → 474, from typing runDoctor's reports array and narrowing two test files' rejection callbacks properly rather than annotating them any, which is what the ratchet's own message asks for.


pnpm run check green — 484 + 10 tests. doctor 0 errors 0 warnings, memory verify and changelog verify clean. Every guard in here is mutation-proven; the details are on each card.

🤖 Generated with Claude Code

https://claude.ai/code/session_01D3LTdq3mzMAQ98rwBegGjU

One theme, five cards, and three of them had a premise that did not survive being
run.

T-0160 — `--root` took the directory as given and checked nothing, so
`doctor --root packages/workfile` reported six issues, exited 0, and indexed that
package's docs as the workspace's. The marker check `cwd` always had now applies
to an explicit root too, strictly rather than as a walk: `--root` is an assertion
by the caller and resolving it upward would be a second surprise. `--allow-new`
is the way through, and it reaches that branch now — it only ever reached the
path that already refused one, which is how this gap existed at all.

T-0218 — every doctor finding carries the reporter that produced it. The card
said every module returns `{ module, issues }`; only the integration registry
does, so stamping `report.module` produced `undefined` on 1396 of 1397 findings
in a real workspace. The module is named where `runDoctor` calls each reporter,
which is the only place that knows. `code` is untouched on purpose: namespacing
it would read better and would stale every accepted baseline at once, so a test
fails if `issueIdentity` grows the field.

T-0223 — the stale-filename rule was cards-only, and memory records, managed
documents and unreleased fragments derive their names from their titles
identically. Written once now, in the layer that holds every kind — the same
argument `duplicate-record-id` already makes there — and `doctor --fix` repairs
all four. On Fube it finds 53 findings that were invisible. What it will not
touch is stated where the rule is: an indexed README is not ours to rename, a
released fragment is history, a release is named after its version. It caught my
own hand-rename of LRN-0033 one commit after I made it.

T-0219 — a claim board entry carries the session behind its claim, so the guard
stops having to recover one from an actor's tail that an explicit `--actor` does
not have. Two agents sharing one prompt now. Building the board before writing
the session file made a session ask about its own card, which the existing
one-separation-rule test caught; the signal is written first. That test was also
modelling the claim as sessionless and agreeing by coincidence, so it now compares
the rule the guard actually applies.

T-0147 — reproduced before being fixed, as the card insisted: with the ceiling
lowered, `project_agent_context` returned `truncated: {"records":1}` where its own
schema declares a boolean. The transport's marker is `resultTruncated` now,
declared in every tool's schema rather than merely allowed.

Two strict-ratchet improvements fell out rather than regressions: 488 → 474,
from typing `runDoctor`'s reports array and narrowing two test files' rejection
callbacks properly instead of annotating them `any`.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01D3LTdq3mzMAQ98rwBegGjU
@vercel

vercel Bot commented Aug 7, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
workfile Ready Ready Preview Aug 7, 2026 11:08pm
workfile-site Ready Ready Preview Aug 7, 2026 11:08pm

CodeQL reported `js/polynomial-redos` as high against `routeRoots` in
`docs/validation.ts`, and it is right: `replace(/\/+$/, "")` retries the anchored
`+` from every start position, so a value of N slashes costs O(N²). The alert
landed on `main` this morning with T-0217 and blocked a pull request that has
nothing to do with it.

The spelling was written five times across the package and once more in the hook.
Only the `routeRoots` one was reported, because it is the one whose taint CodeQL
can follow from a declared value — so fixing that one alone would have left five
copies of the same shape and the next report to whichever of them grows an input
first. `stripTrailingSlashes` in `core/glob.ts` is a linear loop and all five go
through it; the hook keeps its own copy, since it imports nothing from the
package, and the scope it strips comes off a card that can arrive from a fork.

T-0224 records what this does not do: nothing stops the sixth copy.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01D3LTdq3mzMAQ98rwBegGjU
@illodev
illodev merged commit eb12a11 into main Aug 7, 2026
16 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