From 80ec0791eb43366ef126dd7150896bd70ad3c785 Mon Sep 17 00:00:00 2001 From: Tom Boucher Date: Fri, 7 Aug 2026 06:16:22 -0400 Subject: [PATCH] fix(#3052): preserve frontmatter last_activity_desc on same-date body prose conflict (#3140) * fix(#3052): preserve frontmatter last_activity_desc on same-date body prose conflict preferNewerLastActivity only preserved last_activity_desc when the derived date was OLDER than the existing frontmatter date. When the dates matched (same-date), the derived body prose (potentially stale) overwrote the authoritative frontmatter desc. Fix: when derDate === exDate, also preserve the frontmatter desc. * chore(#3052): backfill changeset PR number 3140 --------- Co-authored-by: sim --- .changeset/rapid-sloths-romp.md | 5 +++ src/state.cts | 7 ++++ tests/state.test.cjs | 57 +++++++++++++++++++++++++++++++++ 3 files changed, 69 insertions(+) create mode 100644 .changeset/rapid-sloths-romp.md diff --git a/.changeset/rapid-sloths-romp.md b/.changeset/rapid-sloths-romp.md new file mode 100644 index 000000000..a929b46db --- /dev/null +++ b/.changeset/rapid-sloths-romp.md @@ -0,0 +1,5 @@ +--- +type: Fixed +pr: 3140 +--- +**`state planned-phase` no longer overwrites authoritative `last_activity_desc`** — when the frontmatter and body had the same activity date but different descriptions, the write path preserved the date but overwrote the frontmatter's description with stale body prose. Same-date frontmatter desc is now preserved. (#3052) diff --git a/src/state.cts b/src/state.cts index 0d4561b6b..b8e84bb3d 100644 --- a/src/state.cts +++ b/src/state.cts @@ -1392,6 +1392,13 @@ function preferNewerLastActivity( if (existingFm['last_activity_desc'] !== undefined) { derivedFm['last_activity_desc'] = existingFm['last_activity_desc']; } + } else if (derDate === exDate) { + // #3052: same-date — frontmatter is authoritative for this date, so + // preserve its last_activity_desc rather than letting the derived body + // prose (which may be stale) overwrite it. + if (existingFm['last_activity_desc'] !== undefined) { + derivedFm['last_activity_desc'] = existingFm['last_activity_desc']; + } } } diff --git a/tests/state.test.cjs b/tests/state.test.cjs index cb7edb15a..7def31cf7 100644 --- a/tests/state.test.cjs +++ b/tests/state.test.cjs @@ -2895,6 +2895,63 @@ describe('state planned-phase command', () => { }); }); +// ───────────────────────────────────────────────────────────────────────────── +// #3052: state planned-phase must not overwrite authoritative same-date +// last_activity_desc from stale body prose +// ───────────────────────────────────────────────────────────────────────────── + +describe('#3052: planned-phase preserves same-date last_activity_desc', () => { + let tmpDir; + + beforeEach(() => { + tmpDir = createFixture(); + }); + + afterEach(() => { + cleanup(tmpDir); + }); + + test('same-date conflicting desc: frontmatter desc is preserved', () => { + // Frontmatter has authoritative desc; body has stale desc for the SAME date + fs.writeFileSync( + path.join(tmpDir, '.planning', 'STATE.md'), + [ + '---', + 'last_activity: 2020-09-10', + 'last_activity_desc: authoritative description', + '---', + '', + '# Project State', + '', + '**Status:** Planning', + '**Last Activity:** 2020-09-10 — stale description', + '**Current Phase:** 1', + '', + ].join('\n'), + ); + + const result = runGsdTools( + ['state', 'planned-phase', '--phase', '1', '--plans', '3'], + tmpDir, + { GSD_NOW_MS: String(Date.parse('2020-09-10T15:00:00.000Z')) }, + ); + assert.ok(result.success, `Command failed: ${result.error}`); + + const stateContent = fs.readFileSync(path.join(tmpDir, '.planning', 'STATE.md'), 'utf-8'); + // Extract only the frontmatter (between --- fences) to check the desc + const fmMatch = stateContent.match(/^---\r?\n([\s\S]*?)\r?\n---/); + const frontmatter = fmMatch ? fmMatch[1] : ''; + assert.ok( + frontmatter.includes('authoritative description'), + 'frontmatter last_activity_desc must be preserved when same-date body prose conflicts', + ); + assert.ok( + !frontmatter.includes('stale description'), + 'stale body desc must NOT appear in frontmatter (it may remain in body prose)', + ); + }); +}); + // ───────────────────────────────────────────────────────────────────────────── // bug #1070 regression: "Complete ✓" terminal status must yield to planned-phase // ─────────────────────────────────────────────────────────────────────────────