feat(mcp): enter_project names active milestones with no steps as open work (#4076)
CI & Build / Python lint (push) Successful in 2s
CI & Build / Plugin hooks (push) Successful in 8s
CI & Build / Build & push image (push) Canceled after 0s
CI & Build / integration (push) Canceled after 42s
CI & Build / TypeScript typecheck (push) Canceled after 42s
CI & Build / Python tests (push) Canceled after 47s
CI & Build / Python lint (push) Successful in 2s
CI & Build / Plugin hooks (push) Successful in 8s
CI & Build / Build & push image (push) Canceled after 0s
CI & Build / integration (push) Canceled after 42s
CI & Build / TypeScript typecheck (push) Canceled after 42s
CI & Build / Python tests (push) Canceled after 47s
A plan written as a milestone with a description and no steps was invisible to the session handshake: it lists the 5 most recently touched milestones (#4045), and touching is a step changing, so a step-less milestone can never qualify. FabledLibrarian's roadmap (nine such milestones) sat unseen while later plans were opened as new milestones beside the ones that already described them. enter_project adds `unplanned_milestones`: active milestones with no steps, in roadmap order, id/title/description, up to 10 with an omitted count, none repeated from the recent list, and absent when there are none. The docstring says what they are for: check them before starting a new milestone, and add steps to a match with create_records(milestone_id=...). Milestone 415 step 1. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01821k5B3Ysecp9fNYs92Kuy
This commit is contained in:
@@ -283,3 +283,29 @@ def brief_milestone_summary(
|
||||
)[:limit]
|
||||
brief = [{k: r[k] for k in _BRIEF_FIELDS if k in r} for r in kept]
|
||||
return brief, len(rows) - len(kept)
|
||||
|
||||
|
||||
def unplanned_milestones(
|
||||
rows: list[dict], *, exclude_ids: set[int] = frozenset(), limit: int | None = None,
|
||||
) -> tuple[list[dict], int]:
|
||||
"""Active milestones with no steps yet, as (rows, omitted).
|
||||
|
||||
A plan written as a milestone with a description and no steps is open work
|
||||
that nothing else names. It is never "touched" — touching is a step
|
||||
changing — so the recency list that brief_milestone_summary(limit=) builds
|
||||
can never reach it, and progress reads 0% either way. A project whose
|
||||
roadmap was written that way ended up with every later plan opened as a
|
||||
new milestone beside the one that already described it (milestone 415).
|
||||
|
||||
`exclude_ids` drops milestones a caller already listed. Kept in roadmap
|
||||
order (order_index, then creation), the order they were written in.
|
||||
Rows are id, title and description: what a reader needs to recognise the
|
||||
plan, and not its body, which get_milestone reads.
|
||||
"""
|
||||
found = [
|
||||
{"id": r["id"], "title": r.get("title"), "description": r.get("description")}
|
||||
for r in rows
|
||||
if r.get("status") == "active" and not r.get("total") and r["id"] not in exclude_ids
|
||||
]
|
||||
kept = found if limit is None else found[:limit]
|
||||
return kept, len(found) - len(kept)
|
||||
|
||||
Reference in New Issue
Block a user