Mechanical rename produced by scripts/msd-rename.cjs: gsd/Gsd/GSD -> msd/Msd/MSD across contents and paths, upstream package/repo coordinates -> @golem15/msd-core and golem15com/msd-core. Deep links into upstream history, sibling upstream packages, the GSD-2 import feature, CHANGELOG.md and .changeset/ are kept as-is. Hand edits on top: MSD block-letter banner and logos, LICENSE copyright line, package/plugin identity, regenerated lockfile, install-tree fixtures, derived registries and benchmark baseline; migration checksum baseline re-locked (MSD keeps its own install state, so no install had applied the old sums); sort-order and regex-escaped expectations in tests adjusted.
6.4 KiB
How to change the STATE.md schema
Every key in .planning/STATE.md's frontmatter is declared once, in
src/state-md-schema.cts. The field-classification tables, the shipped template and the five
reference documents are all derived from it. This page is how you add, change or remove a key
without any of those falling out of step.
If you only want to know what the keys are, read the STATE.md reference instead.
Add a key
1. Declare it in src/state-md-schema.cts.
my_new_key: {
type: 'string',
cardinality: 'optional',
source: 'body',
preservation: 'preserve-when-unchanged',
bodySource: ['My New Key'],
bodyLabel: 'My New Key',
emitted: 'when-present',
},
Every column is required except enum, guard, mergeStrategy, bodySource, bodyLabel and
acceptedShapes. What each means is documented on the type itself — read it there rather than
copying a neighbouring row and hoping.
2. Build, then regenerate. In that order.
npm run build:lib
npm run regen:derived
The order matters and getting it wrong fails quietly. The generator reads the compiled
msd-core/bin/lib/state-md-schema.cjs, not the TypeScript source. Regenerating before building
regenerates against the previous schema, produces artifacts that look plausible, and commits a
document that disagrees with the code you just wrote. If you are ever unsure whether the build is
current, run npm run build:lib again — it is cheap and idempotent.
3. Commit the regenerated artifacts. They are generated and committed:
msd-core/templates/state.mddocs/reference/state-md.mdand itsja-JP,zh-CN,ko-KR,pt-BRsiblings
4. Add the human-facing rows by hand. Two tables in the reference documents are deliberately not generated — see What is generated and what is not. Add your key's row to the Field reference table in each locale. The parity check will tell you if you miss one.
Change or remove a key
Same two commands. Removing a key also means removing its row from the Field-reference tables in all five locales, or the parity check fails naming each one.
Before you change a key's preservation or source, read
ADR-3408 §8 — those columns drive what survives a
STATE.md write, and a change there is a behavior change, not a documentation edit.
What the check is telling you
npm run lint:generated-sync runs node scripts/gen-state-md-docs.cjs --check, and lint:ci runs
it for you. It exits non-zero with a reason code and the file and region involved.
| Reason | What happened | What to do |
|---|---|---|
region_stale |
A generated region does not match what the schema would produce. | npm run build:lib && npm run regen:derived, then commit the result. |
markers_missing |
A target file has no STATE-MD-SCHEMA marker pair for a region. |
Add the marker pair where the region belongs. The generator never invents a location. |
marker_unclosed |
A :START: marker has no matching :END:. |
Fix the markers. The generator refuses to write rather than guess where the region ends — a wrong guess would eat hand-written prose. |
field_reference_drift |
The Field-reference table's row set disagrees with the schema. | Add the missing row, or remove the row for a key that no longer exists. |
status_values_drift |
The Status-values table disagrees with the schema's status enum. |
Same. |
--json gives you the same information structurally if you are scripting against it.
What is generated and what is not
| Region | Generated? |
|---|---|
| The template's frontmatter block | yes |
### Status lifecycle |
yes, in all five locales |
### Field cardinality |
yes, in all five locales |
| Field reference table | no — row set parity-checked only |
| Status values table | no — row set parity-checked only |
| All prose outside a marked region | no, ever |
The last three are the point. The Field-reference and Status-values tables carry per-row prose —
Purpose, When populated, Matched text — that is genuinely hand-translated. The Japanese
Matched-text column reads `discussing` を含む, not the English. Generating those tables from
a single English source would overwrite four languages' translations every time anyone regenerated.
So the schema owns the row set, which is what drift actually means, and translators own the
prose.
If you edit inside a marked region, the next --write will overwrite you and --check will report
it first. If you edit outside one, nothing touches it.
Adding a language
Copy an existing locale's reference/state-md.md, translate the prose, and keep the
STATE-MD-SCHEMA marker pairs where they are. Then run the two commands above; the generator fills
every marked region for the new locale, and the parity check starts holding it to the same row set
as the rest.
Column headers come from a per-locale string table in the generator — add yours there so the generated tables are not headed in English.
Keys the schema does not model
active_phase, next_action and next_phases are real frontmatter keys (#2833)
that are documented but sit outside the schema. They are grandfathered by exact name in
KNOWN_SCHEMA_GAP_FIELDS, so the parity check tolerates those three and no others — a fourth
undocumented key fails, which is what stops the list quietly becoming a wildcard.
If you are adding one of those three to the schema properly, remove its name from that list in the same change.
Why the schema exists
Before it, this key set was declared in four places that had to agree by hand, and they did not:
one table carried last_activity, another did not. The five reference documents disagreed too —
the section documenting the status values was missing from all four translations, and it is the
section that matters for #3853.
ADR-3473 §8.8 is the contract, and its governing idea is worth keeping in mind when you edit the schema: the schema declares what the code does, not what it should do. If you find yourself writing a row that describes intended behavior, you are writing a document that lies — declare today's behavior and fix the code separately.