Merge pull request #4173 from jeremymcs/claude/gsd-step-guidance-5FNrM

Add user feedback when completing steps in step mode
This commit is contained in:
Jeremy McSpadden 2026-04-14 06:42:37 -05:00 committed by GitHub
commit a09b69e27d
2 changed files with 76 additions and 1 deletions

View file

@ -104,6 +104,7 @@ import {
updateSliceProgressCache,
unitVerb,
hideFooter,
describeNextUnit,
} from "./auto-dashboard.js";
import { existsSync, unlinkSync } from "node:fs";
import { join } from "node:path";
@ -233,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;
@ -1025,8 +1038,17 @@ export async function postUnitPostVerification(pctx: PostUnitContext): Promise<"
}
}
// Step mode → show wizard instead of dispatch
// Step mode → show wizard instead of dispatch.
// Without this notify(), /gsd in step mode finishes a unit and silently
// exits the loop, leaving the user with no hint to /clear and /gsd again.
if (s.stepMode) {
try {
const nextState = await deriveState(s.basePath);
ctx.ui.notify(buildStepCompleteMessage(nextState), "info");
} catch (e) {
debugLog("postUnit", { phase: "step-wizard-notify", error: String(e) });
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/);
});