From dbec81d5adc6959ce6193a6b6d4c55d1caf310dc Mon Sep 17 00:00:00 2001 From: Tom Boucher Date: Sun, 12 Jul 2026 16:06:37 -0400 Subject: [PATCH] test(#2177): cover frontmatter progress: shadow + suffix preservation Adds the frontmatter-bearing STATE.md fixture the suite lacked. Asserts the body Progress: line (not the YAML progress: key) is the update target, the descriptive suffix after [bar] NN% is preserved, the frontmatter block is not mangled, and a body with no Progress: line reports updated:false even when the frontmatter has a progress: key (no false success). --- tests/state.test.cjs | 79 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 79 insertions(+) diff --git a/tests/state.test.cjs b/tests/state.test.cjs index 94163ec52..496d8ed4f 100644 --- a/tests/state.test.cjs +++ b/tests/state.test.cjs @@ -1295,6 +1295,85 @@ describe('cmdStateUpdateProgress (state update-progress)', () => { assert.ok(output.reason !== undefined, 'should have a reason'); }); + // ── #2177: frontmatter `progress:` key must not shadow the body Progress: line ── + + test('#2177 frontmatter progress: key is not matched — body Progress: line is the target', () => { + // A STATE.md carrying YAML frontmatter (which writeStateMd adds to every + // STATE.md). The lowercase `progress:` key used to be matched first by the + // case-insensitive pattern, mangling the frontmatter and leaving the body + // line stale. + fs.writeFileSync( + path.join(tmpDir, '.planning', 'STATE.md'), + [ + '---', + 'gsd_state_version: 1.0', + 'status: executing', + 'progress:', + ' total_phases: 1', + ' completed_phases: 0', + ' total_plans: 2', + ' completed_plans: 1', + ' percent: 20', + '---', + '', + '# Project State', + '', + 'Progress: [██░░░░░░░░] 20% (1/2 plans complete)', + '', + ].join('\n') + ); + // 1 of 2 plans complete → 50%. + const phaseDir = path.join(tmpDir, '.planning', 'phases', '01'); + fs.mkdirSync(phaseDir, { recursive: true }); + fs.writeFileSync(path.join(phaseDir, '01-01-PLAN.md'), '# Plan\n'); + fs.writeFileSync(path.join(phaseDir, '01-01-SUMMARY.md'), '# Summary\n'); + fs.writeFileSync(path.join(phaseDir, '01-02-PLAN.md'), '# Plan\n'); + + const result = runGsdTools('state update-progress', tmpDir); + assert.ok(result.success, `Command failed: ${result.error}`); + const out = JSON.parse(result.output); + assert.strictEqual(out.updated, true); + assert.strictEqual(out.percent, 50); + + const updated = fs.readFileSync(path.join(tmpDir, '.planning', 'STATE.md'), 'utf-8'); + // The body line advanced to 50% AND its descriptive suffix survived. + assert.ok(/Progress: \[█████░░░░░\] 50% \(1\/2 plans complete\)/.test(updated), + 'body Progress line must update to 50% with suffix preserved'); + // The frontmatter block is intact (not mangled by the old \s*-crosses-newline match). + assert.ok(/ total_phases: 1\n/.test(updated), 'frontmatter total_phases key must survive'); + assert.ok(/ percent:/.test(updated), 'frontmatter percent key must survive'); + }); + + test('#2177 descriptive suffix after the machine segment is preserved', () => { + fs.writeFileSync( + path.join(tmpDir, '.planning', 'STATE.md'), + '# Project State\n\n**Progress:** [█████░░░░░] 50% (2/4 plans done; blocked on API keys)\n' + ); + // 1 of 1 plan complete → 100%. + const phaseDir = path.join(tmpDir, '.planning', 'phases', '01'); + fs.mkdirSync(phaseDir, { recursive: true }); + fs.writeFileSync(path.join(phaseDir, '01-01-PLAN.md'), '# Plan\n'); + fs.writeFileSync(path.join(phaseDir, '01-01-SUMMARY.md'), '# Summary\n'); + + const result = runGsdTools('state update-progress', tmpDir); + assert.ok(result.success); + const updated = fs.readFileSync(path.join(tmpDir, '.planning', 'STATE.md'), 'utf-8'); + assert.ok(/\[██████████\] 100% \(2\/4 plans done; blocked on API keys\)/.test(updated), + 'the machine segment updates to 100% while the suffix is preserved verbatim'); + }); + + test('#2177 no body Progress: line → updated:false even if frontmatter has a progress: key', () => { + fs.writeFileSync( + path.join(tmpDir, '.planning', 'STATE.md'), + ['---', 'progress:', ' percent: 0', '---', '', '# Project State', '', '**Status:** Active', ''].join('\n') + ); + const result = runGsdTools('state update-progress', tmpDir); + assert.ok(result.success); + const out = JSON.parse(result.output); + assert.strictEqual(out.updated, false, + 'a frontmatter progress: key with no body Progress: line must not report a false success'); + }); + test('returns error when STATE.md missing', () => { const result = runGsdTools('state update-progress', tmpDir); assert.ok(result.success, `Command should exit 0: ${result.error}`);