Extract readModifyWriteStateMd's post-sync preservation block into a pure,
field-classification-table-driven applyStatePreservation in the STATE.md
Transition Module. progress / status / stopped_at now join current_phase_name
as table-governed (getFieldClassification), so a preservation-policy change is
a one-row table edit instead of a per-call-site patch.
This realizes the consolidation ADR-1769 / CONTEXT.md already claimed shipped
('Absorbs readModifyWriteStateMd post-sync preservation block') and routes the
#1264 preservation policy through the single field-classification table — the
bug class is now structurally guarded by the table, not just the call-site
shouldResync flag.
Behavior is byte-identical to the pre-amendment inline block (Hyrum-safe — the
15 readModifyWriteStateMd callers' observable preservation is unchanged):
- state/frontmatter/transition + bug regression suite: 847 pass
- phase/milestone/verify (other RMW consumers): 582 pass
- codex (gpt-5.5/high) adversarial review: CLEAN (58,564-case equiv sweep)
ADR-1769 amendment appended documenting #1796.
Closes#1796
Migrate cmdStateSync, cmdStatePrune, cmdStateUpdate (state.cts) onto the
STATE.md Transition Module substrate and close the maintenance bug pair
#1760/#1761 (ADR-1769, epic #1769). Completes the substrate: all 10
lifecycle/maintenance transitions now route through transitionCore.
- Add {kind: 'sync'|'prune'|'update'} to StateTransitionIntent with syncCore,
pruneCore, updateCore in src/state-transition.cts.
- updateCore: body-only single-field update (strip/reassemble), mirroring the
pre-migration cmdStateUpdate contract.
- pruneCore: pure content→content section pruning (Decisions / Recently
Completed / resolved Blockers / Performance Metrics rows at or below cutoff),
byte-identical tokenizeHeadings splicing. Adapter owns currentPhase, dry-run,
and STATE-ARCHIVE.md.
- syncCore: body writes (Total Plans in Phase, Progress bar, Last Activity)
given disk-derived numbers. Adapter owns the disk scan + roadmap scope.
- #1760: cmdStatePrune now derives currentPhase from 'Current Phase' OR 'Phase'
(the canonical template emits 'Phase: X of Y'), so prune engages on
template-conformant STATE.md instead of bailing 'Only 0 phases'.
- #1761: cmdStateSync skips the Progress write (percent=null) when a milestone
version is set in frontmatter but the ROADMAP has no versioned heading for it
(milestone cannot be bounded). Projects without a milestone version are
unaffected. Leaves progress untouched rather than silently writing fallback-
derived wrong values.
- Regression: bug-1760 (prune engages on template field) + bug-1761 (sync
leaves progress untouched when unbounded). ADR-1769 marked Accepted.
All 82 transition + 515 regression tests pass.
Closes#1793
Migrate cmdStatePatch (state.cts) onto the STATE.md Transition Module substrate
and close the curated-field clobber bug class #1743/#1695 (ADR-1769, epic #1769).
- Add {kind: 'patch'} to StateTransitionIntent, with patchCore in
src/state-transition.cts. Applies each caller-supplied {field:value} pair via
stateReplaceField over the full content (body + frontmatter), tracking
updated vs. failed. data.updated/data.failed mirror the CLI output shape.
- Collapse cmdStatePatch to a transitionCore dispatch. Field-name validation
(security) and the resync-progress decision stay in the adapter.
- #1695/#1743 fix: extend the #1230 delta heuristic in readModifyWriteStateMd to
the curated current_phase_name, table-driven via
getFieldClassification('current_phase_name').preservation === 'preserve-always'.
When a write does NOT change the body Phase: source line, the curated
frontmatter value wins over syncStateFrontmatter's body re-derivation (which
harvests a wrong parenthetical aside — #1695). begin/planned/complete-phase
rewrite their body Phase line, so the delta does not fire for them and
current_phase_name still advances.
- Regression: bug-1695-state-patch-clobbers-phase-name.test.cjs — unrelated
patch preserves curated current_phase_name; patching the Phase source advances.
All 70 transition + 493 regression tests pass (state/phase/milestone + #905/#397/#3242 lineages).
Closes#1791
Migrate the STATE.md write path inside cmdMilestoneComplete (milestone.cts)
onto the STATE.md Transition Module substrate (ADR-1769, epic #1769).
- Add {kind: 'milestoneComplete'} to StateTransitionIntent, with
milestoneCompleteCore in src/state-transition.cts (consulting the
field-classification table). Owns the closure write: Status
('<version> milestone complete'), Last Activity, Last Activity Description,
a Current Position reset to 'Awaiting next milestone', and an Operator Next
Steps reset pointing at the next-milestone command.
- Collapse the inline STATE.md transform in cmdMilestoneComplete to a
transitionCore dispatch. The adapter retains writeStateMd (lock + steady-state
syncStateFrontmatter post-sync) and resolves the runtime-specific
next-milestone slash command, injecting it via intent.nextMilestoneCommand.
- The two section resets carry their pre-seam allow-adhoc-markdown waivers
(regex semantics pinned by existing tests; pending collectSection #1372).
- realClock.today() is byte-identical to milestone.cts's local today
(both new Date().toISOString().split('T')[0]).
- Characterization tests pin field updates, both section resets (replace +
insert paths), and frontmatter #1255 parity.
All 66 transition + milestone + state tests pass.
Closes#1789
Migrate cmdStatePlannedPhase and cmdStateMilestoneSwitch (state.cts) onto the
STATE.md Transition Module substrate (ADR-1769, epic #1769).
- Add {kind: 'plannedPhase'} and {kind: 'milestoneSwitch'} to
StateTransitionIntent, with plannedPhaseCore and milestoneSwitchCore in
src/state-transition.cts (consulting the field-classification table).
- plannedPhaseCore owns the template-aware Status/Last Activity updates, Total
Plans in Phase, Last Activity Description, and the Current Position section
(via the inlined mutateCurrentPositionForAdvance twin). The adapter keeps the
resync:false readModifyWriteStateMd wrapper (#500 RC1).
- milestoneSwitchCore owns the new-milestone reset: rebuilt frontmatter
(milestone/name/status='planning'/zeroed progress) + Current Position body
reset. The adapter keeps acquireStateLock + platformWriteSync (NOT
readModifyWriteStateMd — milestoneSwitch rebuilds frontmatter directly).
- Collapse both callbacks to transitionCore dispatches. The now-dead
updateCurrentPositionFields helper and KNOWN_STATUS_PATTERNS import are
removed (their behavior lives in the transition module's
mutateCurrentPositionForAdvance).
- Characterization tests pin the template-aware preserve-authored invariant,
Total Plans, Last Activity narrative, Current Position update, and the full
milestone reset (frontmatter + position body + gsd_state_version preserve +
Accumulated Context preserve).
All 58 transition + 177 state + phase + bug-2630/bug-905 regression tests pass.
Closes#1786
Migrate the STATE.md write path inside cmdPhaseComplete (phase.cts) onto the
STATE.md Transition Module substrate (ADR-1769, epic #1769).
- Add {kind: 'completePhase'} to StateTransitionIntent + completePhaseCore in
src/state-transition.cts. Pure body field mutations (Current Phase shape/name,
Status, Current Plan, Last Activity + Description, Completed/Total Phases +
Progress percent), consulting the field-classification table for touched keys.
Roadmap progress injected via a new optional deps.roadmapProvider.
- Collapse the ~90-line inline STATE.md transform in cmdPhaseComplete to a
transitionCore dispatch. The adapter retains updatePerformanceMetricsSection
(section table) + syncStateFrontmatter (disk-scan post-sync) and the
multi-file atomic transaction (STATE is committed with ROADMAP/REQUIREMENTS,
so readModifyWriteStateMd is not used here).
- deriveProgressFromRoadmap/clampPercent/stateReplaceFieldWithFallback move from
the phase.cts call site into the pure core (no circular dep: phase-lifecycle
and state-document don't import state.cts).
- Characterization tests in tests/state-transition.test.cjs pin the field
updates, roadmap progress derivation, #1255 frontmatter parity, and the
'Phase:' fallback. All 44 transition + 193 phase tests pass.
No user-visible behavior change. cmdPhaseComplete CLI output and 'phase complete'
behavior unchanged.
Closes#1784