Repository navigation
[harvest] Applying e92e9c4 to a target project: 11 defects and 6 smaller items, four with POC patches (you-are-hear #42 / PR #43) #58
Description
Activity
- addedharvestHarvested from a real cascade runHarvested from a real cascade runsource:you-are-hearEvidence from the you-are-hear run (GitHub axis)Evidence from the you-are-hear run (GitHub axis)
on Sep 7, 2026 Update, 2026-09-07: final state, one correction to this report, and two more defects
you-are-hear PR #43 merged as
3f7548a(72 commits; all four required checks green). The branch is gone, so the SHAs below are on that project'smain. Flipping the PR to ready ranclaude-review.yml, and the auto-review found two kit-side defects this report missed — one of which invalidates a claim I made in item 4.Correction to item 4: invoking a fixture from the project sub-block does not make it run
Item 4's POC said the hook fixture is "invoked from the project sub-block of § Verification so it runs on every block run". That is false, and for a reason that generalises to every project. The block runs under
bash -e. The advisory-exemplar assertion in the hook-registry loop is at line 131 of the 238-line extracted block, and it exits 1 on any project that wires and registers the two advisory hooks — the divergence item 5 describes, and the one § Hook authoring tells projects to make. Everything after line 131 therefore never executes.Measured on that repository: a raw
bash -erun of the live-extracted block ends onadvisory exemplar analyze-on-edit.sh is registered, andhook-payloads-fixture: okappears zero times in its output.What is dead in that window is not marginal. Lines 132 to 238 carry the
${CLAUDE_PROJECT_DIR}placeholder-form check, the four tier tokens, the two-views paragraph check, the kit's own launch-root guard payload dry-runs, the Stop hook's live-tree check, the reviewers'## Writing memorydiff, the Reviewer-agent-memory row check, the P4 conventions checks (Licensing,.gitignoreanchoring, the issue-less branch form, the lockfile counter-line), the.githubstarter and ADR-lint checks, the roadmap and executor term checks, the review-automation template checks, the cascade-events and Amendments checks, theLSPtool check, theframing.md indexabsence check, the context-budget print, both sentinels, and the entire project sub-block. Roughly 45% of the suite, including the two payload dry-runs the kit added precisely so hook behaviour is asserted on every run.So item 5 is not cosmetic and not only about a red line: a project that follows § Hook authoring loses the back half of its verification block silently. Three repair shapes, in the order I would consider them:
- Make the exemplar arm project-conditional (or move it into the project sub-block), which is what item 5 already proposed.
- Order the block so the project sub-block runs first, so a kit-side known-red never masks project checks.
- Collect failures instead of aborting: run each check, record red lines, exit non-zero at the end. This is the only shape where one deliberate exemption cannot hide an unrelated regression, and it fits the block's own stated standard that a permanently red line is a suite nobody runs.
The local fix taken there was none of those, because the block is the kit's and the project did not want to edit a check out of it: the fixture now has a second runner, a
misetask in the project's gate task dependency list, so it fires on every gate run and in CI (916f580). The block keeps its invocation for a fully-neutralized run. If the kit adopts shape 3, that second runner becomes redundant, which is the better outcome.New defect: the Stop hook blocks the auto-review's own hand-off in the claude-code-action container
Reproduced by the reviewer, live, during the review of PR #43 — the run blocked on:
BLOCKED: a reviewer memory tree exists outside the repository root: ./.claude-pr/.claude/agent-memory.claude-pr/isclaude-code-action's staging copy of the PR branch's tooling: it holds the branch's.claude/including a copy of the committed rootagent-memory/tree, alongside a working tree checked out at the base. Nothing was dispatched from a subdirectory; there is no fork. The kit's own hook fires (its hard-coded prune list does not name.claude-pr), and the ignore-driven replacement in item 1 fires too unless the project ignores that path — rule 1 does not save it, because the directory needing the prune is.claude-pr, whose basename is none of the three protected names.This is kit-wide rather than project-specific:
claude-review.ymlis the kit's own blueprint template, so any project adopting both the Stop hook and the review workflow gets it. Two costs, worth weighing separately:- The Stop tier blocks the hand-off of every session in that container.
stop_hook_activecaps it at one block, so a review still completes, but it burns a turn and the block is indistinguishable from a real one. - The remediation text is actively wrong there. It says to "move each
<reviewer>/directory's files into the root tree … delete the forked tree", which, followed literally, deletes the harness's staging directory mid-run. A guard whose false-positive path instructs the agent to delete infrastructure is worse than a guard that misses.
Two-part fix taken there (
39ed311), both worth upstreaming:- An anchored
/.claude-pr/entry in.gitignore, after which the ignore-driven prune covers it with no hook change. For the kit that means adding the line to the scaffold's.gitignorestarter ingithub-starter-templates.md, beside the harness-transient entries §.gitignoreanchoring already prescribes. - The
BLOCKEDmessage now says that a path belonging to tooling rather than to a dispatched agent — a staging copy, a container's workspace, a vendored checkout — is not a fork, that nothing should be moved or deleted, and that the route is an anchored ignore entry.
Correction to item 2's POC: the stderr capture needs a writability probe
Item 2's POC (capture
find's stderr, warn when non-empty) introduces its own silent miss if copied as written. Withmktempabsent and the fallback path unwritable, the2>"$scan_err"redirection fails beforefindruns:forksstays empty,[ -s "$scan_err" ]is false because the file was never created, and the hook exits 0 on a tree it never scanned. Probe the path first and degrade to/dev/nullwith a warning that the walk is unmonitored; and skip/dev/nullin the cleanup, or every degraded stop also printsrm: cannot remove '/dev/null'. Both were caught by running the fixture from item 4 against the fix (694b8ed,cec420c).Related, and cheap: § Hook authoring's header shape asks for
Blocked:/Allowed:/Timing:/Path:/Tier:but not for the hook's dependency set. That hook's header claimed "No jq dependency" while the rewrite had addedgit(the root and the prune list) andmktemp(the stderr capture). ADepends:line naming each dependency and what its absence costs — fail open, degrade unpruned, degrade unmonitored — would have caught it at authoring time.New smaller item: the ADR status-cell separator is pinned in two places a real project's index can already contradict
docs/adr/README.md's starter andcbk-conventions-reference.md§ ADR relation grains both pinAccepted · Refines ADR-0007 (D2). A project whose index predates that text uses whatever it used — there,Accepted — refines ADR-0003 (D3)— andadr-newmerged from the kit would have written the second spelling into the same table, which is exactly the multi-surface drift § Multi-surface facts exists to prevent. Fixed there on both governing surfaces (36720d7), withadr-newnow told to read the index before writing a row rather than assume a separator. For the kit, the smallest change is that instruction inadr-new: the separator is a project's existing convention, not the kit's to pin.Everything else in this report stands
The auto-review checked the round-1 dispositions and the six refuted sweep findings against the code and agreed with them as written, so items 6 to 11 and the smaller items are unchanged. Items 1 and 3's POCs are unchanged and now merged.
Second application of
e92e9c4— echosphere (Linear axis), research pass only, 2026-09-12echosphere's ECH-39 is the same issue shape as you-are-hear #42 (
[cascade:meta] Sync .claude/ to context-builder-kit harvest 3), authored 2026-09-07 before this report existed. Before executing it I ran this issue — body and follow-up comment — against the kit tree ate92e9c4and the spec (six surface readers → two adversarial verifiers each → a completeness critic; load-bearing claims then re-run by hand). The run was aborted for a re-rough-in; nothing landed. Items 1–11, the smaller items and the comment's additions all reproduce ate92e9c4as stated, with two calibrations: theMEASUREMENT / SPIKE VARIANTspelling has one spaced hit (rough-in-spec-template.md:256), no collapsed hit, and no mechanized consumer anywhere in.claude/; and the "empty-events transcript" test gap is asserted byagent-cost-fixture.sh:35on the natural reading — it holds only for a transcript with zero parseable lines.What the second application adds, kit-side:
-
The verification block's first
bash -eabort on a target that does not ship.mcp.json.exampleiscbk-conventions-reference.md:493, not the advisory arm.absent grep -rn -i "…" .claude/ README.md .mcp.json.example— grep exits 2 on the missing path,absent()reads any non-1 exit as "not a clean miss" and exits 1, at extracted line ~21 of 228. Item 5's arm at:603is never reached. Green on the kit only because the kit ships the file; the check needs a[ -f .mcp.json.example ]guard or the scaffold must make the file mandatory. A migration note beside it: the retired-vocabulary literal is split in this check (opinionate[d] profile), but the88b1ede-era project sub-block shipped the un-split form (! grep -rn -i "opinionated profile\|opinionated_profile" .claude/, echospherecbk-conventions.md:319); a project that re-homes its sub-block into the reference half per the split then trips its own check. Worth one sentence in the re-homing guidance. -
The bracket-truncation idiom trips spellcheckers. Under a
typosconfig that includes hidden directories (echosphere's_typos.tomlsetsignore-hidden = falsedeliberately, and forbids blanket excludes),cbk-conventions-reference.mdfails with four errors::506 onl(github-onl[y]),:516 summar,:588 chec(double-chec[k]),:594 doub(doub[t]). A target whose gate spellchecks.claude/cannot take the file byte-identical and keep its gate green. Two shapes: ship anextend-ignore-resnippet for the idiom, or split on the leading letter ([g]ithub-only,[d]oubt) so the dictionary word stays intact — whether every remainder is clean under typos' dictionary needs a run. -
Item 7, sharpened, and a design gap behind it. The roster prompt at
review-sweep.js:112asks for hints "as bare paths (e.g. src/schema/)" — with the trailing slash — and:131strips exactly that character before:141's barestartsWith, so a fully compliant roster read still yields boundary-less hints. Behind it: thepathHintsform cannot express a segment anchor at all. echosphere's pre-e92e9c4REVIEWER_TRIGGERSmatched a crate family with/^echosphere-[^/]+\//; moving to the runtime roster read is a regression for that shape, and the only stable form is enumerating each directory with its slash (and a PR that touches only.claude/cannot exercise the hint, so the "first run doubles as the roster check" only ever proves the name parsed). -
Item 9:
hallucinationsis dereferenced twice —finish-ab.js:159and:161. A?? []at:159alone leaves the crash live. -
N2 and the kit's own
.gitignore:3-9. The kit tells target projects to commit.claude/agent-memory/("DELETE this line") — which is exactly the exit under which the.claude-pr/false positive fires, since the staging copy then carries the committed tree.grep -rn claude-prover the kit returns nothing, so the anchored-ignore remedy has no portable home other thangithub-starter-templates.md; and the hook's prune list is hard-coded, so on the shipped hook a.gitignoreentry is inert anyway. The other exit is not documented either: a project choosing thelocalscope per its Surface-inventory row gets no ignore guidance for.claude/agent-memory-local/, so its reviewer trees end up untracked and unignored. One caveat for the version range: the.claude-pr/reproduction was onclaude-code-actionv1.0.206; echosphere pins v1.0.193 and whether that version stages.claude-pr/is unverified. -
The block's
bash -e+absent()contract makes "recorded exemptions" unreachable by construction. The comment's repair shape 3 already says this; the sharpening from a second target is that any spec written to the block's own vocabulary ("fix or record every red line") is unsatisfiable, because a recorded red aborts before both sentinels. Shape 3 (collect, then exit non-zero) is the only one under which "recorded" means anything.
Provenance: the full record — per-item reproduction, the echosphere-specific dispositions, and the assessor-vs-verifier adjudications — is on echosphere ECH-39 (Linear). Nothing above is a request to split; filing it against the existing items is the intent.
-
Second application of
e92e9c4— echosphere, executed (j4th/echosphere#37, 2026-09-13)The research pass above became ECH-40 (a re-rough-in of ECH-39) and was executed: branch
chore/ech-40-kit-sync-harvest-3, 24 commits, draft PR j4th/echosphere#37. Method: a three-way merge, not hand-reconciliation —git merge-filewith base = the kit at the install commit (88b1edeb), ours = the project, theirs =e92e9c4. Ten conflict hunks across seven files; every other project fill auto-resolved. The verification block runs literal-green underbash -efrom the repo root (three sentinels, exit 0); the review floor (/simplify,pr-review-toolkit:review-pr) plus the copiedreview-sweep.jsran on the result. What the execution adds, kit-side — items 1–7 are defects or gaps in the kit tree ate92e9c4, 8–10 calibration:-
adr-conformance-reviewer.md:55assumes the adr-starters register's id form. "A citation … already recorded wrong there is cited by itsC-number" is true ofscaffold/references/adr-starters/corrections.md(### C-NNN —,:27) and false for a target whose register predates the starter — echosphere'sdocs/adr/corrections.mdnumbers entries in prose under per-ADR sections ("entry 10"). The reviewer then looks for ids the file does not have. Fix: "by the register's own id form", or a bootstrap-checklist disposition row for the reviewer beside the rule files. -
The path-scoping callouts describe the shipped state after stamping.
logging.md:8andtesting.md:11read "As shipped it carries a placeholder: replace<ext>…" / "Replace<ext>with the project's test-file extension(s) …"; the bootstrap checklist rows (bootstrap_checklist_template.md:90–91) say stamped and never say to rewrite or delete the callout, andscaffold/SKILL.md:315's "no<ext>left" clause is scoped to the globs — so a stamped target carries a paragraph telling the reader to stamp it. On echosphere four review dimensions flagged it as rot (8 of the sweep's 12 unverified findings were this one item). Related:testing.md's set is<ext>-shaped, and a language with inline unit tests (Rust's#[cfg(test)]) has no test-file extension — the honest stamp there is**/tests/**alone, which every reviewer then flags as missing the inline modules. One sentence in the callout naming that case would settle it. -
adr-new/SKILL.md:73— "A new ADR connects to an existing one through one of two relationships" sits under## Refines vs Supersedes vs Extends(:69), which names three, and the section goes on to describe four grains (Supersedes:,Refines:, clause-scopedSupersedes: ADR-NNNN Dn,Extends:). Count rot from the grain additions — let the list be the count. Separately:Promotes:is a slot the skill fills (:32,:48,:58) and never defines in that section. -
cbk-conventions.mdcontradicts itself on a frame's## Pre-flight checkstable. The Quick reference row (:234) says "Adding a## Pre-flight checksrow to a frame | Append-only edit to the frame's## Pre-flight checkstable"; the Mutation discipline row fordocs/cbk/frame-NN.md(:192) names## Rough-in eventsas the only append-only table, "otherwise immutable post-commit". Framing writes the pre-flight table at frame creation and rough-in reads it; no skill appends to it. Either the frame row gains the table or the Quick-reference row goes. -
settings.json_comment_hooks, STOP tier: "exits 2 while a.claude/agent-memory/tree exists anywhere but the root" — the hook huntsagent-memoryandagent-memory-localalike (detect-forked-agent-memory.sh:15,:61,:70). The comment undercounts by one scope. -
Item 2 above, sharpened by the gate. A
[default] extend-ignore-reof[A-Za-z-]+\[[A-Za-z]\]does exemptonl[y]/chec[k], and also exempts any misspelling that abuts a one-letter bracket anywhere in the tree —recieve[d]went unflagged under typos-cli 1.49.0 while barerecievewas caught. The right shape is a[type.<name>]table withextend-glob = ["cbk-conventions-reference.md"]carrying the rule alone; typos honors it (the reference file goes clean, a scratch file outside the glob stays red). The kit skills' bracket matches are index notation (R[i]/R[j]/R[N]andM[n]inrough-in/references/hitl-question-bank.md,M[i]inframing/references/procedure.md,IC-[m]) plus one ordinary inflection bracket (allow[s],scaffold/references/github-starter-templates.md:207) — typos flags none of them, so none needs an exemption. Wherever the kit tells a target how to spellcheck.claude/, this is the form to name. -
Nothing wires the block into a target's gate. The kit's own CI runs ADR immutability only, and a target that adopts the block as written runs it by hand (
awkextraction,bash -e) — so the registry↔tier↔table checks, the## Writing memoryparity, the executor-pair diff and the always-loaded budget fire only when someone remembers. The extraction is two documentedawklines; the blueprint tooling template could ship it as a task the gate calls. A suggestion, not a defect — echosphere has not wired it yet either. -
Item 7 (roster hints), calibration evidence. With the roster line written as enumerated trailing-slash prefixes (
echosphere-core/, … ,firmware/,proto/), the copiedreview-sweep.jsroster read parsed all four reviewers, dispatched the three cross-cutting ones, and reported the domain reviewer as not path-matched on a rules-and-config diff (.claude/,.gitignore,.mcp.json.example,_typos.toml,docs/adr/) that touches none of its enumerated crate prefixes — the runtime read works as designed; the boundary loss stands as reported. Echosphere pins the enumeration toCargo.toml's[workspace] memberswith a check in its project sub-block, so the list is derived rather than believed. -
Item 1 (ignore-driven prune), the recorded residual reproduced independently. A fresh reviewer built
build/nested/.claude/agent-memory/<x>/under an ignoredbuild/and got exit 0 — the "stray under an explicitly ignored build directory → 0 (pruned for speed …)" scenario in the POC's own table. The kit's own hook ate92e9c4prunes a hand-kept-namelist (node_modules,target,build,_build,dist,.venv—:70–72), so the same tree is exit 0 there too; reproduced with both hooks.require-repo-root-for-agents.shrefuses Task/Agent/Workflow dispatch whose working directory is not the git top-level (fails open with a warning outside any checkout), so the residual is a tree placed under an ignored directory by hand or by tooling. In the ported version (you-are-hear's, echosphere's) the header sentence "Never prune a path that could BE or CONTAIN the tree this hook hunts for" promises more than its basename-only skip delivers; whichever version the kit adopts, the residual belongs in the header, not only in the POC's scenario table. -
What went right. Recording the install commit makes the next sync a merge: of the 48
copyrows whose project copy carried lines the kit lacks, 47 were byte-identical to the kit at88b1edeb— pure kit drift, derivable from the base, no reconciliation at all. Echosphere now records the synced sha in its conventions preamble; the kit could tell every target to record the install commit and sync bymerge-file. And the block'sbash -e+absent()contract did what the comment's shape 3 predicted: every red was fixed, none recorded — including two of the target's own pre-split checks (the path-drift and "Deferred meta-issues" greps), which match the kit's skill content by construction and had to be retired rather than re-homed.
Project-side findings (a hybrid status-cell example, a sed range that read a manifest to EOF, live backticks in a ported heredoc) were echosphere's own and are fixed on #37; none is the kit's.
-
Addendum — the host-side auto-review on the same PR (j4th/echosphere#37, 2026-09-13)
Two more items from
claude-review.yml(echosphere's is the blueprint template as provisioned; line refs are echosphere's.github/workflows/claude-review.yml). The ready-flip run —actions/runs/34785442101— endederror_max_turns("Reached maximum number of turns (60)") after 7m16s, having posted nothing but the action's "Claude encountered an error" comment. The PR carries 137 changed files, 88 of them byte-identical kit copies.-
The default path's turn cap and the prompt's every-file contract cannot both hold on a sync-sized diff, and the failure mode posts nothing. The prompt says "Begin by enumerating every changed file … and walk each one — 'thorough' means every changed file is examined and accounted for"; the default path runs
--max-turns 60with--disallowedTools Agent(single thread,:322–:323). 137 files > 60 turns by construction: the run spent 66 Bash calls reading the tree (git show HEAD:<file>one by one) and produced zero review text before the cap. Under the deliverable-trap rule the artifact is the review, and here there is none — not a partial summary, not the files it did cover. Two fixes, either or both: make the cap scale with the diff (a--max-turnsderived from the changed-file count, or theclaude-deep-reviewpath — which keepsAgent— auto-selected above a file-count threshold instead of by hand-applied label); and have the prompt degrade explicitly ("if the diff exceeds N files, review the authored set the PR body names and say what was not read") so a capped run still posts what it has. Echosphere skipped the re-run withskip-claudeon the operator's call — the two/finishfan-outs had already covered the authored files — but a first real diff of this shape should not need that judgment. -
origin/mainis absent under the checkout the template configures, and the reviewer's recovery is unallowlisted.actions/checkoutruns withfetch-depth: 0butref: ${{ github.event.pull_request.head.sha }}(:156–:157), so the base branch is never fetched: the reviewer's firstgit diff origin/main...HEAD -- mise.tomlreturnedfatal: origin/main...HEAD: no merge base. It then triedgit fetch origin main --depth=50,--deepen=100,--unshallow(twice) andgit fetch origin main— all five refused ("This command requires approval": the allowlist isgh pr view/diff/comment/checksandgh run view/list,:323) — six of the sixty turns gone before it fell back togh pr diff, which the prompt already names. Fix: either fetch the base in the checkout step (a secondfetchof${{ github.event.pull_request.base.ref }}, orfetch-depth: 0withoutref:and a checkout of the head sha after), or state in the prompt thatgh pr diffis the only diff source andorigin/maindoes not exist in this checkout. Allowlistinggit fetchis the weaker fix — it spends network on what the checkout should have provided.
Not a kit defect, for completeness: the Stop hook did not fire in the container — every
detect-forked-agent-memorystring in the log is the reviewer reading the hook file — so the anchored/.claude-pr/ignore from the 2026-09-07 comment holds on a real run.-
The
_example_PostToolUse_*stanza silently disables the entirehooksblockFound while applying
e92e9c4to echosphere (Claude Code 2.1.270, 2026-09-13). This is a defect in the kit's § Hook authoring convention itself, not in a project's fill — and its blast radius is every guard the kit ships.Symptom
With the kit's
.claude/settings.jsonas shipped, no hook in the file runs. All four tiers are inert: hard-deny (immutable ADRs, lock files, frozen corpus, commits onmain, agent launch root), ask-gate (gh prstate), advisory (format-on-edit), and stop (forked agent-memory). The file is valid JSON, the scripts are healthy, the trust dialog is accepted, and nothing warns.A settings checker reports it as:
PreToolUse/PermissionRequest hooks are declared outside "hooks" (at the top level or under another key)
That wording is misleading — there are no
PreToolUseentries outsidehooks. The trigger is_example_PostToolUse_analyzer, a top-level key whose value is a hook-matcher object (matcher+hooks). The loader appears to reject the file's hook configuration wholesale when it finds a hook-shaped object outsidehooks.Evidence — single-variable test
Probe:
git commit --dry-run -m xonmain, whichprotect-main-branch.shmust block (exit 2)._example_PostToolUse_analyzervalueProbe result object (as the kit ships it) not blocked — hook never invoked, no output same key, value converted to a string blocked — PreToolUse:Bash hook error: protect-main-branch.sh: BLOCKED: git commit on 'main'Nothing else changed; the
hooksblock was byte-identical across both. The change hot-reloaded mid-session, no restart. Fed a crafted payload directly, the script returns exit 2 with the right message in both states — so the scripts were never the problem, only whether the harness invoked them.I did not isolate whether the loader keys on the object's shape or on the event name in the key. The checker's message names
PreToolUse/PermissionRequestwhile the offending key saysPostToolUse, which points at shape rather than name — but that's inference, not a test.Why this is worse than a lint nit
The kit's own thesis is that "hooks enforce non-negotiables more reliably than instructions" (
workflows.md§ Anti-patterns,cbk-conventions-reference.md§ Mechanize the gates). Every project that adopts the kit and follows § Hook authoring gets that guarantee inverted: the guards are present, documented, registered, verified by the gate — and never run. There is no failure signal.Note also that
cbk-conventions-reference.md's verification block pins the broken shape:... and has("_example_PostToolUse_analyzer") and (has("_example_PostToolUse_formatter") | not) ...so
mise run checkstays green while the hooks are dead, and deleting the key to fix the hooks turns the gate red.Suggested fix
§ Hook authoring currently says:
Exemplar stanzas ship commented out. An advisory hook the project must wire to its stack (a formatter, an analyzer) ships unregistered with its registration as an
_example_PostToolUse_*key besidehooksJSON has no comments, so "commented out" is being approximated by a live object — which is exactly what breaks. Options, cheapest first:
- Ship the exemplar's value as a string holding the snippet. One-line change to the kit's
settings.json; thehas()assertion in the verification block still passes unmodified, since it is type-agnostic. Least churn, keeps the exemplar where an operator will find it. - Move the exemplar out of
settings.json— intoanalyze-on-edit.sh's header (which already documents the stanza) or asettings.example.json. Cleaner separation; requires editing the verification block.
Either way § Hook authoring's bullet needs rewording, and it's worth a verification-block check that no top-level key in
settings.jsonholds a hook-shaped object — that's the invariant, and it's onejqline.Reproduction
# with the kit's settings.json as shipped, from a repo on main: git commit --dry-run -m probe # expect BLOCKED; observe it run jq '.["_example_PostToolUse_analyzer"] = "…snippet as a string…"' \ .claude/settings.json > /tmp/s && cp /tmp/s .claude/settings.json git commit --dry-run -m probe # now BLOCKED, as intended
- Ship the exemplar's value as a string holding the snippet. One-line change to the kit's
Residue after harvest 4 — 2026-09-29 audit
Reopening. #59 closed this issue COMPLETED, but its own definition of done — every item landed or explicitly a non-goal — was not met. This comment is the residue, item by item. Each residue is now applied in crease-data/crease#281 (open), so the next mint can take it mechanically.
How the audit was made
This is the method proposed as the gate for the next mint.
- Every ask in the body and in each comment was listed as one entry: 41 in all, with 37 real items.
- For each entry, the audit found where the harvest-4 spec or feat: harvest 4 — the kit as applied: settings liveness, the hook stdin/exit contract, the exercised fork detector, and the rest of #58 #59 claims it (a cluster heading, a D-number or a PR bullet).
- It then checked the claim against the kit tree at
74edf84(file and line, grep commands recorded) and gave a verdict: landed, partial, declined, or not an item. - A refute-by-default verifier re-checked every non-landed verdict.
The result:
- 32 of 37 items landed;
- 4 landed only in part;
- 1 declined item's promised mitigation never landed;
- several landed items left a smaller residue, mostly introduced by the fix itself.
Every closure in the kit was made from inside the kit: #54–#57, #59 and
1e10363. None was manual or from outside. #33 was audited the same way and is clean except for one dropped note, carried here as B.The residue, and where each fix is
The table gives the crease commit (on crease-data/crease#281) and the test that proves each.
# #58 item Residue at 74edf84Fix crease commit · proof R1 body item 4 No durable behavioural coverage of the hooks' branches. The block has structural checks over all nine hooks plus about ten probes. protect-immutable-adrs.shand the knowledge-backend ask-gate have zero behavioural cases;protect-main-branch.shhas no allow or master case;guard-pr-state.shcovers only merge;protect-lock-files.shcovers 2 of 10 arms. § Hook authoring › Verify by payload says the branches are asserted inhook-contract-fixture.sh, which holds structure only.Behavioural fixtures, one per hook family (below). § Verify by payload names them. f7c5659hook-guards-fixture.sh: 50 cases, then 57 after a verification pass found three branches no case reached (baregh pr merge,git commitwith nothing after it, the$PWDfallback;a12e547).hook-payloads-fixture.sh(29 cases, ported from you-are-hear);protected-paths-hook-fixture.sh(57). The main-branch guard's payload-cwd precedence is pinned both ways, with two throwaway repos that disagree and each winning when it should; neither the kit's over-buffer probe nor its older tests pinned it. Proven against mutants of each hook: twelve in all. The fixture names this project'srequire-notion-ok.sh, so the kit copy renames itrequire-knowledge-backend-ok.sh.R2 smaller item 1 A meta's children, titled [<slug>:<meta-tag>:R<#>], still fail/finishStep 1's four-form title check, which contradicts the sentence around it.The child form is the fifth admitted form in finish.md,finish-command.mdand bothfinish-procedure.mdcopies.b7625cb· the block's byte-parallel diffsR3 2026-09-07 comment (separator) adr-new/SKILL.md:52still pinsAccepted · Dn superseded by ADR-${NNNN}for the clause-scoped annotation, against line 51's "the separator is the index's convention". § ADR relation grains still pins·.The conventions name the starter's form as the starter's; a target's index sets its own cell and separator. For the kit's adr-new:52: don't pin a form. Tell the executor to read the index's rows, which can hold more than one form. crease's holds two: the Status cell for a parent with no relation parenthetical (ADR-0011's row), and inside the Title parenthetical for one that has it (ADR-0016's row).0f3fb98,5fbe124R4 2026-09-13 comment item 7 Nothing in the templates wires the block into check. A target learns it only from § Verification › Run it or the runner's header.blueprint/references/templates/tooling.md: the minimum task set gains a verification taskcheckdepends on, with a rule that it is a leg ofcheck.scaffold/references/bootstrap_checklist_template.md: a verification-matrix row that runs it and expects both sentinels.f2323e1,80f6e57R5 2026-09-13 comment items 2 and 6 (declined) The non-goal promised "a one-line note in § Verification's preamble names the idiom's cost". No such note exists anywhere in the kit (grep spellcheck/typos/extend-ignore: 0). The note: an absentcheck brackets one letter so the grep never matches its own line, and a spellchecking target sees a typo. Exempt the idiom in that one file, never repo-wide: intypos, a[type.<name>]table withextend-globnamingcbk-conventions-reference.mdand anextend-ignore-refor the idiom. The shape was checked against typos'docs/reference.md.23f9dc5R6 body item 2 Landed, but the partial-scan warning has no fixture; hook-contract-fixture.shprobes only the degrade path.hook-payloads-fixture.shcarries a partial-scan case: an unreadable directory makes the warning print and the hook exit 0.covered by R1's fixture R7 2026-09-13 comment item 5, H3 The spec says github-starter-templates.md's.gitignorestarter gained anchored/.claude-pr/. That file has no.gitignoresection at all.github-starter-templates.md§.gitignore— the harness block:/.claude/agent-memory-local/,/.claude/worktrees/,/.claude-pr/, and the kit's bytecode under.claude/workflows/, each pin-asserted.f81c487·git check-ignorepins in the commit bodyR8 body item 3 New in the fix: the *.lockfallback's remediation says "add its name to the hook's exemption comment". No such comment exists, and nothing would read one."If this file is NOT a lock file, give it a case arm of its own above the *.lock)arm in this hook (an arm that exits 0), and say so in the PR."abe5970(red) →073c0b1·hook-guards-fixture.shasserts the new route and the absence of "exemption comment"R9 body item 6 Implied and not landed: § Applied instances quotes "a top:workhorse cost ratio of 3.2× pooled" (and agent-cost.py"3.2x to 3.6x") with no note that it was priced at the old 0.1× top-tier cache rate.Stated conditionally, because the run's model is not known from here. Claude Code 2.1.257, which made Fable 5.1 the default Fable, was published 2026-09-01T17:15Z, the day of the run. If the drafters ran on Fable 5.1 (0.025×) the figures are upper bounds, and the transcripts' model ids settle it. e2b88beR10 2026-09-13 comment item 3 "Enumerate directories; a segment anchor cannot be a prefix" lives only in a review-sweep.jscode comment. The rules a project reads when writing its roster line never say it.pr-review.md's craft rule andpr-review-reference.md§ Authoring: a family of directories has no prefix form, so enumerate each and add one when the family grows.49420c0R11 body item 5 (follow-on) Once a target registers an advisory hook, the two-views check also goes red until cbk-conventions.md§ Mutation discipline names it. TheRegister:header and § Hook authoring don't say so."Wiring one is three edits, not one: the stanza into hooks.PostToolUse, the hook's name into the project sub-block'sADVISORY_WIRED, and the hook's name intocbk-conventions.md§ Mutation discipline's two-views paragraph." In § Hook authoring and in theRegister:header of both advisory exemplars.546fc71R12 addendum item 11 Ask (b) landed, but the degrade clause's [N]comes with no way to size it against--max-turns 60.A comment above claude_args:--max-turnsis spent per tool call, not per round trip (echosphere measured it: five Reads in one response still cost five turns). At one Read a file, keep about a third of the cap forgh, inline comments and the summary: N = 40 against 60, and the two move together.713cc5c(crease's filled workflow also gains the clause it never had)R13 2026-09-13 comment item 9 The detector's rule 1 still says "Never prune a path that could BE or CONTAIN the tree", which the header's Residual line contradicts. "Never prune a directory whose own name is one of the three this rule protects — the two the hook hunts, agent-memoryandagent-memory-local, and.claude, which holds them … A tree nested inside some OTHER ignored directory is pruned with it — the Residual above, a limit of this rule, not a promise it keeps."48beaea,0e536a5R14 several Kit-internal traceability slips, nothing to apply in a target: H3 claims a .gitignorestarter that does not exist (R7); H4 says it "closes … item 6" against D31 and § Non-goals; the spec's cluster headings cite "the 2026-09-13 comment", and three comments carry that date; several items are covered only by a non-goal's wording, never by number.For the kit's next spec: cite comments by id, and trace every item by number. — B from #33 #33's review-layer calibration note (j4th/you-are-hear#43: the floor, the round-2 sweep and the flip's auto-review each caught what the previous layer missed, and "none of them was the layer that found the previous layer's bug") landed nowhere. pr-review-reference.md§ Anti-patterns › "Folding one review layer into another".d06330cTwo more residues are recorded, not applied:
- The
adr-newheading "Refines vs Supersedes vs Extends" is cosmetic ([harvest] Applying e92e9c4 to a target project: 11 defects and 6 smaller items, four with POC patches (you-are-hear #42 / PR #43) #58 comment item 3). Its sentence is fixed. - The correction to item 4's "Shape 2" (run the project sub-block first) is declined by D31's fail-fast. Under fail-fast, a kit-side red still hides the state of a target's project checks. It is a declined residual, not an untracked one.
The hook fixtures (R1, R6, R8) are bash, need only
gitandjq, and run against throwaway trees, so they port as they are. The verification block in crease runs all three.The same final check swept all three repos for learnings with no kit home after the harvest-4 cutoff. What it found beyond #58 is filed as #70 (supply chain and shell safety in the templates), #71 (kit-owned code and a target's formatter scope), #72 (review-sweep), #73 (the verification block as a gate) and #74 (review and verification conventions). The rest go as comments on #60, #63, #66, #67, #68 and #69. Each post carries the crease commit and the test that proves it. #33 stays closed: its one dropped note is B above.
- added a commit that references this issue
on Oct 2, 2026
Harvest report from the first target-project application of
e92e9c4: you-are-hear issue #42, landed as PR #43 (chore/42-kit-sync-harvest-3, 67 commits, the kit's verification block adopted wholesale). 63 files were taken byte-identical, 38 merged, and the block now runs green there with two recorded exemptions (that project wires and registers the two advisory hooks instead of shipping them as exemplars).The defects below were found by applying the kit, not by reading it. Four carry a POC patch already exercised in that repository; the rest are reproductions with a suggested fix. Every item is independently filable — say the word and I will split them into one issue each, as with #48–#53.
Confirmed, with a POC patch
1.
detect-forked-agent-memory.sh: the hand-kept prune list is unmaintainable, and every ignore-driven replacement has three trapsThe shipped list is
-o -name node_modules -o -name target -o -name build -o -name _build -o -name dist -o -name .venv. On a Dart/Flutter monorepo it missed.dart_tool(the tool cache every package has) and carried five names that stack does not use. Reading the project's own ignore rules instead is the obvious fix, and it is where the traps are:find -path, which is a glob. An ignored directory named*(a legal Unix name) becomes-path './*', and*in-pathcrosses/, so it prunes the first top-level entry the walk reaches and the scan returns clean with empty stderr. Reproduced with an A/B control: with a genuine straypackages/foo/agent-memorypresent, the hook exits 2; create an ignored directory named*and it exits 0; remove it and it exits 2 again.agent-memory/unanchored is the natural spelling for thelocalscope, and some projects ignore.claude/wholesale. Either makes the hook's own skip list hide the fork.--directorycollapses a wholly-ignored subtree into its topmost ancestor, and in a repository with nothing committed yet that ancestor ispackages/, which no rule names. Pruning it skips most of the tree.POC (you-are-hear
a83069dandfa6b0d3):Seven scenarios, each in a fresh throwaway tree: stray tree 2; ignored directory named
*2 (0 before); unanchoredagent-memory/2;.claude/ignored wholesale 2; collapsed ancestor 2; stray under an explicitly ignored build directory 0 (pruned for speed, and not a directory agents are dispatched from); clean tree 0.2.
detect-forked-agent-memory.sh:find … 2>/dev/nullturns a partial scan into a clean verdictIf
findcannot read a subdirectory, its stderr is discarded and its exit status is never checked, so a fork under that directory is missed and the hook reports clean. Same class as the silent miss above. POC: capture stderr to a temp file and warn when it is non-empty, naming what could not be read (you-are-heara83069d).3.
protect-lock-files.shhas nopubspec.lockarmcbk-conventions-reference.md§ Dependency settle-window says hand-edits to lock files are blocked by this hook, and the Dart/Flutter ecosystem's lock file is not in the case arm, so on that stack the rule was prose with no mechanism behind it. POC (6d05234), three lines:Worth considering a
*.lockfallback arm with a generic remediation, so the next ecosystem is covered by construction rather than by an edit.4. § Hook authoring puts the dry-run table in the PR body, so hook coverage is prose that nothing re-runs
The kit ships assertion-driven fixtures for
agent-cost.py,review-sweep.jsandfinish-ab.js, and for the two highest-stakes surfaces (a HARD-DENY guard and a Stop-tier hook) it asks for a table of expected and observed exits in the PR body. On a real run that table is written once and never re-checked; three of the guards' branches turned out to have never been exercised at all.POC:
hook-payloads-fixture.sh(you-are-hearc55e964,28494c3), invoked from the project sub-block of § Verification so it runs on every block run. It asserts, against throwawaygit inittrees undermktemp, every branch both headers document: deny forAgent, the legacyTaskandWorkflow; allow at the root and for a non-matching tool; the symlinked and trailing-slash spellings of the root; thecwdfallback from a controlled directory in both directions; the three fail-open paths (no checkout, nojq, no sourced helper); and for the detector, both memory scopes, the second stop understop_hook_active, the ignore-driven prune and its three traps, the worktree exemption, a subdirectory project dir, nojq, a non-checkout, and a partial scan. Ten mutants fail it, including two that had slipped through an earlier version of the same fixture.Two notes for the kit's own copy: state which branches are asserted by mutation rather than by payload (the guard's
-zrace branch is not payload-reachable), and never let a case depend on the directory the fixture is launched from. The first version of this fixture failed misleadingly when run from a subdirectory, for exactly that reason.Confirmed, no patch offered (kit-side design calls)
5. The verification block's advisory-exemplar arm contradicts § Hook authoring for any project that wires them
§ Hook authoring says a project copies the advisory hooks, wires its analyzer and formatter into the case arms, and registers them. The block's registry loop then treats registration as a hard failure:
case "$b" in format-on-edit.sh|analyze-on-edit.sh) [ "$n" -eq 0 ] || { echo "advisory exemplar $b is registered"; exit 1; }. A project that follows the instructions gets a permanently red block, which the block's own preamble calls the worst outcome. On the kit's tree the assertion is correct, so the repair is to make the arm project-conditional or move it into the project sub-block rather than flip the comparison.6.
agent-cost.pyprices Fable 5.1 cache reads at 0.1xcost += … + t['cr'] * pi * 0.10 + …, and the docstring says "cache reads at 0.1x input". The pricing page says: "Cache hits and refreshes on Claude Fable 5.1 and Claude Mythos 5.1 are priced at 0.025x the base input price. All other models use the standard 0.1x multiplier" (platform.claude.com/docs/en/build-with-claude/prompt-caching§ Pricing, read 2026-09-07). Every Fable figure the script produced is therefore an upper bound, including the ratios quoted inorchestration-reference.md§ Applied instances. The fixture exercises Opus and Sonnet only, both on the standard multiplier, so nothing catches it.7.
review-sweep.js: the path-hint match loses the directory boundaryd.pathHintsare stripped of trailing slashes and matched withf.startsWith(h), so a hint ofpackages/yah_corematches a changed file inpackages/yah_core_extra/. Demonstrated in node:startsWithtrue,f === h || f.startsWith(h + "/")false.8.
review-sweep.js: the rosteragent()is the one call outside aparallel()thunkagent()throws when a budget ceiling is reached. Every other call in the file is inside a thunk, where the runtime's catch turns a throw intonulland the file's degrade path takes over. A throw at the roster call ends the run with nodroppedCoverage, nogateLineand no partial result, which contradicts the file's own stated degrade-never-fail design.9.
finish-ab.js:armlabels are not validated for uniqueness, andhallucinationsis dereferenced unguardedanonids are checked for uniqueness before dispatch andarmlabels are not, althoughotherFile()depends on them differing; two arms labelledAthrow inside the prompt builder after paying for the dispatch. Separately,wellFormedvalidatesrankingbut the flag accounting then readsj.hallucinations.filter(...)with no?? [], unlike the arm-side code which documents "a result missing the fields is dropped and named, never dereferenced".10. The test harnesses'
parallel()stubs swallow every exceptiontry { return await t() } catch { return null }inreview-sweep-accounting.mjsandt().catch(() => null)infinish-ab-shape.mjsconvert a bug in the test's own mock into the "agent returned nothing" path. Demonstrated by a verifier: a scenario with one typo in its mock passes all three of its assertions while its gate line reports zero verifiers. The runtime resolves a failed agent tonullrather than throwing, so the catch models no real failure mode.11. The block's launch-root dry-runs discard the hook's own diagnostic
Both payload runs redirect stdout and stderr to
/dev/null, so when the assertion fails the block prints its generic message and the hook's reason is gone. The neighbouring Stop-hook check deliberately keeps stderr and says so in its failure message.Smaller items
/finishitem 1 admits no meta-issue title. It accepts[<slug>:F<#>:R<#>],[<slug>:bug]and[<slug>:enh]with<slug>a workstream locked in the blueprint.cbk-conventions-reference.md§ Contribution intake prescribes[<meta-tag>:…]for a cascade or tooling gap, and § Title-prefix scheme defines[<slug>:meta]and[<slug>:<meta-tag>:R<#>]. A tooling meta-issue therefore fails the executor's first precondition by construction. This report's own source issue is one:[cascade:meta] Sync .claude/ …, proceeded past deliberately at the plan gate.framing/references/procedure.md:107reads "See rough-in's the rough-in skill'sreferences/plan-mode-prompts.md" — a duplication from the cross-skill citation rewrite. The verification block's reference-existence loop parses the<name> skill'sform, so the intended text is "See the rough-in skill's …".cwdis the session's launch directory and does not follow a shellcd.code.claude.com/docs/en/hooks§ Reference scripts by path states the opposite: "cwdfollows Claude: thecwdfield in the hook's input JSON is the worktree root after Claude enters a worktree, and the new directory after Claude runscd" (read 2026-09-07). The guard is correct under either reading, since it judges thecwdthe payload carries, but the header should carry both and name the disagreement as a re-verify trigger.rough-in-spec-template.mdspells the second variantMEASUREMENT / SPIKE VARIANTwith spaces around the slash, while consumers looking for the block tend to grepMEASUREMENT/SPIKE VARIANT. Worth settling on one spelling.finish-ab.js'sunattributedFlagsaccounting has no scenario; the least-loaded-owner bound inreview-sweep.jsis only exercised with two converging reporters;agent-cost.py'sminutes=Nonepath, its empty-events transcript and its--jsonmissing-argument exit are not in the fixture.agent-cost.pybuilds atotalsdict solely to destructure it, writing the same five-key tuple twice;finish-ab.jsevaluatesj.effort ?? "high"twice in one object literal. Separately,finish-ab.jsandreview-sweep.jsnow carry the same dispatch-retry-reindex shape with two different reindex idioms, one of themO(n²); worth converging and naming the pattern once.What went right, for calibration
The sync itself was mechanical where the kit intended it to be: 63 files byte-identical, the executor pair re-spliced and byte-parallel in both directions, the block's project sub-block filled without touching the kit sub-block, and the two enumerated exemptions were the only red lines. The
## Review gateblock, the bounded sweep with its runtime roster read and itsgateLine, and the measurement-variant spec blocks all landed and were used in the same PR that installed them.