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.
This commit is contained in:
Jeremy 2026-04-14 06:11:53 -05:00
parent 8fec87b6f2
commit 2cec5a1014
2 changed files with 67 additions and 17 deletions

View file

@ -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";
}

View file

@ -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>): 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/);
});