Skip to content

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.

LocationRole today
apps/sdlc/lib/model/ops/_update.tsshared frontmatter-set engine (task/backlog/standard update, standard supersede) — reserializes the whole block via dumpFrontmatter
apps/sdlc/lib/model/entities/task/prs.tsappends a URL to the prs: array — reserializes the whole block
apps/sdlc/skills/task-work/start_task.tssets 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.tspass/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

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.

  1. Add the shared seam. editFrontmatterFields(text, edits) in lib/util/frontmatter.tssplitFrontmatterFenceseditYaml(payload, edits) → strip the one trailing newline editYaml may add → joinFrontmatterFences. editYaml owns only the YAML payload; the fences and body stay lexical.
  2. Migrate _update.ts. Keep the value-based change detection + schema gate (build the merged view for validation), but emit a Set/Remove FieldEdit[] batch and write via the seam instead of dumpFrontmatter. A null update is a Remove; a differing value is a Set; a semantically-equal value is a no-op that touches no bytes.
  3. Migrate prs.ts, start_task.ts, ensure_ready_mutate.ts the same way. start_task loses its whole bare-scalar PyYAML-fidelity machinery (span-preservation makes it moot) and appends the post-mortem stub to the body separately; ensure_ready builds pass/fail FieldEdit[] batches (presence-guarded Removes).
  4. Leave the generators canonical. authoring.ts, entity.ts, migrate.ts keep dumpFrontmatter; entities audit validates schema, not canonical form, so a span-preserved (bare-date / comment-bearing) entity introduces no new drift.
  5. 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.
LocationKindChange
apps/sdlc/lib/util/frontmatter.tsmodifyadd the editFrontmatterFields span-preserving seam over editYaml
apps/sdlc/lib/model/ops/_update.tsmodifyemit FieldEdit[] + write via the seam; drop the dumpFrontmatter reserialize
apps/sdlc/lib/model/entities/task/prs.tsmodifyset prs: via the seam (drop the fm param); update the append_pr_url / close-commit callers
apps/sdlc/skills/task-work/start_task.tsmodifyset status/last_reviewed via the seam; delete the bare-scalar PyYAML bookkeeping
apps/sdlc/skills/task-ensure-ready/ensure_ready_mutate.tsmodifypass/fail FieldEdit[] batches via the seam
apps/sdlc/package.json, bun.lock, tsconfig.jsonmodifyadd the @sksizer/yaml-splice workspace dep + tsc paths resolution
packages/ts/yaml-splice/index.tsmodifyone-line exactOptionalPropertyTypes compat fix surfaced by the strict consumer
  • AC-1: editFrontmatterFields exists in lib/util/frontmatter.ts and edits only the named fields’ bytes via editYaml, leaving fences and body lexical.
  • AC-2: All four writers (_update.ts, prs.ts, start_task.ts, ensure_ready_mutate.ts) build FieldEdit[] batches and write through the seam; no field-edit writer calls dumpFrontmatter.
  • AC-3: A field change through _update.ts preserves 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) keep dumpFrontmatter; dumpFrontmatter stays in util/yaml.ts.
  • AC-5: bunx tsc --noEmit is clean, bun test apps/sdlc is fully green (tests reconciled to the span-preserving output), and entities audit shows no new drift.
  • 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.
  • 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.

T-3MZD


← Back to Tasks