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.