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 <sim@local>
This commit is contained in:
Tom Boucher
2026-08-07 06:16:22 -04:00
committed by GitHub
parent 3c1e358a2d
commit 80ec0791eb
3 changed files with 69 additions and 0 deletions

View File

@@ -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)

View File

@@ -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'];
}
}
}

View File

@@ -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
// ─────────────────────────────────────────────────────────────────────────────