From 19041b88241677ab3de3e9ba6bce8ffcfe04cb52 Mon Sep 17 00:00:00 2001 From: Ben Lamm Date: Sun, 3 May 2026 11:58:56 -0400 Subject: [PATCH] test(#2641): lockdown tests from self-adversarial pass MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Self-run adversarial pass on PR #3046 before next reviewer round-trip. Three lockdown tests added — none uncovered new bugs, all lock current behavior so a future change doesn't silently flip a convention. 1. Single-quote YAML version (`milestone: 'v0.9'`) Parity with the existing double-quote test. The strip pattern `/^["']|["']$/g` handles both — locked here so a future change to either character class doesn't silently regress one form. 2. Heading-anchor wins over
fallback (precedence lock) When a ROADMAP has BOTH `### v0.9` heading AND `
v0.9` block, the heading-level lookup matches first and the fallback never fires. Test asserts the heading slice is returned starting at offset 0 AND the synthesized `## v0.9 ... details-anchored` heading is NOT prepended (proves fallback didn't run). Also documented in-test that the heading-anchor slice naturally includes downstream
blocks verbatim — a property of the heading path, not of this PR's fallback. 3. Multiple
blocks for same version → first match wins `content.match(detailsPattern)` (non-`g`) returns first match in document order. Locked so a future change to the matcher (e.g. switching to `matchAll` and picking last) doesn't silently change which block is treated as active. Adversarial-checklist coverage on commit 781cc6f8: - Boundary cases: empty / whitespace / single-char / single-quote / double-quote / digit-suffix (`v0.91`) / dot-suffix (`v0.9.0`) / hyphen-suffix (`v0.9-rc.1`, intentional same-milestone match per existing currentVersionStr convention) — all covered. - Sibling consistency: parseMilestoneFromState, getMilestoneInfo, extractCurrentMilestone all strip quotes identically. - Comment-vs-behavior: walked nested-guard, empty-guard, lookahead, tag-strip by hand against the regex; all comments accurate. - Downstream consumers: roadmapAnalyze + roadmapGetPhase both verified end-to-end via tests + FAMP smoke. - Failure-mode locality: all fall-through paths produce loud failures (empty arrays, `{found:false}`); no silent confident-wrong outputs. 48/48 roadmap.test.ts tests pass. --- sdk/src/query/roadmap.test.ts | 101 ++++++++++++++++++++++++++++++++++ 1 file changed, 101 insertions(+) diff --git a/sdk/src/query/roadmap.test.ts b/sdk/src/query/roadmap.test.ts index 7ff5e3bcf..64208b2cc 100644 --- a/sdk/src/query/roadmap.test.ts +++ b/sdk/src/query/roadmap.test.ts @@ -622,6 +622,107 @@ Detail expect(result).not.toMatch(/^##.*/m); }); + // ─── Bug #2641 (lockdown): single-quote YAML version ─── + it('bug-2641: handles single-quote YAML version (milestone: \'v0.9\') in STATE.md', async () => { + // Parity coverage with the double-quote test. The strip pattern + // `/^["']|["']$/g` handles both — locked here so a future change to + // either character class doesn't silently regress one form. + const roadmap = `# Roadmap + +
+v0.9 Local-First Bus — Phase Details + +### Phase 3: Polish +**Goal:** Add polish. +
+`; + const stateSingle = `---\nmilestone: 'v0.9'\n---\n# State\n`; + await writeFile(join(tmpDir, '.planning', 'STATE.md'), stateSingle); + 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 (lockdown): heading wins when BOTH heading and
match ─── + it('bug-2641: markdown heading anchor wins over
fallback', async () => { + // The
fallback only fires when the heading-level lookup MISSES. + // If a ROADMAP has both `### v0.9 …` heading AND `
v0.9 …` + // for the same version, the heading anchor must win. Locks precedence so a + // future refactor doesn't accidentally flip the order and silently change + // which slice gets returned. + const roadmap = `# Roadmap + +### v0.9 Local-First Bus (heading-anchored) + +### Phase 1: Heading-anchored Phase +**Goal:** From the heading slice. + +
+v0.9 Local-First Bus — Phase Details (details-anchored) + +### Phase 99: Details-anchored Phase +**Goal:** From the details slice. +
+`; + const state = `---\nmilestone: v0.9\n---\n# State\n`; + await writeFile(join(tmpDir, '.planning', 'STATE.md'), state); + await writeFile(join(tmpDir, '.planning', 'ROADMAP.md'), roadmap); + + const result = await extractCurrentMilestone(roadmap, tmpDir); + + // Heading slice is what got returned — original `### v0.9` heading + // present, Phase 1 from the heading slice present. + expect(result).toContain('### v0.9 Local-First Bus (heading-anchored)'); + expect(result).toContain('### Phase 1: Heading-anchored Phase'); + // Critical: the
fallback did NOT fire, so no synthesized + // `## ` heading is prepended. (The heading-anchor slice extends to the + // next milestone boundary and includes the downstream
block + // verbatim — that's a property of the heading-anchor path, not the + // fallback. We're locking which CODE PATH ran, not how its output looks.) + expect(result).not.toMatch(/^##\s+v0\.9 Local-First Bus.*details-anchored/im); + // The original heading must appear at the START of the slice (the + // heading-anchor path returns content starting at the matched heading). + expect(result.indexOf('### v0.9 Local-First Bus (heading-anchored)')).toBe(0); + }); + + // ─── Bug #2641 (lockdown): multiple
blocks for same version ─── + it('bug-2641: when multiple
match the version, the FIRST is returned', async () => { + // `content.match(detailsPattern)` (non-`g`) returns the first match in + // document order. Lock this so a future change to the matcher (e.g. + // switching to `matchAll` and picking the last) doesn't silently change + // which block is treated as the active milestone. Document-order-first is + // intentional: in real ROADMAPs, the active milestone is conventionally + // listed before any duplicates (e.g. retro-active or branch-merge artefacts). + const roadmap = `# Roadmap + +
+v0.9 Local-First Bus — Phase Details (FIRST) + +### Phase 1: First-block Phase +**Goal:** Should be returned. +
+ +
+v0.9 Local-First Bus — Phase Details (SECOND) + +### Phase 99: Second-block Phase +**Goal:** Should NOT be returned. +
+`; + const state = `---\nmilestone: v0.9\n---\n# State\n`; + await writeFile(join(tmpDir, '.planning', 'STATE.md'), state); + await writeFile(join(tmpDir, '.planning', 'ROADMAP.md'), roadmap); + + const result = await extractCurrentMilestone(roadmap, tmpDir); + + expect(result).toContain('### Phase 1: First-block Phase'); + expect(result).not.toContain('### Phase 99: Second-block Phase'); + expect(result).toMatch(/^##\s+v0\.9 Local-First Bus.*FIRST/m); + }); + // ─── Bug #2422: same-version sub-heading truncation ─────────────────── it('bug-2422: does not truncate at same-version sub-heading (## v2.0 Phase Details)', async () => { const roadmapWithDetails = `# ROADMAP