From 99b2bddd1422e092f68e9ac3ba95c633289d7f81 Mon Sep 17 00:00:00 2001 From: Tom Boucher Date: Wed, 6 May 2026 15:38:53 -0400 Subject: [PATCH] fix(milestone-archive): expose searched roots and parse canonical STATE milestone --- get-shit-done/bin/lib/phase.cjs | 5 ++++- get-shit-done/bin/lib/verify.cjs | 2 +- ...bug-3164-milestone-archive-layout.test.cjs | 21 ++++++++++++++++++- 3 files changed, 25 insertions(+), 3 deletions(-) diff --git a/get-shit-done/bin/lib/phase.cjs b/get-shit-done/bin/lib/phase.cjs index f4ab64ad4..4f9177171 100644 --- a/get-shit-done/bin/lib/phase.cjs +++ b/get-shit-done/bin/lib/phase.cjs @@ -214,7 +214,7 @@ function cmdFindPhase(cwd, phase, raw) { const planBase = planningDir(cwd); const normalized = normalizePhaseName(phase); - const notFound = { found: false, directory: null, phase_number: null, phase_name: null, plans: [], summaries: [] }; + const notFound = { found: false, directory: null, phase_number: null, phase_name: null, plans: [], summaries: [], searched_directories: [] }; // Build candidate search dirs: flat layout first, then milestone-archive layout. const searchDirs = []; @@ -230,6 +230,9 @@ function cmdFindPhase(cwd, phase, raw) { } } catch { /* no milestones dir */ } + notFound.searched_directories = searchDirs.map((searchDir) => + toPosixPath(path.join(path.relative(cwd, planBase), path.relative(planBase, searchDir)))); + for (const searchDir of searchDirs) { try { const entries = fs.readdirSync(searchDir, { withFileTypes: true }); diff --git a/get-shit-done/bin/lib/verify.cjs b/get-shit-done/bin/lib/verify.cjs index 68c50872c..d5d6585be 100644 --- a/get-shit-done/bin/lib/verify.cjs +++ b/get-shit-done/bin/lib/verify.cjs @@ -420,7 +420,7 @@ function getActiveMilestoneArchiveDir(planBase) { const statePath = path.join(planBase, 'STATE.md'); if (fs.existsSync(statePath)) { const state = fs.readFileSync(statePath, 'utf-8'); - const m = state.match(/^\s*milestone:\s*([^\r\n#]+)\s*$/mi); + const m = state.match(/^\s*(?:\*\*)?milestone(?:\*\*)?:\s*([^\s\r\n#]+).*$/mi); if (m && m[1]) { const milestone = m[1].trim(); const candidate = path.join(planBase, 'milestones', `${milestone}-phases`); diff --git a/tests/bug-3164-milestone-archive-layout.test.cjs b/tests/bug-3164-milestone-archive-layout.test.cjs index e2d67688a..72563bd72 100644 --- a/tests/bug-3164-milestone-archive-layout.test.cjs +++ b/tests/bug-3164-milestone-archive-layout.test.cjs @@ -139,7 +139,7 @@ describe('#3164 — validate consistency: milestone-archive layout', () => { fs.writeFileSync( path.join(tmpDir, '.planning', 'STATE.md'), - 'milestone: v1.7\n# Session State\n\nPhase: 65\n' + '# Session State\n\n**Milestone:** v1.7 Current Milestone\nPhase: 65\n' ); fs.writeFileSync( path.join(tmpDir, '.planning', 'ROADMAP.md'), @@ -240,4 +240,23 @@ describe('#3164 — find-phase: milestone-archive layout', () => { `Expected deterministic archive ordering (v1.2 before v1.10), got directory: ${out.directory}` ); }); + + test('find-phase not-found payload includes searched_directories', () => { + setupMilestoneArchiveProject(tmpDir, { + milestone: 'v1.7', + phases: ['64-secondary-grader-fix'], + roadmapPhases: ['64'], + }); + + const result = runGsdTools('find-phase 999', tmpDir); + assert.ok(result.success, `find-phase should succeed with found:false payload: ${result.error}`); + + const out = JSON.parse(result.output); + assert.strictEqual(out.found, false, `find-phase 999 should return found:false, got: ${JSON.stringify(out)}`); + assert.ok(Array.isArray(out.searched_directories), 'searched_directories should be an array on not-found payload'); + assert.ok( + out.searched_directories.includes('.planning/milestones/v1.7-phases'), + `searched_directories should include active archive dir, got: ${JSON.stringify(out.searched_directories)}` + ); + }); });