feat(self): deterministic fan-out smoke test against a fixture target (closes #64) - #65
Merged
Merged
Conversation
…closes #64) Phase 2 of the self-host plan (#11). smoke-fanout.sh stages examples/fixture-target as a consumer-shaped temp repo (fixture adapter as .claude/gates.json + the REAL gate.sh), then plays a recorded implementer: isolated worktree -> canned diff -> module-boundary assert -> build/lint/test gates through gate.sh -> ff-merge to main. Failure path included: a broken canned diff must fail the build gate non-zero. No agents, no tokens, no network - deterministic and CI-safe (option (a) from the #11 plan). Wired into the self adapter's 'test' gate so it runs in the 'self / test' CI job on every PR; test_affected stays static-only (Stop-hook fast path). Verified under the strict sandbox (all git ops in $TMPDIR). The failure-path check caught a real bug during development: the outer gate.sh's GATES_FILE leaks into the inner fixture gate.sh, which then skips every gate (vacuous pass). Inner gates now run with env -u GATES_FILE. Co-Authored-By: Claude Opus 4.8 <[email protected]> Claude-Session: https://claude.ai/code/session_01NTZEsfFT4mrZpv8CY67Frd
…moke failure CI (node 24) failed the fixture's 'node --test test/' while node 20 passed it locally — directory-arg semantics differ across majors. Use an explicit shell glob (test/*.test.js) instead. Also: the smoke swallowed inner gate output, so the CI log showed only 'gate test failed' with no cause — now it prints the gate's last 40 lines on failure and logs the toolchain versions up front. Co-Authored-By: Claude Opus 4.8 <[email protected]> Claude-Session: https://claude.ai/code/session_01NTZEsfFT4mrZpv8CY67Frd
robercano
approved these changes
Jul 7, 2026
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.
Closes #64 — Phase 2 of the self-host plan (#11).
What
examples/fixture-target/— a tiny consumer-shaped repo (onefixture-coremodule oversrc/, node-only gates, node test)..claude/self/smoke-fanout.sh— deterministic end-to-end smoke of the fan-out scaffold: stages the fixture into a temp git repo with the realgate.sh(consumer layout), plays a recorded implementer (isolated worktree → canned diff → module-boundary assert → build/lint/test gates throughgate.sh→ ff-merge to main), plus the failure path: a broken canned diff must fail the build gate non-zero.testgate → runs in the existingself / testCI job on every PR.test_affectedstays static-only (Stop-hook fast path).Why deterministic (no live agents)
Option (a)+(b) from the #11 plan: live fan-outs need tokens and are nondeterministic — bad CI fit. The scaffold (worktrees, boundaries, gate plumbing, merge) is what regresses silently; that's what this locks down, on the fixture, never on the harness itself (no bootstrap regress).
The failure-path check already paid for itself
First CI-shaped run caught a real env-leak bug: the outer
gate.shexportsGATES_FILE=.claude/self/gates.json; the inner fixturegate.shinherited it, couldn't find that path in the fixture, and skipped every gate — passing vacuously on broken code. Inner gates now runenv -u GATES_FILE.Testing
GATES_FILE=.claude/self/gates.json gate.sh test: PASSCI=1(verbose runner path): PASS$TMPDIR).🤖 Generated with Claude Code