Skip to content

T-PTDG-retire-pyyaml-emitter

Status: closed/done · Impact: medium · Complexity: small

lib/util/yaml.ts carried a ~550-line port of PyYAML’s emitter (analyze_scalar, implicit resolvers, column-tracking scalar writers), kept byte-identical to the retired Python implementation’s yaml.safe_dump output. The Python substrate is fully deleted, so the byte-parity contract has no counterparty — the port is pure legacy. Replace it with the npm yaml package’s serializer under one house options object, and re-state the emitter’s invariants as SEMANTIC (round-trip equality + idempotence), not byte parity with a dead codebase.

LocationRole today
apps/sdlc/lib/util/yaml.tsThe PyYAML emitter port: PlainScalar, implicit-resolver regexes, Emitter scalar writers, pyStrip — behind dumpFrontmatter.
apps/sdlc/lib/util/yaml.test.tsPins PyYAML represent_none parity output.
apps/sdlc/lib/model/entity.tsScaffold assembly via dumpFrontmatter (new files).
apps/sdlc/lib/model/authoring.tsauthorEntity emission via dumpFrontmatter (new files).
apps/sdlc/lib/model/ops/migrate.tsWhole-block re-emission during migrations.
apps/sdlc/lib/util/diagnostics.tsProse says the PyYAML emitter “stays” — stale.
apps/sdlc/skills/task-ensure-ready/ensure_ready_mutate.tsHeader still claims PyYAML-compatible emission; the code already moved to span-preserving edits.
apps/sdlc/skills/entities-migrate/tests/entities_migrate.test.tsEval pins a PyYAML-quoted timestamp.
  • lib/util/yaml.ts becomes the thin canonical seam: parse/load re-exports, dumpFrontmatter over yaml’s stringify with one exported house options object (sortMapEntries: false, lineWidth: 0, singleQuote: true, indentSeq: false — chosen to match the corpus style), coercion (coerceYamlValue/coerceFrontmatter) retained so raw numbers land as the strings the schemas declare. PlainScalar/plain and the emitter machinery are deleted.
  • Invariants become semantic: parse(dump(x)) deep-equals x; dumping is idempotent. Visible style deltas (measured over the 795-file corpus): date/timestamp-like strings emit bare (they were quoted only so PyYAML would not retype them — no YAML-1.1 reader remains), multiline strings emit as block scalars, quote style falls to the yaml package’s rules. Only whole-block writers restyle existing files (scaffold/authoring touch new files only; migrate runs are already restyle events; all in-place field writers are span-preserving via @sksizer/yaml-splice), so there is no corpus churn outside deliberate rewrites.
  • Stale PyYAML prose corrected (diagnostics.ts, ensure_ready_mutate.ts); remaining mentions are historical narration only.
  1. Rewrite lib/util/yaml.ts (delete the emitter port; keep the parse chokepoint, YamlValue, coercion; add the house options export).
  2. Replace yaml.test.ts parity pins with house-style pins + semantic round-trip/idempotence cases over representative frontmatter shapes.
  3. Update the one golden that pinned PyYAML quoting (entities_migrate.test.ts bare-timestamp expectation) and the stale prose in diagnostics.ts / ensure_ready_mutate.ts.
  4. Full suite + typecheck.
LocationKindChange
apps/sdlc/lib/util/yaml.tsmodifyEmitter port deleted; thin stringify seam + house options.
apps/sdlc/lib/util/yaml.test.tsmodifyParity pins → house-style + semantic invariants.
apps/sdlc/lib/util/diagnostics.tsmodifyStale “emitter stays” prose corrected.
apps/sdlc/skills/task-ensure-ready/ensure_ready_mutate.tsmodifyStale PyYAML-emission header corrected.
apps/sdlc/skills/entities-migrate/tests/entities_migrate.test.tsmodifyQuoted-timestamp golden → bare.
  • AC-1: lib/util/yaml.ts contains no PyYAML emulation (no implicit resolver regexes, no scalar writers, no PlainScalar); dumpFrontmatter is a stringify call under the exported house options.
  • AC-2: yaml tests assert semantic round-trip + idempotence and the house style (bare dates, indentless sequences, '5' coercion, block scalars); no test in the repo requires byte parity with PyYAML.
  • AC-3: full bun test for apps/sdlc passes and tsc --noEmit is clean; the only golden change is the migrate eval’s bare timestamp.
  • AC-4: remaining “PyYAML” mentions in apps/sdlc source are historical narration only (grep-verifiable — no live requirement).
  • Corpus-wide restyle commit: not needed — in-place writers are span-preserving, so existing files restyle only under deliberate whole-block rewrites (migrate).
  • @sksizer/yaml-splice and the span-preserving field editors — already the write path; untouched.
  • Historical references to PyYAML in docs/planning entities (records, not requirements).
  • none
  • Operator directive (2026-07-31): “ditch any legacy requirements based on pyyaml, as we are no longer using it at all.” Surfaced while discussing why start_task.ts stayed out of T-LWEH’s scope (its then byte-faithful PyYAML re-emission); the write paths had since moved to span-preserving edits, leaving the emitter port with no remaining justification.

← Back to Tasks