Skip to content

refactor(rules): five worktree bullets each re-stated the same silent-failure frame (#193) - #458

Merged
ZacxDev merged 3 commits into
mainfrom
consolidate-worktree-rules-193
Aug 13, 2026
Merged

refactor(rules): five worktree bullets each re-stated the same silent-failure frame (#193)#458
ZacxDev merged 3 commits into
mainfrom
consolidate-worktree-rules-193

Conversation

@ZacxDev

@ZacxDev ZacxDev commented Aug 13, 2026

Copy link
Copy Markdown
Contributor

Closes clawgate Task #193.

scripts/tests/test_rules_size.py stated in its own comments that eviction had been run to completion, and that the answer to the next real rule was consolidation, not another ceiling bump — naming "the four scattered worktree bullets" as the obvious candidate. PR #447 then added two recovered rules, taking the cluster to five. This does the consolidation it asked for.

What was actually duplicated

stated more than once where now
the git worktree add … -b <branch> origin/<main-branch> recipe sync-a-branch bullet and the cross-repo bullet once, in the git -C <repo> form that covers both; cross-repo references it
"never two file-modifying agents in one checkout" as a hazard and as a prohibition, inside one bullet once as each
the silent-failure framing ("no error, the worktree is created successfully") spelled out by the cross-repo, session and .envrc bullets hoisted once

Which bullets merged into which

Three separate "a worktree does not isolate X" rules became one frame with three enumerated surfaces:

🔴 But a worktree isolates a working DIRECTORY only — three surfaces it does NOT isolate, each failing SILENTLY (worktree created, no error):

  • 🔴 the REPO it is built from … → archive: cross-repo-worktree
  • 🔴 the SESSION … → archive: worktree-not-session
  • 🔴 the ENVIRONMENT … → archive: worktree-envrc

Each surface keeps its own 🔴, its own imperative, its own failure shape and its own → archive: pointer — so none depends on the frame to carry its scope, and it survives being quoted in isolation. A fourth surface can join the list without re-stating the frame.

Left structurally alone: the sync-a-branch bullet (now holding the canonical recipe), the take-another-ref bullet, the base-clone re-sync bullet, and the pgrep/pkill bullet in Shell & Tooling — its /proc/<pid>/cwd clause is the one PR #447's audit had to restore, and it is untouched here.

A correctness fix, not only a byte one

The old text asserted "A worktree isolates the REPO, not the SESSION" while the bullet directly beneath it said the worktree is built from the wrong repo. Both cannot be true. "isolates a working DIRECTORY only" is the archive's own wording and is wider than either.

Byte delta

claude/RULES.md 33,300 → 33,229 B (−71). Headroom 1,900 → 1,971 (floor 900).

That is a small number and it is the finding, not a shortfall: every clause that survived is scope-bearing, so the only bytes available were genuine restatement. The frame itself costs ~78 B; it is paid for by ~150 B of real dedup. Recorded in the gate module so nobody reads this as evidence that another −71 B is sitting there — it is not, and the next real rule still costs ceiling.

What the 71 B bought: a fourth worktree hazard now attaches to the enumerated list for the cost of its own text, instead of arriving as a sixth sibling bullet re-stating the frame (~250 B of restatement, measured against the four it would have copied).

MAX_BYTES deliberately NOT ratcheted down. The slack predates this change; tightening the gate is a policy call that belongs in its own commit where it can be argued on its merits.

Nothing moved to the archive

There was no un-evicted narrative left in the cluster to move — the same "eviction has been run to completion" the module already recorded. The three archive sections had their *Supports:* quote lines updated to match the new core wording, and test_rules_size.py's comment quoting the now-superseded rule name was corrected (a comment is a claim too).

Qualifiers considered for trimming and deliberately KEPT

Each would have let the shortened rule pass while the hazard survived in a shape the original covered — the exact failure mode test_rules_size.py:128-133 forbids and that the #447 trim hit.

  • "Two failure modes, only one loud" + both modes. The point of the pair is that you would not necessarily notice. Keeping only the silent mode loses the recognition tell; keeping only the loud one loses the dangerous case. Neither alone carries it.
  • "that still satisfies the isolation mandate, so this is not an exception to it." Without it, "For cross-repo work do NOT pass isolation: "worktree"" reads as licence to skip isolation entirely.
  • "file-modifying" and "in one checkout" on the never-two prohibition. A bare "never two" was written and then reverted — it silently dropped both qualifiers and left the antecedent to the reader.
  • "Read-only agents (audits/research/exploration) don't need one." The parenthetical is what makes "read-only" operational rather than a judgement call.
  • "working in a tree where its brief makes no sense." Arguably subsumed by the two failure modes — which is precisely the "it's covered elsewhere" reasoning the docs(rules): two multi-agent lessons that never landed, recovered from a merged branch #447 trim used, so it stays.
  • The ../<repo>-<topic> path convention. Dropped when the recipe was unified into the -C form, restored once a cold read caught that it was concrete guidance and not framing.
  • The per-surface 🔴 markers. Hoisting one marker onto the frame would have saved ~15 B and left each surface without its priority marker if ever extracted.

Verification

  • All 5 functions in scripts/tests/test_rules_size.py pass (called directly — python3 on PATH has no pytest).
  • Each negative-controlled, each going red with its own message: ceiling forced below current size (OVER its hard ceiling), headroom forced above the gap (no working headroom left), RULES_MD → missing file, ARCHIVE_MD → missing file, and a bogus → archive: no-such-anchor-here injected.
  • A claim-coverage checker asserts all 56 distinct claims from the original 6-bullet block are still present, and records the 7 deliberate rewordings with the reason each is not a narrowing. It was itself controlled: empty file → all 56 reported missing (positive control); five single-claim deletions → red (negative controls); unmutated → green.
  • Every → archive: anchor still resolves (test_every_archive_pointer_resolves).
  • Authoritative gate nix build .#checks.x86_64-linux.pytests, read by content: TOTAL collected=9429 passed=9428 skipped=1 failed=0, all 17 targets above floor, no === FAILURES ===, no timeout panic, RESULT: PASS (exit=0).
  • Cold read of every consolidated bullet as a reader who never opens the archive. This is what caught four narrowings, all fixed before commit: the dropped ../<repo>-<topic> convention, the bare "never two", "the flag" without an antecedent inside the sub-bullet, and the lost bold on the .envrc consequence clause.

🤖 Generated with Claude Code

ZacxDev and others added 3 commits August 13, 2026 15:40
…-failure frame (#193)

`scripts/tests/test_rules_size.py` said eviction had been run to completion and
that the answer to the next real rule was consolidation, not another ceiling
bump — naming "the four scattered worktree bullets" as the candidate. PR #447
then made it five. This does the consolidation it asked for.

The cluster stated three things more than once:

  - the `git worktree add … -b <branch> origin/<main-branch>` recipe, written
    out twice (sync-a-branch, and again for cross-repo). Now once, in the
    `git -C <repo>` form that covers both, referenced by the cross-repo rule.
  - "never two file-modifying agents in one checkout", stated as a hazard and
    again as a prohibition inside the same bullet. Now once as each.
  - the silent-failure framing ("no error, the worktree is created
    successfully"), spelled out separately by the cross-repo, session and
    .envrc bullets. Now hoisted once.

Structurally: three separate "a worktree does not isolate X" rules became one
frame — **a worktree isolates a working DIRECTORY only** — with three
enumerated unisolated surfaces, the REPO it is built from, the SESSION, and the
ENVIRONMENT. Each keeps its own 🔴, its own imperative, its own failure shape
and its own `→ archive:` pointer, so none of them depends on the frame to carry
its scope, and a fourth surface can join the list without re-stating it.

The frame is also a correctness fix: the old text asserted "A worktree isolates
the REPO, not the SESSION" while the bullet directly below it said the worktree
is built from the wrong repo. Both cannot be true. "isolates a working
DIRECTORY only" is the archive's own wording and is wider than either.

claude/RULES.md 33,300 -> 33,229 B (-71). That is a small number and it is the
finding, not a shortfall: every surviving clause is scope-bearing, so the only
bytes available were genuine restatement. Recorded in the gate module so nobody
reads this as evidence that another -71 B is sitting there.

Nothing moved to the archive — there was no un-evicted narrative left in the
cluster to move, which is the same "eviction has been run to completion" the
module already recorded. The three archive sections had their `*Supports:*`
quote lines updated to match the new core wording.

Qualifiers considered for trimming and deliberately KEPT (each would have let
the shortened rule pass while the hazard survived in a shape the original
covered):

  - "Two failure modes, only one loud" + both modes. The point of the pair is
    that you would not necessarily notice; neither mode alone carries it.
  - "that still satisfies the isolation mandate, so this is not an exception to
    it" — without it, "do NOT pass the flag" reads as licence to skip isolation.
  - "file-modifying" and "in one checkout" on the never-two prohibition. A bare
    "never two" was written, then reverted: it silently dropped both qualifiers.
  - "Read-only agents (audits/research/exploration) don't need one" — the
    parenthetical is the routing surface that makes "read-only" operational.
  - "working in a tree where its brief makes no sense" — arguably subsumed by
    the two failure modes, which is exactly the reasoning the #447 trim used.
  - the `../<repo>-<topic>` path convention, dropped when the recipe was
    unified and restored once a cold read caught it.

MAX_BYTES deliberately NOT ratcheted down. The headroom predates this change,
and tightening the gate is a policy call that belongs in its own commit.

Verification: all 5 functions in test_rules_size.py pass, and each was
negative-controlled — ceiling forced below current size, headroom forced above
the gap, RULES_MD and ARCHIVE_MD pointed at a missing file, and a bogus
`→ archive:` anchor injected — each going red with its own message. A separate
claim-coverage checker asserts all 56 distinct claims from the original block
are still present and records the 7 deliberate rewordings with their reasons;
it was itself controlled (empty file -> all 56 reported missing; five
single-claim deletions -> red; unmutated -> green). Authoritative gate:
TOTAL collected=9429 passed=9428 failed=0, RESULT: PASS (exit=0).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…roduced

Audit of #458 found no lost claims -- the consolidation itself is sound -- but two
🟡s in the NEW frame text, both of the "a frame is a claim too" class this file
keeps getting bitten by. Fixed here rather than in a follow-up, because the frame
is what a skimming agent retains.

1. The verb fitted one of its three surfaces and INVERTED the sign on another.
   "three surfaces it does NOT isolate" is right for SESSION (scratchpad and branch
   namespace leak in). For REPO the failure is TARGETING, not isolation -- the
   worktree is perfectly isolated, just of the wrong repo. For ENVIRONMENT it read
   backwards: "does not isolate the environment" implies the environment is SHARED,
   when the hazard is that it is ABSENT (.envrc never comes with the checkout).
   That false implication is precisely the belief the .envrc rule exists to kill.
   Now "the surfaces it does NOT hand you", which covers all three.

2. "each failing SILENTLY" was hoisted from ONE bullet and asserted of three. Only
   the cross-repo bullet ever claimed it; grep of origin/main confirms neither the
   SESSION nor the .envrc bullet contains `no error`, `created successfully` or
   `silently`. Worse, the ENVIRONMENT sub-bullet documents two LOUD manifestations
   in its own text -- a toolchain binary "missing", and an un-`direnv allow`ed
   .envrc erroring on every `cd`. The claim that IS true of all three is that
   `worktree add` succeeds without warning you, so that is what it now says.

3. "three surfaces" read as a CLOSED enumeration. A fourth already exists eleven
   lines later -- the machine/process surface at the pgrep/pkill bullet, where a
   box-wide pattern reaches a sibling agent's processes (-> archive:
   sibling-agent-kill); the archive's own wording is "not a private repo and not a
   private machine". The frame now says the list is not closed, and the gate module
   records why.

Also corrects three comment claims in scripts/tests/test_rules_size.py, since a
comment is a claim too and this module is where the next maintainer will look:
- "five bullets became one recipe + one frame" was 5 -> 2; it is 5 -> 3 (the
  base-clone re-sync bullet stayed top-level).
- the ~250 B saving is an ESTIMATE of a hypothetical, not a measurement, and is now
  labelled as one.
- the comment restated MIN_HEADROOM_BYTES as a literal ("~1,970 vs the 900 floor"),
  which the module's own docstring forbids for drift reasons. Replaced with a
  pointer to the constants.

And requotes the three `*Supports:*` lines in RULES-ARCHIVE.md: two of them did not
match the core verbatim (ASCII `--`/`->` for `—`/`→`, and a capitalisation), so an
archive->core grep missed. All four quoted substrings now grep to exactly 1 hit.
Nothing enforces this direction -- test_every_archive_pointer_resolves only checks
core->anchor -- so it is eyes-only.

RULES.md 33,229 -> 33,264 B (+35) against the ceiling and headroom floor the module
owns; all 5 tests pass. MAX_BYTES and MIN_HEADROOM_BYTES untouched, no assert
changed -- the audit proved the gate module's AST identical to main's before this
commit, and this commit keeps it comment-only.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Delta audit of 7eada94..09e78ab found the fix round sound on every claim -- and
one regression it introduced, inside the very comment block whose purpose was
comment accuracy. The block still read "Result: 33,300 -> 33,229 B" and "That
-71 B is the honest yield" after the frame reword had spent 35 B back. A reader
at main measures 33,264 against a comment asserting 33,229, with the headline
yield overstated roughly 2x.

Now records both numbers and why they differ: the consolidation landed 33,229,
the audit found the new frame verb described only one of its three surfaces
correctly, and the reword cost 35 B. Both ship in one merge, so -36 B is what
the tree sees.

The audit also settled the question I raised against my own fix -- whether
changing the frame verb to suit REPO and ENVIRONMENT had broken SESSION. It had
not: "does NOT hand you" is better for REPO (the failure is targeting, not
isolation) and fixes the sign inversion on ENVIRONMENT, and while the old verb
was marginally more direct for SESSION, no claim is lost -- the sub-bullet still
spells the leakage out verbatim. Silence is preserved and arguably strengthened:
"a SUCCESSFUL `worktree add`" names the moment the silence occurs, which is what
defuses the contradiction with ENVIRONMENT's two loud downstream tells.

Verified after: RULES.md 33,264 B, all 5 tests pass, and the gate module's
ast.dump SHA is unchanged from origin/main (a5c3e59bea8d9b87) -- still provably
comment-only, no assert or constant touched.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@ZacxDev
ZacxDev merged commit 9fac7cb into main Aug 13, 2026
@ZacxDev
ZacxDev deleted the consolidate-worktree-rules-193 branch August 13, 2026 22:16
ZacxDev added a commit that referenced this pull request Aug 15, 2026
…d for without narrowing one (#193) (#489)

* docs(rules): three measured lessons folded into existing bullets, paid for without narrowing one (#193)

The consolidation half of #193 already landed as #458 (five worktree bullets
became three). What was left was the harder half: fold three lessons measured
2026-08-14 into that cluster without a ceiling bump.

All three attach to bullets that already exist, because that is where a reader
already is when the hazard fires:

- `cp -a` of a worktree gives ZERO git isolation — a worktree's `.git` is a FILE
  holding `gitdir: …`, so the copy shares the ORIGINAL's git dir, index, refs and
  reflog, and a `git commit` inside it lands on the real branch. This is the
  fourth surface under "a worktree isolates a working DIRECTORY only", and it
  cost 394 B — its own text only, reusing the frame. It also sharpens the SESSION
  surface, whose own advice ("restore from `cp -a`") is what tells an agent to
  make these copies.
- A guard on PROSE is walkable by rewording — extends "SPELLED rather than
  STRUCTURAL" (same question, new artifact type: when the thing under test IS a
  sentence, "assert the STATE" has no referent, so pin the whole normalised
  string).
- A fixture whose value EQUALS the constant it tests — extends "pick fixtures
  whose fields are pairwise distinct" in the mutation bullet (same collision,
  different pair: fixture vs constant, not field vs field).

The last two reuse the archive anchor already on their bullet, so neither added a
`→ archive:` tag. Evidence for all three is in RULES-ARCHIVE.md.

The additions overshot the budget by 202 B, reclaimed without narrowing anything:
the stash section stated "for ANY reason" three times, so the third was dropped
and the other two kept verbatim; and the pkill bullet's incident numbers moved to
`sibling-agent-kill`, which already held them verbatim. The clause saying
`/proc/<pid>/cwd` cannot separate two agents that both sit in the base clone was
NOT touched — PR #447 trimmed exactly that and re-opened the incident.

Numbers are owned by scripts/tests/test_rules_size.py; its comment ledger records
this pass, and the 2026-08-13 entry's explicitly-unmeasured prediction (that a
fourth hazard would attach for the cost of its own text) is now measured at one
instance.

The cold read caught the only regression: "never two" had been shortened from
"never two file-modifying ones" and read as forbidding a second read-only agent
that the previous sentence permits. No test performs that check.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* fix(rules-gate): the comment telling the next shaver what is load-bearing under-reported the one bullet this PR shortened

Audit findings on this PR. Both are claims in the gate's OWN source — the file a
future author reads to decide what is safe to trim — and both cost zero
`RULES.md` bytes, which is the point: the gated file is untouched.

🔴 THE COMMENT SAID A BULLET WAS UNTOUCHED WHEN IT SHIPPED 18 B SHORTER

It recorded the caught regression ("never two" -> "never two file-modifying
ones", 20 B restored) but not that the shipped wording is the MIDDLE position,
18 B below what was there before. To the next person sizing that bullet up it
read as untouched, which is exactly backwards for the one bullet whose
over-shortening this PR had to catch by cold read. Now states the shipped
wording, why the antecedent still survives (the full form appears earlier in the
same bullet), and that shaving further breaks it.

🔴 "202 B RECLAIMED WITHOUT NARROWING" MISATTRIBUTED ITS OWN SOURCES

An audit measured it: only **133 B** came out of existing rules (stash -89,
pkill -26, worktree -18). The remaining ~69 B came from trimming the three NEW
additions. The comment named only the two existing-rule sources, so it read as
though the whole 202 B came from prose already in the file — making
"reclaim without narrowing" look about 50% cheaper as a recipe than it is.

## The other direction of the anchor check was asserted in prose and gated nowhere

This module claimed anchors "resolve both directions" while only computing
`referenced - anchors`. The inverse — an archive section nothing points at — is
the same rot one step over, and this module's own `_archive_anchors` docstring
already argues the case: *"a hand-maintained list drifts, and a drifted list
steers a maintainer into duplicating an entry that already has a home."* An
orphan is how that happens: the evidence exists, nothing routes to it, so the
next author writes it again in the core, where bytes are the scarce thing.

⚠ **Labelled as an INVARIANT GUARD, not counted as regression coverage.** It was
green the moment it was written (0 orphans), so it has never caught the bug it
describes. Watched to fail rather than assumed:

- inject `## zzz-orphan-control` that nothing references -> RED with its own
  message, at its own assertion;
- rename the same section `retired-zzz-orphan-control` -> GREEN again.

The second is the control that matters: it proves the `retired-*` exclusion is
live rather than decorative. A prefix exclusion nobody exercises is how a guard
quietly stops covering the case it was scoped around.

Verified: `RULES.md` and `RULES-ARCHIVE.md` are byte-identical to the parent
commit — this spends none of the 32 B of slack the PR lands on.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
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