Tandem: close-phase (PM hat)
Operate as PM hat. close-phase is the phase-level analogue of close-out-story: where
close-out-story is the per-story Definition-of-Done gate, this is its per-phase counterpart —
invoked after an execution-strategist chat / execute-batch run finishes, to wrap a whole
phase up safely and integrate it.
Opener counterpart:
start-phaseis the opener this skill closes against.start-phaseopens a phase — it cuts the phase branchphase/<phase-id>offmainper the shared phase-branch convention (recorded in40-Decisions/, ADR-0045);close-phasecloses that same phase — it merges that branch back tomain(Steps 6–7). Both skills obey the one branch convention, so the branch the opener creates is exactly the one the closer merges.
It runs in a fixed order, each step gated on the one before it:
- Phase-scope detection — resolve the set of stories in an explicit target.
- Done-gate — every phase story must be
done, or abort and list the gaps. - Retrospective — what shipped, what went well / what didn't, the phase metrics.
- Follow-up capture — file BACKLOG items + an ADR backstop for anything surfaced.
- Board update — write the phase report, update
MONITOR.md, regenerate the dashboard. - Gated merge to
main— integrate only when the merge gate passes; never force-merge.
Dry-run-until-gated. Steps 3–5 only read and append artefacts; the merge step is hard-gated and never force-merges (see "Integration"). If the done-gate fails, the skill stops and reports — it never partially wraps up an incomplete phase.
Step 1 — Phase-scope detection (EXPLICIT target — never guess)
Take an explicit phase / chat / epic target from the user — never infer which phase to close from ambient state. Accepted targets:
- a strategist phase or chat id (e.g.
CHAT-02) from anEXECUTION-STRATEGY-*.jsonsidecar — resolve to thestories[]listed under that chat / phase; - an
EPIC-NN(or a singleFEAT-NN.M) — resolve the set of stories belonging to that epic / feature by globbing32-Stories/EPIC-NN/....
Resolve the set of stories in the target phase before doing anything else, then echo the resolved list (id + status) back to the user so the scope is explicit and reviewable. If the target is ambiguous, missing, or resolves to zero stories, stop and ask for a concrete phase / chat / epic — do not guess which stories are in scope.
Step 2 — Done-gate (every phase story must be done)
Verify every resolved phase story is status: done. This is a hard gate:
- If all phase stories are
done→ proceed to the retrospective (Step 3). - If any are not
done→ abort and list the not-done stories (eachid+ its currentstatus), so the operator knows exactly which stories still block the close. Do not compile a retro, capture follow-ups, update the board, or merge for an incomplete phase.
This mirrors close-out-story's gate-then-act discipline at the phase level: the gate is
non-negotiable, and the abort-and-list path is the load-bearing behaviour — a half-closed phase
is worse than an un-closed one.
Step 3 — Compile the phase retrospective
Once the done-gate passes, compile a phase retrospective — derived from the phase's own
artefacts (its stories, their paired testplans, and the 34-Bugs/ + 40-Decisions/ filed
during the phase), never invented from memory. Three derived parts, plus one recalled part
(Step 3a):
- What shipped — list the phase's
donestories and the PASS results of their paired testplans (the TCs that verify each story). One line per story: what it delivered. - What went well / what didn't — a short, honest reflection: what went well this phase, and what didn't go well / what to improve next phase. Keep it specific to this phase's work, not generic platitudes.
- Metrics — the phase's hard numbers, read straight from the artefacts: bugs filed
(
34-Bugs/), ADRs created (40-Decisions/), and the execution lanes used (the serial / parallel lanes from theexecution-strategiststrategy this phase ran under).
Because every part is sourced from the phase artefacts — the stories' and testplans' statuses
and results, the bugs and ADRs filed in the phase — the retro is reproducible and auditable, not a
subjective recollection. This is phase-cadence, distinct from the time-cadence retros
(weekly-monitor / monthly-retro): it closes one phase, not a calendar window.
Step 3a — Recall what the run actually recorded (augment, never replace)
The three parts above reconstruct the phase from the paper trail it left behind. The retro ledger holds what the run observed at the time — the friction, the kit signals, the estimate-vs-actual calls each close-out recorded as it happened. Recall it rather than re-deriving it:
node _00-Project-Management/93-Scripts/retro-report.js --phase <phase-id>
Paste what it returns, verbatim, as a ## Recalled from the retro ledger section of the phase
report. It is generated by 93-Scripts/retro-report.js, the one aggregator the phase retro,
monthly-retro and reflect all share — so none of the three re-implements the join.
AUGMENT, NEVER REPLACE. The recalled section sits alongside the three derived parts; it
does not stand in for any of them. This is easy to state and easy to violate under editing
pressure, because deleting a derived section makes the report shorter and the recalled section
looks like it covers the same ground. It does not: the derived parts say what the artefacts
record, the recalled part says what the run noticed. A phase report missing either half is
incomplete, and 93-Scripts/tests/close-phase-retro.test.js :: augments-not-replaces fails on
exactly that.
A phase with no ledger records is normal, not a warning. The output says
No retro records for this phase. and the rest of the report is unaffected. Paste that sentence
as returned — do not leave the section blank (a blank section reads as an omission, or worse as a
measured zero), and do not fall back to another phase's records. Every phase closed before
EPIC-26 has no records at all, so this is the common case for a while and must read as ordinary
output.
A malformed or unreadable ledger degrades the same way. retro-report.js skips a bad line,
counts it, and reports the count in the trailing provenance comment. Do not add a second error
path here — the tolerance already lives in the aggregator, and duplicating it is how two
components start disagreeing about what "the ledger is broken" means.
Step 4 — Capture follow-ups (BACKLOG + ADR backstop)
Before touching the board, sweep the phase for loose ends and capture them so nothing surfaced during the work is lost:
- Follow-up capture (BACKLOG) — for any tech-debt, deferred idea, or follow-up
the phase surfaced, file a BACKLOG item (
11-Backlog/BACKLOG-NNNN-<slug>.md, from91-Templates/BACKLOG.template.md). This mirrorsreflect/refine-backlog: a follow-up that isn't filed is a follow-up that's lost. - ADR backstop — verify an ADR exists for every non-obvious decision the phase made (the
ADR-on-the-spot rule). If a decision was made during the phase but no ADR was filed, file
the missing ADR now (
40-Decisions/ADR-NNNN-<slug>.md) as a backstop, so the phase's decisions are all on record before the phase closes.
Step 5 — Update the board
Write the phase up and refresh the live board:
- Phase report — write the retrospective (Step 3) and its recalled section (Step 3a) plus
the captured follow-ups (Step 4) to the phase-report home,
41-Reports/phases/PHASE-<phase-id>-<YYYY-MM-DD>.md(the home is fixed by a recorded ADR — see "Recorded decisions" below). Both halves go in the same document: the derived parts first, the recalled section after them, each under its own heading so a reader can tell which is which. - MONITOR — update
42-Monitor/MONITOR.md: a phase summary plus a one-line revision-history entry dated today. - Dashboard — regenerate it with
npm run pm:dashso42-Monitor/DASHBOARD.htmlreflects the closed phase. - Retro ledger — the phase-level line is written after integration, not here. It carries
--merge, which Steps 6/6a/7 have not decided yet at this point. See "Retro ledger line" after Step 7.
Recorded decisions
The skill's name + phase-granularity and the phase-report home are settled once in an ADR
(40-Decisions/), not re-litigated each phase. The home choice is 41-Reports/phases/PHASE-* (the
phase report is a generated execution artefact alongside EXECUTION-STRATEGY-*), rather than
14-Retros/ (reserved for the time-cadence weekly/monthly retros).
Step 6 — Integration: merge the phase to main (gated)
The integration step runs after the wrap-up — the retro, follow-up capture, and board update
(Steps 3–5) all happen first; only then does the phase merge to main.
Step 6a — Already-merged detection (retro-only path)
Before evaluating the merge gate, detect whether the phase branch has already been merged
to main — the real order of events sometimes runs ahead of the board (this kit hit exactly this:
a phase branch was fast-forwarded into main in git while the board still said "merge pending").
Probe true ancestry, not a clean working tree:
git merge-base --is-ancestor phase/<phase-id> main && echo "ALREADY-MERGED" || echo "NOT-MERGED"
- Already-merged (
git merge-base --is-ancestor phase/<phase-id> mainexits 0 — the phase branch tip is reachable frommain): route to the retro-only path. Steps 2–5 (done-gate, retrospective, follow-up capture, board + MONITOR + dashboard) still run in full; only the merge itself (Step 6 gate + Step 7 mechanism) is skipped and marked already-integrated in the phase report and the MONITOR revision-history line (e.g. "merge: already-integrated — phase branch is an ancestor ofmain"). Nothing is force-merged or re-merged; the retro + board update are the whole job. - Not-merged (
--is-ancestorexits non-zero): the normal path — fall through to the merge gate (below) and Step 7.
Gate on true ancestry, never on a clean tree. --is-ancestor is true only when the phase
branch tip is fully reachable from main; a partially-merged branch (some commits in main,
tip not yet) returns non-zero and therefore takes the normal merge path — a partial merge must
not be mistaken for a complete one. A clean working tree alone says nothing about whether the
phase was integrated, so it is not the signal used here.
On the not-merged (normal) path, before anything reaches main, a hard merge gate must
pass — all four items:
- All phase stories
done— re-confirm the Step-2 gate still holds for every story in the phase. npm run pm:lintgreen — the PM artefacts validate.- Build / tests green — the project's build and tests pass per
PROJECT-CONTEXT.md's quality commands (scoped to the area the phase changed). - Clean working tree —
git status --porcelainis empty (no uncommitted changes).
If any gate item is unmet, the skill refuses to merge and reports which item failed — it does not proceed. A blocked merge names the failing gate item so the operator knows exactly what to fix; it never merges a phase that hasn't cleared all four.
Dashboard note (ADR-0082). In a consumer repo the generated DASHBOARD.html is
gitignored by pm:install policy, so this merge can never fail unlinking it. The kit repo
itself still tracks its live dashboard (deliberate exception) — if a merge conflict lands on
it there, regenerate with npm run pm:dash rather than hand-resolving the diff.
Step 7 — Merge mechanism: PR-default vs gated direct
Once the merge gate (Step 6) passes, integrate via one of two mechanisms — never a force-merge:
- Open a PR — the review-friendly default. Open a pull request from the phase branch
(
phase/<phase-id>— the branchstart-phasecut offmainper the shared convention, ADR-0045) tomainand surface the PR command / link for the operator to review and merge. - Gated direct merge — for a solo / no-review workflow, a direct merge to
mainis allowed, but only once the Step-6 gate has passed.
In both cases the skill surfaces the PR / merge command or link rather than force-merging — it
never force-merges and never bypasses the gate. No gh CLI is assumed: surface a
copy-pasteable command or link; don't hard-call a host API. The PR-vs-gated-direct default and the
gate composition are recorded in an ADR (40-Decisions/) so the integration path is settled once.
Step 7a — Log the approval (10-Inbox/APPROVALS.md)
A merge to main is a manual, gated approval — when the operator confirms it (the gated
direct merge, or merging the surfaced PR), append a one-line approval entry to
10-Inbox/APPROVALS.md so the sign-off survives beyond the chat transcript (audit + handover).
The same applies on the already-integrated retro-only path (Step 6a): record that the close was
confirmed even though no fresh merge ran. Append (newest at the bottom):
- <ISO 8601 timestamp> — <what was approved> — by: <who> — gated: <artefact / command>
e.g. - 2026-06-06T15:55:00+01:00 — merge phase/p1-outcome-contract to main — by: operator — gated: close-phase Step 7 (gated direct merge).
- Use the system clock for the timestamp (ISO 8601 with offset), not the chat-stated date.
- It's a one-line append, not a ceremony.
10-Inbox/is not a linted artefact folder, so the entry must keep the file valid plain markdown. - If
10-Inbox/APPROVALS.mddoes not exist yet, create it (header + the convention documented at the top of that file) and append the first entry. See the file for the full convention.
Retro ledger line (after the integration decision)
Append the phase-level line now — not at Step 5 — because --merge is only knowable once
Steps 6/6a/7 have settled how the phase actually integrated. Always exits 0 and never blocks the
close; skip silently if the script is absent.
node _00-Project-Management/93-Scripts/retro-capture.js --level phase --id <EPIC-NN> --phase <EPIC-NN> [--chats <n>] [--stories <n>] [--merge <pr|direct|already-integrated>] [--friction "…"] [--artefact-gap <ID>] [--kit-signal "…"]
--idtakes the epic formEPIC-NN, or the feature formFEAT-NN.Mwhen the phase is feature-scoped — never the chat id, even when Step 1 resolved the phase target from a chat. A phase line sharing an id with a chat line would collide on the documented join key (ADR-0109), where story and chat ids are expected to be 1:1 againstusage-log.jsonl.--phaserepeats that same id, so the phase's own line is picked up by aretro-report.js --phase <id>rollup alongside the item and chat lines it summarises.--mergerecords how the phase actually integrated, including thealready-integratedretro-only path from Step 6a — so the rollup can tell a real merge from a no-op close. Use the enum values verbatim; any other word refuses the whole record.--chats— chats the strategist planned for this phase. Omit when the phase was not strategist-planned and therefore has no chats to count; do not substitute 1.--stories— items that reacheddonein this phase, i.e. the Step 2 done-gate's count.- Write this line whether or not the ledger holds any lines for the phase. Step 3's artefact-derived retrospective never depends on ledger completeness (PRD §A.6); the ledger augments it, and a phase with zero lines still closes with its retrospective intact.
Non-negotiable rules (from CLAUDE.md)
- Operates as PM hat; the phase-level analogue of
close-out-story. - The done-gate (Step 2) and the merge-gate (Step 6) are hard gates — abort and report on any unmet item; never partially close a phase and never force-merge.
- ADR-on-the-spot for any non-obvious phase decision; auto-raise a BUG for any defect.
- Status / timestamp flips, the MONITOR update, and the dashboard regen follow the kit's
"when you change a status" rule (atomic edit + same-response board update +
npm run pm:dash).
End-of-session summary (always emit)
- Phase target + resolved stories (done / not-done).
- Done-gate: PASS / aborted (+ the not-done gaps).
- Retro written (+ path); follow-ups captured (BACKLOG + ADR list); board updated (MONITOR + dashboard).
- Merge gate: PASS / blocked (+ the failing item); mechanism: PR link or gated direct merge.
- Next step: the surfaced PR / merge command, or the gate gap to fix.
Reset conversation Mode to Neutral
A closed phase means the active frame's work is done. Always reset:
node _00-Project-Management/93-Scripts/mode.js set neutral --by auto-neutral --session <session_id>
Announce it: "Phase closed — Tandem mode reset to Neutral." This is also how dual
returns to Neutral. Use the session ID from the session context.