fix(rules): a shortened rule line must not decide what it says about holding (#3851)
CI & Build / Python lint (push) Successful in 3s
CI & Build / Plugin hooks (push) Successful in 8s
CI & Build / integration (push) Successful in 40s
CI & Build / TypeScript typecheck (push) Successful in 52s
CI & Build / Python tests (push) Successful in 1m24s
CI & Build / Build & push image (push) Successful in 26s

CI run 6485 was red. Six failures, three causes, and only one of them was a
stale test.

THE REAL DEFECT. The compact branch dropped the `seen` TAIL along with the
trigger, so a rule the session had already been told rendered exactly like
one it had not. #3750's whole argument is that those are different claims —
a repeat is rendered precisely because the session may no longer HOLD what
it was told — and the tail is the entire difference a reader can act on.
test_a_rule_the_session_already_holds_is_referenced_not_re_offered caught it
within one commit, which is that guard working as intended.

Fixed by keeping the tail and dropping only the trigger, which is both the
cheaper and the safer cut: a trigger runs 300-400 characters after #3855, a
tail about 100. Re-measured on the real renderer — top-full-plus-references
is ~299 tokens against ~568 for five full lines, so about 2x the old single
line rather than the 1.4x claimed before, for four more rules and no lost
information. The comments carrying the old figure are corrected rather than
left to read as a decision nobody made.

THE FIXTURE THAT STRADDLED THE BAND. `_THREE_HITS` spanned 0.81-0.74 against
a 0.05 band, so the act arms dropped its lowest hit and four cases of
test_both_recorders_report_the_same_rules_for_one_call failed reporting a
count mismatch — under a message blaming the exclusion filter. A guard
pointing confidently at the wrong subsystem costs more than no guard,
because it is believed. Scores retightened to 0.81/0.80/0.79 and the
precondition is now asserted by a named test, so a future band change is
told where the problem is instead of through four confusing failures.

THE STALE CONSTANT GUARD. test_the_rule_arm_asks_for_one_rule_not_two pinned
RULEHINT_LIMIT == 1 — a real decision, correctly guarded, for a world with a
resident set. Rewritten to pin what replaced it, as relationships rather
than values (rule 115): the arm can return several, and rules are narrowed
HARDER than the notes menu because they measured flatter, not sharper.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011cPyzNnegXHr5iRMzzy5KJ
This commit is contained in:
2026-09-11 14:07:32 -04:00
co-authored by Claude Opus 5
parent 10343a6019
commit 40189147d2
4 changed files with 121 additions and 27 deletions
+29 -2
View File
@@ -1026,10 +1026,37 @@ async def test_a_shown_hit_is_not_counted_as_suppressed():
_THREE_HITS = [
(0.81, fake_rule(id=156, title="A wait with no deadline is a bug")),
(0.77, fake_rule(id=157, title="A loop re-arms in a finally")),
(0.74, fake_rule(id=161, title="Reach the forge through its MCP tools")),
(0.80, fake_rule(id=157, title="A loop re-arms in a finally")),
(0.79, fake_rule(id=161, title="Reach the forge through its MCP tools")),
]
def test_the_three_hit_fixture_sits_inside_the_rule_band():
"""The fixture's own precondition, asserted rather than commented (#3851).
The act arms band before they dedup, so a fixture whose spread straddles
`_RULEHINT_BAND` loses its lowest hit to the BAND and then reports a count
mismatch — under a message blaming the exclusion filter. That is the
failure this file is least able to survive: a guard pointing confidently
at the wrong subsystem costs more than no guard, because it is believed.
Not hypothetical. The spread was 0.07 against a 0.05 band, and four cases
of `test_both_recorders_report_the_same_rules_for_one_call` failed that
way the moment the band shipped.
Widening the band leaves this alone; narrowing it past the spread must
retighten these scores, and says so here rather than through four
confusing failures elsewhere.
"""
from scribe.services import plugin_context as pc
spread = _THREE_HITS[0][0] - _THREE_HITS[-1][0]
assert spread < pc._RULEHINT_BAND, (
f"the rule-arm fixture spans {spread:.3f} against a band of "
f"{pc._RULEHINT_BAND}: the act arms will drop its lowest hit as "
"out-of-band, and every count assertion below will blame the "
"exclusion filter for it"
)
_ARMS = [
("write_path_rule", _run_arm),
("pre_tool_rule", _run_tool_arm),