Roadmap: Maintain · git:20260813.199d770 · 2026-08-13 · sha256 3da980cdc7997260
Roadmap: Maintain git:20260813.199d770A
Immutable. This exact content is served forever at /api/v1/blob/3da980cdc7997260.
---
name: "Roadmap: Maintain"
description: "Sync roadmap statuses after work lands — recompute from the dependency graph and refresh every projection, with optional codebase reconciliation"
when_to_use: "When roadmap task statuses might have drifted from actual progress — after a batch of merges, or periodically to keep the dashboard trustworthy. Pass 'reconcile' (or ask to check the roadmap against the code) to also infer done/unblocked tasks from the actual codebase."
model: sonnet
effort: high # high rather than medium: the reconcile path infers task completion from codebase evidence
metadata:
glyph: ᛊ
family: roadmap
bundle: roadmap-system
disable-model-invocation: false # invocable by Claude so it can offer a status sync after merges; the reconcile confirmation gate still applies
allowed-tools: ["Read", "Glob", "Grep", "Edit", "Bash(python3:*)", "Bash(git:*)"]
argument-hint: '[milestone id | "reconcile" to also check against the codebase]'
---
Keep the roadmap coherent across its artefacts — `.claude/roadmaps.json` (machine-readable source of truth; operate on the active non-`archived` phase), the PHASE file it names (task list + dependency diagram), `docs/reports/ROADMAP_OVERVIEW.md` (prose overview) and the HTML dashboard if one exists — by recomputing statuses from the dependency graph and synchronising every artefact. Optionally, first reconcile the graph itself against the actual codebase.
Shared conventions: `~/.claude/library/references/roadmap-conventions.md`. The CLI is `python3 "$HOME"/.claude/library/scripts/roadmap.py`.
> **Behaviour note.** The recompute (Steps 2–7) is **mechanical**, not inferred. It does *not* guess whether a blocker still applies, does *not* suggest promoting tasks to "in progress" (there is no in-progress state), and does *not* delete completed nodes from the diagram. A task's status is a deterministic function of its `dependsOn`, computed by `roadmap.py recompute`. The **only** sanctioned exception is Step 0: an opt-in, evidence-gated reconciliation against the codebase that proposes `done` calls and blocker-edge removals for you to confirm before anything is written. Outside Step 0, a gate or blocker clears only by a deliberate edit to the graph.
If `$ARGUMENTS` names a milestone (e.g. `M3`), scope your *reporting* to that milestone; the recompute always reads the whole graph, because dependencies cross milestones. If `$ARGUMENTS` says `reconcile` (or the user's intent is "check the roadmap against what's actually built" / "I just finished a batch, update the roadmap"), run Step 0 first.
## Steps
### 0. Codebase reconciliation (opt-in — only on a reconcile run)
Skip this step entirely on a plain status-sync run. Run it when asked to reconcile, or after the user reports finishing a batch of work.
1. **Detect + load.** Same guard as Step 1: `roadmap.py detect` (stop on exit 2/3). Read `roadmaps.json`.
2. **Build the candidate set.** From `roadmaps.json` directly, take every task with status `todo`, `blocked`, or `paused`. Skip `done`, `out_of_scope`, and `deferred` — never reconsider those. Note that `roadmap.py ready --json` only returns effective-`todo` tasks, so it under-covers this set on its own — use it purely to *order* the `todo` portion by leverage (`transitiveUnblocks` / `isMilestoneSink`), so the highest-impact tasks get checked first and you can stop early on a large roadmap; `blocked`/`paused` candidates still come from the direct JSON read and are checked because their dependency might now be satisfied in code even though they aren't yet `todo`.
3. **Bound the search by recency.** Get the changed-file set since the last reconciliation: if a prior run left a "last reconciled at `<sha>`" marker (see step 7), use `git diff --name-only <sha>..HEAD`; otherwise use a recent window (`git log --oneline -30` and `git diff --name-only HEAD~30..HEAD`, or the whole history for a small/new repo). Only search within this changed-file set — never the whole tree.
4. **Search per candidate, not in bulk.** For each candidate, derive 1–3 concrete terms from its `description`/`notes` (a filename, symbol, route, component name) and `Grep`/`Glob` for them within the changed-file set. Read a file only when a search hits.
5. **Classify each candidate**, applying the evidence rule below:
- **Proposed done** — the described feature is fully present (whole task, not partial), with concrete evidence (file:symbol, route, test, call-site).
- **Proposed unblock** — only for a blocker that is *not* itself a roadmap task, or is an external gate: something satisfied in code (or lifted externally) with no task ID to flip to `done`. Propose removing that specific `dependsOn`/gate entry (and the gate's `blocks[]`). When the blocker *is* another task, don't propose a separate unblock — proposing that blocking task `done` (above) is sufficient, since `recompute` cascades the unblock automatically once its status changes.
- **Unconfirmed** — plausible but not conclusive (e.g. partial implementation). Never promote to done or unblock from here.
- **Reverse drift** — a `done` task whose code can no longer be found. Report only; never revert a terminal status automatically.
6. **Evidence rule (conservatism):**
- Positive, specific, whole-task evidence only — absence of a match is never evidence of anything.
- A partially-implemented task stays exactly where it is (there's no in-progress state to move it to).
- Never touch `done`, `out_of_scope`, or a root-seeded held `paused`/`deferred`.
- Never infer a gate as cleared casually — gates are external by design; propose removing a gate dependency only on genuine, specific evidence, and only through the gate below.
7. **Confirmation gate.** Present the proposal before writing anything:
```
Proposed done (codebase evidence):
{ID} — {description}
evidence: {file:symbol / route / test}
Proposed unblock (prerequisite satisfied — remove edge):
{ID} — remove dependency on {depId or gateId}
evidence: {…}
Unconfirmed (needs your call):
{ID} — {why uncertain}
Reverse drift (marked done, code not found):
{ID} — {what's missing}
```
Then ask: *"Does this look right? I'll apply what you confirm and recompute."* Only the approved subset proceeds. Record the current `git rev-parse HEAD` as this run's "last reconciled at" marker for next time (mention it in the final report; no separate file needed unless the user wants one persisted).
8. **Hand off.** Feed every approved `done` ID and every approved edge removal into Step 2 below, exactly as if the user had typed them. Continue with Steps 1–7 as normal (Step 1's format check still applies; Step 2 now has explicit edits to make).
### 1. Read the artefacts and check the format
Run `python3 "$HOME"/.claude/library/scripts/roadmap.py detect`. Exit **3** = old simple format — **stop and tell the user to run `roadmap-migrate` first**. Exit **2** = could not locate/parse — ask the user for the path. Only proceed on exit 0.
Read `.claude/roadmaps.json`, the active phase's PHASE file (its `path`), and `docs/reports/ROADMAP_OVERVIEW.md`.
### 2. Apply the explicit status changes and edge removals requested
If the user is marking tasks `done` (or resetting them to `todo`/`blocked`), edit those `status` fields in `roadmaps.json` first — preserving tab indentation, field order (`id, description, status, dependsOn, softDependsOn, iterative, notes, assignee`), and the `notes`/`iterative`/`assignee`/`softDependsOn` values exactly. The recompute in step 3 sets every *derived* status; you only hand-edit terminal decisions (`done`, `out_of_scope`) and deliberate parked seeds. Never touch or infer `assignee` here — this step edits status only.
If the user asks to add or remove a soft (optional, best-effort) link between two nodes, edit the relevant task's `softDependsOn` array directly — see the conventions reference for direction and semantics. Soft edges never go through the recompute in step 3; they're pure data, picked up automatically when the diagram regenerates in step 5.
If Step 0 ran, this is also where its **approved** edits land: set `status: done` on each approved done-ID, and remove each approved dependency/gate edge from the relevant task's `dependsOn` (and the gate's `blocks[]`, keeping the two in parity). Apply only what was explicitly approved — an unconfirmed or reverse-drift item from Step 0 is never written here.
### 3. Recompute every derived status (delegated to the script)
Run `python3 "$HOME"/.claude/library/scripts/roadmap.py recompute --render`. It applies the fixed-point recompute under the precedence rule `deferred > paused > blocked > todo`, **writes the corrected statuses back** atomically, and refreshes the HTML artefact when one exists. The refreshed artefact renders every collapsible section closed by default (template convention); never hand-add `open` attributes to it. It prints each `{ID}: old -> new` change; capture that list — it drives the projection sync below.
Failure modes, all surfaced by exit 1 with a message — stop and report, never work around:
- **Cycle in `dependsOn`** — the graph must be fixed (loops belong on the `iterative` flag, not a real back-edge).
- **Non-canonical file formatting** — the file is not tab-indented as the conventions require; ask the user before re-running with `--reformat` (it rewrites the whole file, not just statuses).
The rule it applies is in the conventions reference; you never compute it by hand.
### 4. Synchronise the PHASE file task lines
For each task whose status changed (from the script's output), update its line in the PHASE file. If the file uses status sub-sections, move the task under the matching sub-section (omit any that would be empty); otherwise leave it in place. Update the checkbox and trailing annotation:
| Status | Checkbox | Trailing annotation |
| ----------------------------------- | -------- | ---------------------------------------------------------- |
| `done` | `- [x]` | none |
| `blocked` | `- [ ]` | `_(blocked — depends on {comma-separated dependsOn IDs})_` |
| `paused` | `- [ ]` | `_(paused — reconvene {gateId})_` |
| `deferred` | `- [ ]` | `_(deferred to a later phase)_` |
| `todo` with dependencies (all done) | `- [ ]` | `_(depends on {IDs})_` |
| `todo` with empty `dependsOn` | `- [ ]` | none |
| `out_of_scope` | `- [ ]` | left as authored (terminal) |
Line format: `- [ ] **{ID}** — {description}` + annotation. Indented `- Note:` sub-bullets stay attached to their task and are never moved. Do not reorder tasks — they stay in milestone/category/sequence order.
Soft edges have no annotation of their own — they're carried entirely by the Mermaid diagram (step 5), never by a checkbox-line annotation.
### 5. Regenerate the Mermaid dependency diagram
Replace the entire fenced `mermaid` block under `## Dependency Diagram` with the output of:
```bash
python3 "$HOME"/.claude/library/scripts/roadmap.py graph --mermaid --direction LR
```
Wholesale replacement — never line-edit class lists or recolour by hand. The generated block carries the canonical classDefs, every edge under the terminal milestone convention, and correct `class` statements, so the diagram cannot drift from the JSON. (A legacy diagram that used the `open` class or old hexes is fixed by this same replacement.) `softDependsOn` entries render as dotted edges (`X -.-> Y`) in this same output — never hand-add a dotted edge afterwards; it authors as data or it gets wiped by the next reconcile.
### 6. Keep ROADMAP_OVERVIEW.md in sync
A pure status update does not change the task total, so the header count usually holds. If it changed (after an add/remove), update `**N tasks across M milestones.**` — get the number from `python3 "$HOME"/.claude/library/scripts/roadmap.py stats`. Do not rewrite the prose narrative.
### 7. Validate and report
Run `python3 "$HOME"/.claude/library/scripts/roadmap.py validate` — it must report clean. Then report each `{ID}: old → new` status change grouped by milestone, whether the diagram block was regenerated, whether the HTML artefact was refreshed, and the overview count if it changed. On a reconcile run, also restate the Step 0 proposal outcome (what was approved and applied, what was left unconfirmed, any reverse drift) and the commit SHA to use as next time's "last reconciled at" marker.
---
## Notes
- roadmaps.json is the source of truth; when it and the PHASE file disagree, recompute from roadmaps.json.
- The recompute (Steps 2–7) is mechanical — never infer status from descriptions, external context, or likelihood of completion outside Step 0. A gate clears by a deliberate edit (removing the gate ID from `dependsOn` and its `blocks[]`), never by casual judgement.
- Step 0 is the one sanctioned exception: codebase-inferred `done` calls and blocker-edge removals, always evidence-backed and always confirmed before Step 2 writes them. It is opt-in — only runs on a reconcile request, never silently.
- `done` and `out_of_scope` are terminal; root-seeded parked tasks are held as authored (details in the conventions reference). Step 0 never re-opens or flips these; reverse drift is reported, not corrected automatically.
- No in-progress state — if asked to mark something "in progress", clarify the six options.