diff --git a/.changeset/1993-milestone-ws-requirements-header.md b/.changeset/1993-milestone-ws-requirements-header.md new file mode 100644 index 000000000..aa53bf197 --- /dev/null +++ b/.changeset/1993-milestone-ws-requirements-header.md @@ -0,0 +1,5 @@ +--- +type: Fixed +pr: 2015 +--- +**`milestone complete --ws` requirements archive header now points at the workstream REQUIREMENTS.md** — the archive header string hardcoded the root path (`` `…see .planning/REQUIREMENTS.md` ``), so a workstream archive directed readers at the wrong file even though #1917 had already fixed the archive *locations* to land inside the workstream. The display path is now derived from the same workstream-aware `reqPath` the writer uses (`path.relative(cwd, reqPath)`), so root behavior is byte-identical and the workstream case correctly reads `.planning/workstreams//REQUIREMENTS.md`. (#1993) diff --git a/src/milestone.cts b/src/milestone.cts index 6b078e3ac..cafb7717a 100644 --- a/src/milestone.cts +++ b/src/milestone.cts @@ -284,7 +284,13 @@ function cmdMilestoneComplete(cwd: string, version: string, options: MilestoneCo // Archive REQUIREMENTS.md if (fs.existsSync(reqPath)) { const reqContent = fs.readFileSync(reqPath, 'utf-8'); - const archiveHeader = `# Requirements Archive: ${version} ${milestoneName}\n\n**Archived:** ${today}\n**Status:** SHIPPED\n\nFor current requirements, see \`.planning/REQUIREMENTS.md\`.\n\n---\n\n`; + // Derive the display path from the same source the writer uses (reqPath), so a + // workstream archive header points at `.planning/workstreams//REQUIREMENTS.md` + // instead of the hardcoded root path (#1993). Root case is byte-identical. + // Normalize to POSIX separators so the header is cross-platform (Windows + // path.relative yields backslashes; the original literal was forward-slash). + const reqDisplay = path.relative(cwd, reqPath).split(path.sep).join('/'); + const archiveHeader = `# Requirements Archive: ${version} ${milestoneName}\n\n**Archived:** ${today}\n**Status:** SHIPPED\n\nFor current requirements, see \`${reqDisplay}\`.\n\n---\n\n`; platformWriteSync(path.join(archiveDir, `${version}-REQUIREMENTS.md`), archiveHeader + reqContent); } diff --git a/tests/milestone.test.cjs b/tests/milestone.test.cjs index d09a94dda..65c0d26b9 100644 --- a/tests/milestone.test.cjs +++ b/tests/milestone.test.cjs @@ -758,6 +758,36 @@ describe('#1911 — milestone complete --ws archives to the workstream', () => { 'must not archive to root .planning/milestones/ in workstream mode', ); }); + + test('#1993 — --ws requirements archive header points at the workstream REQUIREMENTS.md, 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'); + 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}`); + + const archivedReq = fs.readFileSync( + path.join(wsBase, 'milestones', 'v2.0-REQUIREMENTS.md'), 'utf-8', + ); + // Header must point readers at the WORKSTREAM requirements file... + assert.ok( + archivedReq.includes('see `.planning/workstreams/ws1/REQUIREMENTS.md`'), + `workstream archive header must reference the workstream path; got:\n${archivedReq.split(/\r?\n/).slice(0, 6).join('\n')}`, + ); + // ...and must NOT point at the root path (the #1993 bug). + assert.ok( + !/\bsee\s+`\.planning\/REQUIREMENTS\.md`\b/.test(archivedReq), + 'workstream archive header must not hardcode the root REQUIREMENTS.md path', + ); + }); }); // ─────────────────────────────────────────────────────────────────────────────