Data tooling
Every verb on this page runs against documents and needs nothing above Ring 0: no evaluator, no capabilities, no server. They are the daily drivers of the data-format adopter — convert, format, canonicalize, hash, compare, validate, query, and diff — and each is exit-code-honest so it slots straight into CI. The verbs that execute programs live with Ring 1 (code-tooling); the full subcommand catalog is in tooling.
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;--losslessmakes 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).
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/core/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 validate
Validates a document against a .cxs schema
(spec/core/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 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 §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
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.cxbin
$ cx table dump data.cxbin --to=cx | head
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
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/core/canonical.md §1.2).
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.,
:intvs:u32— the annotation IS part of the data). - Element order changed when order is
data-significant (per
spec/core/canonical.md §2.1).
And ignores:
- Comments (
# lineand[# 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_000vs1000000). - 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 ofelement-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-addedhas nobefore). The field is omitted, notnull— per the four-way null/empty/missing distinction ofspec/core/code.md §8.13this 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
# One-time per repo
git config diff.cx.command 'cx diff'
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