fix(#1993): milestone --ws requirements archive header points at workstream path (#2015)

* 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:
Tom Boucher
2026-07-05 14:03:43 -04:00
committed by GitHub
parent ed31e52b67
commit e2bfe4dc67
3 changed files with 42 additions and 1 deletions

View File

@@ -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',
);
});
});
// ─────────────────────────────────────────────────────────────────────────────