roadmap-planner
Prove an execution roadmap's task order is correct by re-deriving it from the source of truth
(the vision + spec), then diffing the derivation against the existing plan.
The core insight: a roadmap is a schedule over a dependency graph. Most planning errors are not
"wrong task" — they are producer-after-consumer: a task that produces something (a contract, a
schema, an SDK, a service) is scheduled after a task that consumes it. Reading the roadmap top-to-
bottom never catches this; only building the actual dependency graph does. So we derive the tasks
from use-cases, wire the graph explicitly, and let a deterministic pass find every ordering violation.
You produce two artifacts, never a silent overwrite:
- a freshly-derived, dependency-ordered roadmap (a proposal), and
- a delta report vs the existing roadmap — ordering violations, missing/extra tasks, circular
dependencies found + how they were broken, and compaction candidates.
The human reviews the delta and decides what to adopt. The existing roadmap is often hand-crafted with
context the spec doesn't capture; the derivation's job is to challenge it, not replace it.
Inputs (parameterize per project)
- Corpus — the vision/spec/requirements docs to derive from. Point it at wherever the project keeps
its source of truth (a
docs/spec/** tree, a product-requirements doc, a genesis/seed file, ADRs).
Actors + use-cases usually live in the requirements/product docs; modules/engines/services in the
architecture/design docs. If the project has no written spec, derive from the README + issue tracker
and flag the thin provenance as a finding.
- Existing roadmap — the plan to validate. Default:
project.json → roadmap.path (this baseline's
default is docs/roadmap-execution-plan.md).
- Buckets — the delivery lanes, project-defined. Set them in the
tasks.json top-level buckets
array; they only rank ties in ordering. A single-lane project can use one bucket (e.g. app); a
platform/product split might use platform, product, web, app. There is no fixed lane set.
If the user names a different corpus or roadmap, use those. If no roadmap exists yet, skip the delta
and emit only the derived roadmap.
The method (10 steps)
Steps 1–4 are read-heavy discovery — run them as a multi-agent workflow when the corpus is large
(see "Execution engine"). Steps 5–10 are synthesis + mechanical graph work in main context.
A running rule for every step: derive AND reconcile. Do not derive in a vacuum. The spec usually
already declares modules (e.g. a module-boundaries chapter) and actors (e.g. a user-roles chapter).
Derive fresh from use-cases, then reconcile against what the spec declares. Every disagreement
(a module you derived that the spec omits, or vice-versa) is a finding, not something to silently
paper over.
-
Actors → use-cases. Read the corpus and list every actor and the use-cases each performs. The
union of use-cases is the complete solution. Capture each use-case with a stable id, its actor, and
a one-line intent. Cite the source doc/section. This is the ground truth everything else traces to.
-
Black-box → modules. Treat the system as one black box that satisfies all use-cases, then break
it into n functional modules by cohesion (a module owns a coherent slice of the use-cases). Name
modules by capability, not technology.
-
Interfaces + NFRs. For each module, state its high-level interface (the intent it exposes to
other modules — not signatures) and extract the non-functional requirements that bear on it
(security/tenancy, performance, availability, compliance, versioning, maintainability/diagnosability).
NFRs become task constraints (a hard deps/seamDeps edge when they gate) and feed the
non-functional scoring lens (Step 8b) — they are not free-floating prose.
-
Platform vs solution. Cluster modules into platform (reusable, product) vs solution
(customer-specific assembly). This is the level-1 system design. A module that encodes customer
logic is solution; a reusable capability is platform. Record the rationale per module.
4a. Inherited-default seams (cross-cutting). Extract the project's inherited-defaults family — the
cross-cutting concerns every feature is expected to pick up automatically (commonly security, audit,
tenancy, and observability; a project's spec may name its own). These concerns create no hard deps
edge — feature code compiles and runs without them — so a plain producer→consumer graph scatters
them to the tail. For each concern: (a) apply the contract/backend split — a seam/contract task
(the interface every consumer is written against; scheduled early) and a backend task (the concrete
sink/implementation; deferrable); (b) draw a soft seamDeps edge from each first-consumer task to
the seam/contract task (Step 8). The helper floats the seam early and relaxes it (advisory) only if a
hard edge legitimately forbids earliness. Reconcile: if the project treats a concern as an
automatic default, a feature epic with no seam edge to that applicable default is itself a finding —
a seam-after-consumer waiting to happen — not something to pass over.
-
Epics. Cluster use-cases by high-level intent → n epics. An epic is a coherent capability a
demo/release can show. Each use-case lands in exactly one epic.
-
Typed tasks. Within each epic, split its use-cases into three task categories:
Infrastructure (scaffolding, storage, wiring, contracts), Business Logic (domain rules,
engines), Interface (APIs, UI, SDKs). One use-case may yield tasks in more than one category.
-
Bucket + intra-epic order. Map each typed task onto one of the 4 buckets
(platform / solution / web / app) and arrange within the epic. Natural intra-epic flow is usually
Infrastructure → Business Logic → Interface, and platform → solution → web/app — but that is a
heuristic, not the truth. The truth is the dependency graph (Step 8), which overrides any bucket
heuristic.
-
Dependency graph + cycle-breaking (DETERMINISTIC). Give every task a stable id and declare its
dependency edges (A depends on B means B must ship before A). Then run the helper:
node .claude/skills/roadmap-planner/scripts/graph.mjs analyze <tasks.json>. It topologically sorts, detects cycles, and
flags any task ordered before a task it depends on. On a cycle, split both tasks into
independent sub-tasks that break it (e.g. extract the shared contract A and B both need into a
third task both depend on), update the edges, and re-run. This is recursive — keep splitting
until the graph is acyclic. The judgment (what depends on what, how to split) is yours; the cycle
detection + topo-sort is the script's. Also declare seamDeps (soft/seam edges) alongside deps
for the Step-4a inherited-default concerns: the helper floats seams early, relaxes any a hard edge
forbids (advisory, never a gridlock), and — when order is present — flags a seam-after-consumer
(an admitted seam scheduled at/after its consumer) as a blocker (exit 3). See references/graph-schema.md.
8b. Priority scoring — two lenses (WSJF). The edges answer "is this order legal?"; the
score answers "among legal orders, which is good?" Impact is stakeholder-relative — a single
impact number hard-codes one stakeholder (usually business) and starves engineering-discipline work
(the meta-cause of seam-after-consumer). So score each task through two lenses: functionalValue
(business value — product-owned) and nonFunctionalValue (maintainability / diagnosability /
security-posture — owned by the architecture owner, not the feature stakeholder; that ownership is
the structural seat that stops engineering from being business-scored). The helper ranks the
topologically-ready set by weightedAverage(functionalValue, nonFunctionalValue; weights) / effort
(top-level weights, default 50/50) — it never crosses a hard edge, so it only sequences work
that is already eligible. This buys deferral for free: a low-value/high-effort task (e.g. a
deferred backend from Step 4a) floats as late as its dependents allow — no milestone node needed.
Genuine hard phase-gates stay deps. For a judgment call the score gets wrong, set
scoreOverride + a required overrideReason (a human-directed call; auditable). Scoring
is advisory — it never blocks. See references/graph-schema.md.
-
Ordered roadmap. The topological order (with buckets + epics preserved) is the derived
execution roadmap. Emit it. Sanity-check the emitted order: the helper proves the order is
legal, not sensible — if a backend/Infrastructure/hardening task lands before the Business-Logic
it supports, that is an under-scored or under-anchored deferral (raise its consumers' scores, add the
real gate as a dep, or set a scoreOverride), not a valid plan.
-
Compaction. Re-run node .claude/skills/roadmap-planner/scripts/graph.mjs compact <tasks.json> to list merge candidates —
tasks in the same epic+bucket with no dependency between them (parallelizable → one slice), or a
chain A → B where B depends only on A in the same bucket (collapsible). Reducing task count
raises velocity: every extra task boundary is intake/spec/review ceremony. Merge where it doesn't
hide a real dependency or blow up slice size. Compaction never reorders across a real edge.
The graph helper (scripts/graph.mjs)
The deterministic core of Step 8/10. It reads a tasks JSON and does the mechanical graph work so
"is the order correct?" is a computed answer, not a vibe. Full I/O contract + the tasks JSON schema:
references/graph-schema.md. Quick reference:
node .claude/skills/roadmap-planner/scripts/graph.mjs analyze <tasks.json> — topo-sort; report cycles (with the cycle path),
and order violations (a task whose order is ≤ a dependency's order, i.e. scheduled too
early). Exit 0 = acyclic + no violations; exit 2 = cycle; exit 3 = ordering violation(s).
node .claude/skills/roadmap-planner/scripts/graph.mjs order <tasks.json> — print one valid dependency-respecting order (stable:
ties broken by bucket rank then id) — the Step 9 order.
node .claude/skills/roadmap-planner/scripts/graph.mjs compact <tasks.json> — list Step-10 merge candidates.
The JSON is your structured output from Steps 1–7 (tasks with id, epic, bucket, category,
title, deps, and — for validating an existing roadmap — order = its current position). You build
it; the script judges it. Keep the intermediate tasks.json in the workspace so a re-run is cheap.
Execution engine (large corpus → multi-agent workflow)
Steps 1–4 read the whole corpus (here: ~45 spec chapters). That is a fan-out shape: one reader per
doc-cluster extracting {actors, use-cases, modules, interfaces, NFRs} as structured JSON, merged in
main context. Use the Workflow tool for this when the corpus is more than a handful of files —
parallel readers, then a synthesis/dedup barrier, then the (single-context) Steps 5–10. For a small
corpus, a single-context read is fine. The workflow is the engine; this skill is the recipe it runs.
Fan-out sketch (adapt to the corpus):
- Readers (parallel): partition the corpus (e.g. requirements/roles/ACs in one cluster;
architecture modules/engines/services in another; the API/UI/data chapters). Each returns structured
actors/use-cases/modules/NFRs with source citations.
- Synthesis (barrier): dedup + reconcile into one actor/use-case/module set; flag spec-vs-derived
disagreements.
- Order + compact: build
tasks.json, run the helper, break cycles, emit derived roadmap + delta.
Output
Write two files (default under docs/roadmap/, or where the user says):
derived-<YYYY-MM-DD>.md — the derived, dependency-ordered roadmap: actors/use-cases traceability
table, module + platform/solution split, epics, and the ordered typed tasks with their deps.
delta-<YYYY-MM-DD>.md — findings vs the existing roadmap, ranked by severity:
- Ordering violations (producer-after-consumer) — the highest-value finding; name the task, the
prerequisite it precedes, and the fix.
- Circular dependencies found + how they were broken.
- Missing / extra tasks — in the derivation but not the roadmap (or vice-versa), each traced to
a use-case (or flagged as unjustified).
- Compaction candidates — over-fragmented epics that can merge without hiding a dependency.
- Spec-vs-derivation disagreements (Step-1/4 reconciliation).
Lead the delta with a one-paragraph verdict: is the current order sound, and what are the top 3 fixes?
Constraints
- Never blindly overwrite the existing roadmap. Emit a proposal + delta; the human adopts.
- The dependency graph is the source of truth for order, not the bucket/category heuristics. If the
graph and the heuristic disagree, the graph wins and that disagreement is a finding.
- Every task traces to a use-case; every use-case traces to an actor + a spec citation. A task with
no use-case behind it is a finding (possible gold-plating). A use-case with no task is a gap.
- Deterministic where it can be. Cycle detection, topo-sort, and violation-flagging go through the
helper, not eyeballing. The model supplies judgment; the script supplies proof.
- Reduce, then stop. Compaction serves velocity; don't over-merge into mega-tasks that hide
dependencies or exceed a sane slice size.