Summary
Post-merge automation: when a Story's MR merges to the kahuna branch, locate the Phases section in the parent Plan issue and check off the corresponding story's DoD checkbox.
Context
Filed as explicitly-deferred work from Dev Spec docs/phase-epic-taxonomy-devspec.md §5.1 (Plan Issue Canonical Template). The Plan issue template's Phases section has a per-story DoD checklist with lines like - [ ] #N merged — <description>. The template ships as part of cc-workflow#499.
Template clarification: the shipped Plan template uses ### Phase N — <Name> H3 subsections with **DoD:** checklists. There are no <!-- BEGIN: phases-checklist --> HTML comment markers in the shipped template; the draft reference to those markers is stale. Checkbox parsing targets the ## Phases H2 section, identifies ### Phase subsections within it, and matches lines by story number.
The value: Pair can see at-a-glance Plan progress without opening phases-waves.json or the wave-status panel.
The deferral reason: not load-bearing for the taxonomy rework itself. Plan checkboxes can be toggled by hand until automation ships.
Implementation Steps
Chosen shape: sdlc-server MCP primitive (plan_mark_story_done). Reasons: (a) the mutation logic lives where other Plan/Phase mutations live; (b) other skills (future /plan-status) get a reusable primitive; (c) Prime(post-flight) already calls MCP tools, so one more call is cleaner than shelling to gh. See ## Implementation Sketch below for the rejected alternative.
Step 1 — Add plan_mark_story_done handler in mcp-server-sdlc
Create handlers/plan_mark_story_done.ts in mcp-server-sdlc. Input schema:
z.object({
plan_ref: z.string(), // bare number "581" or "owner/repo#581"
story_id: z.number().int().positive(),
repo: z.string().optional(), // override if Plan is in a different repo
})
Handler logic (platform: GitHub first; GitLab via getAdapter):
- Fetch Plan body:
getAdapter({ repo }).fetchIssue({ number: planNumber, repo }) — reuse existing fetch-issue-{github,gitlab} adapters. If ok: false, return { ok: false, error, warn_only: true }.
- Locate
## Phases section: call parseSections(body) from lib/spec_parser.ts. Key is phases. If section is absent or empty, return { ok: false, warn_only: true, error: "Plan body has no ## Phases section" }.
- Find and flip the checkbox line: within the
phases section content, search line by line for:
/^- \[([ x])\] #<story_id>\b/
- If the captured group is already
x → idempotent return { ok: true, action: "already_ticked" }.
- If the captured group is
→ replace with [x], reconstruct the full body (pre-Phases + Phases-section with the one line flipped + post-Phases), write back via gh issue edit <plan_number> --body <tempfile> (GitHub) or glab issue update <plan_number> --description <tempfile> (GitLab).
- If no matching line found → return
{ ok: false, warn_only: true, error: "no checkbox for story #<story_id> found in ## Phases section" }.
- Write-back strategy: write the modified full body to a temp file (
/tmp/plan-tick-<planNumber>.md), then execSync("gh issue edit <planNumber> --body-file /tmp/plan-tick-<planNumber>.md --repo <owner/repo>"). Delete the temp file after. This avoids shell-quoting issues with large bodies.
- Return:
{ ok: true, action: "ticked", plan_ref, story_id, line_replaced: "<old line>" }.
File path: mcp-server-sdlc/handlers/plan_mark_story_done.ts
Step 2 — Register the handler
In mcp-server-sdlc/handlers/_registry.ts, add plan_mark_story_done to the handler import list using the same auto-discovery pattern (import.meta.glob) already present. No further wiring needed if the registry uses glob auto-discovery.
Step 3 — Add corresponding adapter methods (if needed)
If the write-back path differs significantly between GitHub and GitLab, add:
mcp-server-sdlc/lib/adapters/plan-mark-story-done-github.ts
mcp-server-sdlc/lib/adapters/plan-mark-story-done-gitlab.ts
For v1, the execSync("gh issue edit ...") path is acceptable as a shell-out (consistent with existing handlers like wave_close_issue that shell to wave-status). Defer the full adapter split unless GitLab support is needed immediately.
Step 4 — Update /nextwave Prime(post-flight) prompt
In skills/nextwave/SKILL.md, update the Prime(post-flight) Step 5 prompt block:
Current Step 5:
- Merge all flight PRs via
pr_merge. On merge, call wave_close_issue(X) and wave_record_mr(issue_number=X, mr_ref=<url>) per issue. Call wave_flight_done(M) after all merges land.
New Step 5:
- Merge all flight PRs via
pr_merge. On merge, per issue X:
a. wave_close_issue(X)
b. wave_record_mr(issue_number=X, mr_ref=<url>)
c. Read **Plan:** #M from the story issue's ## Metadata section (via spec_get(issue_ref=X)). If M is present and not N/A, call plan_mark_story_done({plan_ref: M, story_id: X}). A warn_only: true failure is logged to the merge report but does NOT abort the merge sequence.
Call wave_flight_done(M) after all merges land.
File: skills/nextwave/SKILL.md — find the exact Step 5 block and apply the replacement above.
Step 5 — Build and deploy
cd mcp-server-sdlc
bun run build # compiles dist/
# Verify handler appears in registry output:
bun run dist/index.js list 2>/dev/null | grep plan_mark_story_done
Install updated binary per the project's install procedure (typically ./install.sh or bun link). Restart the Claude Code session to reload the MCP binary.
Test Procedures
Unit tests — file: mcp-server-sdlc/tests/plan_mark_story_done.test.ts
| Test Name |
Purpose |
test_tick_unchecked_story |
Given a Plan body with - [ ] #42 merged — description in ## Phases, calling tickStory(body, 42) returns the body with [x] on that line and no other lines changed. |
test_idempotent_already_ticked |
Given - [x] #42 merged — description, tickStory(body, 42) returns { action: "already_ticked" } and does not modify the body. |
test_no_phases_section |
Given a Plan body without ## Phases, the handler returns { ok: false, warn_only: true } and does not throw. |
test_story_not_in_phases |
Given a Plan body with a Phases section that has no line for #99, the handler returns { ok: false, warn_only: true } — does not throw, does not corrupt the body. |
test_multi_phase_correct_line_flipped |
Plan body has Phase 1 (#10, #11) and Phase 2 (#12, #13). Calling tickStory(body, 12) flips only #12; the other three lines remain [ ]. |
test_write_back_called_once |
Stub the gh issue edit shell-out. Verify it is called exactly once per invocation, with the correct --body-file and --repo arguments. |
test_write_back_not_called_when_idempotent |
Already-ticked case: verify the stub is never called. |
Integration verification (manual, run once before merge):
- File a scratch Plan issue in a test repo with a Phases section containing three story DoD checkboxes (
- [ ] #X, - [ ] #Y, - [ ] #Z).
- Call
plan_mark_story_done({ plan_ref: "<issue_number>", story_id: X }) via the MCP tool (through Claude Code's tool panel).
gh issue view <issue_number> --json body --jq .body — confirm #X line shows [x], #Y and #Z still show [ ].
- Repeat for
#Y, then #Z. Confirm each flip is isolated and the prior ticks are preserved.
- Re-run for
#X (already ticked) — confirm the tool returns already_ticked and the body is unchanged.
- Run a minimal kahuna wave end-to-end (single story, existing Plan with checklist): confirm the Plan DoD checkbox ticks automatically after the story MR merges to kahuna.
Acceptance Criteria
Not In Scope
- Two-way sync (editing the Plan issue checklist by hand → updating wave state). One-way only: wave state → Plan issue.
- Retroactive fill of checkboxes for already-merged stories (e.g. during migration of in-flight legacy Plans per cc-workflow#499 Phase 4). If useful, that's a separate story.
Implementation Sketch
Two viable shapes (evaluated at filing time):
- sdlc-server primitive:
plan_mark_story_done({plan_id, story_id}) — locates the phases section by H2 heading, finds the line matching #<story_id>, flips [ ] → [x]. Invoked by /nextwave after pr_merge returns success for a story. Selected — see ## Implementation Steps.
- Skill-body logic in /nextwave: inline the mutation via
gh issue edit --body or glab issue update --description. Rejected: logic duplication, harder to test, no reuse path.
Dependencies
- Depends on cc-workflow#499 Phase 3 (Plan template ships;
### Phase N — <Name> H3 structure is canonical).
Metadata
Wave: 2
Plan: #581
Wave Master: N/A
— rules-lawyer 📜 (cc-workflow)
Summary
Post-merge automation: when a Story's MR merges to the kahuna branch, locate the
Phasessection in the parent Plan issue and check off the corresponding story's DoD checkbox.Context
Filed as explicitly-deferred work from Dev Spec
docs/phase-epic-taxonomy-devspec.md§5.1 (Plan Issue Canonical Template). The Plan issue template'sPhasessection has a per-story DoD checklist with lines like- [ ] #N merged — <description>. The template ships as part of cc-workflow#499.Template clarification: the shipped Plan template uses
### Phase N — <Name>H3 subsections with**DoD:**checklists. There are no<!-- BEGIN: phases-checklist -->HTML comment markers in the shipped template; the draft reference to those markers is stale. Checkbox parsing targets the## PhasesH2 section, identifies### Phasesubsections within it, and matches lines by story number.The value: Pair can see at-a-glance Plan progress without opening
phases-waves.jsonor the wave-status panel.The deferral reason: not load-bearing for the taxonomy rework itself. Plan checkboxes can be toggled by hand until automation ships.
Implementation Steps
Chosen shape: sdlc-server MCP primitive (
plan_mark_story_done). Reasons: (a) the mutation logic lives where other Plan/Phase mutations live; (b) other skills (future/plan-status) get a reusable primitive; (c) Prime(post-flight) already calls MCP tools, so one more call is cleaner than shelling togh. See## Implementation Sketchbelow for the rejected alternative.Step 1 — Add
plan_mark_story_donehandler inmcp-server-sdlcCreate
handlers/plan_mark_story_done.tsinmcp-server-sdlc. Input schema:Handler logic (platform: GitHub first; GitLab via
getAdapter):getAdapter({ repo }).fetchIssue({ number: planNumber, repo })— reuse existingfetch-issue-{github,gitlab}adapters. Ifok: false, return{ ok: false, error, warn_only: true }.## Phasessection: callparseSections(body)fromlib/spec_parser.ts. Key isphases. If section is absent or empty, return{ ok: false, warn_only: true, error: "Plan body has no ## Phases section" }.phasessection content, search line by line for:x→ idempotent return{ ok: true, action: "already_ticked" }.→ replace with[x], reconstruct the full body (pre-Phases + Phases-section with the one line flipped + post-Phases), write back viagh issue edit <plan_number> --body <tempfile>(GitHub) orglab issue update <plan_number> --description <tempfile>(GitLab).{ ok: false, warn_only: true, error: "no checkbox for story #<story_id> found in ## Phases section" }./tmp/plan-tick-<planNumber>.md), thenexecSync("gh issue edit <planNumber> --body-file /tmp/plan-tick-<planNumber>.md --repo <owner/repo>"). Delete the temp file after. This avoids shell-quoting issues with large bodies.{ ok: true, action: "ticked", plan_ref, story_id, line_replaced: "<old line>" }.File path:
mcp-server-sdlc/handlers/plan_mark_story_done.tsStep 2 — Register the handler
In
mcp-server-sdlc/handlers/_registry.ts, addplan_mark_story_doneto the handler import list using the same auto-discovery pattern (import.meta.glob) already present. No further wiring needed if the registry uses glob auto-discovery.Step 3 — Add corresponding adapter methods (if needed)
If the write-back path differs significantly between GitHub and GitLab, add:
mcp-server-sdlc/lib/adapters/plan-mark-story-done-github.tsmcp-server-sdlc/lib/adapters/plan-mark-story-done-gitlab.tsFor v1, the
execSync("gh issue edit ...")path is acceptable as a shell-out (consistent with existing handlers likewave_close_issuethat shell towave-status). Defer the full adapter split unless GitLab support is needed immediately.Step 4 — Update
/nextwavePrime(post-flight) promptIn
skills/nextwave/SKILL.md, update the Prime(post-flight) Step 5 prompt block:Current Step 5:
New Step 5:
File:
skills/nextwave/SKILL.md— find the exact Step 5 block and apply the replacement above.Step 5 — Build and deploy
Install updated binary per the project's install procedure (typically
./install.shorbun link). Restart the Claude Code session to reload the MCP binary.Test Procedures
Unit tests — file:
mcp-server-sdlc/tests/plan_mark_story_done.test.tstest_tick_unchecked_story- [ ] #42 merged — descriptionin## Phases, callingtickStory(body, 42)returns the body with[x]on that line and no other lines changed.test_idempotent_already_ticked- [x] #42 merged — description,tickStory(body, 42)returns{ action: "already_ticked" }and does not modify the body.test_no_phases_section## Phases, the handler returns{ ok: false, warn_only: true }and does not throw.test_story_not_in_phases#99, the handler returns{ ok: false, warn_only: true }— does not throw, does not corrupt the body.test_multi_phase_correct_line_flipped#10,#11) and Phase 2 (#12,#13). CallingtickStory(body, 12)flips only#12; the other three lines remain[ ].test_write_back_called_oncegh issue editshell-out. Verify it is called exactly once per invocation, with the correct--body-fileand--repoarguments.test_write_back_not_called_when_idempotentIntegration verification (manual, run once before merge):
- [ ] #X,- [ ] #Y,- [ ] #Z).plan_mark_story_done({ plan_ref: "<issue_number>", story_id: X })via the MCP tool (through Claude Code's tool panel).gh issue view <issue_number> --json body --jq .body— confirm#Xline shows[x],#Yand#Zstill show[ ].#Y, then#Z. Confirm each flip is isolated and the prior ticks are preserved.#X(already ticked) — confirm the tool returnsalready_tickedand the body is unchanged.Acceptance Criteria
phases-checklistsection/nextwaveinvokes it after a Story's MR merges to kahunaNot In Scope
Implementation Sketch
Two viable shapes (evaluated at filing time):
plan_mark_story_done({plan_id, story_id})— locates the phases section by H2 heading, finds the line matching#<story_id>, flips[ ]→[x]. Invoked by/nextwaveafterpr_mergereturns success for a story. Selected — see## Implementation Steps.gh issue edit --bodyorglab issue update --description. Rejected: logic duplication, harder to test, no reuse path.Dependencies
### Phase N — <Name>H3 structure is canonical).Metadata
Wave: 2
Plan: #581
Wave Master: N/A
— rules-lawyer 📜 (cc-workflow)