fix: state mutations recurse when Current Position has dollar amounts (#3463)

* fix(state): prevent  backreference expansion in state mutations

* chore(changeset): set pr field for #3463

* test(state): use structured STATE parser in bug-3454 regression
This commit is contained in:
Tom Boucher
2026-05-13 13:54:30 -04:00
committed by GitHub
parent d0f916728b
commit c5d4cf35e6
5 changed files with 171 additions and 7 deletions

View File

@@ -533,6 +533,21 @@ describe('stateBeginPhase', () => {
const content = await readFile(join(tmpDir, '.planning', 'STATE.md'), 'utf-8');
expect(content).toContain('Plan: 1 of 3');
});
it('preserves literal dollar amounts in Current Position body', async () => {
const { stateBeginPhase } = await import('./state-mutation.js');
const withBudget = MINIMAL_STATE.replace(
'Last activity: 2026-04-08 -- Phase 10 execution started',
'Last activity: 2026-04-08 -- Phase 10 execution started\nBudget: $2,500 max test',
);
await setupTestProject(tmpDir, withBudget);
await stateBeginPhase(['11', 'State Mutations', '3'], tmpDir);
const content = await readFile(join(tmpDir, '.planning', 'STATE.md'), 'utf-8');
expect(content).toContain('Budget: $2,500 max test');
expect((content.match(/^Budget:/gm) || []).length).toBe(1);
});
});
// ─── stateAdvancePlan ───────────────────────────────────────────────────────
@@ -557,6 +572,23 @@ describe('stateAdvancePlan', () => {
expect(data.advanced).toBe(true);
expect(data.current_plan).toBe(3);
});
it('keeps literal dollar amounts stable after multiple updates', async () => {
const { stateAdvancePlan } = await import('./state-mutation.js');
const withBudget = MINIMAL_STATE.replace(
'Last activity: 2026-04-08 -- Phase 10 execution started',
'Last activity: 2026-04-08 -- Phase 10 execution started\nBudget: $2,500 max test',
).replace('Plan: 2 of 3', 'Plan: 1 of 20');
await setupTestProject(tmpDir, withBudget);
for (let i = 0; i < 8; i += 1) {
await stateAdvancePlan([], tmpDir);
}
const content = await readFile(join(tmpDir, '.planning', 'STATE.md'), 'utf-8');
expect(content).toContain('Budget: $2,500 max test');
expect((content.match(/^Budget:/gm) || []).length).toBe(1);
});
});
// ─── stateAddDecision ───────────────────────────────────────────────────────