Tooling

The CX toolchain is one binary (`cx`) and a small fleet of editor integrations built on top of it. The binary is the same V codebase that backs `libcx` — every binding, every CI gate, and every editor talks to the same parser, the same canonicalizer, the same evaluator. There is no second implementation to drift against. This section is the working developer reference for that surface: what each subcommand does, how the editor tooling wires up, what the linter checks, what the diff reports, and how you embed CX into existing build systems.

The cx CLI

`cx` is the one-stop command-line interface. It evaluates, formats, canonicalizes, hashes, compares, diffs, lints, validates, converts, and renders CX documents. Every subcommand is a thin shim over the same V core (`vcx/cmd/main.v` dispatches on `argv[1]`), so behaviour is identical to what every host-language binding sees.

The CLI follows a small handful of conventions. **Running a file is the default action**: `cx program.cx` parses and evaluates it (a pure-data document evaluates to itself). Input comes from a file path or stdin; output goes to stdout. Exit code 0 means success, 1 means a user-visible diagnostic (lint finding, diff delta, validation failure, non-equivalent compare), 2 means an internal or I/O error. Subcommands that gate on severity (`cx lint`, `cx validate`) accept `--fail-on=THRESHOLD` to tighten the gate; default is `--fail-on=error`.

The CLI surface is exercised by the conformance corpora under `conformance/*.cxd` (lint, diff, schema-validate, delimited projections, …) run by `make test-vcx-suite` — the fixtures are the normative reference where this guide and the binary disagree.

Subcommand catalog

The full shipped set. `cx --help` lists every subcommand — the catalog and the dispatch are generated from one table in `vcx/cmd/main.v`, so they cannot drift — and every subcommand answers `cx <subcommand> --help` with its own usage:

command purpose
cx FILE Parse + evaluate a document / program (the default action)
cx fmt FILE Lossless canonical formatter (preserves comments, anchors; normalizes whitespace/quoting)
cx canonical FILE Strict canonical text (strips presentation; data-equivalent output)
cx hash FILE SHA-256 hex of the strict-canonical bytes
cx eq A B Exit 0 iff strict-canonical(A) == strict-canonical(B)
cx diff A B Semantic diff over strict-canonical forms
cx lint FILE Style + correctness warnings
cx validate FILE --schema=S.cxs Schema validation
cx table VERB Table API surface (info / dump / load)
cx demo Self-contained showcase, no I/O, < 1s
cx scaffold KIND Typed, commented skeleton on stdout (config / data / doc / log / table)
cx lock Generate / verify cx.lock from [?lib] directives
cx lsp Language server on stdio
cx diagram FILE Program/data containment diagram (mermaid / svg / png)
cx code-diagram FILE Sequence diagram of a CX program
cx code-tree FILE Evaluation-tree rendering of a CX program
cx store-serve Serve a cx-store (reference store server)
cx store-health Store health probe
cx store-token Mint store access tokens
cx store-rotate-kek Rotate the store key-encryption key
cx eval FILE Alias of the default run action; prefer cx FILE
cx select PATH FILE CXPath query over a document — matches in canonical CX, one per line
cx version Version / build info (same output as -v / --version)

There is **no** `cx parse` / `cx convert` / `cx modify` / `cx schema` / `cx help` subcommand — parsing and conversion ride the projection flags below, updates are programs you run with `cx FILE`, and schema checking is `cx validate`. Ad-hoc selection has its own verb: `cx select 'PATH' [FILE]` evaluates one CXPath expression against a document (see below).

Projection and conversion flags

Format projection is a top-level flag surface, not a subcommand. One flag picks the output projection of an input document; `--from` / `--to` drive explicit conversions:

              $ cx --ast config.cx                 # JSON AST
           $ cx --json config.cx                # JSON projection
           $ cx --yaml config.cx                # YAML projection
           $ cx --xml --compact config.cx       # XML, minimised
           $ cx --from=md --to=cx README.md     # Markdown in, CX out
           $ cx --from=cx --to=json --lossless order.cx
           $ echo '[hello world]' | cx --json -  # stdin
            
  • Projection flags: `--ast` `--cx` `--xml` `--json` `--yaml` `--toml` `--md` `--csv` `--tsv` `--psv`, each optionally with `--compact`.
  • `--from=cx|xml|json|yaml|toml|md|csv|tsv|psv --to=cx|xml|json|yaml|toml|md|csv|tsv|psv` — explicit conversion; `--lossless` makes XML carry per-value types (`<cx:T>`) for an exact round-trip.
  • `--include-root=DIR` — resolve `[?cx include=…]` against DIR before projecting (include resolution is opt-in; without a root the directive is preserved).

Running programs and capability flags

`cx program.cx` evaluates a program. A separate data input rides `--data=INPUT.cx` (or `--data=-` for stdin): the input is loaded via the data reading and bound as `$doc` / `$input` before evaluation, and the caller-supplied input **wins** — the program's own data roots never rebind it. Without `--data`, the program's first data root binds implicitly; a document-driven program run without any input raises the loud unbound-`$doc` error rather than silently matching nothing. The four `examples/` tours run exactly this way (`cx examples/code-tour.cx --data=examples/code-tour.input.cx`).

Unknown flags on this surface are **hard usage errors** (exit 2, naming the flag) — including misspelled `--allow-*` grants. Nothing on the run surface is silently ignored.

Effects are **deny-by-default**: a program that reads files, writes files, touches the network, reads environment variables, spawns subprocesses, or asks for randomness must be granted that capability explicitly on the command line (denied effect points raise the catchable capability error `CXER0271`):

              $ cx report.cx --data=orders.cx       # orders.cx binds as $doc
           $ cx build_site.cx --allow-read --allow-write
           $ cx serve.cx --allow-net
           $ cx pipeline.cx --allow-all          # every capability
            

The capability set: `--allow-read`, `--allow-write`, `--allow-net`, `--allow-env`, `--allow-clock`, `--allow-random`, `--allow-subprocess`, `--allow-eval`, `--allow-secret-reveal`, and the blanket `--allow-all`. See security for the security model.

A `cx eval FILE` alias exists for the same action; prefer the plain `cx FILE` spelling — the alias adds nothing.

cx fmt

Pretty-prints in **lossless canonical** form. Comments, anchors, and authorial structure are preserved; whitespace and quoting are normalised. Reads the file argument (or stdin) and writes the formatted document to stdout.

              $ cx fmt config.cx                    # formatted, to stdout
           $ cx fmt config.cx > config.fmt.cx && mv config.fmt.cx config.cx
            

cx canonical / cx hash / cx eq

`cx canonical` emits the **strict canonical text** per [`spec/canonical.md`](../../spec/canonical.md): comments stripped, attributes sorted, anchors expanded, whitespace collapsed, quoting normalised. Two data-equivalent inputs produce identical output — that is the contract `cx hash` (SHA-256 of those bytes) and `cx eq` (exit 0 iff the strict-canonical forms match, 1 if they differ, 2 on error) build on.

              $ cx canonical config.cx | shasum -a 256   # equivalent to cx hash
           $ cx hash config.cx
           $ cx eq prod.cx proposed.cx && echo unchanged || echo changed
            

cx diff

Semantic diff — walks the strict-canonical forms, so presentation-only edits produce no delta. See diff for the output contract. Flags: `--format=unified|json|summary` (default `unified`), `--no-color`. Exit codes match `diff(1)`: 0 equal, 1 differs, 2 error.

              $ cx diff prod.cx proposed.cx
           $ cx diff --format=json prod.cx proposed.cx
           $ cx diff prod.cx proposed.cx && echo no-op   # CI gate
            

cx lint

