Migration
From v0.7.x to v0.8.0
v0.8.0 is the first stable tag after v0.7.5 (v0.7.6 was skipped intentionally — see backlog decision `d-2026-05-22-04`). v0.8.0 is the pre-release API/format-stability boundary; there are no external users yet, so the renames below were applied directly across the codebase rather than shipped as user-facing tooling. This section is the reference for the shape of the changes.
**Rename: programs → code.** Every internal `programs` reference becomes `code`. The brand layer ("CX is data and code") was renamed immediately on d-2026-05-22-01; the internal rename cascades through:
| was | is |
|---|---|
| cx_program_eval | cx_code_eval |
| vcx/programs/ | vcx/code/ |
| spec/programs.md | spec/code.md |
| conformance/programs.txt | conformance/code.txt |
| _cx_program_diagram (wasm) | _cx_code_diagram (wasm) |
| --- in_cxl (fixture marker) | --- in_code (fixture marker) |
**`[?find]` is retired.** `[?find]` was always sugar for `[?for]` over a pattern. The migration is one of two forms:
| was | is |
|---|---|
| [?find [user $u] :yield $u] | //user or [?for [user $u] [yield $u]] |
| [?find [user @active=true $u] :yield $u] | //user[@active=true] |
| [?find [user [email $e]] :yield $e] | //user/email |
| count([?find [user @active=true $u] :yield $u]) | [$count //user[@active=true]] |
**CXPath is now a value kind.** A path expression like `//user` parses to a `cx.PathNode` value, evaluates to a sequence, and round-trips canonically as the terse form. The retired v0.7.0 CXPath subset is replaced by XPath 3.1 alignment — see [`spec/cxpath_alignment.md`](../../spec/cxpath_alignment.md).
**Multi-arm `[?match]`.** `[?match]` now supports N arms with `[case …]` / `[when …]` / `[else …]` clause children, scalar literal patterns, `_` wildcard, and `[where …]` guards. The 2-arg form (single arm, raises `CXER0100` on miss) is preserved unchanged.
[# v0.7.x — nested if chain (legacy slot form) #]
[?if (name($n) = 'prose') :then [p $n]
:else [?if (name($n) = 'code') :then [pre $n]
:else ()]]
[# v0.8.0 — multi-arm match (clause-child form) #]
[?match $n
[case [prose $p] [p $p]]
[case [code $c] [pre $c]]
[else ()]]
**`[?modify]` is new.** There was no idiomatic update directive in v0.7.x — code had to rebuild via `[?for]`. v0.8.0 ships the eleven-action vocabulary (`[set V]` / `[delete]` / `[using F]` / `[rename N]` / `[set-attr N V]` / `[delete-attr N]` / `[append V]` / `[prepend V]` / `[insert-before V]` / `[insert-after V]` / `[replace V]`). See modify.
**Two-layer bindings.** See [`spec/bindings.md`](../../spec/bindings.md). Layer 1 is the canonical 16-method surface, identical across V/Python/Go/Rust. Layer 2 is opt-in host idiom packs (`cxlib.idioms` in Python, `cxlib/idioms` in Go, `cxlib::idioms` in Rust). v0.7.x had a single flat surface per binding; v0.8.0 codifies the separation. See bindings.
**Binding scope reduced to four.** v0.8.0 ships only V (native), Python, Go, Rust. The TypeScript, Java, C#, Ruby, Kotlin, and Swift bindings are moved to `lang/_archived/` (preserved, not deleted). Restoration is opt-in once Layer-1 is stable; community contributions accepted but not blocked on. The make tests for archived bindings are no longer in the default `make test` target.
The renames above are **mechanical** — they do not attempt to convert hand-rolled nested `[?if]` chains to multi-arm `[?match]` (that requires semantic understanding of what each branch tests). The conversion is straightforward by hand once you know the multi-arm form (see match).
From XML
XML is the closest projection to CX — same element / attribute / body shape. Migration is one-shot: `cx convert input.xml --to cx --out input.cx`.
- **DOCTYPE / DTD** — drop. Use a CX schema (`.cxs`) instead. See schema.
- **Namespaces** — `xmlns:*` declarations become CX attributes on the root element; namespace-qualified element names use the same `prefix:name` form. See [`spec/namespaces.md`](../../spec/namespaces.md).
- **CDATA** — projects to triple-quoted strings.
- **Processing instructions** — preserved as `[?pi …]` elements; their bodies are opaque text.
- **XPath queries** — port to CXPath. The selection core is identical (see [`spec/cxpath_alignment.md`](../../spec/cxpath_alignment.md)); you drop XPath's `for`/`let`/`if`/`function` constructs in favor of the CX directives.
- **XSLT / XQuery transforms** — port to CX code. There is no automated converter; the structural mapping is direct because the data model is the same.
From JSON / YAML / TOML
These are projection formats. Migration is again one-shot: `cx convert input.json --to cx --out input.cx`.
The projection rules are deterministic and reversible (see json, yaml, toml). What you gain on the CX side:
- **Comments.** JSON has none; YAML and TOML have ad-hoc comments. CX has structured `[; … ]` comments that survive `cx fmt` and round-trip across projections.
- **Schemas.** JSON Schema / YAML schemas are external artifacts. CX schemas are first-class CX, in the same parser, with the same toolchain.
- **Queries.** JSONPath / `jq` / YAML query systems are format-specific. CXPath works on any projected document regardless of source format.
- **Updates.** No idiomatic pure-functional update for JSON / YAML / TOML at the format level. `[?modify]` gives you one.
- **Identity.** Canonical JSON, JCS, and `jq -S` exist but are not universally adopted. CX has one canonical form built into every tool.
Retired surfaces and replacements
Surfaces removed or renamed across v0.7.x → v0.8.0, with their replacement.
| retired | replacement | source |
|---|---|---|
| [?find] | [?for] over pattern, or CXPath //name | CXPath value-kind alignment |
| [- comment -] | [; comment ] | lexer consolidation |
| :contains predicate hack | ancestor:: axis | CXPath value-kind alignment |
| :direct modifier hack | following-sibling:: axis | CXPath value-kind alignment |
| XPath general-comparison = | value-comparison only | CXPath value-kind alignment |
| cx_program_eval (C ABI) | cx_code_eval | programs → code rename |
| in_cxl (fixture marker) | in_code | programs → code rename |
| cxl-* playground starter ids | code-* starter ids | Phase 7.3 |
| TypeScript / Java / C# / Ruby / Kotlin / Swift bindings | archived under lang/_archived/ | d-2026-05-22-03 |
| Layer-1-only flat binding surface | Layer 1 + Layer 2 | d-2026-05-22-07 |
**Archived bindings are not deleted.** Files move to `lang/_archived/<lang>/` and remain in git history. The restoration path: each archived binding can return on opt-in once Layer 1 is stable. Community contributions are welcome on the archived path; they are not blocked on by the core team.
**Doc-gen pipeline consolidated.** v0.8.0 ships a single pre-publish documentation generator at `scripts/gen_guide/` rendering `docs-src/canonical/` into the multi-page guide at `docs/guide/` — eat-our-own-dog-food via `cx eval` on every section. See docgen for the pipeline and [`make guide`] for the entry point.
Extend and contribute
CX is open to contribution at every layer. This subsection is the developer-facing how-to: filing issues, adding fixtures, proposing changes, shipping a binding, debugging a failure.
Filing an issue
Issues live in the project tracker. Every report should include: minimal CX reproducer (one file), `cx --version` output, error code (e.g. CXER0103) + message, expected vs observed behaviour, and the workaround you used if any. For parse / canon / hash bugs, a 5-line CX snippet is usually enough; for evaluator bugs include the directive shape and the input doc.
Adding a conformance fixture
The conformance suite is `conformance/*.txt` files in per-feature buckets (code.txt for directives, data_bin.txt for binary wire, etc.). Each fixture has a `=== test: id` header + `--- in_cx` / `--- call` / `--- out_text` (or `out_err`) blocks. To add one:
- Identify the right file (`code.txt` for code-language features; `binding_api.txt` for Layer-1 parity).
- Add the fixture block following existing format.
- Run the runner (`devbox run -- make test-vcx` for V conformance; `make test-binding-api-parity` for Layer-1 cross-binding).
- If the runner errors on your fixture, the implementation doesn't handle it — that's the bug; file it. If runner green, your fixture is now a regression gate.
Proposing a design change
CX changes that affect the spec / ABI / surface are made by editing the spec — the spec is the single source of truth. The workflow:
- **Draft** the change as an edit to the relevant spec file under `spec/`, with was-vs-is tables, context, the specific decisions, alternatives considered, and conformance gates.
- **Register** any new backlog item in `docs-src/canonical/backlog.cxd` with `:status draft`.
- **Review window** — submit for owner + community review. Iterate the tables until reviewers can scan the design in 2-3 minutes.
- **Ratify** — flip status to `accepted` in `backlog.cxd`.
- **Implement** — landing PRs cite the ratified spec change. Its conformance gates block the next release.
Keep spec edits scannable: lead with was-vs-is tables and keep the prose tight so reviewers can grasp the design quickly.
Shipping a new binding
Contributing a binding for a language not currently in the Tier-1 set (V / Python / Go / Rust):
- Implement the 16-method Layer-1 surface (spec/bindings.md §2.1) — case-adjusted for host conventions but otherwise identical.
- FFI extern declarations against the C ABI symbols in `include/cx.h`. Match the version's cap bits.
- Call `cx_init()` once per process; call `cx_thread_register()` at every FFI chokepoint (or use a per-thread guard pattern).
- Add a quickstart in `docs-src/canonical/sections/07-bindings.cxd`.
- Wire conformance — your binding must pass `conformance/binding_api.txt` byte-identically (gate 28.6). The runner pattern lives at `lang/python/conformance_code.py` / `lang/go/cxlib/conformance_code_test.go` — mirror in your language.
- PR to `lang/<your-name>/cxlib/` with the binding code plus the conformance harness.
For restoring an archived binding (TypeScript / Java / C# / Ruby / Kotlin / Swift): see `lang/_archived/README.md` for the recipe.
Debugging an implementation
Reproducing and triaging a CX bug:
- **Reduce the repro.** Get to the smallest input that triggers the failure (one CX file, ideally < 20 lines).
- **Capture diagnostic output.** `cx eval --trace` shows per-directive timing + span emission; `cx eval --explain` shows the AST + dispatch path.
- **Bisect** if the bug appeared between versions — `git bisect run sh -c 'cx repro.cx; test $? = 0'`.
- **Check the conformance suite** — does a similar fixture exist? If yes, what's different about yours?
- **File a fixture** alongside the bug report (see §add-fixture) so the fix lands a regression gate.
For V-side debugging: the impl lives at `vcx/code/*.v` (post-rename, was `vcx/programs/`). Build with `devbox run -- make -C vcx build`; run unit tests with `make test-vcx`.
Updating the project memory
The project memory (`memory/` directory in this repo's CLAUDE.md context) tracks long-running state across sessions: design decisions, deferred work, known issues, perf state. When you land a decision or surface a new issue, update the relevant memory file. The convention is in CLAUDE.md and the `memory/MEMORY.md` index.