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
204 lines
8.6 KiB
Python
204 lines
8.6 KiB
Python
"""An act surfaces a SET of rules, and rank decides how loudly (#3851).
|
|
|
|
WHY THIS EXISTS
|
|
|
|
`RULEHINT_LIMIT` was 1. That was right while retrieval merely SUPPLEMENTED a
|
|
33-rule resident set — one salient rule beside everything already loaded. It
|
|
stops being right the moment milestone 394 removes residency, because then
|
|
this arm is the whole delivery, and `git push origin dev` is governed by
|
|
rules 1, 2, 9 and 140 at once, each of which alone permits the mistake the
|
|
others catch.
|
|
|
|
Two instruments, and they answer different questions:
|
|
|
|
- `_rule_band` decides HOW MANY. A fixed k fills its slots whether or not
|
|
anything deserves them; a band keeps only what scored close to the top,
|
|
so one clearly-relevant rule still shows one.
|
|
- `compact` decides HOW LOUD. Measured at #3851: a full line is ~143 tokens
|
|
once the trigger is rendered, so five of them cost ~646 before every Bash
|
|
call. Top-full-plus-references costs ~198.
|
|
|
|
WHAT THIS PINS
|
|
|
|
Structure, never wording — the lines are prose and will be rewritten:
|
|
|
|
1. The band keeps the top hit and everything within `_RULEHINT_BAND`, and
|
|
drops what falls outside. Falsified below from both sides: a hit just
|
|
inside survives, a hit just outside does not.
|
|
2. Rank decides volume — the first line carries the trigger, later lines do
|
|
not, and every line names its rule's id so any of them can be pulled.
|
|
3. The band reads SCORES ONLY. A top hit the session has already seen still
|
|
anchors the band, and its score still sets the cutoff. This is the axis
|
|
independence the renderer already keeps between `kind` and `seen`, and
|
|
the regression it prevents is subtle: letting the ledger reorder the
|
|
band would make "you were told this" change what counts as relevant.
|
|
|
|
The band width itself is deliberately NOT pinned. It is a tuning value with
|
|
a comment recording the measurement behind it, and a test asserting 0.05
|
|
would fail on every future retune while proving nothing about behaviour —
|
|
so the cases below express their scores as offsets from the constant.
|
|
"""
|
|
import pytest
|
|
|
|
from scribe.services.plugin_context import (
|
|
_RULEHINT_BAND,
|
|
_rule_band,
|
|
_rule_hint_line,
|
|
)
|
|
from tests.helpers import fake_rule
|
|
|
|
_TRIGGER = "about to run git push with an earlier CI run still unread"
|
|
|
|
|
|
def _hit(score: float, rule_id: int):
|
|
return (score, fake_rule(id=rule_id, title=f"rule {rule_id}",
|
|
when_to_apply=_TRIGGER))
|
|
|
|
|
|
def test_an_empty_result_stays_empty():
|
|
"""No hits is not a crash and not a phantom line."""
|
|
assert _rule_band([]) == []
|
|
|
|
|
|
def test_the_band_keeps_a_hit_just_inside_it():
|
|
"""The whole point: a close second rule reaches the agent."""
|
|
top = 0.75
|
|
hits = [_hit(top, 1), _hit(top - _RULEHINT_BAND + 0.01, 2)]
|
|
assert [r.id for _s, r in _rule_band(hits)] == [1, 2]
|
|
|
|
|
|
def test_the_band_drops_a_hit_just_outside_it():
|
|
"""And the band must actually BIND, or it is a fixed k wearing a hat."""
|
|
top = 0.75
|
|
hits = [_hit(top, 1), _hit(top - _RULEHINT_BAND - 0.01, 2)]
|
|
assert [r.id for _s, r in _rule_band(hits)] == [1]
|
|
|
|
|
|
def test_one_clearly_better_rule_still_surfaces_alone():
|
|
"""The behaviour the old limit of 1 got right, which must not regress.
|
|
|
|
A moment with a single relevant rule shows one line, because the corpus
|
|
said so — not because a constant capped it.
|
|
"""
|
|
hits = [_hit(0.80, 1), _hit(0.55, 2), _hit(0.54, 3)]
|
|
assert [r.id for _s, r in _rule_band(hits)] == [1]
|
|
|
|
|
|
def test_a_flat_cluster_surfaces_together():
|
|
"""Measured shape of this corpus: adjacent rules sit ~0.02 apart.
|
|
|
|
Four rules governing one act is the `git push` case the step exists for,
|
|
and at the measured spacing they must arrive together rather than the
|
|
ranker picking one of four near-ties.
|
|
"""
|
|
hits = [_hit(0.757, 2), _hit(0.735, 7), _hit(0.726, 1), _hit(0.711, 9)]
|
|
assert [r.id for _s, r in _rule_band(hits)] == [2, 7, 1, 9]
|
|
|
|
|
|
def test_the_band_is_computed_from_scores_not_from_the_ledger():
|
|
"""A seen top hit still anchors the band (#3750 x #3851).
|
|
|
|
`_rule_band` never learns what the session has seen — dedup happens after
|
|
it, in the arms. Pinned here because the tempting "reorder so a fresh rule
|
|
leads" would silently change the cutoff, and the failure is invisible: the
|
|
arm would still emit lines, just the wrong set.
|
|
"""
|
|
hits = [_hit(0.80, 1), _hit(0.78, 2), _hit(0.60, 3)]
|
|
kept = _rule_band(hits)
|
|
# Independent of any `already` set, because it is not consulted.
|
|
assert [r.id for _s, r in kept] == [1, 2]
|
|
|
|
|
|
@pytest.mark.parametrize("seen", [False, True])
|
|
def test_the_leading_line_carries_the_trigger(seen):
|
|
"""Rank 0 gets the full rendering, on either tail."""
|
|
line = _rule_hint_line(
|
|
fake_rule(id=4, title="dev is home", when_to_apply=_TRIGGER),
|
|
where="to this Bash call", seen=seen, compact=False,
|
|
)
|
|
assert _TRIGGER in line
|
|
assert "get_rule(4)" in line
|
|
|
|
|
|
@pytest.mark.parametrize("seen", [False, True])
|
|
def test_a_later_line_cites_its_rule_without_quoting_the_trigger(seen):
|
|
"""Rank > 0 is a reference: identity and pointer, no trigger.
|
|
|
|
The trigger is the expensive half — 300-400 characters after #3855 — and
|
|
the leading line has already demonstrated the shape. Both assertions
|
|
matter: dropping the trigger is the saving, and keeping `get_rule(id)` is
|
|
what makes the saving safe, because a cited rule the reader cannot pull is
|
|
just noise.
|
|
"""
|
|
line = _rule_hint_line(
|
|
fake_rule(id=4, title="dev is home", when_to_apply=_TRIGGER),
|
|
where="to this Bash call", seen=seen, compact=True,
|
|
)
|
|
assert _TRIGGER not in line
|
|
assert "dev is home" in line
|
|
assert "get_rule(4)" in line
|
|
|
|
|
|
def test_shortening_a_line_does_not_decide_what_it_says_about_holding():
|
|
"""A reference still tells a repeat from a first surfacing (#3750 x #3851).
|
|
|
|
This is the regression the first cut of #3851 actually shipped: the
|
|
compact branch dropped the tail along with the trigger, so a rule the
|
|
session had already been told read exactly like one it had not. #3750's
|
|
whole argument is that the two 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.
|
|
|
|
`compact` and `seen` are independent axes. How much room a line gets is a
|
|
fact about its rank; whether the session holds it is a fact about the
|
|
ledger; and neither may be allowed to answer the other's question.
|
|
"""
|
|
rule = fake_rule(id=4, title="dev is home", when_to_apply=_TRIGGER)
|
|
seen = _rule_hint_line(rule, where="here", seen=True, compact=True)
|
|
fresh = _rule_hint_line(rule, where="here", seen=False, compact=True)
|
|
assert seen != fresh
|
|
assert "no longer hold it" in seen
|
|
assert "not in this session's loaded set" in fresh
|
|
|
|
|
|
def test_a_compact_line_is_materially_shorter_than_a_full_one():
|
|
"""The cost claim, asserted rather than left in a comment.
|
|
|
|
Not a token count — that would pin the tokenizer. Half the characters is
|
|
the property that makes widening the arm affordable, and it is what fails
|
|
if a later edit puts the trigger back into the compact branch.
|
|
|
|
Measured against a REALISTIC trigger, because that is where the saving
|
|
lives: the rules this arm carries run 300-400 characters of trigger after
|
|
#3855, and a toy one-line trigger would make this pass on a compact branch
|
|
that had stopped saving anything.
|
|
"""
|
|
long_trigger = (
|
|
"Opening or merging a `dev`->`main` pull request, running "
|
|
"`git push origin main`, `git tag`, or minting a release, image tag "
|
|
"or other public artifact. Also whenever CI has just gone green and "
|
|
"the next step feels like shipping it, and whenever an earlier merge "
|
|
"this session reads like standing permission for the next one."
|
|
)
|
|
rule = fake_rule(id=4, title="dev is home", when_to_apply=long_trigger)
|
|
full = _rule_hint_line(rule, where="here", seen=False, compact=False)
|
|
compact = _rule_hint_line(rule, where="here", seen=False, compact=True)
|
|
assert len(compact) * 2 < len(full)
|
|
|
|
|
|
def test_a_preference_keeps_its_noun_when_compact():
|
|
"""Force survives the shortening (#3849).
|
|
|
|
`kind` and `compact` are independent axes. A preference rendered as a
|
|
reference must still not read as a rule — the noun is the whole of the
|
|
visual difference, so losing it in the compact branch would make every
|
|
cited preference bind.
|
|
"""
|
|
line = _rule_hint_line(
|
|
fake_rule(id=5, title="pace debugging", kind="preference",
|
|
when_to_apply=_TRIGGER),
|
|
where="here", seen=False, compact=True,
|
|
)
|
|
assert "preference" in line.lower()
|
|
assert "standing rule" not in line.lower()
|