From 64090702250b78cebe08f5096405fac5b86f1fd2 Mon Sep 17 00:00:00 2001 From: Tom Boucher Date: Tue, 24 Mar 2026 23:36:56 -0400 Subject: [PATCH] fix: block /gsd quick when auto-mode is active (#2420) --- .../gsd/commands/handlers/workflow.ts | 8 ++ .../gsd/tests/quick-auto-guard.test.ts | 100 ++++++++++++++++++ 2 files changed, 108 insertions(+) create mode 100644 src/resources/extensions/gsd/tests/quick-auto-guard.test.ts diff --git a/src/resources/extensions/gsd/commands/handlers/workflow.ts b/src/resources/extensions/gsd/commands/handlers/workflow.ts index 9a0169931..10282fbcc 100644 --- a/src/resources/extensions/gsd/commands/handlers/workflow.ts +++ b/src/resources/extensions/gsd/commands/handlers/workflow.ts @@ -188,6 +188,14 @@ export async function handleWorkflowCommand(trimmed: string, ctx: ExtensionComma return true; } if (trimmed === "quick" || trimmed.startsWith("quick ")) { + if (isAutoActive()) { + ctx.ui.notify( + "/gsd quick cannot run while auto-mode is active.\n" + + "Stop auto-mode first with /gsd stop, then run /gsd quick.", + "error", + ); + return true; + } await handleQuick(trimmed.replace(/^quick\s*/, "").trim(), ctx, pi); return true; } diff --git a/src/resources/extensions/gsd/tests/quick-auto-guard.test.ts b/src/resources/extensions/gsd/tests/quick-auto-guard.test.ts new file mode 100644 index 000000000..f48f4e925 --- /dev/null +++ b/src/resources/extensions/gsd/tests/quick-auto-guard.test.ts @@ -0,0 +1,100 @@ +/** + * Tests that /gsd quick is blocked when auto-mode is active. + * + * Relates to #2417: /gsd quick freezes terminal when auto-mode is active. + * The fix adds an isAutoActive() guard in handleWorkflowCommand before + * delegating to handleQuick. + */ + +import { describe, it, mock, beforeEach, afterEach } from "node:test"; +import assert from "node:assert/strict"; +import { readFileSync } from "node:fs"; +import { join } from "node:path"; + +// ─── Structural test: verify the guard exists in source ────────────────────── + +describe("/gsd quick auto-mode guard (#2417)", () => { + it("handleWorkflowCommand checks isAutoActive() before calling handleQuick", () => { + // Read the source file and verify the guard is structurally present + const src = readFileSync( + join( + import.meta.dirname, + "..", + "commands", + "handlers", + "workflow.ts", + ), + "utf-8", + ); + + // Find the quick command block + const quickBlockMatch = src.match( + /if\s*\(\s*trimmed\s*===\s*"quick"\s*\|\|\s*trimmed\.startsWith\("quick "\)\s*\)\s*\{([\s\S]*?)\n \}/, + ); + assert.ok(quickBlockMatch, "quick command block exists in handleWorkflowCommand"); + + const quickBlock = quickBlockMatch[1]; + + // Verify isAutoActive guard comes BEFORE handleQuick call + const guardIndex = quickBlock.indexOf("isAutoActive()"); + const handleQuickIndex = quickBlock.indexOf("handleQuick("); + + assert.ok(guardIndex !== -1, "isAutoActive() guard exists in quick command block"); + assert.ok(handleQuickIndex !== -1, "handleQuick() call exists in quick command block"); + assert.ok( + guardIndex < handleQuickIndex, + "isAutoActive() guard appears before handleQuick() call", + ); + }); + + it("guard shows error message mentioning /gsd stop", () => { + const src = readFileSync( + join( + import.meta.dirname, + "..", + "commands", + "handlers", + "workflow.ts", + ), + "utf-8", + ); + + // The error message should tell the user to stop auto-mode first + assert.ok( + src.includes("/gsd quick cannot run while auto-mode is active"), + "error message explains that quick cannot run during auto-mode", + ); + assert.ok( + src.includes("/gsd stop"), + "error message mentions /gsd stop as the resolution", + ); + }); + + it("guard returns true (handled) to prevent falling through", () => { + const src = readFileSync( + join( + import.meta.dirname, + "..", + "commands", + "handlers", + "workflow.ts", + ), + "utf-8", + ); + + // After the isAutoActive() check and notify, there should be a `return true` + // before the handleQuick call + const quickBlockMatch = src.match( + /if\s*\(\s*trimmed\s*===\s*"quick"\s*\|\|\s*trimmed\.startsWith\("quick "\)\s*\)\s*\{([\s\S]*?)\n \}/, + ); + assert.ok(quickBlockMatch); + const quickBlock = quickBlockMatch[1]; + + // The guard block should have its own return true before handleQuick + const guardBlock = quickBlock.slice(0, quickBlock.indexOf("handleQuick(")); + assert.ok( + guardBlock.includes("return true"), + "guard block returns true before handleQuick is reached", + ); + }); +});