Add an optional ### Phase: <name> grouping level over ## Steps and an optional ## Decisions section to the plan spec format, and turn build's existing resume-from-first-unmarked-step behaviour into a designed checkpoint loop, so a long sequential plan can be implemented across several context windows without re-deriving earlier decisions.
Long plans have to be implemented in one sitting today, because nothing in the spec marks a safe stopping point and nothing records the decisions an earlier session already made. Phases add declared checkpoints and a Decisions ledger so a plan can be picked up cold in a fresh context window. Done when a phased plan survives render, extract, and re-render with its phases intact, and build stops at the first phase boundary with a resume command.
Read and implement all steps in the plan at docs/plans/add-plan-phase-checkpoints.md — Phase checkpoints and a Decisions ledger for plan specs. Verify against the plan's Tests, Verification, and Acceptance Criteria before reporting done. If everything passed, mark completion in docs/plans/add-plan-phase-checkpoints.md — tick each step's [x] marker and each criterion's - [x], set status: completed — and re-render the HTML from the spec. If any check failed, leave status: in-progress and say which.
More ways to run this plan — goal & workflow prompts, file path
Achieve this goal: Phase checkpoints and a Decisions ledger for plan specs. The plan at docs/plans/add-plan-phase-checkpoints.md describes one approach — use it as reference, but optimize for the outcome. Verify against the plan's Tests, Verification, and Acceptance Criteria before reporting done. If everything passed, mark completion in docs/plans/add-plan-phase-checkpoints.md — tick each step's [x] marker and each criterion's - [x], set status: completed — and re-render the HTML from the spec. If any check failed, leave status: in-progress and say which.
add-plan-phase-checkpoints.html
docs/plans/add-plan-phase-checkpoints.html
docs/plans/add-plan-phase-checkpoints.md
Context
The story behind this plan — what prompted the work and why it matters now.
Implementation plans that exceed roughly ten steps consume more context than one session can hold. The repo already reaches this conclusion: guidelines/right-sizing.md line 35 tells the author that a plan needing more than ten steps "is probably two plans — split it". It then offers no mechanism to split with. A grep of the whole plugin for parent, child, and subplan concepts returns nothing.
Two capabilities are missing, and only one of them is about parallelism. The workflow prompt already fans out across subagents, which helps when slices are independent — migrations, renames, per-file sweeps. Context exhaustion bites hardest on long sequential plans, where step seven depends on decisions made in step two, and there fan-out does nothing. The fix for that shape is checkpointing.
Most of the machinery already exists. build/SKILL.md line 152 resumes from the first unmarked step, so implement-some-steps, clear the context, re-run already works by accident. What is missing is a declared safe stopping point and a record of decisions already settled. The ## Completion Report section is a gap ledger, not a decision ledger, so a resumed session re-derives — or contradicts — choices the first session made.
The format is bidirectional, and that is the main cost driver. buildDigest is documented as the exact inverse of parseSpecMarkdown, and extractSections derives a spec back out of rendered HTML for legacy HTML-only plans. A new grouping level therefore has to be carried at four sites — parse, render, extract-from-DOM, re-emit — with test-build-plan-html.mjs and test-extract-plan-spec.mjs guarding fidelity. Anything carried at fewer than four sites is a silent data loss rather than a missing feature.
One hazard is already latent. parseSpecMarkdown splits the Steps chunk with split(/\n(?=\d+\.\s)/) and folds each piece to a single line with inline(), so a ### Phase: heading placed between steps two and three is appended to step two's Verify: text with no parse error at all. Phases are actively unsafe to author until step 1 of this plan lands, which is also why this plan does not use phase headings itself.
Alternatives weighed. A phases: frontmatter key holding step ranges (phases: 1-3 Setup, 4-6 Build) would cost almost nothing — the frontmatter parser already keeps arbitrary keys and the digest round trip could not break — but inserting a step silently shifts every later range, so the grouping rots in exactly the situation it is meant to survive. Headings are self-maintaining and were chosen for that reason. Full parent and child plan files were also considered and deliberately deferred: they additionally solve authoring size, but they force a position on progress rollup, gallery nesting, and archiving before phases have been used even once. Phase boundaries are designed here to be extractable into child files later.
The remaining decisions, recorded here as well as in this plan's own ## Decisions section because the renderer skips that section until step 5 lands and the gallery, the extractor, and plan reviewers all read the rendered HTML. A phase name renders as an <h3> inside a data-phase wrapper, so phases join the document outline for screen-reader navigation while the attribute carries extraction. Phases group the same flat step numbering, so adding them to an in-progress plan keeps every existing [x] marker valid and build still resumes at the first unmarked step rather than at a phase start. The boundary offers to compact the session rather than only stopping, and build prints the /compact command rather than running it, since compaction is a user-typed CLI built-in and not a callable tool — safe mid-plan only because durable state lives in the spec rather than the conversation. finalize-plan is in scope because build/SKILL.md line 303 requires the two skills' completion rules to stay consistent. The checkpoint contract in both skills is guarded by a prose grep in the style of test-exitplanmode-guard.sh, which catches deletion of the contract rather than proving runtime behaviour.
The renderer has two homes, and that shaped the step order. tests/plugins/test-build-plan-html.mjs line 837 asserts the three files under kit/plugins/plan-agent/scripts/ are byte-identical to their repo-root counterparts under scripts/, so every renderer edit lands at the root and is re-copied into the bundle before any test runs. The extractor is not mirrored at all — extract-plan-spec.mjs ships only at the repo root.
workflow: never is set deliberately. Four of the twelve steps — 1, 2, 3, and 5 — are ordered edits to a single file, scripts/lib/plan-spec.mjs, so subagents fanning out would conflict on it. The renderer's own heuristic counts files and directories and would otherwise license fan-out this plan cannot use.
Decisions
Choices already settled — read these before re-opening any of them.
- Phase boundaries are
### Phase:headings rather than aphases:frontmatter range list — headings survive step insertion and reordering, which range lists do not. - The Decisions section renders rather than staying markdown-only, so the ledger is visible on the plan page and in the gallery, not just to an agent reading the spec.
buildstops at a phase boundary by default and takes--continueto push through, matching the skill's existing headless contract of stopping rather than choosing.- The phase boundary offers to compact the session rather than only stopping, because continuing in the same session with reclaimed context is usually what the user wants;
buildprints the/compactcommand rather than running it, since compaction is a user-typed CLI built-in and not a callable tool. - Compaction is safe mid-plan only because durable state lives in the spec — step markers,
status:, and the Decisions ledger — so a lossy summary of the conversation costs nothing the next phase needs. - A phase name renders as an
<h3>inside adata-phasegroup wrapper — the heading puts phases in the document outline for screen-reader navigation, and the wrapper carries the attribute extraction reads. - Phases are pure grouping over the same flat step numbering, so adding them to an already in-progress plan keeps every existing
[x]marker valid andbuildstill resumes at the first unmarked step rather than at a phase start. - The checkpoint contract in
buildandfinalize-planis guarded by a prose grep in the style oftest-exitplanmode-guard.sh, accepting that this catches deletion of the contract rather than proving the runtime behaviour. finalize-planis in scope becausebuild/SKILL.mdline 303 requires the two skills' completion rules to stay consistent.- Parent and child plan files are out of scope; phase boundaries are shaped so they can be extracted into child files in a later change.
Files that change
Every file this plan touches, and what happens to each one.
scripts/lib/plan-spec.mjsmodified phase-aware step splitting, Decisions parsing, digest re-emission, DOM extractionscripts/build-plan-html.mjsmodified group step cards by phase, render the Decisions sectionscripts/lib/plan-shell.mjsmodified phase header helper, SECTION_CHROME entry for decisions, phase CSSkit/plugins/plan-agent/scripts/build-plan-html.mjsgenerated re-copied from the repo-root source- kit/plugins/plan-agent/scripts/lib/
plan-spec.mjsgenerated re-copied from the repo-root sourceplan-shell.mjsgenerated re-copied from the repo-root source
kit/plugins/plan-agent/skills/build/SKILL.mdmodified phase checkpoint loop and the --continue overridekit/plugins/plan-agent/skills/finalize-plan/SKILL.mdmodified refuse to complete a plan with unfinished phaseskit/plugins/plan-agent/skills/implementation-plan/SKILL.mdmodified note phases and Decisions in the renderer-derives list- kit/plugins/plan-agent/skills/implementation-plan/guidelines/
section-catalog.mdmodified syntax entries for both new sectionsright-sizing.mdmodified replace the dead-end split advice with the phase profile
- tests/plugins/
test-plan-phases.mjsnew objective-verification smoke testtest-build-plan-html.mjsmodified phase and Decisions render casestest-extract-plan-spec.mjsmodified phase and Decisions extraction cases
.claude-plugin/marketplace.jsonmodified plan-agent 8.5.1 to 8.6.0kit/plugins/plan-agent/CHANGELOG.mdmodified 8.6.0 entry
Steps
The step-by-step work, in order — each step says what to do, why it matters, and how to check it worked.
parseSpecMarkdown in scripts/lib/plan-spec.mjs phase-aware by splitting the Steps chunk on ^###\s+Phase: lines before the existing numbered-item split, returning sections.phases as an ordered array of { name, firstStep, lastStep } and null when no heading is present.
Verify: text with no error raised, so phase headings silently corrupt content until this lands.verify string contains a # character.buildDigest as a ### Phase: <name> line above the first step of each phase, keeping the flat numbering unchanged.
buildDigest is documented as the exact inverse of parseSpecMarkdown, so a phase it does not emit disappears whenever a spec is reconstructed from HTML.parseSpecMarkdown(buildDigest(parsed)).phases is deep-equal to parsed.phases for the two-phase fixture.extractSections by matching the data-phase attribute on each phase group wrapper, and extend the existing stripHeading helper to remove the phase <h3> as well as the section <h2>.
extract-plan-spec.mjs derives specs from legacy HTML-only plans, and an unstripped <h3> would leak the phase name into the first step's extracted action text.extractSections against the Step 4 render output and confirm the phase names come back in document order with no step action containing a phase name.phaseHeader helper to scripts/lib/plan-shell.mjs that emits <div class="phase-group" data-phase="…"><h3>…</h3>, and group the step cards under those wrappers in build-plan-html.mjs, leaving every .step-card in flat document order inside them.
plan-shell.mjs line 1324 counts .step-card elements with querySelectorAll, so nesting that breaks the selector would silently zero the progress bar, and an <h3> keeps the outline at h1 to h2 to h3 with no skipped level for screen-reader navigation.## Decisions section end-to-end — parse it as a bullet list in parseSpecMarkdown, re-emit it in buildDigest, extract it in extractSections, add a decisions key to SECTION_CHROME, and render it with a sectionCard call placed after Context.
## Completion Report records gaps rather than decisions, so reusing it would conflate the two.scripts/build-plan-html.mjs, scripts/lib/plan-spec.mjs, and scripts/lib/plan-shell.mjs — over their counterparts under kit/plugins/plan-agent/scripts/, after steps 1 through 5 are complete and before any test run.
tests/plugins/test-build-plan-html.mjs line 837 asserts the bundled copies are byte-identical to the repo-root sources, so editing only one side fails an existing test and ships a phase-blind renderer to anyone who installs the plugin.diff scripts/build-plan-html.mjs kit/plugins/plan-agent/scripts/build-plan-html.mjs and the same for both lib/ files each report no differences.skills/build/SKILL.md as a phase checkpoint loop — implement one phase, run its verify gate, append the decisions made to ## Decisions, then reach the boundary offer — with a --continue flag that pushes straight through and no behaviour change for a spec that declares no phases.
--continue and the resume line, confirm the unphased path in Step 2 still reads as a single uninterrupted walk, and confirm a phased spec carrying partial [x] markers is documented as resuming at the first unmarked step rather than at a phase start.AskUserQuestion presenting Compact and continue (recommended), Stop here — resume later, and Continue without compacting, where the compact branch prints the /compact command with focus instructions naming the spec path and the phase just finished, then stops so the user can run it.
/compact is a user-typed CLI built-in rather than a callable tool, so the skill can only recommend it, and compaction is safe mid-plan precisely because the durable state — step markers, status, and the Decisions ledger — already lives in the spec rather than in the conversation./compact line appear in the skill, and that the headless path falls back to reporting the options rather than choosing one.skills/finalize-plan/SKILL.md about phases so it refuses to set status: completed while any phase still holds unmarked steps, recording each unfinished phase as a ## Completion Report bullet instead.
build/SKILL.md line 303 states that finalize-plan applies the same completion rules and instructs that the two be kept consistent when either changes, so a phase-blind finalize-plan would close out a plan that stopped at its first checkpoint.in-progress with the unfinished phase named in the report.guidelines/section-catalog.md with their exact syntax, and replace the "probably two plans — split it" sentence in guidelines/right-sizing.md with a phase profile naming when a plan earns phases, then add both to the renderer-derives list in implementation-plan/SKILL.md.
### Phase: example.tests/plugins/test-build-plan-html.mjs and tests/plugins/test-extract-plan-spec.mjs with phase and Decisions cases, and add tests/plugins/test-plan-phases.mjs asserting the render-extract-re-render cycle preserves both, that an unphased spec renders unchanged, and that build/SKILL.md and finalize-plan/SKILL.md still carry their phase contract strings.
test-exitplanmode-guard.sh, which guards a required skill string the same way..claude-plugin/marketplace.json and add the matching kit/plugins/plan-agent/CHANGELOG.md entry describing both new sections, the build checkpoint loop, and the finalize-plan gate.
kit/plugins/ to ship a version exceeding the value on main, and a new spec section is a minor bump.BASE_REF=main node scripts/check-plugin-versions.mjs exits 0.Tests
The tests that prove the change does what it promises.
Definition of done
The plan counts as done when every statement below is true — check each one off as you verify it.
Final check
One last pass to confirm the whole change works end to end.
Author a scratch spec with three steps split across two ### Phase: headings and a ## Decisions section carrying two bullets. Render it with node scripts/build-plan-html.mjs <spec>.md -o <spec>.html and confirm exit 0, two phase headers each preceding their own steps, a Decisions card, and a progress bar whose total reads 3. Run node scripts/extract-plan-spec.mjs <spec>.html — the extractor ships only at the repo root, not in the plugin bundle — and confirm the printed spec carries both phase headings and both Decisions bullets; re-render that extracted spec and diff the two HTML files to confirm the cycle is stable.
Then exercise the checkpoint loop for real: run /plan-agent:build <spec>.md and confirm it implements only the first phase, appends a Decisions bullet, sets status: in-progress, and stops with a resume command rather than continuing into phase two. Re-run the same command and confirm it resumes at the first unmarked step of phase two rather than redoing phase one. With phase two still unmarked, run /plan-agent:finalize-plan <spec>.md and confirm it refuses to set status: completed and names the unfinished phase in the Completion Report. Separately, add phase headings to a copy of an already in-progress committed plan whose early steps carry [x] and confirm those markers still render as completed cards.
Finally run the existing suite — node tests/plugins/test-build-plan-html.mjs, node tests/plugins/test-extract-plan-spec.mjs, and node tests/plugins/test-backfill-digest.mjs — plus BASE_REF=main node scripts/check-plugin-versions.mjs, and re-render one committed plan from docs/plans/ to confirm an unphased plan is untouched.
Wrapping up
Three gates that must all pass before this plan is marked completed.
Completion Report
- Version shipped as 8.6.0, not 7.1.0
- plan-agent was already at 8.5.1 when this landed; 7.1.0 would fail the version guard
- Phase and Decisions styling is element-local, not new shared CSS
- the plan stylesheet is emitted verbatim into every plan, so a new rule breaks the byte-identical-unphased criterion
- The checkpoint loop lives in build/references/phase-checkpoints.md, not inline in SKILL.md
- that core was at 598 of the 600-word ceiling test-progressive-disclosure.sh enforces
- stripHeading was left stripping only h2
- step cards are sliced at their own div boundaries so a phase h3 can never reach a step's action text, and stripping h3 would drop legitimate headings from legacy Context sections