Decomposes an existing epic into milestones. The skill drives the conversation about what to ship in what order; aiwf owns id allocation and per-milestone commits.
An epic spec exists. The user says: "break E-NNNN into milestones", "plan the work for the auth epic", "sequence the milestones for X".
-
Read the epic spec. Open work/epics/E-NNNN-<slug>/epic.md. Understand:
- The goal โ what the epic is delivering.
- The scope (in / out).
- The constraints โ what each milestone must respect.
- The success criteria โ what "done" looks like at epic close.
-
Decompose into milestones. Each milestone:
- Is independently shippable. After a prior milestone lands, the system is in a coherent state even if this one never runs.
- Has clear, testable acceptance criteria.
- Targets 1โ3 days of focused work. If a candidate is bigger, split it. If smaller, fold it into a sibling.
- Has explicit dependencies (or none). Forward-flowing โ a later milestone may depend on an earlier one; never the reverse.
-
Sequence them. Foundational first. Group related work; don't scatter concerns. Identify any milestones that can be parallelized (no dependency between them).
-
Allocate each milestone via aiwf. For each one in sequence:
aiwf add milestone --epic E-NNNN --title "<imperative title>"
aiwf allocates the next free M-NNNN (global, not epic-scoped), creates work/epics/E-NNNN-<slug>/M-NNNN-<slug>.md with the minimal body skeleton, sets parent: E-NNNN in frontmatter, and produces one commit per milestone with aiwf-verb: add trailers.
-
Replace each milestone's body with the rich template at .claude/templates/milestone-spec.md (materialized by aiwf update; if it's missing, run aiwf update rather than copying an existing milestone spec). Fill in:
- Goal โ 1โ2 sentences.
- Context โ what exists before; what must be in place; why now.
- Acceptance criteria โ created as AC entities via
aiwf add ac, not freehand template prose (see the AC-creation block below the template list).
- Constraints โ non-negotiable invariants for this milestone.
- Design notes โ locked decisions; reference ADRs by id.
- Out of scope โ what this milestone explicitly does NOT do.
- Dependencies โ prior milestones, external deps, decision records.
Frontmatter (id, parent, status: draft) was set by aiwf add โ don't touch.
Land each filled-in body through the trailered verb, not a plain git commit:
aiwf edit-body M-NNNN
aiwf edit-body <id> commits the working-copy body bytes with provenance trailers in one atomic operation; a plain git commit against a milestone spec would trip the kernel's provenance-untrailered-entity-commit finding โ the same finding step 6 warns about for depends_on edits. See the aiwf-edit-body skill for the --body-file and --reason variants.
Create the acceptance criteria as AC entities now โ at plan time, not deferred to milestone start. For each criterion, run aiwf add ac (which appends the AC to the milestone's frontmatter acs[] and scaffolds its ### AC-N body heading), then fill that heading with the testable contract via aiwf edit-body:
aiwf add ac M-NNNN --title "<observable behavior>"
aiwf edit-body M-NNNN
Creating and body-filling the ACs here โ before the merge-to-main step below โ is what keeps a milestone from ever landing on main with zero ACs or empty AC bodies; the milestone-draft-incomplete-acs check (subcodes zero-acs / empty-body) surfaces exactly that gap on a draft milestone. aiwfx-start-milestone's preflight then expects the ACs to already exist, treating on-the-spot creation as a recovery fallback for a hand-written spec.
-
Declare milestone dependencies via verb, not by hand-editing frontmatter. Two writer surfaces, each producing one atomic commit with aiwf-verb trailers when the list actually changes โ re-declaring the list already stored converges to exit 0 with no commit:
aiwf add milestone --epic E-NNNN --tdd <policy> \
--title "..." --depends-on M-PPPP[,M-QQQQ]
aiwf milestone depends-on M-NNNN --on M-PPPP[,M-QQQQ]
aiwf milestone depends-on M-NNNN --clear
Replace-not-append semantics: a second --on invocation replaces the list, it does not extend. To add a single dependency to an existing list, pass the full updated list. --on and --clear are mutually exclusive. Each id passed to --depends-on or --on must already resolve to an existing milestone โ typos and forward-references are refused with an error naming the unresolvable id. Cycle detection happens at the next aiwf check (and pre-push hook); the writers don't pre-check global DAG validity.
Do not hand-edit depends_on: in frontmatter. Bless-mode aiwf edit-body refuses frontmatter changes, and a plain git commit against the milestone file trips the kernel's provenance-untrailered-entity-commit warning. Both writer verbs above leave a trailered commit that aiwf history M-NNNN can render whenever they change the list.
-
Update the epic's Milestones list. Edit the epic spec to list all milestones in execution order. Use the format from the epic template โ link, one-line description, dependencies.
-
Update ROADMAP.md by running:
aiwf render roadmap --write
--write only rewrites the file on disk โ it does not commit. If the content changed, stage and commit it:
git add ROADMAP.md
git commit -m "docs(roadmap): regenerate after planning E-NN milestones" \
--trailer "aiwf-verb: plan-milestones" \
--trailer "aiwf-entity: E-NN" \
--trailer "aiwf-actor: human/<id>"
The trailer keys are quoted from CLAUDE.md ยง"Commit conventions" verbatim โ variant casings (e.g. Aiwf-Verb) fail the kernel's trailer-keys policy. Skip the git add/git commit if the render reported the file already up to date.
-
Confirm the sequence with the user. Walk through the milestone list together. Identify any scope adjustments before drafting begins.
-
Merge planning to main. Planning is closed; the entity tree on this ritual branch now diverges from main. Default behavior is to merge to main now so the freshly-allocated M-NNNN ids, the epic's updated Milestones list, and any depends_on edges are visible to other worktrees, machines, or operators. Held on a long-lived branch, planning data is hostage: other Claude Code sessions see only main's view, parallel epics walk separate filesystem-only next-free-id views (id collisions surface only at eventual merge), and milestone branches stack on a long-lived parent โ making the epic-wrap diff balloon.
Prompt the user as a strong recommendation with explicit decline:
Planning is closed. Default behavior is to merge to main now. Decline only with a specific reason โ entity shape uncertain, near-term re-planning expected, team convention overrides. Merge now? (Y/n)
When the operator confirms, drive the in-place merge:
git checkout main
git merge --ff-only <ritual-branch>
When the operator declines, capture the one-line reason in the conversation transcript so future readers know.
Workflow assumption โ single checkout, not a worktree. The skill assumes the operator runs planning in a single checkout (the same one calling the skill). Planning is sequential conversation; it doesn't benefit from worktree-level parallelism, and the cwd-and-session switching that worktrees impose adds friction without payoff. Worktrees are an implementation-phase tool, not a planning-phase tool.
Either way, run this only after the planning commits have landed on main (per step 10) so implementation branches fork cleanly from main rather than stacking on the ritual branch.