Style + correctness warnings. See lint for the rule catalog. Flags: `--format=text|json|summary` (default `text`), `--fail-on=info|warn|error|none` (default `error`), `--disable=ID1,ID2` to suppress checks, `--only=ID` to run one.

              $ cx lint config.cx
           $ cx lint --fail-on=warn --format=json config.cx
           $ cx lint --only=CX-L004 pipeline.cx
            

cx validate

Validates a document against a `.cxs` schema ([`spec/schema.md`](../../spec/schema.md)). Flags: `--schema=SCHEMA.cxs` (required), `--fail-on=info|warn|error|none` (default `error`), `--mode=open|strict|closed` (overrides the schema-mode directive), `--apply-defaults` (materialise schema-default attribute values). Exit 0 if no diagnostics at/above the threshold, 1 if any, 2 on I/O or schema failure.

              $ cx validate users.cx --schema=users.cxs
           $ cx validate users.cx --schema=users.cxs --mode=strict --fail-on=warn
            

cx table

The public Table API surface for CXCol / `[table[…]]` payloads: `cx table info FILE` (column/row counts, types, byte size), `cx table dump FILE --to=cx` (round-trip via the Table API), `cx table load FILE --to=cx` (symmetric inverse). Parquet / Arrow output (`--to=parquet|arrow`) defers to the `libcx_arrow` bridge. See analytics.

              $ cx table info data.cxcol
           $ cx table dump data.cxcol --to=cx | head
            

cx diagram / code-diagram / code-tree

Visualization commands. `cx diagram FILE` renders a containment diagram (`--format=mermaid|svg|png`, `-o out.file`); `cx code-diagram FILE` renders a locked sequence-diagram view of a program; `cx code-tree FILE` renders the evaluation tree. The playground's Diagram toggle uses the same renderers via WASM (see playground).

              $ cx diagram config.cx --format=mermaid
           $ cx code-diagram pipeline.cx
           $ cx code-tree pipeline.cx
            

cx scaffold / cx demo / cx lock

`cx scaffold KIND` drops a typed, commented skeleton on stdout — KIND ∈ `config`, `data`, `doc`, `log`, `table`. `cx demo` runs a self-contained showcase (typed round-trip, `[table[…]]` → CSV, a CX program) with no file I/O and no network. `cx lock` generates or verifies `cx.lock` from a project's `[?lib]` directives — `--check`, `--update NAME`, `--output PATH`.

              $ cx scaffold config > app.cx
           $ cx demo
           $ cx lock --check
            

cx lsp

Starts the language server on stdio (JSON-RPC 2.0 per LSP 3.17). See lsp for the capability surface. Editors spawn it on demand — see editors.

              $ cx lsp    # editor connects on stdio
            

cx select

CXPath query over one document — `cx select 'PATH' [FILE]` (stdin when FILE is `-` or absent). The document binds as `$doc`; PATH is a single CXPath value expression (`$doc/…`, `/…`, or `//…`, predicates included). Matches print one per line in canonical CX, in document order; attribute-axis matches materialize as `[name value]` fields. Exit 0 with at least one match, 1 on an empty match set (grep-style), 2 on error. A pure read — no capability grants accepted or needed. Contract: [`spec/misc/cli.md`](../../spec/03-approved/misc/cli.md) §3.8.

              $ cx select '//user[= $_@role admin]' users.cx
           [user name=Alice role=admin]
           $ cx select '$doc/user@name' users.cx
           [name 'Alice']
           [name 'Bob']
           $ cat users.cx | cx select '//user' && echo matched
            

Editor integration

CX ships first-class editor support for VS Code, Neovim, Helix, and any editor that speaks LSP. Three layers stack on top of the `cx` binary: TextMate grammar for display contexts, LSP for live editing intelligence, tree-sitter for structural / injection duty. See [`tooling/install/README.md`](../../tooling/install/README.md) for one-command install scripts.

VS Code

The extension lives at `tooling/vscode/`. It bundles: TextMate grammar (`syntaxes/cx.tmLanguage.json`), language configuration (brackets, comments), snippets (`snippets/cx.json`), and an LSP client that spawns `cx lsp` on demand. Install via the published `.vsix` or `code --install-extension tooling/vscode/cx-language.vsix`.

              # Build and install locally
           cd tooling/vscode
           npm install
           npm run package        # produces cx-language.vsix
           code --install-extension cx-language.vsix

           # Or from the bundled installer
           bash tooling/install/install-vscode.sh
            

The extension keys off `.cx`, `.cxs`, and `.cxd` file extensions. LSP client config (server path, env vars, tracing) lives in the VS Code settings panel under "CX Language". A sample workspace settings file is at [`tooling/lsp/vscode.example.json`](../../tooling/lsp/vscode.example.json).

Neovim

Neovim integration uses `nvim-treesitter` for structural highlighting + LSP via `nvim-lspconfig` (or the built-in LSP client on Neovim 0.11+). A sample config is at [`tooling/lsp/neovim.example.lua`](../../tooling/lsp/neovim.example.lua); the install script [`tooling/install/install-nvim.sh`](../../tooling/install/install-nvim.sh) drops the config block into `~/.config/nvim/lua/cx.lua` and registers the LSP.

              # One-shot install
           bash tooling/install/install-nvim.sh

           # Manual: copy the example into init.lua
           cat tooling/lsp/neovim.example.lua >> ~/.config/nvim/init.lua
            

The tree-sitter grammar is installed via `:TSInstall cx` once the parser is available on the tree-sitter registry. Until then, the install script builds the parser locally from `tooling/tree-sitter-cx/` and links it into the Neovim parser-search path.

Helix

Helix consumes the tree-sitter grammar directly and connects to `cx lsp` for diagnostics, hover, and completion. Drop the [`tooling/lsp/helix.example.toml`](../../tooling/lsp/helix.example.toml) snippet into your `~/.config/helix/languages.toml`.

              # ~/.config/helix/languages.toml
           [[language]]
           name = "cx"
           scope = "source.cx"
           file-types = ["cx", "cxs", "cxd", "cxl"]
           comment-token = "#"
           indent = { tab-width = 2, unit = "  " }
           language-servers = ["cx-lsp"]

           [language-server.cx-lsp]
           command = "cx"
           args = ["lsp"]
            

IntelliJ / JetBrains via LSP

JetBrains IDEs gain CX support through their generic LSP plugin: install "LSP4IJ" (or the Ultimate-edition built-in LSP support on 2024.3+), and register `cx lsp` as the server for `*.cx`, `*.cxs`, `*.cxd`. TextMate highlighting comes from the bundled `tooling/syntax/cx.tmLanguage.json` via the TextMate plugin. The setup is identical for Rider, GoLand, PyCharm, RustRover, and IDEA.

A dedicated JetBrains plugin (richer than the LSP-only path) is planned; tracked in [`ROADMAP.md`](../../ROADMAP.md).

Sublime / Zed / other LSP clients

Any editor that speaks LSP 3.17 over stdio can drive the CX language server. The minimal configuration is: file pattern `*.cx`, command `cx lsp`, transport stdio. The install scripts under `tooling/install/` cover the common cases; for anything else, the contract is just LSP.

Language server (LSP)

The LSP server provides the same intelligence to every editor that speaks LSP. Implementation: `vcx/cmd/lsp.v` plus `vcx/cmd/lsp_features.v`. Speaks JSON-RPC 2.0 over stdio; no separate binary, no port, no daemon. Run-time entry point is `cx lsp`.

Capabilities

The server advertises the following capabilities in its `initialize` response:

