T-68IB-adopt-yaml-splice-and-reconcile-plan
Status: closed/done · Impact: medium · Complexity: large
Turn yaml-splice from an extracted seed into an adopted library on the SDLC (TypeScript)
side — the writer-migration half of
M-2VYE-yaml-splice-extraction-and-writer-migrations. Migrate the four SDLC TypeScript
frontmatter field-edit writers off their parse-and-reserialize (dumpFrontmatter) paths
onto @sksizer/yaml-splice’s span-preserving editYaml, so a field edit changes only the
edited field’s bytes and human comments, key order, and quoting survive. This deletes the
whole-block reserialize side effect — the active corruption risk (lost YAML comments)
D-7N7I names — from every core SDLC write path. The Rust-consumer adoption already shipped
in #1064, so this task carries the second-plus consumers that clear the derived-library
promotion bar.
The extraction (T-3MZD) plus the Rust-consumer adoption (#1064) are done; the crate is
published to TypeScript as @sksizer/yaml-splice over WASM. On the SDLC side the field-edit
writers still parse the block, mutate a Record, and re-serialize the WHOLE frontmatter via
dumpFrontmatter — recanonicalizing (and so destroying comments / human key order) on every
write.
| Location | Role today |
|---|---|
apps/sdlc/lib/model/ops/_update.ts | shared frontmatter-set engine (task/backlog/standard update, standard supersede) — reserializes the whole block via dumpFrontmatter |
apps/sdlc/lib/model/entities/task/prs.ts | appends a URL to the prs: array — reserializes the whole block |
apps/sdlc/skills/task-work/start_task.ts | sets status + last_reviewed (and appends the post-mortem body stub) — reserializes the whole block, with bespoke PyYAML bare-scalar bookkeeping to stay byte-faithful |
apps/sdlc/skills/task-ensure-ready/ensure_ready_mutate.ts | pass/fail gate: sets readiness_verified_at, downshifts status, sets definition_gap — reserializes the whole block |
whole-document generators (model/authoring.ts, model/entity.ts, model/ops/migrate.ts) | compose / re-canonicalize whole documents — correctly stay on dumpFrontmatter |
Proposed
Section titled “Proposed”The four field-edit writers migrated onto editYaml through one shared
editFrontmatterFields seam; only the edited field’s bytes change and comments / key order /
quoting elsewhere survive verbatim; the whole-document generators left on canonical
dumpFrontmatter; existing tests reconciled to the span-preserving expectation; and a new
test proving a # comment survives a field edit. dumpFrontmatter stays in util/yaml.ts
for the generators.
Approach
Section titled “Approach”- Add the shared seam.
editFrontmatterFields(text, edits)inlib/util/frontmatter.ts—splitFrontmatterFences→editYaml(payload, edits)→ strip the one trailing newlineeditYamlmay add →joinFrontmatterFences.editYamlowns only the YAML payload; the fences and body stay lexical. - Migrate
_update.ts. Keep the value-based change detection + schema gate (build the merged view for validation), but emit aSet/RemoveFieldEdit[]batch and write via the seam instead ofdumpFrontmatter. Anullupdate is aRemove; a differing value is aSet; a semantically-equal value is a no-op that touches no bytes. - Migrate
prs.ts,start_task.ts,ensure_ready_mutate.tsthe same way.start_taskloses its whole bare-scalar PyYAML-fidelity machinery (span-preservation makes it moot) and appends the post-mortem stub to the body separately;ensure_readybuilds pass/failFieldEdit[]batches (presence-guardedRemoves). - Leave the generators canonical.
authoring.ts,entity.ts,migrate.tskeepdumpFrontmatter;entities auditvalidates schema, not canonical form, so a span-preserved (bare-date / comment-bearing) entity introduces no new drift. - Reconcile tests + prove the payoff. Update the few tests that asserted the old
reserialized output (bare vs single-quoted inserted dates) and add a comment-preservation
test through the migrated
_update.ts.
Files to touch
Section titled “Files to touch”| Location | Kind | Change |
|---|---|---|
apps/sdlc/lib/util/frontmatter.ts | modify | add the editFrontmatterFields span-preserving seam over editYaml |
apps/sdlc/lib/model/ops/_update.ts | modify | emit FieldEdit[] + write via the seam; drop the dumpFrontmatter reserialize |
apps/sdlc/lib/model/entities/task/prs.ts | modify | set prs: via the seam (drop the fm param); update the append_pr_url / close-commit callers |
apps/sdlc/skills/task-work/start_task.ts | modify | set status/last_reviewed via the seam; delete the bare-scalar PyYAML bookkeeping |
apps/sdlc/skills/task-ensure-ready/ensure_ready_mutate.ts | modify | pass/fail FieldEdit[] batches via the seam |
apps/sdlc/package.json, bun.lock, tsconfig.json | modify | add the @sksizer/yaml-splice workspace dep + tsc paths resolution |
packages/ts/yaml-splice/index.ts | modify | one-line exactOptionalPropertyTypes compat fix surfaced by the strict consumer |
Acceptance criteria
Section titled “Acceptance criteria”- AC-1:
editFrontmatterFieldsexists inlib/util/frontmatter.tsand edits only the named fields’ bytes viaeditYaml, leaving fences and body lexical. - AC-2: All four writers (
_update.ts,prs.ts,start_task.ts,ensure_ready_mutate.ts) buildFieldEdit[]batches and write through the seam; no field-edit writer callsdumpFrontmatter. - AC-3: A field change through
_update.tspreserves a#comment, unedited fields’ quoting, and key order — pinned by a new test. - AC-4: The whole-document generators (
authoring.ts,entity.ts,migrate.ts) keepdumpFrontmatter;dumpFrontmatterstays inutil/yaml.ts. - AC-5:
bunx tsc --noEmitis clean,bun test apps/sdlcis fully green (tests reconciled to the span-preserving output), andentities auditshows no new drift.
Out of scope
Section titled “Out of scope”- The crate implementation itself — T-3MZD-extract-yaml-splice-crate.
- Whole-document canonical write and the lossless markdown CST core.
- Nested-path, sequence-item, and merge-patch edits — D-7N7I defers to V2.
Dependencies
Section titled “Dependencies”- T-3MZD-extract-yaml-splice-crate — the crate must exist before adoption or writer
migration can begin. Recorded in
depends_on.
Discovery context
Section titled “Discovery context”- From the same polish-experience review (PR #1042) that produced T-3MZD. M-2VYE’s success criteria (two writer migrations off the reserialize path, machine-writer classification, the ≥2-consumer promotion bar) are the executable core of this task.
Depends on
Section titled “Depends on”T-3MZD