diff --git a/.changeset/eager-ibex-bark.md b/.changeset/eager-ibex-bark.md new file mode 100644 index 000000000..467e97c0e --- /dev/null +++ b/.changeset/eager-ibex-bark.md @@ -0,0 +1,5 @@ +--- +type: Fixed +pr: 1844 +--- +**`init milestone-op` now counts project_code-prefixed phase directories correctly** — fully shipped milestones using the standard prefixed directory layout no longer report `completed_phases: 0` or stay falsely incomplete. (#1844) diff --git a/src/init.cts b/src/init.cts index 6c12e0a18..52f2a576f 100644 --- a/src/init.cts +++ b/src/init.cts @@ -64,7 +64,7 @@ const { extractCurrentMilestone, } = roadmapParser; const { pathExistsInternal, generateSlugInternal, toPosixPath } = coreUtils; -const { normalizePhaseName, phaseTokenMatches } = phaseId; +const { normalizePhaseName, phaseTokenMatches, stripProjectCodePrefix } = phaseId; const { pruneOrphanedWorktrees } = worktreeSafety; const { @@ -1204,7 +1204,7 @@ function cmdInitMilestoneOp(cwd: string, raw: boolean): void { const entries = fs.readdirSync(phasesDir, { withFileTypes: true }); for (const e of entries) { if (!e.isDirectory()) continue; - const m = e.name.match(/^(\d+[A-Z]?(?:\.\d+)*)/); + const m = stripProjectCodePrefix(e.name).match(/^(\d+[A-Z]?(?:\.\d+)*)/); if (!m) continue; diskPhaseDirs.set(canonicalizePhase(m[1]), e.name); } diff --git a/tests/init.test.cjs b/tests/init.test.cjs index 924c7f5a2..e665e0281 100644 --- a/tests/init.test.cjs +++ b/tests/init.test.cjs @@ -967,6 +967,47 @@ describe('cmdInitMilestoneOp', () => { assert.strictEqual(output.all_phases_complete, true); }); + test('project_code-prefixed phase directories count as completed milestone phases (#1836)', () => { + seedPhase(tmpDir, 'PROJ-01-setup', { + 'PROJ-01-01-PLAN.md': '# Plan', + 'PROJ-01-01-SUMMARY.md': '# Summary', + }); + fs.writeFileSync( + path.join(tmpDir, '.planning', 'config.json'), + JSON.stringify({ project_code: 'PROJ' }, null, 2) + ); + fs.writeFileSync( + path.join(tmpDir, '.planning', 'STATE.md'), + [ + '---', + 'gsd_state_version: 1.0', + 'milestone: v1.0.0', + 'milestone_name: Test Milestone', + 'status: executing', + '---', + '', + ].join('\n') + ); + fs.writeFileSync( + path.join(tmpDir, '.planning', 'ROADMAP.md'), + [ + '# Roadmap', + '', + '## 🚧 v1.0.0 Test Milestone', + '### Phase 1: Setup', + '', + ].join('\n') + ); + + const result = runGsdTools('init milestone-op', tmpDir); + assert.ok(result.success, `Command failed: ${result.error}`); + + const output = JSON.parse(result.output); + assert.strictEqual(output.phase_count, 1); + assert.strictEqual(output.completed_phases, 1); + assert.strictEqual(output.all_phases_complete, true); + }); + test('archive directory scanning', () => { fs.mkdirSync(path.join(tmpDir, '.planning', 'archive', 'v1.0'), { recursive: true }); fs.mkdirSync(path.join(tmpDir, '.planning', 'archive', 'v0.9'), { recursive: true });