fix(#2641): strip YAML quotes from STATE.md milestone version

Address CodeRabbit review on PR #3046: extractCurrentMilestone read the
`milestone:` value from STATE.md frontmatter via `.trim()` only, while
parseMilestoneFromState() and getMilestoneInfo() both also strip
surrounding YAML quotes via `.replace(/^["']|["']$/g, '')`.

For projects whose STATE.md uses quoted YAML (`milestone: "v0.9"`),
`version` carried literal quotes, `escapedVersion` became `\"v0\.9\"`,
and neither the markdown-heading regex nor the new <details><summary>
fallback could match anything — falling through to
stripShippedMilestones() and reintroducing the same archived-milestone
misrouting this PR addresses.

Strip quotes for parity. Three-line addition + one new test.
All 41 roadmap.test.ts tests pass.
This commit is contained in:
Ben Lamm
2026-05-02 23:34:22 -04:00
committed by Tom Boucher
parent 592b676414
commit ba6a3efc3e
2 changed files with 35 additions and 2 deletions

View File

@@ -437,6 +437,33 @@ describe('extractCurrentMilestone', () => {
expect(result).not.toContain('Old phase');
});
// ─── Bug #2641 (CodeRabbit follow-up): quoted YAML version normalization ───
it('bug-2641: handles quoted YAML version (milestone: "v0.9") in STATE.md', async () => {
// STATE.md may use quoted YAML (`milestone: "v0.9"`). Without quote-stripping,
// version would carry literal quotes, escapedVersion would be `\"v0\.9\"`,
// and neither the markdown-heading regex nor the <details><summary> fallback
// would match — falling through to stripShippedMilestones and reintroducing
// the archived-milestone misrouting this PR addresses. Parity with
// parseMilestoneFromState() and getMilestoneInfo() (which both strip quotes).
const roadmap = `# Roadmap
<details>
<summary>v0.9 Local-First Bus (active) — Phase Details</summary>
### Phase 3: Polish
**Goal:** Add polish.
</details>
`;
const stateQuoted = `---\nmilestone: "v0.9"\n---\n# State\n`;
await writeFile(join(tmpDir, '.planning', 'STATE.md'), stateQuoted);
await writeFile(join(tmpDir, '.planning', 'ROADMAP.md'), roadmap);
const result = await extractCurrentMilestone(roadmap, tmpDir);
expect(result).toContain('### Phase 3: Polish');
expect(result).toMatch(/^##\s+v0\.9 Local-First Bus/m);
});
// ─── Bug #2641: tolerate attributes on <details> tag (e.g. <details open>) ───
it('bug-2641: finds active milestone in <details open><summary>vX.Y …</summary>', async () => {
// GitHub auto-renders <details open> for sections that should default to

View File

@@ -153,13 +153,19 @@ export async function getMilestoneInfo(projectDir: string, workstream?: string):
* @returns Content scoped to current milestone
*/
export async function extractCurrentMilestone(content: string, projectDir: string, workstream?: string): Promise<string> {
// Get version from STATE.md frontmatter
// Get version from STATE.md frontmatter.
// Strip optional surrounding YAML quotes (e.g. `milestone: "v0.9"`) for parity
// with parseMilestoneFromState() above and getMilestoneInfo()'s STATE.md path.
// Without this, a quoted version yields `escapedVersion = '\\"v0\\.9\\"'`
// which matches neither markdown headings nor <summary> text, falling
// through to stripShippedMilestones() — and reintroducing the same archived-
// milestone misrouting this fallback addresses.
let version: string | null = null;
try {
const stateRaw = await readFile(planningPaths(projectDir, workstream).state, 'utf-8');
const milestoneMatch = stateRaw.match(/^milestone:\s*(.+)/m);
if (milestoneMatch) {
version = milestoneMatch[1].trim();
version = milestoneMatch[1].trim().replace(/^["']|["']$/g, '');
}
} catch { /* intentionally empty */ }