fix(rules): planning reads list rules by id and title instead of restating them (#4081)
CI & Build / Python lint (push) Successful in 3s
CI & Build / Plugin hooks (push) Successful in 8s
CI & Build / integration (push) Successful in 53s
CI & Build / TypeScript typecheck (push) Successful in 1m3s
CI & Build / Python tests (push) Failing after 1m4s
CI & Build / Build & push image (push) Skipped

start_planning on project 2 replied with 92,645 characters, 65k of them
applicable_rules. Milestone 414 made a project's listing every global rule
tagged to an area it works in (before, the rules of subscribed rulebooks, and
project 2 subscribed to none), and the non-brief rules_payload sent each as a
full rule_brief. get_milestone, get_project and get_task carried the same.

Every rules_payload form now lists: id, title, the topic a global rule sits in,
and `via` for a co_surfaces partner. get_rule reads one in full, and retrieval
delivers them in full when work matches — the reasoning #4045 applied to the
handshake. A test pins 81 full-length rules under 6k characters.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01821k5B3Ysecp9fNYs92Kuy
This commit is contained in:
2026-09-15 13:26:48 -04:00
co-authored by Claude Opus 5
parent 0bf7406f42
commit ac01eee040
2 changed files with 49 additions and 14 deletions
+25
View File
@@ -193,3 +193,28 @@ async def test_list_milestones_lists_every_milestone_without_plans():
out = await list_milestones(project_id=5)
assert len(out["milestones"]) == 30
assert all("body" not in m for m in out["milestones"])
def test_a_planning_read_lists_rules_without_restating_them():
"""#4081: a project's listing is every global rule tagged to its areas, so
the full rule_brief of each put start_planning at 92k characters. Planning
reads name the rules; get_rule reads one."""
from scribe.services.rulebooks import rules_payload
applicable = {
"rules": [{"id": i, "title": f"r{i}", "statement": PLAN, "when_to_apply": PLAN,
"topic_title": "git", "relations": [{"note": PLAN}]} for i in range(50)]
+ [{"id": 900, "title": "partner", "statement": PLAN, "via": "co_surfaces"}],
"project_rules": [{"id": 100 + i, "title": f"pr{i}", "statement": PLAN}
for i in range(30)],
"truncated": True,
}
with patch("scribe.services.rulebooks.record_rule_surfaced") as surfaced:
out = rules_payload(applicable, user_id=7, source="start_planning")
assert out["applicable_rules"][0] == {"id": 0, "title": "r0", "topic_title": "git"}
assert out["applicable_rules"][-1] == {"id": 900, "title": "partner", "via": "co_surfaces"}
assert out["project_rules"][0] == {"id": 100, "title": "pr0"}
assert out["applicable_rules_truncated"] is True
assert len(surfaced.call_args.kwargs["rule_ids"]) == 81
assert len(json.dumps(out)) < 6_000, len(json.dumps(out))