From 2cec5a10145eeac8c9c519b5b80a7b60dbf432ff Mon Sep 17 00:00:00 2001 From: Jeremy Date: Tue, 14 Apr 2026 06:11:53 -0500 Subject: [PATCH] test(gsd): cover step-mode completion message helper Extracts the step-complete notification text into buildStepCompleteMessage and STEP_COMPLETE_FALLBACK_MESSAGE so the copy can be unit-tested directly (milestone complete, mid-flight with next unit, unknown phase, and deriveState-failure fallback). Resolves require-tests CI failure on PR #4173. --- .../extensions/gsd/auto-post-unit.ts | 31 +++++------ .../tests/auto-post-unit-step-message.test.ts | 53 +++++++++++++++++++ 2 files changed, 67 insertions(+), 17 deletions(-) create mode 100644 src/resources/extensions/gsd/tests/auto-post-unit-step-message.test.ts diff --git a/src/resources/extensions/gsd/auto-post-unit.ts b/src/resources/extensions/gsd/auto-post-unit.ts index c3a29264f..cb146679d 100644 --- a/src/resources/extensions/gsd/auto-post-unit.ts +++ b/src/resources/extensions/gsd/auto-post-unit.ts @@ -234,6 +234,18 @@ export function detectRogueFileWrites( return rogues; } +export const STEP_COMPLETE_FALLBACK_MESSAGE = + "Step complete. Run /clear, then /gsd to continue (or /gsd auto to run continuously)."; + +export function buildStepCompleteMessage(nextState: import("./types.js").GSDState): string { + if (nextState.phase === "complete") { + return "Step complete — milestone finished. Run /gsd status to review, or start the next milestone."; + } + const next = describeNextUnit(nextState); + return `Step complete. Next: ${next.label}\n` + + `Run /clear, then /gsd to continue (or /gsd auto to run continuously).`; +} + export interface PreVerificationOpts { skipSettleDelay?: boolean; skipWorktreeSync?: boolean; @@ -1032,25 +1044,10 @@ export async function postUnitPostVerification(pctx: PostUnitContext): Promise<" if (s.stepMode) { try { const nextState = await deriveState(s.basePath); - if (nextState.phase === "complete") { - ctx.ui.notify( - "Step complete — milestone finished. Run /gsd status to review, or start the next milestone.", - "info", - ); - } else { - const next = describeNextUnit(nextState); - ctx.ui.notify( - `Step complete. Next: ${next.label}\n` - + `Run /clear, then /gsd to continue (or /gsd auto to run continuously).`, - "info", - ); - } + ctx.ui.notify(buildStepCompleteMessage(nextState), "info"); } catch (e) { debugLog("postUnit", { phase: "step-wizard-notify", error: String(e) }); - ctx.ui.notify( - "Step complete. Run /clear, then /gsd to continue (or /gsd auto to run continuously).", - "info", - ); + ctx.ui.notify(STEP_COMPLETE_FALLBACK_MESSAGE, "info"); } return "step-wizard"; } diff --git a/src/resources/extensions/gsd/tests/auto-post-unit-step-message.test.ts b/src/resources/extensions/gsd/tests/auto-post-unit-step-message.test.ts new file mode 100644 index 000000000..b1b057570 --- /dev/null +++ b/src/resources/extensions/gsd/tests/auto-post-unit-step-message.test.ts @@ -0,0 +1,53 @@ +// GSD-2 — Tests for step-mode completion messages in auto-post-unit + +import test from "node:test"; +import assert from "node:assert/strict"; + +import { buildStepCompleteMessage, STEP_COMPLETE_FALLBACK_MESSAGE } from "../auto-post-unit.ts"; +import type { GSDState } from "../types.ts"; + +function makeState(overrides: Partial): GSDState { + return { + activeMilestone: null, + activeSlice: null, + activeTask: null, + phase: "executing", + recentDecisions: [], + blockers: [], + nextAction: "", + registry: [], + ...overrides, + }; +} + +test("buildStepCompleteMessage: milestone complete surfaces review guidance", () => { + const msg = buildStepCompleteMessage(makeState({ phase: "complete" })); + assert.match(msg, /milestone finished/); + assert.match(msg, /\/gsd status/); + assert.doesNotMatch(msg, /Next:/); +}); + +test("buildStepCompleteMessage: mid-flight step includes next unit label and /clear hint", () => { + const state = makeState({ + phase: "executing", + activeSlice: { id: "S01", title: "Core" }, + activeTask: { id: "T03", title: "Wire notify" }, + }); + const msg = buildStepCompleteMessage(state); + assert.match(msg, /Next: Execute T03: Wire notify/); + assert.match(msg, /\/clear/); + assert.match(msg, /\/gsd to continue/); +}); + +test("buildStepCompleteMessage: unknown phase falls back to generic continue label", () => { + // Cast to bypass Phase union so we exercise the default branch of describeNextUnit. + const state = makeState({ phase: "totally-unknown" as unknown as GSDState["phase"] }); + const msg = buildStepCompleteMessage(state); + assert.match(msg, /Next: Continue/); + assert.match(msg, /\/clear/); +}); + +test("STEP_COMPLETE_FALLBACK_MESSAGE: used when deriveState throws, still points users at /clear + /gsd", () => { + assert.match(STEP_COMPLETE_FALLBACK_MESSAGE, /\/clear/); + assert.match(STEP_COMPLETE_FALLBACK_MESSAGE, /\/gsd/); +});