method status notes
textDocument/didOpen / didChange / didClose shipped full document sync
textDocument/publishDiagnostics shipped parse + lint + schema + CXLS001-004
textDocument/hover shipped element, attr, directive, CXPath
textDocument/completion shipped triggers : / @ $
textDocument/semanticTokens/full shipped 10-token legend
textDocument/formatting shipped lossless fmt
textDocument/definition shipped cross-include and anchor
textDocument/references shipped anchor + alias + merge
textDocument/codeLens shipped diagram preview, eval result
textDocument/codeAction shipped quick-fix for CXLS001-004
textDocument/signatureHelp planned for directive arg slots
textDocument/inlayHint planned inferred types
textDocument/rename planned cross-include rename

Implementation lives in `vcx/cmd/lsp_features.v`. The CXLS001-004 diagnostic emitters per [`tooling/lsp/diagnostics.md`](../../tooling/lsp/diagnostics.md).

Diagnostic codes

Diagnostics flow from three sources, each with its own code prefix:

  • **CXER\\*** — runtime / parse errors from the V core. See [`spec/cx_native_error_codes.md`](../../spec/cx_native_error_codes.md) for the full catalog.
  • **CX-L\\*** — lint findings from `cx_lint` (see lint); same codes the CLI emits.
  • **CXLS\\*** — LSP-only static-analysis diagnostics that don't have a CLI surface. Currently reserved:
code severity trigger
CXLS001 warning unreachable [?match] arm (after [else …] or [case _ …])
CXLS002 hint [?match] with no [else …] (silent () fallout)
CXLS003 hint sibling [case …] arms with similar [when …] predicates (consolidation suggestion)
CXLS004 error [?modify] [set-attr] / [delete-attr] on attribute-step path (compile-time mirror of CXER0100)

Codes are stable strings; editor configs may key off them to surface custom UI (e.g. fold a CXLS003 cluster into a single inlay). See [`tooling/lsp/diagnostics.md`](../../tooling/lsp/diagnostics.md) for the emit contracts.

Hover

