From bae9cf83baeb796d7bb59e98c0ea2c64679a97ea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?T=C3=82CHES?= Date: Thu, 19 Mar 2026 17:33:24 -0600 Subject: [PATCH] fix(gsd): filter non-milestone directories from findMilestoneIds (#1494) (#1508) Directories under .gsd/milestones/ that don't match the M\d+ pattern (e.g. slices/, temp-backup/) are now excluded instead of being returned with their raw name. This prevents rogue directories from blocking auto-mode milestone discovery. Closes #1494 Co-authored-by: Claude Opus 4.6 (1M context) --- src/resources/extensions/gsd/milestone-ids.ts | 5 ++-- .../gsd/tests/queue-reorder-e2e.test.ts | 26 +++++++++++++++++++ 2 files changed, 29 insertions(+), 2 deletions(-) diff --git a/src/resources/extensions/gsd/milestone-ids.ts b/src/resources/extensions/gsd/milestone-ids.ts index aec8cee67..fdd26f7ab 100644 --- a/src/resources/extensions/gsd/milestone-ids.ts +++ b/src/resources/extensions/gsd/milestone-ids.ts @@ -80,8 +80,9 @@ export function findMilestoneIds(basePath: string): string[] { .filter((d) => d.isDirectory()) .map((d) => { const match = d.name.match(/^(M\d+(?:-[a-z0-9]{6})?)/); - return match ? match[1] : d.name; - }); + return match ? match[1] : null; + }) + .filter((id): id is string => id !== null); // Apply custom queue order if available, else fall back to numeric sort const customOrder = loadQueueOrder(basePath); diff --git a/src/resources/extensions/gsd/tests/queue-reorder-e2e.test.ts b/src/resources/extensions/gsd/tests/queue-reorder-e2e.test.ts index b9140c561..bf86c360a 100644 --- a/src/resources/extensions/gsd/tests/queue-reorder-e2e.test.ts +++ b/src/resources/extensions/gsd/tests/queue-reorder-e2e.test.ts @@ -244,6 +244,32 @@ console.log('\n=== E2E: backward compat without QUEUE-ORDER.json ==='); } } +// ═══════════════════════════════════════════════════════════════════════════ +// Test: non-milestone directories are filtered out (#1494) +// ═══════════════════════════════════════════════════════════════════════════ + +console.log('\n=== E2E: non-milestone directories filtered from findMilestoneIds (#1494) ==='); +{ + const base = createFixtureBase(); + try { + writeContext(base, 'M001', '', 'First'); + writeContext(base, 'M002', '', 'Second'); + // Create a rogue non-milestone directory + mkdirSync(join(base, '.gsd', 'milestones', 'slices'), { recursive: true }); + mkdirSync(join(base, '.gsd', 'milestones', 'temp-backup'), { recursive: true }); + + invalidateStateCache(); + const ids = findMilestoneIds(base); + assertEq(ids.length, 2, 'only M001 and M002 returned'); + assertTrue(!ids.includes('slices'), 'slices directory excluded'); + assertTrue(!ids.includes('temp-backup'), 'temp-backup directory excluded'); + assertTrue(ids.includes('M001'), 'M001 included'); + assertTrue(ids.includes('M002'), 'M002 included'); + } finally { + cleanup(base); + } +} + // ═══════════════════════════════════════════════════════════════════════════ // Test: depends_on inline array format removal // ═══════════════════════════════════════════════════════════════════════════