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:
5
.changeset/rapid-pumas-click.md
Normal file
5
.changeset/rapid-pumas-click.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
type: Fixed
|
||||
pr: 1917
|
||||
---
|
||||
**`milestone complete --ws` now archives into the workstream instead of root** — the archive paths (MILESTONES.md, the milestones/ archive dir, and the per-version MILESTONE-AUDIT.md) were hardcoded to root `.planning/`, so a workstream milestone close scattered its artifacts into root and never produced a workstream-local archive. They now derive from the workstream-aware planning base (`planningPaths(cwd).planning`); flat-mode (no --ws) is unchanged. (#1911)
|
||||
@@ -130,8 +130,13 @@ function cmdMilestoneComplete(cwd: string, version: string, options: MilestoneCo
|
||||
const roadmapPath = planningPaths(cwd).roadmap;
|
||||
const reqPath = planningPaths(cwd).requirements;
|
||||
const statePath = planningPaths(cwd).state;
|
||||
const milestonesPath = path.join(cwd, '.planning', 'MILESTONES.md');
|
||||
const archiveDir = path.join(cwd, '.planning', 'milestones');
|
||||
// #1911: derive the archive base from the workstream-aware planning root so
|
||||
// `milestone complete --ws` archives into the workstream, not root. planningPaths(cwd).planning
|
||||
// resolves to the workstream base when GSD_WORKSTREAM is set and to root .planning otherwise
|
||||
// (flat mode is a no-op).
|
||||
const planningBase = planningPaths(cwd).planning;
|
||||
const milestonesPath = path.join(planningBase, 'MILESTONES.md');
|
||||
const archiveDir = path.join(planningBase, 'milestones');
|
||||
const phasesDir = planningPaths(cwd).phases;
|
||||
const today = new Date().toISOString().split('T')[0];
|
||||
const milestoneName = options.name || version;
|
||||
@@ -283,7 +288,7 @@ function cmdMilestoneComplete(cwd: string, version: string, options: MilestoneCo
|
||||
}
|
||||
|
||||
// Archive audit file if exists
|
||||
const auditFile = path.join(cwd, '.planning', `${version}-MILESTONE-AUDIT.md`);
|
||||
const auditFile = path.join(planningBase, `${version}-MILESTONE-AUDIT.md`);
|
||||
if (fs.existsSync(auditFile)) {
|
||||
retryRenameSync(auditFile, path.join(archiveDir, `${version}-MILESTONE-AUDIT.md`));
|
||||
}
|
||||
|
||||
@@ -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',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user