refactor(retrieval): the rule builders compose one moment - _rule_moment runs the arm and its via-lesson step for all three (milestone 456 step 6, #4908)
CI & Build / Python lint (push) Successful in 3s
CI & Build / Plugin hooks (push) Successful in 14s
CI & Build / TypeScript typecheck (push) Successful in 53s
CI & Build / integration (push) Successful in 1m1s
CI & Build / Python tests (push) Successful in 1m54s
CI & Build / Build & push image (push) Successful in 28s

build_prompt_rule_hint, build_tool_rule_hint and build_write_path_hint
each ran the rule arm and then the via-lesson step by hand, the first two
passing the query between them through a _via_query key on the payload.
They now call one composer, _rule_moment, and the split helpers
(_prompt_rule_hint, _tool_rule_hint, _add_rules_via_lessons) and the side
channel are deleted. Output shapes are unchanged: the prompt builder
returns no checkpoint key, the tool builder always does, and
shown_rule_ids stays the direct band.

A structural test pins that only _rule_moment runs a rule arm or the
via-lesson step, and that the three builders are its only callers.

plugin_context.py: 2,418 -> 2,361 lines this step; 3,558 when the
milestone began.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-05 20:25:47 -04:00
co-authored by Claude Opus 5.5
parent 869046dda2
commit d2fa723373
3 changed files with 124 additions and 147 deletions
+4 -4
View File
@@ -22,6 +22,7 @@ import pytest
from scribe.services import plugin_context as pc
from scribe.services import system_rulings as sr
from scribe.services.retrieval_pipeline import RuleResult
# Bound at import, before conftest's autouse stub replaces the module attribute.
from scribe.services.system_rulings import rulings_for_paths as real_rulings_for_paths
from tests.helpers import writepath_cfg
@@ -198,8 +199,8 @@ RULING_LINE = "> Rulings for `src/a.py` — the operator's decisions about A (Sy
@pytest.mark.asyncio
async def test_the_tool_arm_leads_with_rulings_and_hands_back_what_it_showed():
rulings = AsyncMock(return_value={"lines": [RULING_LINE], "system_ids": [3]})
with patch.object(pc, "_tool_rule_hint", AsyncMock(return_value={
"context": "> a rule line", "rule_ids": [8], "checkpoint": {}})), \
with patch.object(pc, "_rule_moment", AsyncMock(return_value=RuleResult(
lines=["> a rule line"], rule_ids=[8]))), \
patch.object(pc, "get_writepath_config", AsyncMock(return_value=writepath_cfg())), \
patch.object(pc.system_rulings_svc, "rulings_for_paths", rulings):
out = await pc.build_tool_rule_hint(
@@ -215,8 +216,7 @@ async def test_the_tool_arm_leads_with_rulings_and_hands_back_what_it_showed():
@pytest.mark.asyncio
async def test_the_tool_arm_adds_no_key_when_no_ruling_was_shown():
with patch.object(pc, "_tool_rule_hint", AsyncMock(return_value={
"context": "", "rule_ids": [], "checkpoint": {}})), \
with patch.object(pc, "_rule_moment", AsyncMock(return_value=RuleResult())), \
patch.object(pc, "get_writepath_config", AsyncMock(return_value=writepath_cfg())):
out = await pc.build_tool_rule_hint(1, "Bash", "ls", project_id=2)
assert "ruling_system_ids" not in out