fix(milestone-archive): expose searched roots and parse canonical STATE milestone

This commit is contained in:
Tom Boucher
2026-05-06 15:38:53 -04:00
parent 56737c057b
commit 99b2bddd14
3 changed files with 25 additions and 3 deletions

View File

@@ -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 });

View File

@@ -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`);

View File

@@ -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)}`
);
});
});