* fix(#1993): milestone --ws requirements archive header points at workstream path The requirements archive header hardcoded the root .planning/REQUIREMENTS.md path, so a workstream (--ws) archive pointed readers at the wrong file even though #1917 fixed the archive LOCATIONS to land inside the workstream. Derive the display path from the same workstream-aware reqPath the writer already uses (path.relative(cwd, reqPath)). Root behavior is byte-identical ('.planning/REQUIREMENTS.md'); the --ws case now correctly reads '.planning/workstreams/<ws>/REQUIREMENTS.md'. - src/milestone.cts: reqDisplay interpolation in the archive header. - tests/milestone.test.cjs: #1993 regression in the #1911 --ws block — header references the workstream path, not the root literal. Closes #1993 * docs(#1993): backfill changeset pr 2015 * fix(#1993): use posix separators in archive header (Windows CI) + CRLF-safe test split - src/milestone.cts: normalize path.relative output to POSIX separators so the workstream archive header renders forward slashes on Windows too (path.relative yields backslashes there; the original literal was posix). - tests/milestone.test.cjs: .split(/\r?\n/) for the CRLF-fragile lint rule.
This commit is contained in:
5
.changeset/1993-milestone-ws-requirements-header.md
Normal file
5
.changeset/1993-milestone-ws-requirements-header.md
Normal file
@@ -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/<ws>/REQUIREMENTS.md`. (#1993)
|
||||
@@ -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/<ws>/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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
Reference in New Issue
Block a user