fix(#1911): milestone complete --ws archives to the workstream (#1917)

cmdMilestoneComplete hardcoded three archive paths to root .planning/
while its siblings (roadmap/requirements/state/phases) used workstream-aware
planningPaths. So `milestone complete <v> --ws <name>` scattered its archive
into root and never created workstream-local milestones/.

Derive the archive base from the workstream-aware planning root:
- milestonesPath / archiveDir / auditFile now use planningPaths(cwd).planning
- flat mode (no --ws) is a no-op (planningPaths(cwd).planning == root .planning)

Regression in tests/milestone.test.cjs: --ws archives into the workstream
milestones dir, not root.

Closes #1911
This commit is contained in:
Tom Boucher
2026-07-02 10:25:11 -04:00
committed by GitHub
parent 8084f626ba
commit 88da609b3e
3 changed files with 56 additions and 3 deletions

View File

@@ -716,3 +716,46 @@ describe('milestone complete explicit version scope (#3043)', () => {
}
});
});
// ─────────────────────────────────────────────────────────────────────────────
// #1911: milestone complete --ws must archive to the workstream, not root
// ─────────────────────────────────────────────────────────────────────────────
describe('#1911 — milestone complete --ws archives to the workstream', () => {
let tmpDir;
beforeEach(() => { tmpDir = createTempProject(); });
afterEach(() => cleanup(tmpDir));
test('--ws archives roadmap/requirements into the workstream milestones dir, not root', () => {
const wsBase = path.join(tmpDir, '.planning', 'workstreams', 'ws1');
fs.mkdirSync(path.join(wsBase, 'phases', '01-foo'), { recursive: true });
fs.writeFileSync(path.join(wsBase, 'STATE.md'), 'milestone: v2.0\nstatus: executing\n');
fs.writeFileSync(
path.join(wsBase, 'ROADMAP.md'),
'# Roadmap\n## Milestones\n- v2.0 Test (Phases 1) — IN PROGRESS\n## Phases\n### Phase 1: Foo\n**Goal:** foo\n',
);
fs.writeFileSync(path.join(wsBase, 'REQUIREMENTS.md'), '# Requirements\n- [ ] REQ-01\n');
fs.writeFileSync(path.join(wsBase, 'phases', '01-foo', '01-SUMMARY.md'), '---\none-liner: foo done\n---\n# Summary\n');
// Root milestones dir pre-exists; it must NOT receive the workstream archive.
fs.mkdirSync(path.join(tmpDir, '.planning', 'milestones'), { recursive: true });
const result = runGsdTools('milestone complete v2.0 --ws ws1 --force', tmpDir);
assert.ok(result.success, `Command failed: ${result.error}`);
// Archive lands inside the workstream.
assert.ok(
fs.existsSync(path.join(wsBase, 'milestones', 'v2.0-ROADMAP.md')),
'v2.0-ROADMAP.md should be archived into the workstream milestones dir',
);
assert.ok(
fs.existsSync(path.join(wsBase, 'milestones', 'v2.0-REQUIREMENTS.md')),
'v2.0-REQUIREMENTS.md should be archived into the workstream milestones dir',
);
// And NOT in root.
assert.ok(
!fs.existsSync(path.join(tmpDir, '.planning', 'milestones', 'v2.0-ROADMAP.md')),
'must not archive to root .planning/milestones/ in workstream mode',
);
});
});