The hand-rolled MD parser is replaced with a markdown-it token-stream
walker. AstTable and AstCodeBlock are dropped from the AST — the
substrate doesn't address into table rows or fence content, and
markdown-it's tokenizer already handles "##/- inside fenced code
should not be a heading/item" correctly without first-class AST
modeling.
Grammar opinions move from parser to lint:
- Indented `## foo` (1-3 spaces) is now a heading
- Empty `## ` is a heading with empty slug
- Ordered lists (`1. step`) become items
- Nested sub-bullets become items at flat level
Each was previously a silent parser refusal — now they are recognized
shapes. Lint rules can flag them (`OC_HEADING_INDENTED`,
`OC_HEADING_EMPTY`, etc.) where authoring conventions require the
narrower shape.
Net: parse.ts drops 301 → 207 LoC; tables/code-blocks scenario tests
removed wholesale (-251 LoC of test surface that pinned dead AST
fields).
The YAML AST/parser/emitter/edit-resolve module is removed. The
substrate now supports md / jsonc / jsonl. Walker, universal verbs,
CLI, and tests are stripped of yaml-kind branches; the `yaml` package
dependency is dropped.
Why: YAML editing was the most complex per-kind module (~750 LoC of
parse/emit/resolve plus walker scaffold) for the smallest surface area
in real-world usage — substrate consumers (lkg, gateway config, agent
metadata) all pivoted to jsonc / md / jsonl. Carrying yaml support
indefinitely was net cost.
Walker depth-cap test that previously relied on YAML's lack of a
parser-level depth cap is rewritten to construct a synthetic JSONC AST
chain by hand, exercising the walker's MAX_TRAVERSAL_DEPTH defense in
isolation from the parser's MAX_PARSE_DEPTH.
Net: -1467 LoC across substrate + tests.
Closes three load-bearing gaps in the substrate's defenses:
- **md sentinel defense-in-depth (I2)**: `setJsoncOcPath` and the jsonl
finalize-via-render path both refuse sentinel-bearing values before
they reach the AST. The md path was deferring entirely to round-trip
echo through `emitMd`, which `acceptPreExistingSentinel` skips by
default. Closes the cross-kind asymmetry.
- **jsonc byte-length cap (N3)**: pre-parse cap on input length, symmetric
with the existing post-parse depth cap at `nodeToJsoncValue`. Without
this, `parseTree` allocates the full tree before our walker notices.
- **md walker union + predicate parity (I3)**: jsonc walkers dispatched
union and predicate at every slot; md dispatched only on
wildcard/ordinal/positional/literal, so `oc://X.md/{Boundaries,Limits}/...`
matched zero items where the same shape on jsonc would match both.
Pins the parity.