docs: the hub carries the root entry points init writes, guarded by a test (#299) - #300
Conversation
…st (#299) Claude Code loads CLAUDE.md and never AGENTS.md, so the root pointer #257 added had nothing to reach it from in the harness this project is developed with: the hub, otherwise indistinguishable from a fresh 2.0 scaffold, had no CLAUDE.md at all. The file is now here, written from claudeScaffold in internal/cli/init.go rather than typed, so its bytes are the constant's. A test in internal/cli/init_test.go reads ../../CLAUDE.md back and compares it against that constant, the way refusals_test.go reads SPEC.md: neither can move without the other, in either direction. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017Zu94NTC5DJskcpMTSofzr
…md (#299) The root AGENTS.md is byte-identical to agentsPointerScaffold and had no test either, so it could drift exactly as CLAUDE.md could have. The guard is now a table over rootEntryPoints: each root file is read off disk and compared against the constant that writes it, and the table is checked against rootEntryPoints in both directions, so a third root entry point cannot arrive unguarded. Confirmed failing on drift in either file and on an unguarded third name. The changelog entry follows. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017Zu94NTC5DJskcpMTSofzr
There was a problem hiding this comment.
Approved. Round one, reviewer seat. The claim this PR makes is checkable by
construction, so I checked it by construction rather than by reading: I built the
binary from this branch, ran gh codecrew init into an empty repo in my working
directory, and diffed what it scaffolded against what the hub now carries.
What I verified
CLAUDE.md is what init writes, byte for byte. A fresh scaffold's
CLAUDE.md and the hub's are identical — same 189 bytes, same md5
(b24ca46b…) — and the same holds for AGENTS.md (172 bytes, identical). This
is the end-to-end version of the PR's claim: not "equal to the constant" but
"equal to what the verb actually produces from it", which is the promise M14-R3's
spirit makes about this repo.
The guard fails on drift, in every direction it claims to cover. Three
mutations, each restored:
- append a line to
CLAUDE.md→TestTheHubsRootEntryPointsAreTheScaffolds
fails, naming the file and printing both strings; - append a line to
AGENTS.md→ fails the same way; - add a third name to
rootEntryPointsininternal/cli/init.go→ fails at
init_test.go:423withrootEntryPoints is [AGENTS.md CLAUDE.md GEMINI.md], and every one of them needs a row in this table.
So the table is genuinely bidirectional, and a fourth harness's root file cannot
arrive unguarded. The test reads from disk with filepath.Join("..", "..", name)
the way internal/cli/refusals_test.go:21 reads SPEC.md; every os.Chdir in
this package's tests restores the working directory and nothing here is
t.Parallel, so the relative read is safe.
The chain a Claude session walks resolves. CLAUDE.md → @AGENTS.md →
root AGENTS.md → @.codecrew/AGENTS.md → the dispatch instructions. Read in
sequence, all three files land. reachesInstructions in internal/cli/init.go
documents exactly this one hop, and a rerun of init on a scaffold shaped like
the hub's reports plain kept existing for both root files with no action needed block — idempotent in what it says as well as in what it writes.
Nothing else moved. Three files in the diff; init's scaffold set,
rootEntryPoints and reachesInstructions are untouched, so there is no
behaviour change for a doc sentence to fall out of step with. I grepped every
CLAUDE.md mention outside internal/ and CHANGELOG.md: README.md:78-79,94,
SPEC.md:99,581-582,626,753 and docs/first-milestone.md, docs/identities.md,
docs/introduction.md all describe what init writes, which is unchanged and now
true of this repo too. The one sentence that records the opposite —
docs/milestones/13-protocol-2-0-the-codecrew-layout-and-what-rides-with-it.md:457,
the M13 Decision that "declines to add a root CLAUDE.md to this hub" — is a
synthesized record of a Decision that was correct when it was taken, and this
task is the operator taking the other branch later. Correct as history, so not a
finding: records are not amended.
Record. Plan on #299 before the first commit (started 18:46:39Z, first commit
18:48:14Z). Both Decisions are comments on the issue, not only in the PR body, and
the follow-up matches what shipped exactly: the first commit guards CLAUDE.md
alone and flags AGENTS.md; the second widens to the table after the
coordinator's instruction, carried as a second commit rather than an amend so the
flag being taken is visible. Closes #299 in the body; #299 is a sub-issue of
#269 and adopts no backlog capture, so no second Closes is owed. CHANGELOG
section under the fresh [Unreleased], replacing Nothing yet., ending (#299)
— the precedent #298 left. Commit subjects conventional, lowercase after the
type, both 78 chars, both (#299). Base 2b75fdc, head 89073e3.
gofmt -l . silent, go vet ./... clean, go test ./... green on a restored
tree; both required checks pass on the PR.
Notes, neither blocking
- The requirement claim is a stretch, and is declared as one. M14-R3 as
worded on #269 is the release flip, the tag and the fleet migration; this is
none of those. The task's Goal and Plan and the PR body all say so in the same
words ("its spirit … no new requirement"), which is the honest way to carry it,
and #269's own Decision comments show the operator adding requirements when a
task needs one. Nothing to fix — recorded so the milestone document has the
reading in front of it: if the synthesizer later wants R3's evidence to be
only the release, this PR is the one task under it that is not. docs:on a commit that adds a Go test. The first commit's type is
docs:and it addsinternal/cli/init_test.goas well asCLAUDE.md; the
second is correctlytest:. Commitlint passes and the split is sensible. A nit
for next time, not a change request.
Closes #299
Claude Code loads
CLAUDE.mdand neverAGENTS.md.initknows that — it writes aCLAUDE.mdinto every repository it scaffolds, a bare@AGENTS.mdimport above a two-line comment, so the harness-neutral entry point is reachable from the one harness that only looks at its own filename. This hub never got one: the root pointer #257 added had nothing to reach it from, and a Claude session dispatched here landed on no instructions at all. Noted by the Codex pre-2.0 scan (finding 10 on #254), recorded as out of scope by the doc-synthesizer's #257 Decision, and asked about by the operator on 2026-09-06.What changed, three files.
CLAUDE.mdat the root, byte for byte whatinitwrites today — generated fromclaudeScaffoldininternal/cli/init.goby a throwaway test kept out of the commit, never typed, so its bytes are the constant's and not an approximation of them. A newTestTheHubsRootEntryPointsAreTheScaffoldsininternal/cli/init_test.go. One### …section inCHANGELOG.mdunder[Unreleased], replacing theNothing yet.placeholder #298 left there.The guard is the point, and it covers both root entry points. The hub's
AGENTS.mdandCLAUDE.mdare each read off disk and compared against the constant that writes them —agentsPointerScaffoldandclaudeScaffold— the wayrefusals_test.goreads../../SPEC.md. Neither can drift in either direction: an edit to a scaffold fails the hub's own suite until the hub follows it, and an edit to a hub file fails until the scaffold does. The table is checked againstrootEntryPointsboth ways as well, so a third root entry point cannot arrive unguarded.AGENTS.mdturned out to be byte-identical to its constant already, so guarding it changed no file — only the fact that it can no longer quietly stop being identical.Verified by making each failure happen: appending a line to
AGENTS.mdfails it, appending a line toCLAUDE.mdfails it, and adding a third name torootEntryPointsfails it with the message naming the unguarded file.No behaviour changes:
init's scaffold set,rootEntryPointsandreachesInstructionsare untouched. The hub now simply carries whatinitwould have written for it, which is the promise M14-R3 makes about this repository shipping as a 2.0 repo.Requirements: M14-R3 (its spirit, as #299's Goal records — the hub ships indistinguishable from a fresh 2.0 scaffold; no new requirement).
Decisions: one on #299, and its follow-up. The first commit guarded
CLAUDE.mdalone and flagged the unguardedAGENTS.mdfor review rather than widening scope unasked; the coordinator took the flag before the review, so the second commit widens the guard to both. No deviations.gofmt,go vet,go build ./...andgo test ./...are clean; branch based onorigin/mainat 2b75fdc (post-#298).🤖 Generated with Claude Code
https://claude.ai/code/session_017Zu94NTC5DJskcpMTSofzr