CI & Build / Python lint (push) Successful in 3s
CI & Build / Plugin hooks (push) Successful in 11s
CI & Build / TypeScript typecheck (push) Successful in 23s
CI & Build / integration (push) Successful in 31s
CI & Build / Python tests (push) Successful in 1m6s
CI & Build / Build & push image (push) Successful in 27s
The ranked rule arm became measurable in M333. The preload did not — and that is the surface whose value is actually in question. `list_always_on_rules`, the SessionStart block and every `rules_payload` caller handed rules over wholesale and emitted nothing, so the resident set's token cost was certain and its usefulness could not be tested even in principle. Bulk deliveries now record as AMBIENT, beside the ranked count and never inside pull-through. Folding them in would mean growing the always-on set depressed the arm's measured precision and trimming it flattered the arm, neither for any reason to do with the arm. `RANKED_SOURCES` inverts the note twin's `AMBIENT_SOURCES` deliberately: there is one ranked rule source and this change adds seven bulk ones, so naming the rare half makes a forgotten surface default to ambient — under-counting it — rather than padding the denominator with surfacings nobody chose. Two lookalike call sites are deliberately left silent, with a test to keep them that way: the write-path etag arm and `rules_etag_for` read the rules to build or compare a MARKER and show nobody anything. No migration — `event` and `source` are plain Text with no CHECK (rule 36 does not apply). Snippet #2858 updated to the new `rules_payload` contract. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011cPyzNnegXHr5iRMzzy5KJ
67 lines
2.2 KiB
Python
67 lines
2.2 KiB
Python
"""Planning service — start_planning creates a MILESTONE seeded as the plan
|
|
container, surfacing the project's applicable Rulebook rules at the planning
|
|
moment so rules land before any work.
|
|
|
|
The milestone IS the plan: its `body` holds the design/intent (Goal/Approach/
|
|
Verification), and the individual steps live as first-class child tasks
|
|
(milestone_id) rather than checkboxes crammed into one body. The legacy
|
|
kind=plan task is retired going forward — start_planning never creates one.
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
from scribe.services import milestones as milestones_svc
|
|
from scribe.services import notes as notes_svc
|
|
from scribe.services import projects as projects_svc
|
|
from scribe.services import rulebooks as rulebooks_svc
|
|
|
|
# The plan body template — design only. Steps are NOT checkboxes here; each
|
|
# step becomes its own child task under this milestone (status, work-logs,
|
|
# priority of its own).
|
|
PLAN_TEMPLATE = """## Goal
|
|
|
|
## Approach
|
|
|
|
## Verification
|
|
"""
|
|
|
|
|
|
async def start_planning(user_id: int, project_id: int, title: str) -> dict:
|
|
"""Create a milestone seeded as a plan container and return it with the
|
|
project's applicable rules + brief context.
|
|
|
|
Returns:
|
|
{
|
|
"milestone": <milestone dict>,
|
|
"applicable_rules": [...],
|
|
"subscribed_rulebooks": [...],
|
|
"applicable_rules_truncated": bool,
|
|
"project_goal": str,
|
|
"open_task_count": int,
|
|
}
|
|
"""
|
|
project = await projects_svc.get_project(user_id, project_id)
|
|
if project is None:
|
|
raise ValueError(f"project {project_id} not found")
|
|
|
|
milestone = await milestones_svc.create_milestone(
|
|
user_id,
|
|
project_id=project_id,
|
|
title=title,
|
|
body=PLAN_TEMPLATE,
|
|
status="active",
|
|
)
|
|
|
|
applicable = await rulebooks_svc.get_applicable_rules(
|
|
project_id=project_id, user_id=user_id,
|
|
)
|
|
_, open_count = await notes_svc.list_notes(
|
|
user_id, is_task=True, status="todo", project_id=project_id, limit=1,
|
|
)
|
|
|
|
return {
|
|
"milestone": milestone.to_dict(),
|
|
**rulebooks_svc.rules_payload(applicable, user_id=user_id, source="start_planning"),
|
|
"project_goal": getattr(project, "goal", "") or "",
|
|
"open_task_count": open_count,
|
|
}
|