fix(#2202): preserve unknown frontmatter keys in syncStateFrontmatter (#2233)

* fix(#2202): preserve unknown frontmatter keys in syncStateFrontmatter

syncStateFrontmatter rebuilds frontmatter from a fixed schema, dropping any
custom/unknown key on every mutating verb. Before reconstruction, merge any
existing frontmatter key the schema does not own. Schema keys still win.

Closes #2202

* docs(#2202): add changeset fragment

* docs(#2202): backfill PR number

* fix(#2202): add regression test + remove redundant type assertion

- tests/state.test.cjs: behavioral regression test asserting custom/unknown
  STATE.md frontmatter keys survive a mutating verb (they were silently dropped
  before the syncStateFrontmatter carry-forward).
- src/state.cts: drop the unnecessary `as Record<string, unknown>` assertion
  that tripped @typescript-eslint/no-unnecessary-type-assertion (the lint-tests
  gate failure).

Refs #2202

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Tom Boucher
2026-07-13 12:40:52 -04:00
committed by GitHub
parent ffd353f080
commit 7ccf57200d
3 changed files with 46 additions and 0 deletions

View File

@@ -1650,6 +1650,16 @@ function syncStateFrontmatter(content: string, cwd: string | undefined): string
derivedFm['progress'] = normalizeProgressNumbers(existingFm['progress']);
}
// #2202: carry forward any existing frontmatter key that the schema does not
// own, so custom/unknown keys are not silently dropped on every mutating verb.
// Schema-owned keys (already in derivedFm from buildStateFrontmatter + the
// preserve guards above) still win.
for (const key of Object.keys(existingFm)) {
if (!(key in derivedFm) && existingFm[key] !== undefined) {
derivedFm[key] = existingFm[key];
}
}
const yamlStr = reconstructFrontmatter(derivedFm as unknown as Frontmatter);
return `---\n${yamlStr}\n---\n\n${body}`;
}