guard: claudemd cap checks the merge union (GAP4) - #710
Open
matt82198 wants to merge 3 commits into
Open
Conversation
The CLAUDE.md line cap only ever saw the branch. A branch could sit at 149/150 and pass while origin/main independently grew, so the merge landed at 151 and busted the cap on main with nothing red on the way in -- three such cascades in one day. Add a `--headroom` mode to tools/claudemd_lint.py that lints the MERGE UNION instead of the working tree: preview the merge with `git merge-tree --write-tree` (falling back to a per-file three-way `git merge-file` over the blobs on pre-2.38 git), then apply the same cap and per-file oversize allowance to the union's line count. A file whose union busts its cap fails even when the branch alone is under it. Exit contract: 0=clean, 1=a union busts its cap, 2=union unreadable (not a git repo, unresolvable ref, undecodable blob). Wired into hooks/pre-push-policy.sh as check_claudemd_headroom, where exit 1 is fail-closed (push blocked) and exit 2 is fail-open with an audit event -- an unfetched base ref is an environment condition, not a policy violation. TDD: fixture repo where base=100 body lines, the branch prepends 48 (149 counted lines, passes the cap outright) and main appends 2, so the union is 151 and must fail. Behavioural RED/GREEN: the pre-change linter exits 0 on that fixture, --headroom exits 1 naming the file. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
# Conflicts: # tests/test_claudemd_lint.py # tools/CLAUDE.md # tools/claudemd_lint.py
…he derivation) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
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.
Problem
The CLAUDE.md line cap only ever saw the branch. A branch could sit at 149/150 and pass while
origin/mainindependently grew, so the merge landed at 151 and busted the cap on main with nothing red on the way in — three such cascades in one day.Fix
tools/claudemd_lint.py --headroomis a new mode that lints the merge union instead of the working tree:git merge-tree --write-tree, falling back to a per-file three-waygit merge-fileover the blobs on pre-2.38 git;ALLOWED_OVERSIZEallowance to the union's line count (hoisted to a module constant so the two paths cannot drift);Flags:
--headroom [--base-ref origin/main] [--head-ref HEAD].Exit contract:
0=clean,1=a union busts its cap,2=union unreadable (not a git repo, unresolvable ref, undecodable blob).Wired into
hooks/pre-push-policy.shascheck_claudemd_headroom(check 10). Exit 1 is fail-closed (push blocked); exit 2 and a missing tool fail open withclaudemd_headroom_skipped_unreadable/_tool_missinglogged — an unfetched base ref is an environment condition, not a policy violation, matching the existing fail-open-for-environment / fail-closed-for-policy split.TDD evidence
Fixture: base = 100 body lines; the branch prepends 48 (149 counted lines — passes the cap outright) and main appends 2, so the union is 151 and must fail.
Behavioural RED/GREEN on that fixture:
Import-level RED (new tests against the pre-change module):
ImportError: cannot import name 'check_headroom'.Tests
tests/test_claudemd_lint.py: +10 cases (TestHeadroomMergeUnion) — branch-passes/union-busts, CLI exit 1, union clean, CLI exit 0, missing base ref →HeadroomError, non-git dir →HeadroomError, CLI exit 2, per-file oversize allowance parity, three-way fallback agrees withmerge-tree, uncommitted working-tree edits excluded. Suite: 45 passed (was 35).bash hooks/pre-push-policy.sh --test: 19/19 (new Test 19 asserts all three arms of the gate's exit contract — missing tool and exit 2 fail open, exit 1 fails closed).python -m unittest discover -s tests): green.encoding_lint,watcher_linter,verify_test_coverage,metrics_gate,test_test_hygiene,test_subprocess_guard.claudemd_driftanddispatch_lintfindings are byte-identical to theorigin/mainbaseline (verified against a detached checkout) — no regression.Dogfooded
tools/CLAUDE.mdsits at exactly 150 on main, so the new entry was written into the existing line rather than appended.--headroom --base-ref origin/mainon this branch is green, and the gate ran for real in this PR's own push.🤖 Generated with Claude Code