iter ext-rename: .ailx → .ail across the live toolchain

The surface-form file extension changes from .ailx to .ail. AILang's
authoring surface now uses the same .ail stem as its canonical JSON
form (.ail.json), giving the language a single coherent extension
family: .ail is the LLM-authored Form A, .ail.json is the canonical
JSON-AST Form B.

Scope (touched):
- 61 example renames examples/**/*.ailx → .ail (git mv)
- 1 rename experiments/.../rendered/ailx.md → ail.md
- 35 content-edited live-toolchain files (crates/, docs/DESIGN.md,
  docs/roadmap.md, docs/PROSE_ROUNDTRIP.md, skills/, bench/reference/*.c,
  experiment crates under experiments/.../{render,harness,master})
- Experiment-crate cohort rename Cohort::Ailx → Cohort::Ail,
  Form::Ailx → Form::Ail, per_cohort/ailx → per_cohort/ail,
  {form-only: ailx} → {form-only: ail}, ```ailx → ```ail

Out of scope (deliberately untouched, to preserve honest history):
- docs/journal-archive.md (content-frozen per CLAUDE.md)
- docs/journals/, docs/specs/, docs/plans/, bench/orchestrator-stats/
- experiments/.../runs/ (frozen LLM-output artefacts; models actually
  saw .ailx — renaming would falsify the experimental record)

Verification: cargo build/test --workspace green; experiment crate
cargo test green; bench/check.py + compile_check.py + cross_lang.py
all 0-regressed; negative grep for ailx|Ailx|AILX outside the
out-of-scope paths returns zero matches.

Opens immediate follow-up: roadmap.md P2 todo `ail check`/build/run
accept .ail extension — after this rename, .ail is canonical
authoring surface but the CLI still produces a misleading JSON-parse
error on `ail check foo.ail`. That's the next iter.
This commit is contained in:
2026-05-12 14:20:27 +02:00
parent 17b370bbb3
commit 72e54f4fd3
97 changed files with 318 additions and 210 deletions
+25 -25
View File
@@ -175,7 +175,7 @@ Trade-off: no inline optimisations through the LLVM API. We rely on
Form (A) is implemented as
the `ailang-surface` crate (parser + printer). Form-A is
gated against drift by `ailang-surface/tests/round_trip.rs`, which
parses every `.ailx` fixture, prints it back, re-parses, and demands
parses every `.ail` fixture, prints it back, re-parses, and demands
canonical-byte equality. `ail render` and both branches
of `ail describe` were rewired to use `ailang_surface::print`, making
form (A) the **sole** text projection of a module — the legacy
@@ -247,7 +247,7 @@ the AST and remain projection-agnostic.
semantic indentation, maximal-munch lexing, context-sensitive
reductions.
2. **AST-isomorphic.** Every surface form maps to exactly one AST
shape. The full bijection between `.ail.json` and `.ailx` (both
shape. The full bijection between `.ail.json` and `.ail` (both
directions, BLAKE3-stable hashing, Float-bits-hex encoding,
workspace-CI enforcement points) is anchored as the top-level
§"Roundtrip Invariant" — this constraint records that Decision
@@ -437,11 +437,11 @@ on the shelf for a future iter only if both fail.
continue to consume `ailang-core::ast::Module` values regardless
of which projection produced them.
- Round-trip test: for every `examples/*.ail.json`, parse the
corresponding hand-written `*.ailx`, canonicalise, and assert
corresponding hand-written `*.ail`, canonicalise, and assert
hash-equivalence to the original. Hash equivalence is the truth
check; the surface ships only if every fixture round-trips
identically.
- CLI: `ail parse <file.ailx> -o <file.ail.json>`. Symmetric to
- CLI: `ail parse <file.ail> -o <file.ail.json>`. Symmetric to
existing `ail render`. **`.ail.json` remains a first-class input**
to every existing subcommand; the parser is a producer, not a
gatekeeper.
@@ -497,7 +497,7 @@ Neither change extends the grammar's rule budget meaningfully:
the 30-production ceiling of constraint 1 is intact (the parser
implements ~28 named productions). All 17 `examples/*.ail.json`
fixtures round-trip identically through `print → parse → canonical
JSON`; the three hand-written `.ailx` exhibits parse to canonical
JSON`; the three hand-written `.ail` exhibits parse to canonical
JSON identical to their corresponding `.ail.json` files.
3. **Tail-call surface.** Decision 8 ships two new
@@ -580,12 +580,12 @@ foreign LLM enough to produce valid output. The current prompt revises this:
- The LLM emits **Form-A** (the canonical authoring surface), not
JSON. JSON-AST stays the only hashable artefact, but it is not
a writing surface. The user runs `ail parse foo.new.ailx`
a writing surface. The user runs `ail parse foo.new.ail`
before `ail check` to produce the canonical JSON.
- `crates/ailang-core/specs/form_a.md` is the complete LLM-targeted
Form-A specification — grammar, every term / pattern / type /
def keyword, schema invariants, pitfall catalogue, four
few-shot modules drawn from `examples/*.ailx`. It is exported
few-shot modules drawn from `examples/*.ail`. It is exported
as `ailang_core::FORM_A_SPEC` and embedded verbatim in every
`merge-prose` prompt.
- `crates/ailang-core/tests/spec_drift.rs` walks every variant of
@@ -615,7 +615,7 @@ so JSON-cohort feedback carries the full anyhow cause chain):
- `meta-llama/CodeLlama-13b-Instruct-hf` —
`experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-9197fd/`
| metric | Qwen / JSON | Qwen / AILX | CodeLlama / JSON | CodeLlama / AILX |
| metric | Qwen / JSON | Qwen / AIL | CodeLlama / JSON | CodeLlama / AIL |
|---|---|---|---|---|
| reached green | 1/4 | 1/4 | 0/4 | 2/4 |
| first-attempt green | 1/4 | 1/4 | 0/4 | 2/4 |
@@ -625,16 +625,16 @@ so JSON-cohort feedback carries the full anyhow cause chain):
| top error class | check (×3) | parse (×3) | check (×2) | parse (×2) |
Both subjects show the same direction on every metric the table
tracks. AILX is cheaper than JSON on prompt tokens (Qwen 61%,
tracks. AIL is cheaper than JSON on prompt tokens (Qwen 61%,
CodeLlama 80% of the JSON cohort's spend) and cheaper on completion
tokens (Qwen 23%, CodeLlama 82%). AILX reached-green is greater than
tokens (Qwen 23%, CodeLlama 82%). AIL reached-green is greater than
or equal to JSON reached-green for each subject (Qwen ties at 1/4;
CodeLlama strictly dominates, 2/4 vs 0/4). The failure-class
symmetry observed on the original Qwen baseline holds for both
subjects: JSON cohorts fail at the typecheck stage, AILX cohorts at
subjects: JSON cohorts fail at the typecheck stage, AIL cohorts at
the parse stage — each form's front-of-pipeline check. Three of the
four first-attempt-green cells observed across the two subjects are
AILX (Qwen-AILX t3, CodeLlama-AILX t1, CodeLlama-AILX t3); the
AIL (Qwen-AIL t3, CodeLlama-AIL t1, CodeLlama-AIL t3); the
fourth is Qwen-JSON t3, the same task the original baseline already
flagged as the JSON-form's only first-attempt success. Two CodeLlama
JSON-cohort cells (`t2_length`, `t4_count_zeros`) terminated as
@@ -642,7 +642,7 @@ JSON-cohort cells (`t2_length`, `t4_count_zeros`) terminated as
IONOS-side terminal error, not a model-output failure; CodeLlama
JSON-cohort numbers therefore average over 2 informative cells, not
4. The Qwen re-run also shifted by one cell against the cma.3
baseline (AILX 2/4 → 1/4, JSON 1/4 → 1/4) despite identical
baseline (AIL 2/4 → 1/4, JSON 1/4 → 1/4) despite identical
temperature=0 inputs; this is IONOS-side state-of-day noise on a
single deterministic re-run, not an effect of the ms.1
feedback-formatting fix (which only changes JSON-cohort prompt
@@ -650,10 +650,10 @@ content).
**Scope of this addendum:** two subjects, n=1 each, deterministic.
This is a second data point pointing in the same direction as the
first, not a verdict. The universal claim of this Decision (".ailx
first, not a verdict. The universal claim of this Decision (".ail
is the AI authoring projection") would need ≥3 subjects with
statistical robustness to ratify; both current points point the
same way (AILX cohort cheaper and at-least-as-green) but neither
same way (AIL cohort cheaper and at-least-as-green) but neither
the sample size nor the cross-call noise observed in the Qwen
re-run is enough to close out the Decision.
@@ -1813,7 +1813,7 @@ would invoke literal-defaulting which axis-7 already excluded.
## Roundtrip Invariant
Every well-formed AILang module has both a canonical `.ail.json`
representation and a textual `.ailx` representation, and the two
representation and a textual `.ail` representation, and the two
are exact projections of the same AST. Concretely, both directions
of the bijection hold:
@@ -1822,7 +1822,7 @@ of the bijection hold:
`canonical::to_bytes(load_module(J))`. The textual surface is
the inverse of the loader composed with the printer; no
information is lost across the round-trip.
2. **text → JSON → text.** For every well-formed `.ailx` text `t`,
2. **text → JSON → text.** For every well-formed `.ail` text `t`,
`parse(t)` is a complete AST, and re-printing `print(parse(t))`
produces a text that re-parses to the same AST (modulo
formatting). The parser is total over the well-formed surface
@@ -1854,13 +1854,13 @@ inherit the gate automatically):
- `crates/ailang-surface/tests/round_trip.rs::print_then_parse_round_trips_every_fixture`
— for every `.ail.json`, `print` then `parse` produces canonical-
byte-equal output. Direction 1 above.
- `crates/ailang-surface/tests/round_trip.rs::parse_then_print_then_parse_is_idempotent_on_every_ailx_fixture`
— for every `.ailx` text `t`, `parse(t)` and `parse(print(parse(t)))`
- `crates/ailang-surface/tests/round_trip.rs::parse_then_print_then_parse_is_idempotent_on_every_ail_fixture`
— for every `.ail` text `t`, `parse(t)` and `parse(print(parse(t)))`
produce canonical-byte-equal AST. Direction 2 above.
- `crates/ailang-surface/tests/round_trip.rs::every_ailx_fixture_matches_its_json_counterpart`
— for every `.ailx` with a same-stem `.ail.json` counterpart,
- `crates/ailang-surface/tests/round_trip.rs::every_ail_fixture_matches_its_json_counterpart`
— for every `.ail` with a same-stem `.ail.json` counterpart,
`parse` of the text yields canonical bytes equal to the JSON
counterpart. Pins the hand-authored `.ailx` corpus against
counterpart. Pins the hand-authored `.ail` corpus against
drift between the two forms at the fixture level.
- `crates/ailang-core/tests/schema_coverage.rs::every_ast_variant_is_observed_in_the_fixture_corpus`
— every variant of `Def`, `Term`, `Pattern`, `Literal`, `Type`,
@@ -1882,7 +1882,7 @@ never by relaxing the test.
### Why this is anchored at top level
The invariant is a property of the language identity, not of any
one surface-design Decision. Decision 6 introduces the `.ailx`
one surface-design Decision. Decision 6 introduces the `.ail`
surface and lists round-trip-as-property as one of its
constraints, but the property is load-bearing for every
downstream concern that treats the two forms as exchangeable:
@@ -2186,7 +2186,7 @@ ail check <module.ail.json> — loads, validates, typechecks
ail manifest <module.ail.json> — table: name :: type !effects [hash]
ail describe <module> <name> — detail of a definition (form-A body)
ail render <module.ail.json> — JSON-AST → form-A text (exact inverse of `parse`)
ail parse <module.ailx> — form-A text → canonical JSON-AST
ail parse <module.ail> — form-A text → canonical JSON-AST
ail prose <module.ail.json> — JSON-AST → form-B (lossy human prose, no parser)
ail merge-prose <m.ail.json> <m.prose.txt>
— compose the LLM-mediator prompt for the prose round-trip
@@ -2403,7 +2403,7 @@ What **is** supported (and used as the smoke test for the pipeline):
`(term-ctor std_pair.Pair MkPair x y)` and `(pat-ctor MkPair x y)`
inside that scrutinee. Std-library demos (`examples/std_*_demo.ail.json`)
exercise this end-to-end.
- **AI-authoring text surface, form (A)** (Decision 6). The `ailang-surface` crate parses `.ailx` form-A
- **AI-authoring text surface, form (A)** (Decision 6). The `ailang-surface` crate parses `.ail` form-A
text into a canonical `ailang-core::ast::Module` and prints any module
back as form-A text. `ail render` and `ail describe` use it as the
sole text projection; `ail parse` is the inverse direction. Round-trip