Thread sourceReplyDeliveryMode into Codex/OpenClaw tool construction and annotate the message tool description for message-tool-only turns so visible replies use message(action=send).\n\nAlso adds focused regression coverage and a changelog entry.
Removes four path-syntax features that added complexity without
enough real-world use:
- **Quoted-segment escape sequences**: kept quoted segments themselves
(still needed for keys with `/` or `.`) but dropped `\"` and `\`
escapes. Quoted content is now byte-literal, refused if it contains
`"` or `\`. Simplifies scanBracketAware (drops the `escaped` state),
unquoteSeg (no decode loop), quoteSeg (no escape encoding).
- **CSS-style predicate operators**: removed `*=` (substring), `^=`
(prefix), `$=` (suffix). Predicate set is now `=`, `!=`, and the
numeric `<` / `<=` / `>` / `>=`. Lint rules needing substring
matching can use `findOcPaths(*)` + JS filter.
- **`$first` positional**: alias for index 0 (or first-declared key).
Use the literal `0` instead.
- **`-N` negative index**: rarely useful beyond `-1`, which is already
covered by `$last`. The "negative numeric key on object" case
(Telegram supergroup IDs) still works — non-indexable containers
fall through to literal-key lookup.
Behavior loss: zero in observed real workspace files. Tests for the
dropped features removed (15 tests).
Net: -22 LoC src + 15 fewer tests. Total package: 10,570 → 10,275 LoC.
Substrate simplifications and broad comment cleanup:
**Walker collapse (find.ts)**: three near-parallel walkers
(walkJsonc, walkJsonl + walkJsonlInsideLine, walkMd + walkMdInsideBlock
+ walkMdInsideItem) shared the same segment-shape dispatch — union /
predicate / `*` / `**` / positional / literal — with different child
types. Extracted as a single `dispatchSeg<T>(ops, ...)` that takes a
per-kind `WalkOps<T>` table; each walker is a thin wrapper. The three
md walkers fold into one `walkMd(level, ...)` polymorphic on a
`MdLevel` discriminated union. JSONL routes the post-line-slot
descent through walkJsonc via a WeakMap-tagged ast holder.
**CLI (cli.ts)**: each command duplicated four times the same
try/catch/emit/exit dance — missing-arg check, parseOcPath try/catch,
OcEmitSentinelError catch. Extracted as `requireArg`, `tryParse`,
`catchSentinel`. Folded `RawPathOptions` into `PathCommandOptions`
(identical shape) and collapsed `.option(...)` chains via
`withCommonOpts`.
**universal.ts**: setJsoncLeaf and setJsonlLeaf were near-identical
(resolve, refuse root, coerce, set, wrap). Extracted as
`setStructuredLeaf<A, M>` with optional `onLine` for jsonl's
whole-line replacement. Inlined setMdLeaf (7-line passthrough) into
setOcPath. Dropped four `throw new Error("unreachable")` statements
TS exhaustive checking already covers.
**oc-path.ts**: 35 `throw new OcPathError(...)` sites compress through
a `fail()` helper. File-slot containment check (absolute, parent-dir,
control chars) extracted as `validateFileSlot` so parse + format share
the same defense. Three structural-nesting throws in formatOcPath
collapse into two. Three near-parallel string scanners
(`indexOfTopLevel`, `splitRespectingBrackets`, `validateBrackets`) fold
through one `scanBracketAware(s, onChar, onUnbalanced)` helper.
**jsonl/edit.ts**: pickLineIndex compressed; line-address dispatch
shares the value-line filter as a small helper.
**Internal review codes stripped**: P-NNN, F-NN, I-NN, H2-NN, OP-NN,
R-NN, T-NN, S-NN, BFJ-NN, RJC-NN, RJL-NN, FM-NN, A-NN, B-NN, CC-NN,
wave-NN — these were review-process artifacts (sprint identifiers,
finding IDs, pitfall taxonomy IDs) that mean nothing to a reader who
didn't participate in the originating review. Test names rewritten
human-readable; comments lose their P-NNN bookmarks; describe blocks
drop the wave-NN prefix.
**Pitfalls test consolidated**: `tests/scenarios/pitfalls.test.ts`
(637 LoC, organized by P-NNN) replaced by `security-and-limits.test.ts`
(288 LoC) — unique coverage migrates over with descriptive names;
duplicates of OP-/R-/etc. tests are dropped.
**Comment cleanup**: per CLAUDE.md "default to writing no comments;
add one only when the WHY is non-obvious", trimmed multi-paragraph
WHY-prose on every public export, running prose inside function
bodies that restated what the next line of code said, section-divider
comments in test files, and module-level doc-comments that paraphrased
the file name. Kept load-bearing context: NFC re-check after grow,
quote-aware split symmetry, multi-char operator precedence, sentinel
guard catch routes, WeakMap holder rationale.
**MdAst slimmed**: `tables` and `codeBlocks` fields removed — substrate
addressing doesn't go inside them, and markdown-it's tokenizer
already excludes them from heading/item misparse without
first-class AST modeling.
Net reduction across the 10 consolidation/cleanup commits: ~3,800 LoC.
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.