diff --git a/get-shit-done/bin/lib/state.cjs b/get-shit-done/bin/lib/state.cjs index dd3845c3b..d93f2e2ac 100644 --- a/get-shit-done/bin/lib/state.cjs +++ b/get-shit-done/bin/lib/state.cjs @@ -733,9 +733,20 @@ function stripFrontmatter(content) { } function syncStateFrontmatter(content, cwd) { + // Read existing frontmatter BEFORE stripping — it may contain values + // that the body no longer has (e.g., Status field removed by an agent). + const existingFm = extractFrontmatter(content); const body = stripFrontmatter(content); - const fm = buildStateFrontmatter(body, cwd); - const yamlStr = reconstructFrontmatter(fm); + const derivedFm = buildStateFrontmatter(body, cwd); + + // Preserve existing frontmatter status when body-derived status is 'unknown'. + // This prevents a missing Status: field in the body from overwriting a + // previously valid status (e.g., 'executing' → 'unknown'). + if (derivedFm.status === 'unknown' && existingFm.status && existingFm.status !== 'unknown') { + derivedFm.status = existingFm.status; + } + + const yamlStr = reconstructFrontmatter(derivedFm); return `---\n${yamlStr}\n---\n\n${body}`; } diff --git a/tests/state.test.cjs b/tests/state.test.cjs index 7f86f25fc..82023c347 100644 --- a/tests/state.test.cjs +++ b/tests/state.test.cjs @@ -475,6 +475,30 @@ describe('STATE.md frontmatter sync', () => { assert.ok(content.includes('status: paused'), 'frontmatter should reflect latest status'); }); + test('preserves frontmatter status when body Status field is missing', () => { + // Simulate: frontmatter has status: executing, but body lost Status: field + fs.writeFileSync( + path.join(tmpDir, '.planning', 'STATE.md'), + `--- +status: executing +milestone: v1.0 +--- + +# Project State + +**Current Phase:** 03 +**Current Plan:** 03-02 +` + ); + + // Any writeStateMd triggers syncStateFrontmatter — use state update on a field that exists + runGsdTools('state update "Current Plan" "03-03"', tmpDir); + + const content = fs.readFileSync(path.join(tmpDir, '.planning', 'STATE.md'), 'utf-8'); + assert.ok(content.includes('status: executing'), 'should preserve existing status, not overwrite with unknown'); + assert.ok(!content.includes('status: unknown'), 'should not contain unknown status'); + }); + test('round-trip: write then read via state json', () => { fs.writeFileSync( path.join(tmpDir, '.planning', 'STATE.md'),