From e1801f967fd6da1373102a1b34211069a79dd859 Mon Sep 17 00:00:00 2001 From: Tibsfox Date: Mon, 6 Apr 2026 17:50:02 -0700 Subject: [PATCH 1/2] fix(gsd): use isClosedStatus() in dispatch guard instead of raw complete check Replaces `r.status === "complete"` with `isClosedStatus(r.status)` in dispatch-guard.ts so slices completed via the reconciliation replay path (which writes "done") or skipped slices are correctly recognized as closed. This was causing auto-mode to block on dependencies that were actually complete. Fixes #3601 Co-Authored-By: Claude Opus 4.6 (1M context) --- src/resources/extensions/gsd/dispatch-guard.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/resources/extensions/gsd/dispatch-guard.ts b/src/resources/extensions/gsd/dispatch-guard.ts index 59df30db1..c687f1b30 100644 --- a/src/resources/extensions/gsd/dispatch-guard.ts +++ b/src/resources/extensions/gsd/dispatch-guard.ts @@ -5,6 +5,7 @@ import { findMilestoneIds } from "./guided-flow.js"; import { parseUnitId } from "./unit-id.js"; import { isDbAvailable, getMilestoneSlices } from "./gsd-db.js"; import { parseRoadmap } from "./parsers-legacy.js"; +import { isClosedStatus } from "./status-guards.js"; import { readFileSync } from "node:fs"; const SLICE_DISPATCH_TYPES = new Set([ @@ -57,7 +58,7 @@ export function getPriorSliceCompletionBlocker( if (rows.length > 0) { slices = rows.map((r) => ({ id: r.id, - done: r.status === "complete", + done: isClosedStatus(r.status), depends: r.depends ?? [], })); } From 383fbfdd169d45e3d7c2d36241b2d3043c53afa3 Mon Sep 17 00:00:00 2001 From: Tibsfox Date: Mon, 6 Apr 2026 22:24:40 -0700 Subject: [PATCH 2/2] test: add regression test for isClosedStatus dispatch guard Co-Authored-By: Claude Opus 4.6 (1M context) --- .../dispatch-guard-closed-status.test.ts | 33 +++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 src/resources/extensions/gsd/tests/dispatch-guard-closed-status.test.ts diff --git a/src/resources/extensions/gsd/tests/dispatch-guard-closed-status.test.ts b/src/resources/extensions/gsd/tests/dispatch-guard-closed-status.test.ts new file mode 100644 index 000000000..6be6a5a5f --- /dev/null +++ b/src/resources/extensions/gsd/tests/dispatch-guard-closed-status.test.ts @@ -0,0 +1,33 @@ +/** + * dispatch-guard-closed-status.test.ts — #3653 + * + * Verify that the dispatch guard uses isClosedStatus() instead of a raw + * `status === "complete"` check when determining whether a slice is done. + * Reconciled slices may carry statuses like "skipped" or "cancelled" which + * are also closed — the raw check caused false dispatch blocks. + */ + +import { describe, test } from "node:test"; +import assert from "node:assert/strict"; +import { readFileSync } from "node:fs"; +import { join, dirname } from "node:path"; +import { fileURLToPath } from "node:url"; + +const __dirname = dirname(fileURLToPath(import.meta.url)); +const sourceFile = join(__dirname, "..", "dispatch-guard.ts"); + +describe("dispatch-guard isClosedStatus migration (#3653)", () => { + const source = readFileSync(sourceFile, "utf-8"); + + test("imports isClosedStatus from status-guards", () => { + assert.match(source, /import\s*\{[^}]*isClosedStatus[^}]*\}\s*from\s*["']\.\/status-guards/); + }); + + test("uses isClosedStatus() for slice done check instead of raw comparison", () => { + assert.match(source, /done:\s*isClosedStatus\(r\.status\)/); + }); + + test("does not use raw status === 'complete' for DB slice rows", () => { + assert.doesNotMatch(source, /done:\s*r\.status\s*===\s*["']complete["']/); + }); +});