Milestone 415: an existing plan is found before a new one is made #159

Merged
bvandeusen merged 8 commits from dev into main 2026-09-15 14:23:32 -04:00
2 changed files with 49 additions and 14 deletions
Showing only changes of commit ac01eee040 - Show all commits
+24 -14
View File
@@ -1112,36 +1112,46 @@ def rules_payload(
(`plugin_context`) — computes a marker and shows nobody anything, and (`plugin_context`) — computes a marker and shows nobody anything, and
counting it would put rules in the denominator that no agent ever saw. counting it would put rules in the denominator that no agent ever saw.
EVERY FORM LISTS, NONE RESTATES. Rules reach a session in full by
retrieval, so these payloads say which constraints exist — id and title,
the topic a global rule sits in, `via` for a co_surfaces partner — and
get_rule reads one. Planning reads carried the full rule_brief until a
project's listing grew to every global rule tagged to its areas
(milestone 414) and start_planning replied with 92k characters (#4081),
the shape #4045 had just removed from the handshake.
`brief` is the session handshake's form (#4045): the project's own rules `brief` is the session handshake's form (#4045): the project's own rules
as id and title, nothing else. Rules reach a session in full by only. Only what is shown is recorded as surfaced.
retrieval, so the handshake lists which of the project's constraints exist
rather than restating them; get_rule reads one. Only what is shown is
recorded as surfaced.
""" """
project_rules = [_rule_line(r) for r in applicable.get("project_rules", [])]
if brief: if brief:
project_rules = [
{"id": r["id"], "title": r["title"]}
for r in applicable.get("project_rules", [])
]
record_rule_surfaced( record_rule_surfaced(
user_id=user_id, rule_ids=[r["id"] for r in project_rules], source=source, user_id=user_id, rule_ids=[r["id"] for r in project_rules], source=source,
) )
return {"project_rules": project_rules} return {"project_rules": project_rules}
rules = [_rule_line(r) for r in applicable.get("rules", [])]
record_rule_surfaced( record_rule_surfaced(
user_id=user_id, user_id=user_id,
rule_ids=( rule_ids=[r["id"] for r in rules] + [r["id"] for r in project_rules],
[r["id"] for r in applicable.get("rules", [])]
+ [r["id"] for r in applicable.get("project_rules", [])]
),
source=source, source=source,
) )
return { return {
"applicable_rules": applicable["rules"], "applicable_rules": rules,
"applicable_rules_truncated": applicable["truncated"], "applicable_rules_truncated": applicable["truncated"],
"project_rules": applicable.get("project_rules", []), "project_rules": project_rules,
} }
def _rule_line(brief: dict) -> dict:
"""One rule as a listing names it: enough to recognise it and fetch it.
Keys a row does not carry are left out rather than sent empty (#2483)."""
line = {"id": brief["id"], "title": brief["title"]}
for key in ("topic_title", "via"):
if brief.get(key):
line[key] = brief[key]
return line
# ── The staleness marker (milestone 323 step 5) ──────────────────────── # ── The staleness marker (milestone 323 step 5) ────────────────────────
# #
# WHAT THIS CAN AND CANNOT SEE. An etag catches a rule that MOVED after a # WHAT THIS CAN AND CANNOT SEE. An etag catches a rule that MOVED after a
+25
View File
@@ -193,3 +193,28 @@ async def test_list_milestones_lists_every_milestone_without_plans():
out = await list_milestones(project_id=5) out = await list_milestones(project_id=5)
assert len(out["milestones"]) == 30 assert len(out["milestones"]) == 30
assert all("body" not in m for m in out["milestones"]) 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))