From 236e9f136740af72cdf33ca631d286f413624c4f Mon Sep 17 00:00:00 2001 From: Tibsfox Date: Mon, 6 Apr 2026 18:35:59 -0700 Subject: [PATCH 1/3] fix(gsd): exclude closed slices from findMissingSummaries check MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Skip slices with status skipped/complete/done when checking for missing SUMMARY files. Skipped slices never produce SUMMARYs by design, and legacy-complete slices may lack them after worktree merge failures. The DB status is authoritative — missing SUMMARY is a cosmetic gap, not evidence the slice was incomplete. Fixes #3620 Co-Authored-By: Claude Opus 4.6 (1M context) --- src/resources/extensions/gsd/auto-dispatch.ts | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/src/resources/extensions/gsd/auto-dispatch.ts b/src/resources/extensions/gsd/auto-dispatch.ts index ccc3921e6..a03d0d043 100644 --- a/src/resources/extensions/gsd/auto-dispatch.ts +++ b/src/resources/extensions/gsd/auto-dispatch.ts @@ -93,14 +93,22 @@ function missingSliceStop(mid: string, phase: string): DispatchAction { /** * Check for milestone slices missing SUMMARY files. * Returns array of missing slice IDs, or empty array if all present or DB unavailable. + * + * Excludes skipped slices (intentionally summary-less) and legacy-complete + * slices whose DB status is authoritative even without on-disk SUMMARY (#3620). */ function findMissingSummaries(basePath: string, mid: string): string[] { if (!isDbAvailable()) return []; - const sliceIds = getMilestoneSlices(mid).map(s => s.id); - return sliceIds.filter(sid => { - const summaryPath = resolveSliceFile(basePath, mid, sid, "SUMMARY"); - return !summaryPath || !existsSync(summaryPath); - }); + const slices = getMilestoneSlices(mid); + // Skipped slices never produce SUMMARYs; legacy-complete slices may lack them + const CLOSED_STATUSES = new Set(["skipped", "complete", "done"]); + return slices + .filter(s => !CLOSED_STATUSES.has(s.status)) + .filter(s => { + const summaryPath = resolveSliceFile(basePath, mid, s.id, "SUMMARY"); + return !summaryPath || !existsSync(summaryPath); + }) + .map(s => s.id); } // ─── Rewrite Circuit Breaker ────────────────────────────────────────────── From 83822c51b37c2efee5d3cebd2dc4350cd8be2c34 Mon Sep 17 00:00:00 2001 From: Tibsfox Date: Mon, 6 Apr 2026 22:21:33 -0700 Subject: [PATCH 2/3] test: add regression test for findMissingSummaries closed-status exclusion Co-Authored-By: Claude Opus 4.6 (1M context) --- .../find-missing-summaries-closed.test.ts | 48 +++++++++++++++++++ 1 file changed, 48 insertions(+) create mode 100644 src/resources/extensions/gsd/tests/find-missing-summaries-closed.test.ts diff --git a/src/resources/extensions/gsd/tests/find-missing-summaries-closed.test.ts b/src/resources/extensions/gsd/tests/find-missing-summaries-closed.test.ts new file mode 100644 index 000000000..a0d0d70b0 --- /dev/null +++ b/src/resources/extensions/gsd/tests/find-missing-summaries-closed.test.ts @@ -0,0 +1,48 @@ +/** + * Regression test for #3669 — findMissingSummaries skips closed slices + * + * When a slice has status "skipped", "complete", or "done", it should be + * excluded from the missing-summary check because closed slices intentionally + * lack SUMMARY files (or their DB status is authoritative). + * + * This is a structural verification test — it reads the source to confirm the + * CLOSED_STATUSES guard exists at the filter site. + */ + +import { describe, test } from 'node:test'; +import assert from 'node:assert/strict'; +import { readFileSync } from 'node:fs'; +import { fileURLToPath } from 'node:url'; +import { dirname, join } from 'node:path'; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = dirname(__filename); + +const source = readFileSync(join(__dirname, '..', 'auto-dispatch.ts'), 'utf-8'); + +describe('findMissingSummaries closed-status exclusion (#3669)', () => { + test('CLOSED_STATUSES set includes skipped, complete, and done', () => { + // The source must define a CLOSED_STATUSES set with all three statuses + assert.match(source, /CLOSED_STATUSES.*=.*new Set\(/, + 'CLOSED_STATUSES set should be defined'); + assert.match(source, /"skipped"/, 'CLOSED_STATUSES should include "skipped"'); + assert.match(source, /"complete"/, 'CLOSED_STATUSES should include "complete"'); + assert.match(source, /"done"/, 'CLOSED_STATUSES should include "done"'); + }); + + test('filter uses CLOSED_STATUSES.has() to exclude closed slices', () => { + assert.match(source, /CLOSED_STATUSES\.has\(s\.status\)/, + 'filter should call CLOSED_STATUSES.has(s.status)'); + }); + + test('findMissingSummaries function exists', () => { + assert.match(source, /function findMissingSummaries\(/, + 'findMissingSummaries function should be defined'); + }); + + test('filter is negated (excludes closed, keeps open)', () => { + // The filter should use !CLOSED_STATUSES.has() to exclude closed slices + assert.match(source, /!CLOSED_STATUSES\.has\(s\.status\)/, + 'filter should negate CLOSED_STATUSES.has() to exclude closed slices'); + }); +}); From 5b72365567368b78316ddf2a1f8505d773bec7d5 Mon Sep 17 00:00:00 2001 From: Tibsfox Date: Mon, 6 Apr 2026 22:33:32 -0700 Subject: [PATCH 3/3] test: update edge-cases test to use pending slices for SUMMARY check Closed slices (complete/done/skipped) are now excluded from findMissingSummaries per #3620. Update the test to use pending status so the missing-SUMMARY block still fires. Co-Authored-By: Claude Opus 4.6 (1M context) --- .../gsd/tests/integration/state-machine-edge-cases.test.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/resources/extensions/gsd/tests/integration/state-machine-edge-cases.test.ts b/src/resources/extensions/gsd/tests/integration/state-machine-edge-cases.test.ts index e8abe3d94..db7b992c8 100644 --- a/src/resources/extensions/gsd/tests/integration/state-machine-edge-cases.test.ts +++ b/src/resources/extensions/gsd/tests/integration/state-machine-edge-cases.test.ts @@ -920,8 +920,10 @@ describe("completion and verification failures", () => { base = createFullFixture(); openDatabase(join(base, ".gsd", "gsd.db")); insertMilestone({ id: "M001", title: "Active", status: "active" }); - insertSlice({ id: "S01", milestoneId: "M001", title: "First", status: "complete" }); - insertSlice({ id: "S02", milestoneId: "M001", title: "Second", status: "complete" }); + // Use "pending" status — closed slices (complete/done/skipped) are + // excluded from SUMMARY checks per #3620. + insertSlice({ id: "S01", milestoneId: "M001", title: "First", status: "pending" }); + insertSlice({ id: "S02", milestoneId: "M001", title: "Second", status: "pending" }); // No S01-SUMMARY.md or S02-SUMMARY.md on disk const ctx = buildDispatchCtx(base, "M001", {