Hover content depends on where the cursor sits:

  • **Element name** — element-doc from the active schema (if any), attribute table, link to the schema source line.
  • **Attribute name** — type, default, schema source.
  • **Directive name** — one-line synopsis + link to the directive page in this guide.
  • **CXPath path expression** — resolved XPath production, live match-count against the current document, axis-name doc.
  • **Layer-2 host idiom** — desugared Layer-1 form (Python comprehension, Go filter chain, Rust iterator). Implementation calls `cxlib.idioms.explain()`.
              [# Hovering '//section[2]/p[@lang=\"en\"]' shows: #]
           [# /section[2]/p[@lang=\"en\"]                    #]
           [# → child::section[position()=2]/child::p[@lang=\"en\"] #]
           [# → matches 3 nodes in this document             #]
           [# → axis `child::` selects direct children (default) #]
            

Completion

Completion triggers on `:`, `@`, `$`, and `/`. The trigger character determines the candidate set:

  • **`:`** — type tags (`:int`, `:string`, `:date`...), directive names (`[?for`, `[?match`...), labeled-slot names inside an active directive.
  • **`@`** — attribute names from the schema or the live document.
  • **`$`** — variable names from `[?let]` bindings in scope.
  • **`/`** — CXPath axes + element names + node-kind tests.

All completion items carry `data.kind` so editors can sort and group them. CXPath items are tagged `data.path_axis=true` per [`tooling/lsp/diagnostics.md`](../../tooling/lsp/diagnostics.md).

Code actions

Code actions are quick-fixes the editor offers for diagnostics. Currently shipped:

  • **CXLS001** (unreachable arm) — "remove unreachable arm". Deletes the arm.
  • **CXLS002** (no `[else …]`) — "add `[else]` arm". Inserts an empty `[else ()]` clause.
  • **CX-L001** (comment style) — "convert `[# #]` to `# line comment`" (and vice versa).
  • **CX-L002** (type-alias style) — "expand to long-form" / "shorten to alias".
  • **CX-L003** (unused anchor) — "remove anchor".
  • **CX-L004** (dangling alias) — "create missing anchor" (with a placeholder body).

CXLS003 consolidation auto-fix is planned (recognise-only currently per [`tooling/lsp/diagnostics.md`](../../tooling/lsp/diagnostics.md) §out-of-scope).

Semantic tokens

The LSP semanticTokens response is the **canonical highlighting source** for editors that consume it. The 10-token legend maps directly to TextMate scopes for cross-editor consistency:

token example
namespace module prefixes, schema-qualified names
keyword directive names ([?for], [?if], [?match]...)
variable $ctx, $-bound names from [?let]
parameter function-parameter names and pattern $bindings
property attribute names
string string literals (single, double, triple quoted)
number int, float, hex, sized-type literals
comment [# block #] and # line comments
operator = + - * / sigils, CXPath operators
decorator type annotations (:int, :date, ...)

Debugging the server

Run with `--verbose` and pipe stderr to a file to trace incoming methods. The server logs each request, its dispatch path, and timing.

              $ cx lsp --verbose 2>/tmp/cx-lsp.log
           $ tail -f /tmp/cx-lsp.log
           [lsp] initialize ← capabilities: hoverProvider=true, ...
           [lsp] textDocument/didOpen ← config.cx (1342 bytes)
           [lsp] textDocument/hover @ 12:8 → 'host' element-doc
            

Smoke-test the JSON-RPC handshake without an editor:

              python3 - <<'EOF' | cx lsp
           import sys, json
           def msg(o):
               b = json.dumps(o)
               sys.stdout.write(f\"Content-Length: {len(b)}\\r\\n\\r\\n{b}\")
           msg({\"jsonrpc\":\"2.0\",\"id\":1,\"method\":\"initialize\",\"params\":{}})
           msg({\"jsonrpc\":\"2.0\",\"id\":2,\"method\":\"shutdown\"})
           msg({\"jsonrpc\":\"2.0\",\"method\":\"exit\"})
           EOF
            

Tree-sitter grammar

`tooling/tree-sitter-cx/` is the tree-sitter grammar for CX. Tree-sitter is scoped to **structural and embedded-language- injection duty** — element / attribute / scalar tokenisation + opaque-PI rendering of `[? ... ]` directives. Per-directive highlighting comes from the LSP semanticTokens, not from tree-sitter.

Grammar shape

The grammar (`grammar.js`) covers everything in [`spec/grammar.ebnf`](../../spec/grammar.ebnf) at the **structural** level: bracketed elements, attribute key=value pairs, scalar literals (string / int / float / bool / date / datetime / null), sigil-prefixed attributes (`#id`, `&anchor`, `*alias`), collection literals (sequence `(...)`, map `{...}`, array — see [[code#collection-literals]]), and comments (`[# #]` and `# line`). Directives — every `[?Name ...]` form — are tokenised as a single opaque `(directive)` region; the internal labeled slots and bodies are not pre-parsed at the tree-sitter layer.

Highlight + injection queries

Queries live in `tooling/tree-sitter-cx/queries/`:

  • **`highlights.scm`** — structural scopes only (element-name, attribute-name, string, number, comment, directive-opaque). Per-directive interior highlighting is delegated to LSP semanticTokens.
  • **`injections.scm`** — embedded-language injection for fenced code blocks (`[code lang='rust' [| ... |]]`). Recognised injection languages: `python`, `rust`, `go`, `js`, `ts`, `java`, `c`, `cpp`, `csharp`, `ruby`, `swift`, `kotlin`, `sh`, `sql`, `html`, `css`, `json`, `yaml`, `toml`, `xml`, `markdown`.
  • **`indents.scm`** — auto-indent rules. CX is bracket-structured; indent inside `[`, dedent at `]`.
  • **`locals.scm`** — variable scoping for `[?let]` bindings.

Regenerating the parser

The committed `src/parser.c` is generated from `grammar.js` by `tree-sitter generate`. The `make tree-sitter` target rebuilds it; CI gates that the committed `parser.c` matches `grammar.js`.

              $ cd tooling/tree-sitter-cx
           $ npm install
           $ npx tree-sitter generate         # regenerate src/parser.c
           $ npx tree-sitter test             # run grammar tests
           $ npx tree-sitter parse sample.cx  # smoke-test on a file
            

`make tree-sitter` from the repo root wraps all of the above and is what the CI lane runs.

Lint rules

`cx lint` warns about issues a formatter can't or shouldn't fix automatically. Lint and fmt are distinct: `cx fmt` *applies* safe transformations; `cx lint` *warns* about issues that need human judgement to fix. The two never overlap — if a check has a safe auto-fix, it belongs to `cx fmt`.

Rule catalog

Five lint checks are currently shipped:

id severity name
CX-L001 info comment-style consistency
CX-L002 info type-annotation form consistency
CX-L003 warn unused anchor
CX-L004 error dangling alias / merge
CX-L005 warn deprecated v3.3→v3.4 pattern

Each check has a normative ID, a default severity, and a documented rationale. Suppression directives and CLI flags accept the ID verbatim.

Severity levels

Three severity levels, normative names:

  • **`info`** — stylistic drift. Doesn't affect correctness. Doesn't fail CI by default.
  • **`warn`** — likely-bug. The code parses, but the result probably isn't what was intended. Fails CI with `--fail-on=warn`.
  • **`error`** — almost-certainly-bug. Document semantics are wrong. Fails CI by default (`--fail-on=error`).

Configuration via .cxlint.cx

Per-project lint config lives in `.cxlint.cx` at the repo root (or any ancestor directory of the linted file). The config file is CX-formatted — same parser, same schema, dogfooded all the way down.

              [# .cxlint.cx — repo-level lint config #]
           [lint
             [# ignore unused anchors in the legacy templates dir #]
             [disable check=CX-L003 path='legacy/**']

             [# v3.3 transition warnings only as info, we'll fix gradually #]
             [severity check=CX-L005 level=info]

             [# treat dangling-alias as warn (not error) in this repo #]
             [severity check=CX-L004 level=warn]]
            

Path globs follow `.gitignore` syntax. `cx lint --config PATH` overrides auto-discovery. Missing config file is not an error — defaults apply.

Per-element suppression

For one-off suppressions inside a document, use the `[?cx lint-disable=...]` / `[?cx lint-enable=...]` directives. Scope is the enclosing element and its descendants.

              [?cx lint-disable=CX-L003]
           [defaults &base host=localhost]   [# no L003 warning #]
           [?cx lint-enable=CX-L003]

           [# lint-disable=all suppresses every check #]
           [?cx lint-disable=all]
           [legacy-block ...]
            

Comma-separated lists accepted: `lint-disable=CX-L003,CX-L005`. `lint-disable=all` is the catch-all.

LSP-only diagnostic codes (CXLS\*)

The CXLS\\* codes (lsp-diagnostics) are surfaced only through the LSP and have no corresponding `cx lint` check. They live in the LSP because they need document-wide static analysis (cross-arm reachability, focus-path type-check) and aren't general-enough to be CLI default behaviour. Suppression inside a document uses `[?cx lint-disable=CXLS001]` (same syntax as the CX-L\\* codes); CLI suppression is moot since `cx lint` doesn't emit them.

Custom rule plugins (planned)

A plugin model for user-defined checks is planned (previously rejected; the question is reopened in [`ROADMAP.md`](../../ROADMAP.md) "Later"). Currently, extending the check set means contributing a check to `vcx/cx/lint.v` upstream. Schema-violation checks are a separate path — they layer in automatically as the schema language gains rules.

Diff semantics

`cx diff` produces a **semantic** diff — tree-structured, canonical-byte-aware, formatting-blind. Two data-equivalent inputs diff to nothing, even if they differ at the text level. This is what makes `cx diff` usable as a CI gate: it reports semantic deltas, not formatting noise.

What cx diff reports (and ignores)

`cx diff` reports differences in the strict canonical form ([`spec/canonical.md §1.2`](../../spec/canonical.md)). Specifically it **reports**:

  • Element added / removed (by canonical path).
  • Element renamed (different canonical name at the same path).
  • Attribute added / removed / value-changed (by canonical name).
  • Body content changed (atomic-value or text-content differences).
  • Type annotation changed (e.g., `:int` vs `:u32` — the annotation IS part of the data).
  • Element order changed *when order is data-significant* (per [`spec/canonical.md §2.1`](../../spec/canonical.md)).

And **ignores**:

  • Comments (`# line` and `[# block #]`).
  • Attribute order on the same element.
  • Anchor / alias / merge details — expanded to the resolved form before diff.
  • Whitespace, indentation, line breaks.
  • Numeric formatting (`1_000_000` vs `1000000`).
  • Quoting style (`'foo'` vs `"foo"`).
  • Element-name aliases that resolve to the same canonical name.

Output formats

Three output modes via `--format`:

  • **`unified`** (default) — element-rooted hunks, `-` / `+` / ` ` prefixes. Human-readable.
  • **`json`** — structured array of change records. Suitable for CI gates and editor integrations.
  • **`summary`** — one-line counts. Useful for shell prompts and PR bots.
              $ cx diff prod.cx proposed.cx
           --- prod.cx
           +++ proposed.cx
           @@ /config/database @@
            host='primary.db'
           -port=5432
           +port=5433
            replica='secondary.db'
            
              [
             {
               \"kind\": \"attribute-changed\",
               \"path\": \"/config/database/@port\",
               \"before\": {\"name\": \"port\", \"value\": 5432, \"type\": \"int\"},
               \"after\":  {\"name\": \"port\", \"value\": 5433, \"type\": \"int\"}
             }
           ]
            

JSON change-record shape

Each record has a stable shape:

  • `kind` — one of `element-added`, `element-removed`, `element-renamed`, `attribute-added`, `attribute-removed`, `attribute-changed`, `body-changed`, `type-changed`, `order-changed`.
  • `path` — CXPath expression locating the change. The same expression drops straight into a CX program (`[?for [in $x PATH] …]`).
  • `before` / `after` — relevant value for the kind. Absent when the side has no value (e.g. `element-added` has no `before`). The field is **omitted**, not `null` — per [`spec/policies.md §2.6`](../../spec/policies.md)'s four-way null/empty/missing distinction this is *missing*.

Git integration

`cx diff` plugs into `git diff` via the standard custom- diff-driver mechanism. Add this to `.gitattributes`:

              *.cx    diff=cx
           *.cxd   diff=cx
           *.cxs   diff=cx
           *.cxcol diff=cx-binary
            
              # One-time per repo
           git config diff.cx.command 'cx diff'
           git config diff.cx-binary.command 'cx diff --binary'

           # Or scaffold it
           cx scaffold gitattributes >> .gitattributes
            

With this in place, `git diff path/to/config.cx` and `git show HEAD path/to/config.cx` use the semantic diff automatically. PR review UIs that respect `.gitattributes` (GitHub, GitLab, Gerrit) pick it up too.

Exit codes

Match `diff(1)`:

  • **`0`** — inputs are data-equivalent (no differences).
  • **`1`** — inputs differ (at least one semantic delta).
  • **`2`** — error (parse failure, file not found, invalid arguments).
              # Use directly as a CI gate
           cx diff prod.cx proposed.cx && echo no-change || review-changes
            

Cookbook — common recipes

Worked examples of CLI compositions that show up repeatedly in real use. Pair-and-match — the CX CLI is designed to compose along the standard Unix pipe.

Filter JSON, project to YAML

Read JSON, query / filter / transform via a CX program, project the result to YAML.

              $ cx --cx input.json > input.cx        # JSON in, CX out
           $ cat input.cx filter.cx | cx > result.cx
           $ cx --yaml result.cx                  # project to YAML
            

The CX form is the pivot — every conversion is a round-trip through the same AST. Appending a program to its data (`cat data.cx program.cx | cx`) evaluates the program against the preceding document.

Validate config before deploy

Strict-profile validation, JSON output for the CI summary.

              $ cx validate config.cx --schema=config.cxs --mode=strict --fail-on=warn \\
             && deploy.sh
            

Semantic diff between two YAML files

Cross-format diff: read YAML on both sides, diff the canonical CX form.

              $ cx diff a.yaml b.yaml --format summary
           3 attributes changed, 1 element added
            

Content hash as cache key

Use the canonical-bytes hash as a cache key in a build pipeline. The hash is stable across formatting changes — reformatting `config.cx` will not bust the cache, but changing a value will.

              $ KEY=$(cx hash config.cx)
           $ test -f cache/$KEY.bin || compile config.cx > cache/$KEY.bin
           $ cp cache/$KEY.bin out.bin
            

Convert CSV to Parquet via CX

Inspect and round-trip a columnar payload through the Table API; Parquet / Arrow output rides the `libcx_arrow` bridge.

              $ cx table info data.cxcol
           $ cx table dump data.cxcol --to=cx | head
           [# --to=parquet|arrow defers to the libcx_arrow bridge (Phase C) #]
            

Lint a whole tree before commit

Recursive lint pre-commit, with shared config.

              $ find . -name '*.cx' -print0 \\
             | xargs -0 cx lint --format=summary --fail-on=warn
            

Render the canonical guide

Build the canonical guide (this document) from sources. See docgen for the pipeline.

              $ make docs
           # outputs into _docs_staging/

           $ make docs-diff
           # diff staging vs live publish

           $ make docs-publish
           # copy staging → docs/
            

Inspect the AST

Read the JSON AST — useful when debugging how the parser shaped a document.

              $ cx --ast doc.cx | jq '.name, (.items | length)'
            

Round-trip through every projection

Assert that a projection round-trip is data-lossless. `--lossless` XML carries per-value types, so the round-tripped document is strict-canonical-equal to the original — the identity contract from canon-cross-format as a CI one-liner.

              $ cx --from=cx --to=xml --lossless doc.cx \\
             | cx --from=xml --to=cx - > back.cx
           $ cx eq doc.cx back.cx && echo lossless
            

Diagnostics and debugging

When a CX program isn't doing what you expect, work the ladder: read the error value (every failure is a position-carrying `[err …]`), lint the file, visualize the program (`cx code-tree` / `cx code-diagram`), reach for the LSP if you want to drive from the editor, and capture a fixture (`conformance/code.cxd`) when you need a reproducible bug report.

Errors carry positions

Every parse or evaluation failure is a structured error with a source position and a stable code — read it first. A raised error that reaches the top level prints as an `[err code=… message=…]` value; the message names the offending line:column and, for retired syntax, the current replacement form.

              $ cx broken.cx
           error: cx-err:CXER0100: parse: infix attribute comparison
           `[@active=…]` in a CXPath predicate is retired — write the
           prefix operator form `[= $_@active …]` (code.md §5.5.2)
           at line 3:20
            

Visualize the program

`cx code-tree FILE` renders the evaluation tree of a program — which directives nest where, what each binds. `cx code-diagram FILE` renders the locked sequence-diagram view (concurrency, channels, service interactions). `cx --ast FILE` shows the parsed shape when you suspect the parser saw something different from what you meant.

              $ cx code-tree pipeline.cx
           $ cx code-diagram workers.cx
           $ cx --ast pipeline.cx | jq '.items[0]'
            

LSP-driven debugging

The LSP surfaces diagnostics live in the editor — parse errors, lint findings, and hover documentation over directives and paths. See lsp for the shipped capability set.

Interactive REPL

An interactive REPL is **planned**. Until it ships, the equivalent is stdin evaluation with a quick shell wrapper — append each expression to the context document and pipe through `cx`:

              # Poor man's REPL
           while read -r line; do
             printf '%s\\n%s\\n' \"$(cat ctx.cx)\" \"$line\" | cx
           done
            

Reproducing a bug as a conformance fixture

When you find behaviour that differs from the spec or from another binding, the fastest way to land a fix is to drop a minimal repro into the conformance corpus. Fixtures under `conformance/*.cxd` are the normative contract — every binding runs them in CI.

Fixtures are CX documents — one `[case …]` element per repro, with the input document, the program, and the expected output as children:

              [case id=my-bug-id level=core
             [tags predicate my-area]
             [in-cx [#
           [doc [item id=1] [item id=2]]
           #]]
             [in-code [#
           [?for [in $i //item[= $_@id 1]] [yield $i]]
           #]]
             [out-text [#
           [item id=1]
           #]]]
            

Drop the case into the corpus file that matches your area (e.g. `conformance/code.cxd` for evaluator bugs, `conformance/core.cxd` for data-layer bugs, `conformance/lint.cxd` for linter bugs), run `make test-vcx-suite`, watch it fail. Then fix the V core to make it pass.

Filing a bug report

A good bug report has four pieces:

  • `cx --version` output (build + commit line).
  • A minimal reproducer — ideally a `conformance/*.cxd` case — that triggers the bad behaviour.
  • Expected output (what you think should happen).
  • Actual output (what does happen).

For LSP / editor bugs, also include the `cx lsp --verbose 2>...` log around the failing request and the editor's LSP trace setting (most editors expose "LSP: Toggle Trace" or similar).

Build-system integration

`cx` is built to slot into the build system you already have. The CLI returns conventional exit codes, reads/writes on stdio, and produces deterministic output — all the properties that let it compose with `make`, `cargo`, `npm`, `bazel`, and CI systems without surprises.

Make

A typical pattern: per-`.cxs` schema validation rule, gated `cx lint` lane, generated outputs as first-class targets.

              CX        := cx
           CX_FILES  := $(shell find config -name '*.cx')
           CX_STAMPS := $(CX_FILES:.cx=.cx.valid)

           %.cx.valid: %.cx config/config.cxs
           \t$(CX) validate $< --schema=config/config.cxs --mode=strict
           \t@touch $@

           .PHONY: lint
           lint:
           \t$(CX) lint --fail-on=warn $(CX_FILES)

           .PHONY: validate
           validate: $(CX_STAMPS)

           .PHONY: check
           check: lint validate
            

Cargo (Rust build.rs)

Use `build.rs` to validate `.cxs`/`.cx` pairs at compile time; once the planned schema-codegen tool ships the same step generates Rust types directly from the schema.

              // build.rs
           use std::process::Command;

           fn main() {
               println!(\"cargo:rerun-if-changed=config/config.cxs\");
               println!(\"cargo:rerun-if-changed=config/default.cx\");

               let status = Command::new(\"cx\")
                   .args([\"validate\", \"config/default.cx\",
                          \"--schema\", \"config/config.cxs\",
                          \"--mode=strict\"])
                   .status()
                   .expect(\"failed to run cx validate — is cx on PATH?\");

               if !status.success() {
                   panic!(\"config validation failed\");
               }

               // Planned: codegen
               // Command::new(\"cx\")
               //     .args([\"schema\", \"codegen\", \"--target\", \"rust\",
               //            \"config/config.cxs\",
               //            \"--output\", \"src/config_gen.rs\"])
               //     .status()
               //     .unwrap();
           }
            

npm scripts

Wire `cx validate` and `cx lint` into `package.json` lifecycle scripts.

              {
             \"scripts\": {
               \"lint:cx\":     \"cx lint --fail-on=warn config/**/*.cx\",
               \"validate:cx\": \"cx validate config/app.cx --schema=config/app.cxs --mode=strict\",
               \"pretest\":     \"npm run lint:cx && npm run validate:cx\",
               \"prebuild\":    \"npm run validate:cx\"
             }
           }
            

Bazel

A custom rule wrapping `cx validate` for hermetic builds. Sketch (treats validation as a generated stamp file):

              # tools/cx.bzl
           def _cx_validate_impl(ctx):
               stamp = ctx.actions.declare_file(ctx.label.name + \".valid\")
               ctx.actions.run_shell(
                   inputs  = [ctx.file.src, ctx.file.schema],
                   outputs = [stamp],
                   command = '''
                       cx validate {src} --schema={schema} --mode=strict \\
                         && touch {out}
                   '''.format(
                       src    = ctx.file.src.path,
                       schema = ctx.file.schema.path,
                       out    = stamp.path,
                   ),
               )
               return [DefaultInfo(files = depset([stamp]))]

           cx_validate = rule(
               implementation = _cx_validate_impl,
               attrs = {
                   \"src\":    attr.label(allow_single_file = [\".cx\", \".cxd\"]),
                   \"schema\": attr.label(allow_single_file = [\".cxs\"]),
               },
           )
            

GitHub Actions

Lint + validate as a PR gate. Reusable workflow that installs `cx` from the release artefacts, runs the toolchain, fails the PR on diagnostics.

              # .github/workflows/cx.yml
           name: CX checks
           on: [pull_request]

           jobs:
             cx:
               runs-on: ubuntu-latest
               steps:
                 - uses: actions/checkout@v4
                 - name: Install cx
                   run: |
                     curl -sSfL https://cx-home.dev/install.sh | sh
                     echo \"$HOME/.cx/bin\" >> $GITHUB_PATH
                 - name: cx fmt --check
                   run: find . -name '*.cx' -print0 | xargs -0 cx fmt --check
                 - name: cx lint
                   run: cx lint --fail-on=warn $(find . -name '*.cx')
                 - name: cx validate
                   run: |
                     for f in config/**/*.cx; do
                       cx validate \"$f\" --schema=config/schema.cxs --mode=strict
                     done
                 - name: cx diff (vs base)
                   run: |
                     git fetch origin ${{ github.base_ref }}
                     for f in $(git diff --name-only origin/${{ github.base_ref }} | grep '\\.cx$'); do
                       cx diff <(git show origin/${{ github.base_ref }}:\"$f\") \"$f\" --format summary
                     done
            

`cx scaffold github-workflow` emits this file as a starting point.

GitLab CI

Equivalent for GitLab CI:

              # .gitlab-ci.yml
           cx:
             image: ghcr.io/cx-home/cx:0.8.0
             script:
               - find . -name '*.cx' -print0 | xargs -0 cx fmt --check
               - cx lint --fail-on=warn $(find . -name '*.cx')
               - for f in config/**/*.cx; do cx validate \"$f\" --schema=config/schema.cxs --mode=strict; done
             rules:
               - if: $CI_PIPELINE_SOURCE == 'merge_request_event'
            

Pre-commit hooks

Wire `cx fmt --check` and `cx lint` into the [pre-commit](https://pre-commit.com/) framework.

              # .pre-commit-config.yaml
           repos:
             - repo: local
               hooks:
                 - id: cx-fmt
                   name: cx fmt --check
                   entry: cx fmt --check
                   language: system
                   files: '\\.(cx|cxd|cxs|cxl)$'
                 - id: cx-lint
                   name: cx lint
                   entry: cx lint --fail-on=warn
                   language: system
                   files: '\\.(cx|cxd)$'
                 - id: cx-validate
                   name: cx validate (strict)
                   entry: cx validate --schema=config/schema.cxs --mode=strict
                   language: system
                   files: '^config/.*\\.cx$'
            

`cx scaffold pre-commit` emits the same snippet.

Doc-gen pipeline

The documentation site (and this canonical guide) is built from `.cxd`/`.cx` sources under `docs-src/` by a Make-driven pipeline at `scripts/gen_docs/`. The pipeline is itself dogfood — the inputs are CX, the templates are CX, the outputs are HTML rendered by running the generator with `cx`.

Make targets

The top-level Makefile splices `-include scripts/gen_docs/docs.mk` to bring the doc targets into scope:

target purpose
make docs build the site into _docs_staging/
make docs-diff diff staging vs the live docs/ tree
make docs-publish copy staging → docs/ with backup on divergence
make docs-clean wipe _docs_staging/
make docs-check broken-link + missing-anchor verification
make docs-all docs + docs-check

Staging vs live

`make docs` writes to `_docs_staging/` (in `.gitignore`). The live tree at `docs/` is touched only by `make docs-publish`, which copies staging → live with a backup on any divergence. This separation means doc builds never dirty the working tree on incidental builds — only an explicit publish step lands changes.

The canonical guide

This document — the canonical CX guide — is built by `make docs-canonical-guide` from sources under `docs-src/canonical/sections/*.cxd`. The pipeline:

  • Reads `docs-src/canonical/manifest.cxd` for the table of contents.
  • For each section file, runs `cx` against the rendering template, emitting an HTML fragment.
  • Concatenates fragments, threads the TOC, expands `[[anchor]]` cross-links.
  • Runs the link-verifier — every `[[anchor]]` and every relative URL must resolve.
  • Writes to `_docs_staging/canonical-guide.html`.

Broken links fail the build. `make docs-check` runs the verifier without the render — useful for fast iteration.

Example snippets

Code examples in the doc sources can pull from `conformance/*.txt` fixtures so they can never drift from working code. The `{{EXAMPLE:fixture-id field}}` placeholder expands at doc-gen time:

              [example lang=cx
             '''{{EXAMPLE:cxpath-basic program}}''']
           [output
             '''{{EXAMPLE:cxpath-basic expected}}''']
            

Outputs are re-recorded from the live `cx` binary, so the docs are always pinned to current behaviour. See `scripts/gen_docs/helpers/example_expand.cx` for the expander logic.

Playground

The CX playground is a browser-embeddable interactive evaluator. It runs the same `libcx` core as the CLI, compiled to WebAssembly. No server round-trip — everything runs in the browser.

WASM distribution

`libcx.wasm` is the WASM build of the V core, exporting a **subset** of the C ABI. The exported surface is sufficient for: `cx_parse`, `cx_canon`, `cx_hash`, `cx_eval`, `cx_code_eval`, `cx_code_diagram`, `cx_select`, `cx_lint`, `cx_diff`. The bridge layer lives at `tooling/playground/cxlib.js`.

ABI capability bits the wasm advertises (per [`spec/abi.md`](../../spec/abi.md) §3):

  • **Bit 29** — `_cx_code_diagram` (Mermaid containment diagram render).
  • **Bit 30** — `_cx_code_tree` (interactive tree model for the output pane).
  • All earlier cap bits the runtime would otherwise advertise on native (0-28).

The web component

`tooling/playground/cx-diagram.js` is a self-contained web component that renders a CX document as a Mermaid containment diagram. Drop it into any page that loads `libcx.wasm`:

              
           

           
            

The component reads `src` (URL or inline `<textarea>` content), calls `cxlib.code_diagram()` to produce Mermaid source, and hands the result to a bundled Mermaid renderer. Bidirectional selection: clicking a tree node in the output pane highlights the source span, and vice versa.

Render tiers

Three tiers per the gate-17 design:

  • **Tier 1** — browser-only Mermaid render. What the playground uses. No external service. Currently shipped.
  • **Tier 2** — HTTP service that shells out to graphviz for richer (DOT-based) renders. Editor / CI integration story. Shipped as `tooling/diagram-svc/`.
  • **Tier 3** — wasm-graphviz inline. Planned follow-up. Eliminates the Tier-2 service hop while keeping the richer-render output quality.

JSON and XML are **not** valid program sources for the diagram — `_cx_code_diagram` accepts CX text only. Containment-only relationships, no FK inference. See [`spec/audits/playground_gate17_design_v1.md`](../../spec/audits/playground_gate17_design_v1.md) for the design rationale.

Embedding in a docs site

The minimum a host page needs:

  • `libcx.wasm` (~ 2.4 MB compressed) served with the right MIME type.
  • `cxlib.js` — the host-side bridge (~ 60 KB).
  • Optionally `cx-diagram.js`, `cx-tree.js`, and `cx-editor.js` web components.

A full playground page (eval + diagram + tree) ships as `tooling/playground/demo.html`. The docs site re-uses these same components, so any concept page that wants an inline interactive surface gets it for free with a one-tag drop-in.

Profiling and benchmarking

CX ships a standard bench harness for the V core (`vcx/tests/runners/code_*_bench.v`) and per-binding bench scripts that exercise the FFI path. The bench numbers feed into the regression-tracking dashboard and the [`spec/governance.md §6`](../../spec/governance.md) SLA table.

Per-directive profiling

A pprof-compatible per-directive profiler is **planned**; no profiling flag ships yet. Today the closest tools are the structured error positions, the `cx code-tree` view, and the V-native bench harness below — see diag-trace.

Bench harness

The bench harness is V-native; runners live at `vcx/tests/runners/code_*_bench.v`. Run individually:

              $ make bench-code-streaming   # streaming evaluator
           $ make bench-code-pattern-compile   # pattern compile cost
           $ make bench-code-http        # http client perf
           $ make bench-code-soak        # long-running soak
           $ make bench-code-cancel      # cancellation responsiveness
           $ make bench-code-gates       # all gate-tracked benches

           $ make bench-streaming        # data-streaming bench
           $ make bench-eval             # general eval cost
           $ make bench-json             # JSON-shape workload
            

Each bench prints a one-line summary (`MB/s`, ops/sec, p50/p95/p99 latency depending on dimension) plus a JSON sidecar that the regression dashboard consumes.

Per-binding bench

The per-binding bench shim is `bench_report.py`. It drives every binding through a common harness and prints comparative numbers.

              $ python3 bench_report.py --bindings python,go,rust
           python  parse 1MB: 92ms   eval count(//x): 12ms
           go      parse 1MB: 38ms   eval count(//x): 4ms
           rust    parse 1MB: 31ms   eval count(//x): 3ms
            

The Go shim has a known SIGSEGV at 1 MB+ inputs as of 2026-05. The fix is tracked. Python's decoder ceiling (~90 ms at 1 MB, pure-Python floor) is architectural — a C extension is a planned future candidate.

Regression tracking

The bench numbers are tracked against [`spec/governance.md §6`](../../spec/governance.md) SLA targets. Current headline numbers:

  • **Streaming evaluator** — 340 MB/s on the JSON-shape workload (`bench_streaming_json.v`); 300 MB/s target met.
  • **`cx diff` 1 MiB / 1% change** — < 50 ms.
  • **`cx lint` 1 MiB / all checks** — < 50 ms.
  • **`cx hash` 1 MiB** — < 30 ms (canonical-bytes + SHA-256).

CI gates on these numbers via a percentile-aware regression check: a single run > 1.5× the rolling median is flagged; > 2× fails the build.

Tooling quick references

Compact reference snippets for the CLI subcommand surface, the editor installer scripts, the tree-sitter grammar workflow, and the conformance / fuzz / bench harnesses. The primary children above (§8.1-§8.12) carry the normative depth; this section is the at-a-glance summary.

CLI quick reference

Every subcommand reads from a file or `-` (stdin) and writes to stdout unless `--output FILE` is passed. Format is auto-detected from the extension or set explicitly with `--from FMT` / `--to FMT`.

              # Format conversion (one-shot).
           cx --cx pizza.cx                 # canonical CX form
           cx --xml pizza.cx                # XML
           cx --json pizza.cx               # semantic JSON
           cx --ast pizza.cx                # AST as JSON
           cx --toml pizza.cx               # TOML

           # From-To.
           cx --from=md --to=cx README.md
           cx --from=cx --to=json --compact order.cx
           cx --from=cx --to=xml --lossless order.cx

           # Subcommands.
           cx fmt FILE              # lossless canonical formatter
           cx canonical FILE        # strict canonical text
           cx hash FILE             # SHA-256 over canonical bytes
           cx eq A.cx B.cx          # exit 0 if data-equivalent
           cx diff A.cx B.cx        # semantic diff
           cx lint FILE             # style + correctness
           cx validate FILE --schema=S.cxs
           cx table info FILE
           cx scaffold KIND         # drop a skeleton on stdout
           cx select '//user' FILE  # CXPath query (exit 1 = no match)
           cx version               # build info (same as -v)

           # Run a program (data + program in one input).
           cat data.cx program.cx | cx
           cx pipeline.cx --allow-read --allow-write

           # Run a program against a separate input document ($doc).
           cx program.cx --data=input.cx

           # Include resolution.
           cx --xml --include-root=./conf main.cx     # resolve [?cx include=…]
            

**Exit codes.** `0` = success (no diagnostics at or above the configured `--fail-on` threshold); `1` = diagnostics found (lint / validate) or comparison returned not-equal; `2` = I/O failure, schema load failure, or unrecoverable parse error.

Editor installer scripts

The `tooling/install/` directory ships one shell script per editor. Each script writes the editor config, registers the `cx lsp` server, and drops the syntax-highlighting plugin. The CX language server is built into the `cx` binary itself — no separate install.

              ./tooling/install/install.sh           # interactive — detects editors on PATH
           ./tooling/install/install-vscode.sh    # VS Code only
           ./tooling/install/install-nvim.sh      # Neovim only
           ./tooling/install/install-lsp.sh       # LSP wiring only, no extension
           ./tooling/install/uninstall.sh         # remove everything
            

**VS Code** — extension at `tooling/vscode`; tree-sitter syntax + vscode-languageclient LSP harness; supports diagnostics, hover, format-on-save via `cx fmt`. **Neovim** — config under `tooling/neovim`; install script writes LSP client config and tree-sitter grammar. **Other editors** — Helix, Emacs (lsp-mode), Sublime Text (LSP plugin), IntelliJ (LSP4IJ) drive `cx lsp` directly. Point the editor at the `cx` binary; register `.cx` / `.cxd` / `.cxs` as file types.

Tree-sitter grammar workflow

The tree-sitter grammar lives at `tooling/tree-sitter-cx`. It is the source of syntax highlighting, code folding, structural selection, and lightweight static analysis in every editor. Prebuilt artifacts ship inside each editor extension; build from source for grammar work.

              cd tooling/tree-sitter-cx
           tree-sitter generate
           tree-sitter parse example.cx
            

Highlight, indent, fold, and locals queries ship alongside the grammar. They drive syntax colours, structural selection, and cursor-jump-to-definition behaviour. Queries are versioned with the grammar; updates flow through editor extensions on each release.

Conformance, fuzzing, benchmarks

The conformance suite at `conformance/*.txt` is the cross-binding contract. Each fixture declares an input and an expected output (`out_cx`, `out_xml`, `out_json`, `out_text`, or `out_err`). Every binding implements a runner that walks the fixture set; a release is gated on byte-identical output across every binding.

              # Conformance.
           make test            # all bindings, all suites
           make test-vcx        # V core conformance
           make test-rust       # Rust binding
           make test-python     # Python binding

           # Fuzzers — vcx/tests/fuzz/, structured-random input.
           make fuzz            # 60-second run of every fuzzer
           make fuzz-parser     # parser only, runs until interrupted
           make fuzz-replay     # replay the saved crash corpus

           # Benchmarks — vcx/tests/runners/streaming_bench.v + eval_features_bench.v.
           make bench           # full bench harness, target/bench.json
           scripts/run_bench_json.py --include-eval
            

Streaming throughput target: 340 MB/s on the comparable bench corpus (M2 Pro, libcx `-prod`) — about 17% under the comparable JSON benchmark on the same workload.

Cookbook — additional recipes

Recipes carried forward from the pre-Phase-B docs cookbook + showcase. The §8.7 children above (recipes §8.7.1-§8.7.9) cover the most common nine; this section adds 13 more focused recipes.

Extract a single value

              echo '//server/@host' | cat config.cx - | cx
            

Appending a query to its data and running the result through `cx` evaluates the query against the preceding document. The CXPath finds the `host` attribute of any `server` element at any depth.

Filter records by attribute

A one-line program filters by predicate — the CXPath predicate `//user[= $_@role 'admin']` selects only admins:

              echo \"[?for [in \$u //user[= \$_@role 'admin']] [yield \$u]]\" \\
             | cat users.cx - | cx
            

Count records

              echo '[$count //order]' | cat orders.cx - | cx
            

Generate a config from a template

              [env host='db.example.com' port=5432 env='prod']
           [?let [= $env [$first //env]]
             [server host=$env@host port=$env@port
                     workers=[?if [= $env@env 'prod'] [then 32] [else 4]]
                     cache=[?if [= $env@env 'prod'] [then '/var/cache/app']
                                                    [else '/tmp/cache']]]]
            
              cat env.prod.cx server.tpl.cx | cx | tail -1 > server.cx
           cx --toml server.cx > server.toml
            

Write a stream

              with cx.stream_writer(open("out.cx", "wb")) as w:
               for record in fetch_records():
                   w.write(record)
            

Compose a document from files

              [server
             [?cx include=lib/defaults.cx]
             port=8080
             log-level=info]
            
              cx --cx --include-root=./conf server.cx > resolved.cx
            

Render a template with includes

              [page
             [?cx include=partials/header.cx]
             [?for [in $item //order/line] [yield [li $item@sku]]]
             [?cx include=partials/footer.cx]]
            
              cx --cx --include-root=./templates page.cx
            

Read a Parquet file into CX

              cx table load orders.parquet --to=cx > orders.cx
            

Share defaults across records

              [defaults &shared timeout=30 retries=3]
           [server *shared name=api]
           [server *shared name=worker retries=5]
            

`&` declares an anchor; `*` merges from it. Local fields override; missing ones inherit.

HTML-safe templating

              [?cx output-target=html]
           [h1 //page/@title]
           [?for [in $u //user] [yield [li $u/@name]]]
            

`[?cx output-target=html]` switches the evaluator into context-aware escaping. Substituted values containing `<`, `>`, `&`, or quotes escape correctly with no manual sprintf.

Emit structured log records

              [?lib 'cx-stdlib/log']
           [$log:info 'sale' fields={pizza: 'Margherita', price: 12}]
           [$log:warn 'low-stock' fields={pizza: 'Diavola', stock: 2}]
            

The `log` module emits structured records to stderr (default) or a configured sink. Fields are a map; level is one of `trace` / `debug` / `info` / `warn` / `error`.

Logfmt parser in CX code

Take a logfmt-style line of key=value records and split it into structured CX.

              ts=2026-05-20T18:42:00Z level=info request_id=abc123 msg=ok
            
              [?lib 'cx-stdlib/strings']
           [?let [= $input 'ts=2026-05-20T18:42:00Z level=info msg=ok']
             [event
               [?for [in $kv [$strings:split $input ' ']] [yield
                 [?let [= $pair [$strings:split $kv '=']]
                   [pair name=[$nth $pair 1] value=[$nth $pair 2]]]]]]]
            

Build system in CX

Targets, depends, run — declared in CX, evaluated by a thirty-line CX code driver.

              [targets
             [target name=all  depends='lib bin']
             [target name=lib  depends='src/foo.o src/bar.o'  run='ar rcs lib.a src/foo.o src/bar.o']
             [target name=bin  depends='main.o lib'           run='cc -o bin main.o lib.a']]
            
              [?lib 'cx-stdlib/strings']
           [?lib 'cx-stdlib/process']
           [?def build impure ($name)
             [?for [in $t //target[= $_@name $name]] [yield
               ([?for [in $d [$strings:split [$string $t@depends] ' ']]
                  [yield [$build $d]]],
                [?if $t@run
                  [then [$process:run ('sh', '-c', [$string $t@run])]]
                  [else ()]])]]]
           [$build 'all']
            

Playground — visualize, offline, highlight

Deep-dive on the in-browser playground UI. Section §8.11 above frames the WASM / web component / tier story; this section is the operator reference for the Tree / Graph / Source-diagram bridge.

How it works

The playground runs entirely in the browser. Pick a starter example from the dropdown, edit it in the Source pane, and the three output tabs underneath show the canonical CX form, the JSON projection, and the XML projection of the parsed tree. No server, no signup. Twelve starter examples ship in the corpus — three data shapes (atom, nested element, whole-shop) and nine program snippets covering the directive surface.

Visualize

The Source pane carries a Diagram toggle in its header; the Output pane carries a Tree | Graph view toggle adjacent to the CX | JSON | XML projection tabs. Tree is the default for every fresh source — Graph is opt-in.

The Source-pane diagram renders the program as a Mermaid graph in a sibling pane. The renderer runs entirely in the browser via libcx.wasm — Mermaid text emitted by `cx_code_diagram`, then rendered to inline SVG by Mermaid.js. Zoom, fit-to-pane, and pan controls layer on top.

The Output-pane Tree view renders the parsed value as an interactive containment-only tree — folder icons for elements with body, file-text icons for prose leaves, tag and at-sign rows for elements and attributes. Built from `cx_code_tree(source)` whose JSON contract carries a `{kind, name?, value?, loc:{start,end}, children?}` record per node. Search, count badges, and standard tree keyboard navigation come along for free.

The Output-pane Graph view auto-detects mode: sources whose top-level program contains at least one `[?directive]` render as a Control-Flow Graph (basic blocks; diamonds for `[?if]`; dispatcher round-rects with per-arm edges for `[?match]`; loop-boxes for `[?for]` with `:bind $x` labels; one update-block per `[?modify]`); pure-data sources render as an Entity-Relationship Diagram with per-instance cardinality inference (`||--o{` for repeating children, `||--||` for singletons). Every `[?def]` body is its own Mermaid sub-graph.

**Bidirectional selection bridge**. Click a tree node and the source-pane editor selects bytes `[loc.start, loc.end]` and scrolls them into view. Move the caret in the source pane and the tree pane binary-searches its loc index for the deepest containing node and scrolls/selects it. Visualizations re-render on Run only — no live debounce — to keep the playground predictable.

Offline use

The browser playground is a static page. Clone the repo, render the guide with `make guide`, and serve `docs/guide/` from any HTTP server (or open via `file://`) — the playground works without an internet connection. The example corpus, the TypeScript bundle, and the CSS all live under `scripts/gen_guide/playground/`.

Syntax highlighting

Every code block on the guide is syntax-highlighted by a small in-house JS module at `scripts/gen_guide/highlight/`. It speaks the languages the docs actually use — `cx`, `shell`, `python`, `go`, `rust`, `javascript`, `typescript`, `java`, `c#`, `ruby`, `kotlin`, `swift`, `v`, `sql`, `ebnf`, `xml`, `html`, `json`, `yaml`, `toml`, `md`. The highlighter is regex-based, around 150 lines, no external library. The Playground panes use the same module on every Run.