diff --git a/bench/reference/compute_collatz.c b/bench/reference/compute_collatz.c index f1f1110..74de671 100644 --- a/bench/reference/compute_collatz.c +++ b/bench/reference/compute_collatz.c @@ -1,6 +1,6 @@ // Hand-C reference for bench_compute_collatz. // -// Same algorithm as examples/bench_compute_collatz.ailx — for each +// Same algorithm as examples/bench_compute_collatz.ail — for each // starting value in [1..N], count Collatz steps to reach 1, sum. // Three sizes: 10k / 100k / 500k starting values. // diff --git a/bench/reference/compute_intsum.c b/bench/reference/compute_intsum.c index c4b3e9f..a56a1d4 100644 --- a/bench/reference/compute_intsum.c +++ b/bench/reference/compute_intsum.c @@ -1,6 +1,6 @@ // Hand-C reference for bench_compute_intsum. // -// Same algorithm as examples/bench_compute_intsum.ailx — accumulate +// Same algorithm as examples/bench_compute_intsum.ail — accumulate // `i * 7` for i in [n, n-1, ..., 1], printing the final acc. // Three sizes: 1M / 10M / 50M iterations. // diff --git a/bench/reference/list_sum.c b/bench/reference/list_sum.c index 1daae84..e9a905b 100644 --- a/bench/reference/list_sum.c +++ b/bench/reference/list_sum.c @@ -1,6 +1,6 @@ // Hand-C reference for bench_list_sum. // -// Same algorithm as examples/bench_list_sum.ailx — build a linked list +// Same algorithm as examples/bench_list_sum.ail — build a linked list // of [0, 1, ..., N-1] via prepending, then sum by linear traversal. // Three workload sizes: 100k / 1M / 3M cells, matching the AILang // fixture exactly so the AILang/C wall-time ratio is fair. diff --git a/bench/reference/list_sum_explicit_free.c b/bench/reference/list_sum_explicit_free.c index 885497d..99168f5 100644 --- a/bench/reference/list_sum_explicit_free.c +++ b/bench/reference/list_sum_explicit_free.c @@ -2,7 +2,7 @@ // // Companion to list_sum.c (which leaks); this version adds explicit // `free()` calls to mirror AILang's explicit-mode RC dec-tax. Pairs -// with examples/bench_list_sum_explicit.ailx via bench/cross_lang.py. +// with examples/bench_list_sum_explicit.ail via bench/cross_lang.py. // // The free pass walks the list after sum, freeing each cell. This is // genuinely O(N) work that bench_list_sum.c never paid; the rc/c diff --git a/bench/reference/tree_walk.c b/bench/reference/tree_walk.c index 1c99cf1..89d78d7 100644 --- a/bench/reference/tree_walk.c +++ b/bench/reference/tree_walk.c @@ -1,6 +1,6 @@ // Hand-C reference for bench_tree_walk. // -// Same algorithm as examples/bench_tree_walk.ailx — build a balanced +// Same algorithm as examples/bench_tree_walk.ail — build a balanced // binary tree of given depth (every value = 1) and sum every node. // Three depths: 16 / 18 / 20 (= 65535 / 262143 / 1048575 nodes). // diff --git a/crates/ail/src/main.rs b/crates/ail/src/main.rs index 7165e18..dcd1a75 100644 --- a/crates/ail/src/main.rs +++ b/crates/ail/src/main.rs @@ -190,7 +190,7 @@ enum Cmd { #[arg(long)] json: bool, }, - /// Parses a `.ailx` source file (form (A)) into canonical + /// Parses a `.ail` source file (form (A)) into canonical /// `.ail.json`. Iter 14c addition; symmetric to `render`. /// /// The form-(A) projection is one of potentially many producers of @@ -269,7 +269,7 @@ fn compose_merge_prose_prompt(original_form_a: &str, edited_prose: &str) -> Stri "You are integrating prose edits back into an AILang module. ROLE -Your job is to produce an updated AILang module in Form-A (an .ailx +Your job is to produce an updated AILang module in Form-A (an .ail file) that reflects the human's prose edits while preserving the load-bearing semantic detail from the original module. @@ -826,7 +826,7 @@ fn main() -> Result<()> { } } Cmd::Parse { path, output } => { - // Read .ailx, parse via the surface crate, emit canonical + // Read .ail, parse via the surface crate, emit canonical // JSON. Symmetric to `render` (which goes the other way). let src = std::fs::read_to_string(&path) .with_context(|| format!("reading {}", path.display()))?; diff --git a/crates/ail/tests/e2e.rs b/crates/ail/tests/e2e.rs index ebd66eb..02932a5 100644 --- a/crates/ail/tests/e2e.rs +++ b/crates/ail/tests/e2e.rs @@ -429,8 +429,8 @@ fn render_parse_round_trip_canonical() { .expect("run ail render"); assert!(render.status.success(), "ail render failed"); - let tmp = std::env::temp_dir().join(format!("ailang_render_e2e_{}.ailx", std::process::id())); - std::fs::write(&tmp, &render.stdout).expect("write rendered ailx"); + let tmp = std::env::temp_dir().join(format!("ailang_render_e2e_{}.ail", std::process::id())); + std::fs::write(&tmp, &render.stdout).expect("write rendered ail"); let parsed = Command::new(ail_bin()) .args(["parse", tmp.to_str().unwrap()]) @@ -2153,7 +2153,7 @@ fn build_and_run_with_rc_stats(example: &str) -> (String, u64, u64, i64) { /// not leak the LCons outer cells. /// /// Background: 18f.2's tail-latency bench found that -/// `bench_latency_explicit.ailx` peaks at the same RSS as the +/// `bench_latency_explicit.ail` peaks at the same RSS as the /// implicit-mode variant despite carrying mode annotations /// throughout the hot path. Diagnosis: in /// diff --git a/crates/ail/tests/roundtrip_cli.rs b/crates/ail/tests/roundtrip_cli.rs index cb931e1..9e2039e 100644 --- a/crates/ail/tests/roundtrip_cli.rs +++ b/crates/ail/tests/roundtrip_cli.rs @@ -62,7 +62,7 @@ fn roundtrip_one(fixture: &Path, tmpdir: &Path) -> Result<(), String> { let bytes_orig = ailang_core::canonical::to_bytes(&original_module); let h_orig = blake3::hash(&bytes_orig); - // Step B: `ail render ` → captured stdout = ailx text. + // Step B: `ail render ` → captured stdout = ail text. let render_out = Command::new(ail_bin()) .args(["render", fixture.to_str().unwrap()]) .output() @@ -75,20 +75,20 @@ fn roundtrip_one(fixture: &Path, tmpdir: &Path) -> Result<(), String> { )); } - // Step C: write the rendered .ailx into the tempdir. + // Step C: write the rendered .ail into the tempdir. let stem = fixture .file_name() .and_then(|n| n.to_str()) .and_then(|n| n.strip_suffix(".ail.json")) .unwrap_or("fixture"); - let tmp_ailx = tmpdir.join(format!("{stem}.round.ailx")); - std::fs::write(&tmp_ailx, &render_out.stdout) - .map_err(|e| format!("write tempfile {}: {e}", tmp_ailx.display()))?; + let tmp_ail = tmpdir.join(format!("{stem}.round.ail")); + std::fs::write(&tmp_ail, &render_out.stdout) + .map_err(|e| format!("write tempfile {}: {e}", tmp_ail.display()))?; - // Step D: `ail parse ` → captured stdout = canonical + // Step D: `ail parse ` → captured stdout = canonical // bytes + trailing newline. let parse_out = Command::new(ail_bin()) - .args(["parse", tmp_ailx.to_str().unwrap()]) + .args(["parse", tmp_ail.to_str().unwrap()]) .output() .map_err(|e| format!("spawn ail parse: {e}"))?; if !parse_out.status.success() { diff --git a/crates/ailang-core/specs/form_a.md b/crates/ailang-core/specs/form_a.md index 34405ca..3862720 100644 --- a/crates/ailang-core/specs/form_a.md +++ b/crates/ailang-core/specs/form_a.md @@ -2,7 +2,7 @@ Form-A is the canonical textual surface of AILang. It is the form that LLMs generate when asked to produce or edit AILang code, and the form -that `ail parse .ailx` reads. The inverse direction — printing +that `ail parse .ail` reads. The inverse direction — printing JSON-AST as Form-A — is `ail render .ail.json`. Round-trip through this pair is the gating contract: `parse(render(m)) == m` for every well-formed module. @@ -26,7 +26,7 @@ Lisp-style S-expression dress that: - omits structural noise (no field tags; positions carry meaning) - has a real parser with positional error messages - round-trips through `ail render` ↔ `ail parse` losslessly -- is the form every existing `examples/*.ailx` is written in +- is the form every existing `examples/*.ail` is written in LLMs generate Form-A; the toolchain converts to JSON. @@ -48,7 +48,7 @@ LLMs generate Form-A; the toolchain converts to JSON. DEF*) ``` -The module name MUST equal the file stem (`bench_list_sum.ailx` → +The module name MUST equal the file stem (`bench_list_sum.ail` → `(module bench_list_sum ...)`). ## Imports @@ -269,11 +269,11 @@ significantly. ## Few-shot corpus -These four modules are real `examples/*.ailx` content. Each one is +These four modules are real `examples/*.ail` content. Each one is parseable and typechecks clean. Pattern-match against them when generating new code. -### 1 — `hello.ailx`: minimal IO program +### 1 — `hello.ail`: minimal IO program ``` (module hello @@ -283,7 +283,7 @@ generating new code. (body (do io/print_str "Hello, AILang.")))) ``` -### 2 — `borrow_own_demo.ailx`: mode annotations on a recursive list +### 2 — `borrow_own_demo.ail`: mode annotations on a recursive list ``` (module borrow_own_demo @@ -333,7 +333,7 @@ generating new code. (do io/print_int (app sum_list xs))))))) ``` -### 3 — `lit_pat.ailx`: literal patterns and nested ctor patterns +### 3 — `lit_pat.ail`: literal patterns and nested ctor patterns ``` (module lit_pat diff --git a/crates/ailang-surface/tests/round_trip.rs b/crates/ailang-surface/tests/round_trip.rs index 2447525..0746fb1 100644 --- a/crates/ailang-surface/tests/round_trip.rs +++ b/crates/ailang-surface/tests/round_trip.rs @@ -10,18 +10,18 @@ //! For every `examples/*.ail.json` fixture, load → `print` → //! `parse` → canonical bytes; assert byte-equal to original. //! -//! 2. `every_ailx_fixture_matches_its_json_counterpart`: hand- -//! authored ground-truth check. For every `examples/*.ailx` +//! 2. `every_ail_fixture_matches_its_json_counterpart`: hand- +//! authored ground-truth check. For every `examples/*.ail` //! fixture, parse → canonical bytes; if a same-stem `.ail.json` //! counterpart exists, assert canonical-byte equality against -//! it. Pins the `.ailx` corpus against semantic drift between +//! it. Pins the `.ail` corpus against semantic drift between //! the two forms at the fixture level. //! -//! 3. `parse_then_print_then_parse_is_idempotent_on_every_ailx_fixture`: +//! 3. `parse_then_print_then_parse_is_idempotent_on_every_ail_fixture`: //! Direction 2 of the Roundtrip Invariant. For every well-formed -//! `.ailx` text `t`, asserts `canonical_bytes(parse(t))` equals +//! `.ail` text `t`, asserts `canonical_bytes(parse(t))` equals //! `canonical_bytes(parse(print(parse(t))))`. Robust against -//! future `.ailx` fixtures without a JSON counterpart. +//! future `.ail` fixtures without a JSON counterpart. use std::path::{Path, PathBuf}; @@ -100,7 +100,7 @@ fn round_trip_one(path: &Path) -> Result<(), String> { Ok(()) } -fn list_ailx_fixtures() -> Vec { +fn list_ail_fixtures() -> Vec { let dir = examples_dir(); let mut paths: Vec = std::fs::read_dir(&dir) .unwrap_or_else(|e| panic!("read_dir({}): {e}", dir.display())) @@ -109,7 +109,7 @@ fn list_ailx_fixtures() -> Vec { .filter(|p| { p.file_name() .and_then(|n| n.to_str()) - .map(|n| n.ends_with(".ailx")) + .map(|n| n.ends_with(".ail")) .unwrap_or(false) }) .collect(); @@ -118,11 +118,11 @@ fn list_ailx_fixtures() -> Vec { } #[test] -fn every_ailx_fixture_matches_its_json_counterpart() { - let fixtures = list_ailx_fixtures(); +fn every_ail_fixture_matches_its_json_counterpart() { + let fixtures = list_ail_fixtures(); assert!( !fixtures.is_empty(), - "no .ailx fixtures found under {}", + "no .ail fixtures found under {}", examples_dir().display() ); @@ -130,32 +130,32 @@ fn every_ailx_fixture_matches_its_json_counterpart() { let mut paired = 0usize; let mut parse_only = 0usize; - for ailx_path in &fixtures { - let text = match std::fs::read_to_string(ailx_path) { + for ail_path in &fixtures { + let text = match std::fs::read_to_string(ail_path) { Ok(t) => t, Err(e) => { - failures.push(format!("{}: read failed: {e}", ailx_path.display())); + failures.push(format!("{}: read failed: {e}", ail_path.display())); continue; } }; let parsed = match ailang_surface::parse(&text) { Ok(m) => m, Err(e) => { - failures.push(format!("{}: parse failed: {e}", ailx_path.display())); + failures.push(format!("{}: parse failed: {e}", ail_path.display())); continue; } }; - // Same-stem counterpart lookup. `.ailx` → `.ail.json`. - let stem = ailx_path + // Same-stem counterpart lookup. `.ail` → `.ail.json`. + let stem = ail_path .file_name() .and_then(|n| n.to_str()) - .and_then(|n| n.strip_suffix(".ailx")) + .and_then(|n| n.strip_suffix(".ail")) .unwrap_or(""); - let json_path = ailx_path.with_file_name(format!("{stem}.ail.json")); + let json_path = ail_path.with_file_name(format!("{stem}.ail.json")); if !json_path.exists() { - // Spec: parse success alone is sufficient for `.ailx` files + // Spec: parse success alone is sufficient for `.ail` files // without a JSON counterpart. (Today: none expected.) parse_only += 1; continue; @@ -166,7 +166,7 @@ fn every_ailx_fixture_matches_its_json_counterpart() { Err(e) => { failures.push(format!( "{}: load_module({}) failed: {e}", - ailx_path.display(), + ail_path.display(), json_path.display() )); continue; @@ -179,7 +179,7 @@ fn every_ailx_fixture_matches_its_json_counterpart() { let s_round = String::from_utf8_lossy(&bytes_parsed).into_owned(); failures.push(format!( "{} vs {}: canonical bytes differ.\noriginal: {s_orig}\nparsed: {s_round}", - ailx_path.display(), + ail_path.display(), json_path.display() )); continue; @@ -189,7 +189,7 @@ fn every_ailx_fixture_matches_its_json_counterpart() { if !failures.is_empty() { panic!( - "{} of {} .ailx fixture(s) failed cross-check (paired ok: {}, parse-only: {}):\n{}", + "{} of {} .ail fixture(s) failed cross-check (paired ok: {}, parse-only: {}):\n{}", failures.len(), fixtures.len(), paired, @@ -198,27 +198,27 @@ fn every_ailx_fixture_matches_its_json_counterpart() { ); } eprintln!( - ".ailx cross-check ok ({} paired, {} parse-only)", + ".ail cross-check ok ({} paired, {} parse-only)", paired, parse_only ); } /// Direction 2 of the Roundtrip Invariant (DESIGN.md §"Roundtrip -/// Invariant"): for every well-formed `.ailx` text `t`, the +/// Invariant"): for every well-formed `.ail` text `t`, the /// composition `parse → print → parse` is idempotent on the AST. /// -/// For the 57 `.ailx` fixtures that have a JSON counterpart this +/// For the 57 `.ail` fixtures that have a JSON counterpart this /// follows logically from `print_then_parse_round_trips_every_fixture` -/// + `every_ailx_fixture_matches_its_json_counterpart`. This test +/// + `every_ail_fixture_matches_its_json_counterpart`. This test /// asserts the property directly so it stays robust for future -/// `.ailx` fixtures without a JSON counterpart, and so the spec's +/// `.ail` fixtures without a JSON counterpart, and so the spec's /// Direction-2 claim has a dedicated enforcement point. #[test] -fn parse_then_print_then_parse_is_idempotent_on_every_ailx_fixture() { - let fixtures = list_ailx_fixtures(); +fn parse_then_print_then_parse_is_idempotent_on_every_ail_fixture() { + let fixtures = list_ail_fixtures(); assert!( !fixtures.is_empty(), - "no .ailx fixtures found under {}", + "no .ail fixtures found under {}", examples_dir().display() ); @@ -266,12 +266,12 @@ fn parse_then_print_then_parse_is_idempotent_on_every_ailx_fixture() { if !failures.is_empty() { panic!( - "{} of {} .ailx fixture(s) failed idempotency check (passed: {}):\n{}", + "{} of {} .ail fixture(s) failed idempotency check (passed: {}):\n{}", failures.len(), fixtures.len(), passed, failures.join("\n\n") ); } - eprintln!("parse→print→parse idempotency ok for {passed} .ailx fixtures"); + eprintln!("parse→print→parse idempotency ok for {passed} .ail fixtures"); } diff --git a/docs/DESIGN.md b/docs/DESIGN.md index b370365..d621ae8 100644 --- a/docs/DESIGN.md +++ b/docs/DESIGN.md @@ -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 -o `. Symmetric to +- CLI: `ail parse -o `. 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 — loads, validates, typechecks ail manifest — table: name :: type !effects [hash] ail describe — detail of a definition (form-A body) ail render — JSON-AST → form-A text (exact inverse of `parse`) -ail parse — form-A text → canonical JSON-AST +ail parse — form-A text → canonical JSON-AST ail prose — JSON-AST → form-B (lossy human prose, no parser) ail merge-prose — 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 diff --git a/docs/PROSE_ROUNDTRIP.md b/docs/PROSE_ROUNDTRIP.md index 3412ea3..c80eac4 100644 --- a/docs/PROSE_ROUNDTRIP.md +++ b/docs/PROSE_ROUNDTRIP.md @@ -19,14 +19,14 @@ modes to watch for. ## What the LLM produces Iter 20f revised a load-bearing piece of this cycle: the LLM emits -**Form-A** (an `.ailx` document — the canonical authoring surface +**Form-A** (an `.ail` document — the canonical authoring surface fixed by Decision 6), not JSON-AST. JSON-AST is the canonical hashable artefact, but it is not a writing surface — every type reference wraps in `{"k": "con", ...}`, every term in `{"t": "...", ...}`, and a single missing `param_modes` entry is a schema error rather than a parse error with a position. -Form-A is the form `examples/*.ailx` are written in, the form +Form-A is the form `examples/*.ail` are written in, the form `ail render` produces, and the form `ail parse` consumes. The full LLM-targeted specification is shipped inside the binary as `ailang_core::FORM_A_SPEC` (sourced from @@ -39,8 +39,8 @@ LLM-targeted specification is shipped inside the binary as 1. ail prose foo.ail.json > foo.prose.txt 2. $EDITOR foo.prose.txt # human edits freely 3. ail merge-prose foo.ail.json foo.prose.txt > prompt.txt -4. cat prompt.txt | > foo.new.ailx -5. ail parse foo.new.ailx > foo.new.ail.json +4. cat prompt.txt | > foo.new.ail +5. ail parse foo.new.ail > foo.new.ail.json 6. ail check foo.new.ail.json 7. mv foo.new.ail.json foo.ail.json # if check is clean ``` @@ -74,7 +74,7 @@ CLI helper. You are integrating prose edits back into an AILang module. ROLE -Your job is to produce an updated AILang module in Form-A (an .ailx +Your job is to produce an updated AILang module in Form-A (an .ail file) that reflects the human's prose edits while preserving the load-bearing semantic detail from the original module. diff --git a/docs/journals/2026-05-12-iter-ext-rename.md b/docs/journals/2026-05-12-iter-ext-rename.md new file mode 100644 index 0000000..31b2bb4 --- /dev/null +++ b/docs/journals/2026-05-12-iter-ext-rename.md @@ -0,0 +1,107 @@ +# iter ext-rename — `.ailx` extension renamed to `.ail` across the live toolchain + +**Date:** 2026-05-12 +**Started from:** 17b370b (post-audit-ms close, working tree clean) +**Status:** DONE +**Tasks completed:** 1 of 1 + +## Summary + +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 that +`ail check` / `build` / `run` actually loads today. + +The earlier `.ailx` extension carried an unmotivated `x`. The +cross-model authoring-form test (cma + ms milestones, closed +earlier today) treated `.ailx` purely as a cohort identifier; +once the empirical case for keeping a separate authoring surface +had been ratified (DESIGN.md §Decision-6 addendum), the awkward +extension was the last asymmetry between Form A and Form B. +User-initiated rename. + +## Scope + +In scope (touched): + +- 61 example files `examples/**/*.ailx → .ail` (57 top-level + + 4 `examples/fieldtest/`) renamed via `git mv`. +- `experiments/.../rendered/ailx.md → ail.md` rename. +- All live toolchain prose and code: `crates/ail/`, + `crates/ailang-surface/`, `crates/ailang-core/specs/form_a.md`, + `docs/DESIGN.md`, `docs/roadmap.md`, `docs/PROSE_ROUNDTRIP.md`, + `bench/reference/*.c`, all of `skills/`, the Cross-Model + Authoring experiment crates under + `experiments/.../{render,harness,master,rendered,README.md}`. +- Cohort identifier `ailx → ail` in the experiment crate (Rust + `Cohort::Ailx → Cohort::Ail`, `Form::Ailx → Form::Ail`, + per-cohort path component, `{form-only: ailx}` marker, + ```ailx codefence language tag). + +Out of scope (untouched, by design): + +- `docs/journal-archive.md` (content-frozen per CLAUDE.md). +- All `docs/journals/`, `docs/specs/`, `docs/plans/`, + `bench/orchestrator-stats/` — historical records describing + states as they were. +- `experiments/.../runs/` — frozen LLM-output artefacts; the + models in those runs actually saw `.ailx`, and renaming the + artefacts would falsify the experimental record. + +The exclusion preserves the journals as honest history. Anyone +chasing why a 2026-05-11 fieldtest names `.ailx` files will find +that the renaming happened today and that the names were correct +at the time of writing. + +## Why boss-direct edit (no planner / implement dispatch) + +Same rationale as iter rt.2: the user fully specified the +transformation (rename `.ailx → .ail`, rename cohort +`ailx → ail`, historical records untouched), down to the three +open questions that were resolved up-front via AskUserQuestion +(cohort name; treatment of frozen runs; treatment of historical +docs). The execution was two `git mv` passes plus one +heavily-scoped `sed` over an explicit 41-file whitelist (plus +two follow-up files surfaced by the negative-grep). No design +judgement deferred to execution time. + +## Files touched (working-tree counts) + +- 61 renames under `examples/`. +- 1 rename `experiments/.../rendered/ailx.md → ail.md`. +- 35 content-edited files: the 41-file SCOPE whitelist minus + the experiment-crate files that had no `ailx` content, plus + two follow-up files surfaced by the negative-grep + (`examples/bench_list_sum_explicit.ail`'s in-source comment; + `experiments/.../rendered/json.md` for three `AILX` acronyms + in prose). + +## Verification + +- `cargo build --workspace` clean. +- `cargo test --workspace` green; the renamed identifiers + `every_ail_fixture_matches_its_json_counterpart` and + `parse_then_print_then_parse_is_idempotent_on_every_ail_fixture` + in `crates/ailang-surface/tests/round_trip.rs` were run + explicitly and pass. +- Experiment crate `cargo test` (out-of-workspace) green. +- `bench/check.py`, `bench/compile_check.py`, `bench/cross_lang.py`: + 0 regressed across all three; 6 improved-beyond-tolerance on + `latency.explicit_at_rc` (same cluster the previous two audits + today already observed; decoupled from this rename — baseline + left pristine). +- Negative grep: `git grep -nIi 'ailx|Ailx|AILX'` outside the + out-of-scope paths returns no matches. + +## Roadmap implications + +The `.ail` extension is now canonical authoring surface but the +CLI does not yet accept it — `ail check foo.ail` still produces +the misleading JSON-parse error. The existing P2 todo +"`ail check`/`build`/`run` accept `.ail` extension" (roadmap.md +line 185) is the natural next iteration: it closes the loop +opened by today's rename and removes the largest first-command +friction for any LLM-author writing in Form A. Promoting that +todo to the immediate next dispatch. diff --git a/docs/journals/INDEX.md b/docs/journals/INDEX.md index f05b8f6..aefc679 100644 --- a/docs/journals/INDEX.md +++ b/docs/journals/INDEX.md @@ -28,3 +28,4 @@ - 2026-05-12 — iter ms.1: pipeline anyhow-chain preservation — `{e}` → `{e:#}` at `pipeline.rs:118` restores `serde_json` parse-error leaf in JSON-cohort feedback; pinning unit test added (14/14 green) → 2026-05-12-iter-ms.1.md - 2026-05-12 — iter ms.2: Qwen3-Coder-Next retroactive re-run + first CodeLlama-13b-Instruct run via IONOS; DESIGN.md §Decision-6 addendum extended from 2-col single-subject to 4-col two-subject; both subjects agree on direction (AILX cheaper + ≥ green); roadmap P3 multi-subject entry removed → 2026-05-12-iter-ms.2.md - 2026-05-12 — audit-ms: milestone close (Multi-subject Authoring-Form Test — CodeLlama Replication) — architect clean, bench all-green (same 5-metric latency.explicit_at_rc improvement cluster as audit-cma earlier today, baseline left pristine for second consecutive audit) → 2026-05-12-audit-ms.md +- 2026-05-12 — iter ext-rename: `.ailx` → `.ail` extension rename across the live toolchain (61 example renames + 35 content edits + experiment-crate cohort rename `ailx → ail`); historical docs (journals, archive, specs, plans, frozen experiment runs) deliberately untouched; cargo+bench all-green; opens follow-up `.ail`-CLI-acceptance iter → 2026-05-12-iter-ext-rename.md diff --git a/docs/roadmap.md b/docs/roadmap.md index b37ca7c..253eb14 100644 --- a/docs/roadmap.md +++ b/docs/roadmap.md @@ -171,7 +171,7 @@ context. Pick the next milestone from P1.)_ type name). In the known-owner branch, list the owner's available type defs as candidates the way `bare-cross-module-type-ref` lists candidates from imports. - - context: fieldtest 2026-05-11 — `examples/ct_3*.ailx` exhibits both branches with identical-shape diagnostics. + - context: fieldtest 2026-05-11 — `examples/ct_3*.ail` exhibits both branches with identical-shape diagnostics. - [ ] **\[todo\]** Workspace search beyond entry-module's directory — `load_workspace` only finds sibling `.ail.json` files in the same directory as the entry module, so any consumer of prelude/std in a @@ -182,12 +182,12 @@ context. Pick the next milestone from P1.)_ - context: fieldtest 2026-05-11 — fieldtest fixtures could not be placed under `examples/fieldtest/` because of this; predates the canonical-type-names milestone but surfaces every time. -- [ ] **\[todo\]** `ail check`/`build`/`run` accept `.ailx` extension — - today `ail check foo.ailx` produces a misleading JSON-parse error +- [ ] **\[todo\]** `ail check`/`build`/`run` accept `.ail` extension — + today `ail check foo.ail` produces a misleading JSON-parse error (`json: expected value at line 1 column 1`) because the loader only accepts `.ail.json`. Either teach the CLI subcommands to auto-parse - `.ailx` internally, or detect the extension and emit a - "did you mean `ail parse foo.ailx`?" hint. The LLM-author's natural + `.ail` internally, or detect the extension and emit a + "did you mean `ail parse foo.ail`?" hint. The LLM-author's natural first command should not be the one that produces a misleading diagnostic. - context: fieldtest 2026-05-11 — orthogonal to canonical-type-names @@ -205,13 +205,13 @@ context. Pick the next milestone from P1.)_ ## P3 — Ideas -- [ ] **\[todo\]** `compare_primitives_smoke.ailx` counterpart — +- [ ] **\[todo\]** `compare_primitives_smoke.ail` counterpart — the canonical happy-path exhibit for canonical-type-names ships only as JSON. Per Decision 6, Surface is the LLM-author surface; - the milestone should have a `.ailx` counterpart. Two paths: author - `examples/compare_primitives_smoke.ailx` to round-trip into the + the milestone should have a `.ail` counterpart. Two paths: author + `examples/compare_primitives_smoke.ail` to round-trip into the existing JSON, OR retire the JSON fixture in favour of - `examples/ct_1_ordering_signum.{ailx,ail.json}` as the new canonical + `examples/ct_1_ordering_signum.{ail,ail.json}` as the new canonical happy-path exhibit. - context: fieldtest 2026-05-11 (spec_gap). - [ ] **\[todo\]** Codegen `lookup_ctor_in_pattern` type-anchoring — diff --git a/examples/bench_closure_chain.ailx b/examples/bench_closure_chain.ail similarity index 100% rename from examples/bench_closure_chain.ailx rename to examples/bench_closure_chain.ail diff --git a/examples/bench_compute_collatz.ailx b/examples/bench_compute_collatz.ail similarity index 100% rename from examples/bench_compute_collatz.ailx rename to examples/bench_compute_collatz.ail diff --git a/examples/bench_compute_intsum.ailx b/examples/bench_compute_intsum.ail similarity index 100% rename from examples/bench_compute_intsum.ailx rename to examples/bench_compute_intsum.ail diff --git a/examples/bench_hof_pipeline.ailx b/examples/bench_hof_pipeline.ail similarity index 100% rename from examples/bench_hof_pipeline.ailx rename to examples/bench_hof_pipeline.ail diff --git a/examples/bench_latency_explicit.ailx b/examples/bench_latency_explicit.ail similarity index 100% rename from examples/bench_latency_explicit.ailx rename to examples/bench_latency_explicit.ail diff --git a/examples/bench_latency_implicit.ailx b/examples/bench_latency_implicit.ail similarity index 100% rename from examples/bench_latency_implicit.ailx rename to examples/bench_latency_implicit.ail diff --git a/examples/bench_list_sum.ailx b/examples/bench_list_sum.ail similarity index 100% rename from examples/bench_list_sum.ailx rename to examples/bench_list_sum.ail diff --git a/examples/bench_list_sum_explicit.ailx b/examples/bench_list_sum_explicit.ail similarity index 97% rename from examples/bench_list_sum_explicit.ailx rename to examples/bench_list_sum_explicit.ail index 03b7740..39a5853 100644 --- a/examples/bench_list_sum_explicit.ailx +++ b/examples/bench_list_sum_explicit.ail @@ -1,6 +1,6 @@ ; Bench fixture: explicit-mode pair of bench_list_sum. ; -; Same algorithm and same sizes as bench_list_sum.ailx (build a list of +; Same algorithm and same sizes as bench_list_sum.ail (build a list of ; 0..N-1, sum it, print) but with full `(borrow)` / `(own)` annotations ; on every fn-param signature in the hot path. Under --alloc=rc the ; codegen now emits `inc`/`dec` instructions: each cell allocated by diff --git a/examples/bench_tree_walk.ailx b/examples/bench_tree_walk.ail similarity index 100% rename from examples/bench_tree_walk.ailx rename to examples/bench_tree_walk.ail diff --git a/examples/borrow_own_demo.ailx b/examples/borrow_own_demo.ail similarity index 100% rename from examples/borrow_own_demo.ailx rename to examples/borrow_own_demo.ail diff --git a/examples/box.ailx b/examples/box.ail similarity index 100% rename from examples/box.ailx rename to examples/box.ail diff --git a/examples/clone_demo.ailx b/examples/clone_demo.ail similarity index 100% rename from examples/clone_demo.ailx rename to examples/clone_demo.ail diff --git a/examples/ct_1_ordering_signum.ailx b/examples/ct_1_ordering_signum.ail similarity index 100% rename from examples/ct_1_ordering_signum.ailx rename to examples/ct_1_ordering_signum.ail diff --git a/examples/ct_2_bare_cross_module.ailx b/examples/ct_2_bare_cross_module.ail similarity index 100% rename from examples/ct_2_bare_cross_module.ailx rename to examples/ct_2_bare_cross_module.ail diff --git a/examples/ct_3_bad_qualified.ailx b/examples/ct_3_bad_qualified.ail similarity index 100% rename from examples/ct_3_bad_qualified.ailx rename to examples/ct_3_bad_qualified.ail diff --git a/examples/ct_3b_bad_qualified_known_module.ailx b/examples/ct_3b_bad_qualified_known_module.ail similarity index 100% rename from examples/ct_3b_bad_qualified_known_module.ailx rename to examples/ct_3b_bad_qualified_known_module.ail diff --git a/examples/ct_4_qualified_class.ailx b/examples/ct_4_qualified_class.ail similarity index 100% rename from examples/ct_4_qualified_class.ailx rename to examples/ct_4_qualified_class.ail diff --git a/examples/eq_demo.ailx b/examples/eq_demo.ail similarity index 100% rename from examples/eq_demo.ailx rename to examples/eq_demo.ail diff --git a/examples/escape_local_demo.ailx b/examples/escape_local_demo.ail similarity index 100% rename from examples/escape_local_demo.ailx rename to examples/escape_local_demo.ail diff --git a/examples/fieldtest/floats_1_newton_sqrt.ailx b/examples/fieldtest/floats_1_newton_sqrt.ail similarity index 100% rename from examples/fieldtest/floats_1_newton_sqrt.ailx rename to examples/fieldtest/floats_1_newton_sqrt.ail diff --git a/examples/fieldtest/floats_2_average_int_list.ailx b/examples/fieldtest/floats_2_average_int_list.ail similarity index 100% rename from examples/fieldtest/floats_2_average_int_list.ailx rename to examples/fieldtest/floats_2_average_int_list.ail diff --git a/examples/fieldtest/floats_3_safe_division.ailx b/examples/fieldtest/floats_3_safe_division.ail similarity index 100% rename from examples/fieldtest/floats_3_safe_division.ailx rename to examples/fieldtest/floats_3_safe_division.ail diff --git a/examples/fieldtest/floats_4_float_to_str_reach.ailx b/examples/fieldtest/floats_4_float_to_str_reach.ail similarity index 100% rename from examples/fieldtest/floats_4_float_to_str_reach.ailx rename to examples/fieldtest/floats_4_float_to_str_reach.ail diff --git a/examples/gc_stress.ailx b/examples/gc_stress.ail similarity index 100% rename from examples/gc_stress.ailx rename to examples/gc_stress.ail diff --git a/examples/hello.ailx b/examples/hello.ail similarity index 100% rename from examples/hello.ailx rename to examples/hello.ail diff --git a/examples/list_map_poly.ailx b/examples/list_map_poly.ail similarity index 100% rename from examples/list_map_poly.ailx rename to examples/list_map_poly.ail diff --git a/examples/lit_pat.ailx b/examples/lit_pat.ail similarity index 100% rename from examples/lit_pat.ailx rename to examples/lit_pat.ail diff --git a/examples/local_rec_as_value.ailx b/examples/local_rec_as_value.ail similarity index 100% rename from examples/local_rec_as_value.ailx rename to examples/local_rec_as_value.ail diff --git a/examples/local_rec_as_value_capture.ailx b/examples/local_rec_as_value_capture.ail similarity index 100% rename from examples/local_rec_as_value_capture.ailx rename to examples/local_rec_as_value_capture.ail diff --git a/examples/local_rec_capture.ailx b/examples/local_rec_capture.ail similarity index 100% rename from examples/local_rec_capture.ailx rename to examples/local_rec_capture.ail diff --git a/examples/local_rec_demo.ailx b/examples/local_rec_demo.ail similarity index 100% rename from examples/local_rec_demo.ailx rename to examples/local_rec_demo.ail diff --git a/examples/local_rec_let_capture.ailx b/examples/local_rec_let_capture.ail similarity index 100% rename from examples/local_rec_let_capture.ailx rename to examples/local_rec_let_capture.ail diff --git a/examples/local_rec_match_capture.ailx b/examples/local_rec_match_capture.ail similarity index 100% rename from examples/local_rec_match_capture.ailx rename to examples/local_rec_match_capture.ail diff --git a/examples/nested_let_rec.ailx b/examples/nested_let_rec.ail similarity index 100% rename from examples/nested_let_rec.ailx rename to examples/nested_let_rec.ail diff --git a/examples/nested_pat.ailx b/examples/nested_pat.ail similarity index 100% rename from examples/nested_pat.ailx rename to examples/nested_pat.ail diff --git a/examples/pat_extract_partial_drop.ailx b/examples/pat_extract_partial_drop.ail similarity index 100% rename from examples/pat_extract_partial_drop.ailx rename to examples/pat_extract_partial_drop.ail diff --git a/examples/poly_rec_capture.ailx b/examples/poly_rec_capture.ail similarity index 100% rename from examples/poly_rec_capture.ailx rename to examples/poly_rec_capture.ail diff --git a/examples/rc_app_let_partial_drop_leak.ailx b/examples/rc_app_let_partial_drop_leak.ail similarity index 100% rename from examples/rc_app_let_partial_drop_leak.ailx rename to examples/rc_app_let_partial_drop_leak.ail diff --git a/examples/rc_drop_iterative_long_list.ailx b/examples/rc_drop_iterative_long_list.ail similarity index 100% rename from examples/rc_drop_iterative_long_list.ailx rename to examples/rc_drop_iterative_long_list.ail diff --git a/examples/rc_let_alias_implicit_param.ailx b/examples/rc_let_alias_implicit_param.ail similarity index 100% rename from examples/rc_let_alias_implicit_param.ailx rename to examples/rc_let_alias_implicit_param.ail diff --git a/examples/rc_let_implicit_returning_app.ailx b/examples/rc_let_implicit_returning_app.ail similarity index 100% rename from examples/rc_let_implicit_returning_app.ailx rename to examples/rc_let_implicit_returning_app.ail diff --git a/examples/rc_let_owned_app_leak.ailx b/examples/rc_let_owned_app_leak.ail similarity index 100% rename from examples/rc_let_owned_app_leak.ailx rename to examples/rc_let_owned_app_leak.ail diff --git a/examples/rc_match_arm_partial_drop_leak.ailx b/examples/rc_match_arm_partial_drop_leak.ail similarity index 100% rename from examples/rc_match_arm_partial_drop_leak.ailx rename to examples/rc_match_arm_partial_drop_leak.ail diff --git a/examples/rc_own_param_drop.ailx b/examples/rc_own_param_drop.ail similarity index 100% rename from examples/rc_own_param_drop.ailx rename to examples/rc_own_param_drop.ail diff --git a/examples/rc_own_param_partial_drop_leak.ailx b/examples/rc_own_param_partial_drop_leak.ail similarity index 100% rename from examples/rc_own_param_partial_drop_leak.ailx rename to examples/rc_own_param_partial_drop_leak.ail diff --git a/examples/rc_pin_recurse_implicit.ailx b/examples/rc_pin_recurse_implicit.ail similarity index 100% rename from examples/rc_pin_recurse_implicit.ailx rename to examples/rc_pin_recurse_implicit.ail diff --git a/examples/rc_tail_sum_explicit_leak.ailx b/examples/rc_tail_sum_explicit_leak.ail similarity index 100% rename from examples/rc_tail_sum_explicit_leak.ailx rename to examples/rc_tail_sum_explicit_leak.ail diff --git a/examples/reuse_as_demo.ailx b/examples/reuse_as_demo.ail similarity index 100% rename from examples/reuse_as_demo.ailx rename to examples/reuse_as_demo.ail diff --git a/examples/std_either.ailx b/examples/std_either.ail similarity index 100% rename from examples/std_either.ailx rename to examples/std_either.ail diff --git a/examples/std_either_demo.ailx b/examples/std_either_demo.ail similarity index 100% rename from examples/std_either_demo.ailx rename to examples/std_either_demo.ail diff --git a/examples/std_either_list.ailx b/examples/std_either_list.ail similarity index 100% rename from examples/std_either_list.ailx rename to examples/std_either_list.ail diff --git a/examples/std_either_list_demo.ailx b/examples/std_either_list_demo.ail similarity index 100% rename from examples/std_either_list_demo.ailx rename to examples/std_either_list_demo.ail diff --git a/examples/std_list.ailx b/examples/std_list.ail similarity index 100% rename from examples/std_list.ailx rename to examples/std_list.ail diff --git a/examples/std_list_demo.ailx b/examples/std_list_demo.ail similarity index 100% rename from examples/std_list_demo.ailx rename to examples/std_list_demo.ail diff --git a/examples/std_list_more_demo.ailx b/examples/std_list_more_demo.ail similarity index 100% rename from examples/std_list_more_demo.ailx rename to examples/std_list_more_demo.ail diff --git a/examples/std_list_stress.ailx b/examples/std_list_stress.ail similarity index 100% rename from examples/std_list_stress.ailx rename to examples/std_list_stress.ail diff --git a/examples/std_maybe.ailx b/examples/std_maybe.ail similarity index 100% rename from examples/std_maybe.ailx rename to examples/std_maybe.ail diff --git a/examples/std_maybe_demo.ailx b/examples/std_maybe_demo.ail similarity index 100% rename from examples/std_maybe_demo.ailx rename to examples/std_maybe_demo.ail diff --git a/examples/std_pair.ailx b/examples/std_pair.ail similarity index 100% rename from examples/std_pair.ailx rename to examples/std_pair.ail diff --git a/examples/std_pair_demo.ailx b/examples/std_pair_demo.ail similarity index 100% rename from examples/std_pair_demo.ailx rename to examples/std_pair_demo.ail diff --git a/examples/unreachable_demo.ailx b/examples/unreachable_demo.ail similarity index 100% rename from examples/unreachable_demo.ailx rename to examples/unreachable_demo.ail diff --git a/experiments/2026-05-12-cross-model-authoring/README.md b/experiments/2026-05-12-cross-model-authoring/README.md index dce977a..cf8e759 100644 --- a/experiments/2026-05-12-cross-model-authoring/README.md +++ b/experiments/2026-05-12-cross-model-authoring/README.md @@ -1,6 +1,6 @@ # Cross-model authoring-form test -Empirical measurement of whether `.ail.json` or `.ailx` is the form a +Empirical measurement of whether `.ail.json` or `.ail` is the form a foreign LLM author reaches for and succeeds with. Single subject for v1: Qwen3-Coder-Next via IONOS. Two blind cohorts; same four tasks. @@ -16,7 +16,7 @@ Parent spec: `docs/specs/2026-05-12-cross-model-authoring-form-test.md`. harness. **Authored in cma.2**, not cma.1. - `render/` — standalone Cargo crate, outside the root workspace, builds the renderer binary. -- `rendered/json.md`, `rendered/ailx.md` — projected mini-specs, +- `rendered/json.md`, `rendered/ail.md` — projected mini-specs, checked into the repo for review. - `harness/` — **Authored in cma.2**. - `runs/-/` — populated by `harness` during a live run. @@ -36,7 +36,7 @@ cargo test --manifest-path experiments/2026-05-12-cross-model-authoring/render/C ``` Three integration tests: `example_roundtrip` (each example loads, -prints to AILX, reparses to the same canonical bytes), `spec_completeness` +prints to AIL, reparses to the same canonical bytes), `spec_completeness` (every AST variant in `ailang_core::ast` is exercised by at least one example), `token_balance` (form-only blocks balanced within ±5% across the two rendered files). diff --git a/experiments/2026-05-12-cross-model-authoring/harness/src/main.rs b/experiments/2026-05-12-cross-model-authoring/harness/src/main.rs index 74b815d..61d7073 100644 --- a/experiments/2026-05-12-cross-model-authoring/harness/src/main.rs +++ b/experiments/2026-05-12-cross-model-authoring/harness/src/main.rs @@ -42,8 +42,8 @@ fn main() -> Result<()> { let rendered_json = std::fs::read_to_string(args.rendered.join("json.md")) .with_context(|| format!("reading {}", args.rendered.join("json.md").display()))?; - let rendered_ailx = std::fs::read_to_string(args.rendered.join("ailx.md")) - .with_context(|| format!("reading {}", args.rendered.join("ailx.md").display()))?; + let rendered_ail = std::fs::read_to_string(args.rendered.join("ail.md")) + .with_context(|| format!("reading {}", args.rendered.join("ail.md").display()))?; let tasks = tasks::load_all(&args.tasks)?; if tasks.is_empty() { bail!("no tasks found in {}", args.tasks.display()); } @@ -52,8 +52,8 @@ fn main() -> Result<()> { let mut run_status = "ok"; let mut completed: std::collections::BTreeSet<(&'static str, String)> = Default::default(); - 'outer: for cohort in [Cohort::Json, Cohort::Ailx] { - let system_prompt = match cohort { Cohort::Json => &rendered_json, Cohort::Ailx => &rendered_ailx }; + 'outer: for cohort in [Cohort::Json, Cohort::Ail] { + let system_prompt = match cohort { Cohort::Json => &rendered_json, Cohort::Ail => &rendered_ail }; for t in &tasks { let (row, consumed) = run_one( &backend, &args, cohort, system_prompt, t, @@ -75,7 +75,7 @@ fn main() -> Result<()> { // walking every (cohort, task) pair. The pairs we never reached // still need a row, with final_status = budget_abort. if run_status == "budget_exceeded" { - for cohort in [Cohort::Json, Cohort::Ailx] { + for cohort in [Cohort::Json, Cohort::Ail] { for t in &tasks { if !completed.contains(&(cohort.as_str(), t.id.clone())) { rows.push(ScoreRow { diff --git a/experiments/2026-05-12-cross-model-authoring/harness/src/mock.rs b/experiments/2026-05-12-cross-model-authoring/harness/src/mock.rs index 9a41d57..2b39a81 100644 --- a/experiments/2026-05-12-cross-model-authoring/harness/src/mock.rs +++ b/experiments/2026-05-12-cross-model-authoring/harness/src/mock.rs @@ -7,7 +7,7 @@ //! "t1_add_three": { "1": { "content": "...", "usage": {...} } }, //! "t2_length": { "1": {...}, "2": {...} } //! }, -//! "ailx": { ... } +//! "ail": { ... } //! } //! ``` //! Where each `""` entry has `"content"` (the program the diff --git a/experiments/2026-05-12-cross-model-authoring/harness/src/pipeline.rs b/experiments/2026-05-12-cross-model-authoring/harness/src/pipeline.rs index 24317d8..e73ad0f 100644 --- a/experiments/2026-05-12-cross-model-authoring/harness/src/pipeline.rs +++ b/experiments/2026-05-12-cross-model-authoring/harness/src/pipeline.rs @@ -12,20 +12,20 @@ use std::time::Duration; #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum Cohort { Json, - Ailx, + Ail, } impl Cohort { pub fn extension(self) -> &'static str { match self { Cohort::Json => "ail.json", - Cohort::Ailx => "ailx", + Cohort::Ail => "ail", } } pub fn as_str(self) -> &'static str { match self { Cohort::Json => "json", - Cohort::Ailx => "ailx", + Cohort::Ail => "ail", } } } @@ -79,8 +79,8 @@ pub fn run_pipeline( let prog_path = workdir.join(format!("prog.{}", cohort.extension())); std::fs::write(&prog_path, program).with_context(|| format!("writing {}", prog_path.display()))?; - // For AILX cohort, parse to JSON first. - let json_path_initial = if matches!(cohort, Cohort::Ailx) { + // For AIL cohort, parse to JSON first. + let json_path_initial = if matches!(cohort, Cohort::Ail) { let out = workdir.join("prog.ail.json"); let parse_out = Command::new(ail_bin()) .arg("parse").arg(&prog_path) diff --git a/experiments/2026-05-12-cross-model-authoring/harness/src/scoring.rs b/experiments/2026-05-12-cross-model-authoring/harness/src/scoring.rs index 29bdf8f..3c10c9a 100644 --- a/experiments/2026-05-12-cross-model-authoring/harness/src/scoring.rs +++ b/experiments/2026-05-12-cross-model-authoring/harness/src/scoring.rs @@ -68,7 +68,7 @@ pub fn write_summary_md(rows: &[ScoreRow], path: &Path) -> Result<()> { let mut f = std::fs::File::create(path) .with_context(|| format!("creating {}", path.display()))?; writeln!(f, "# Cross-model authoring-form test — run summary\n")?; - for cohort in ["json", "ailx"] { + for cohort in ["json", "ail"] { writeln!(f, "## Cohort: {cohort}\n")?; let cohort_rows: Vec<&ScoreRow> = rows.iter().filter(|r| r.cohort == cohort).collect(); if cohort_rows.is_empty() { diff --git a/experiments/2026-05-12-cross-model-authoring/harness/src/strip_locations.rs b/experiments/2026-05-12-cross-model-authoring/harness/src/strip_locations.rs index 67c615c..b74f3fc 100644 --- a/experiments/2026-05-12-cross-model-authoring/harness/src/strip_locations.rs +++ b/experiments/2026-05-12-cross-model-authoring/harness/src/strip_locations.rs @@ -4,7 +4,7 @@ //! The intent (parent spec §strip_locations) is **symmetric //! degradation**: both cohorts lose the localisation information //! their compiler natively produces. The JSON cohort would -//! otherwise get JSON-pointer fragments; the AILX cohort would get +//! otherwise get JSON-pointer fragments; the AIL cohort would get //! `at byte N` offsets. Neither survives this pass. use regex::Regex; diff --git a/experiments/2026-05-12-cross-model-authoring/harness/tests/fixtures/mock_full_run.json b/experiments/2026-05-12-cross-model-authoring/harness/tests/fixtures/mock_full_run.json index 80565d9..979a09b 100644 --- a/experiments/2026-05-12-cross-model-authoring/harness/tests/fixtures/mock_full_run.json +++ b/experiments/2026-05-12-cross-model-authoring/harness/tests/fixtures/mock_full_run.json @@ -49,7 +49,7 @@ } } }, - "ailx": { + "ail": { "t1_add_three": { "1": { "content": "(module garbage", diff --git a/experiments/2026-05-12-cross-model-authoring/harness/tests/mock_full_run.rs b/experiments/2026-05-12-cross-model-authoring/harness/tests/mock_full_run.rs index c62018d..e2d37cb 100644 --- a/experiments/2026-05-12-cross-model-authoring/harness/tests/mock_full_run.rs +++ b/experiments/2026-05-12-cross-model-authoring/harness/tests/mock_full_run.rs @@ -1,7 +1,7 @@ //! End-to-end mock-mode test (parent spec §Testing strategy). //! Runs the harness binary against a canned response file that //! makes (json, t3_main_prints) green on turn 2 and -//! (ailx, t1_add_three) run to the turn limit. Asserts the +//! (ail, t1_add_three) run to the turn limit. Asserts the //! scores.csv is well-formed and the right per-(cohort,task) //! artefacts land on disk. @@ -35,10 +35,10 @@ fn mock_full_run_produces_scores_and_artefacts() { assert!(scores.starts_with("cohort,task_id,first_attempt_green,")); assert_eq!(scores.lines().count(), 1 + 8, "header + 8 rows expected"); assert!(scores.contains("json,t3_main_prints,false,2")); - assert!(scores.contains("ailx,t1_add_three,false,INF")); + assert!(scores.contains("ail,t1_add_three,false,INF")); // Spot-check one per-cohort artefact tree. - let pc = run_dir.join("per_cohort").join("ailx").join("t1_add_three"); - assert!(pc.join("turn_1_program.ailx").exists()); - assert!(pc.join("turn_5_program.ailx").exists()); + let pc = run_dir.join("per_cohort").join("ail").join("t1_add_three"); + assert!(pc.join("turn_1_program.ail").exists()); + assert!(pc.join("turn_5_program.ail").exists()); } diff --git a/experiments/2026-05-12-cross-model-authoring/master/spec.md b/experiments/2026-05-12-cross-model-authoring/master/spec.md index c60182c..93e7913 100644 --- a/experiments/2026-05-12-cross-model-authoring/master/spec.md +++ b/experiments/2026-05-12-cross-model-authoring/master/spec.md @@ -46,8 +46,8 @@ pattern object carries a `p` discriminator. Every literal object carries a `kind` discriminator. {/form-only} -{form-only: ailx} -The AILX surface is parenthesised tagged-head Lisp-style notation. +{form-only: ail} +The AIL surface is parenthesised tagged-head Lisp-style notation. Every form begins with `(` followed by a tag identifier; positional arguments follow; the form closes with `)`. There are no infix operators, no operator precedence, no implicit conversions. @@ -62,9 +62,9 @@ digit. String literals are double-quoted with `\"`, `\\`, `\n`, `\t` escapes; integer literals are signed decimal; the `(unit)` keyword denotes the unit value. -The AILX form is the printed dual of the JSON form: `ail parse -file.ailx` produces the same canonical bytes as the JSON form would, -and `ail render file.ail.json` produces the AILX text. Round-trip +The AIL form is the printed dual of the JSON form: `ail parse +file.ail` produces the same canonical bytes as the JSON form would, +and `ail render file.ail.json` produces the AIL text. Round-trip through this pair is gated by an integration test. {/form-only} @@ -110,7 +110,7 @@ Type::Forall: `{"k": "forall", "vars": [...], "body": ...}` with an optional `constraints` array for class constraints (see section 10). {/form-only} -{form-only: ailx} +{form-only: ail} Type::Con: `Int`, `Bool`, `Str`, `Float`, `Unit`, or a user type name. For parameterised types: `(con List Int)` reads "List of Int"; nested: `(con Map (con Str) (con Int))`. @@ -168,7 +168,7 @@ A fn that consumes a list and returns a new list: `"param_modes": ["own"]`, `"ret_mode": "own"`. {/form-only} -{form-only: ailx} +{form-only: ail} Modes are surface wrappers around the type in parameter and return position. Inside a `(fn-type ...)`: @@ -235,7 +235,7 @@ use snake_case. This is historical and the canonical bytes preserve the difference. {/form-only} -{form-only: ailx} +{form-only: ail} Function definition: `(fn NAME (doc STRING)? (type TYPE) (params NAME*) (body TERM))`. The `doc` clause is optional but recommended. @@ -295,7 +295,7 @@ constructor's declared `fields` in order; nullary constructors carry `"args": []`. {/form-only} -{form-only: ailx} +{form-only: ail} Type definition: `(data NAME (vars TYVAR*)? (doc STRING)? (ctor CTOR-NAME ARG-TYPE*)*)`. The `vars` clause is optional; nullary constructors have an empty argument list. @@ -349,7 +349,7 @@ Pattern variables are linear: each name appears at most once in a single pattern. Repeating a name is a typecheck error. {/form-only} -{form-only: ailx} +{form-only: ail} Match expression: `(match SCRUT (case PAT BODY) (case PAT BODY) ...)`. Arms are introduced by `(case ...)`; the order matters (top- to-bottom). @@ -409,7 +409,7 @@ The value of the `seq` is the value of `rhs`; `lhs`'s value is discarded but its effects count toward the enclosing fn's row. {/form-only} -{form-only: ailx} +{form-only: ail} Effect row on a fn type: `(effects E1 E2 ...)` inside the `(fn-type ...)`. Empty row: omit the `(effects)` clause or write `(effects)`. Common effectful row: `(effects IO)`. Order is sorted @@ -435,10 +435,10 @@ There are five literal shapes. An integer literal is a signed literal is a UTF-8 sequence in double quotes; AILang restricts the authored byte set to ASCII printable (Decision 6 Constraint 3) — non- ASCII bytes are an authoring error. A unit literal is the value of -type Unit, written `(unit)` in AILX and `{"kind": "unit"}` in JSON. +type Unit, written `(unit)` in AIL and `{"kind": "unit"}` in JSON. A Float literal is an IEEE-754 binary64 value. When you author -Floats, write the decimal form (`3.14`) — the AILX parser converts +Floats, write the decimal form (`3.14`) — the AIL parser converts to the 64-bit bit pattern and emits the canonical 16-character lowercase hex string into the JSON. You never author the hex encoding by hand; the toolchain owns that representation. NaN and @@ -464,11 +464,11 @@ Literal variants and their JSON shapes: serialisation drift across `serde_json` versions. Float bit patterns are computed by the toolchain on `ail parse`, -not by the author. When you write `3.14` in AILX, the parser emits +not by the author. When you write `3.14` in AIL, the parser emits `"bits": "40091eb851eb851f"` in the JSON form. {/form-only} -{form-only: ailx} +{form-only: ail} Literal surface forms: - Int: bare signed decimal, e.g. `42`, `-7`, `0`. @@ -533,7 +533,7 @@ Constraint on a polymorphic fn: inside `Type::Forall`, the constraints; omitted from canonical bytes when empty. {/form-only} -{form-only: ailx} +{form-only: ail} Class declaration: `(class NAME (param TYVAR) (method NAME (type METHOD-TYPE) (default BODY)?)*)`. The `default` clause is optional; methods without a default must be supplied by every diff --git a/experiments/2026-05-12-cross-model-authoring/render/src/examples.rs b/experiments/2026-05-12-cross-model-authoring/render/src/examples.rs index 9b135bc..3a2e8fb 100644 --- a/experiments/2026-05-12-cross-model-authoring/render/src/examples.rs +++ b/experiments/2026-05-12-cross-model-authoring/render/src/examples.rs @@ -2,8 +2,8 @@ //! //! - JSON form: read the on-disk canonical bytes verbatim, emit a //! ```json``` fenced block. -//! - AILX form: load the example via ailang_core::load_module, print -//! via ailang_surface::print, emit a ```ailx``` fenced block. +//! - AIL form: load the example via ailang_core::load_module, print +//! via ailang_surface::print, emit a ```ail``` fenced block. use anyhow::{anyhow, Context, Result}; use std::path::Path; @@ -11,7 +11,7 @@ use std::path::Path; #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum Form { Json, - Ailx, + Ail, } pub fn render_example(examples_dir: &Path, id: &str, form: Form) -> Result { @@ -28,11 +28,11 @@ pub fn render_example(examples_dir: &Path, id: &str, form: Form) -> Result { + Form::Ail => { let module = ailang_core::load_module(&path) .with_context(|| format!("load_module on {}", path.display()))?; let text = ailang_surface::print(&module); - Ok(format!("```ailx\n{}\n```\n", text.trim_end())) + Ok(format!("```ail\n{}\n```\n", text.trim_end())) } } } diff --git a/experiments/2026-05-12-cross-model-authoring/render/src/main.rs b/experiments/2026-05-12-cross-model-authoring/render/src/main.rs index 7f10984..720344a 100644 --- a/experiments/2026-05-12-cross-model-authoring/render/src/main.rs +++ b/experiments/2026-05-12-cross-model-authoring/render/src/main.rs @@ -1,4 +1,4 @@ -//! xmodel-render — projects master/spec.md into rendered/json.md and rendered/ailx.md. +//! xmodel-render — projects master/spec.md into rendered/json.md and rendered/ail.md. use anyhow::{anyhow, Context, Result}; use std::path::{Path, PathBuf}; @@ -15,17 +15,17 @@ fn main() -> Result<()> { let segments = splitter::split(&spec_text); let json_out = render_for(&segments, &examples_dir, examples::Form::Json, splitter::Form::Json)?; - let ailx_out = render_for(&segments, &examples_dir, examples::Form::Ailx, splitter::Form::Ailx)?; + let ail_out = render_for(&segments, &examples_dir, examples::Form::Ail, splitter::Form::Ail)?; std::fs::create_dir_all(&rendered_dir) .with_context(|| format!("creating {}", rendered_dir.display()))?; let json_path = rendered_dir.join("json.md"); - let ailx_path = rendered_dir.join("ailx.md"); + let ail_path = rendered_dir.join("ail.md"); std::fs::write(&json_path, json_out) .with_context(|| format!("writing {}", json_path.display()))?; - std::fs::write(&ailx_path, ailx_out) - .with_context(|| format!("writing {}", ailx_path.display()))?; - eprintln!("xmodel-render: wrote {} and {}", json_path.display(), ailx_path.display()); + std::fs::write(&ail_path, ail_out) + .with_context(|| format!("writing {}", ail_path.display()))?; + eprintln!("xmodel-render: wrote {} and {}", json_path.display(), ail_path.display()); Ok(()) } diff --git a/experiments/2026-05-12-cross-model-authoring/render/src/splitter.rs b/experiments/2026-05-12-cross-model-authoring/render/src/splitter.rs index 07823d6..1a7a797 100644 --- a/experiments/2026-05-12-cross-model-authoring/render/src/splitter.rs +++ b/experiments/2026-05-12-cross-model-authoring/render/src/splitter.rs @@ -2,20 +2,20 @@ //! //! Recognises three directives, one per line, no nesting: //! {form-only: json} … {/form-only} -//! {form-only: ailx} … {/form-only} +//! {form-only: ail} … {/form-only} //! {example: } #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum Form { Json, - Ailx, + Ail, } impl Form { fn parse(name: &str) -> Option
{ match name { "json" => Some(Form::Json), - "ailx" => Some(Form::Ailx), + "ail" => Some(Form::Ail), _ => None, } } diff --git a/experiments/2026-05-12-cross-model-authoring/render/tests/example_roundtrip.rs b/experiments/2026-05-12-cross-model-authoring/render/tests/example_roundtrip.rs index f621dea..00f19af 100644 --- a/experiments/2026-05-12-cross-model-authoring/render/tests/example_roundtrip.rs +++ b/experiments/2026-05-12-cross-model-authoring/render/tests/example_roundtrip.rs @@ -39,7 +39,7 @@ fn round_trip_one(path: &Path) -> Result<(), String> { let bytes_parsed = ailang_core::canonical::to_bytes(&parsed); if bytes_original != bytes_parsed { return Err(format!( - "{}: roundtrip diverged.\n--- printed AILX ---\n{}\n--- original bytes len {} vs parsed bytes len {} ---", + "{}: roundtrip diverged.\n--- printed AIL ---\n{}\n--- original bytes len {} vs parsed bytes len {} ---", path.display(), text, bytes_original.len(), @@ -50,7 +50,7 @@ fn round_trip_one(path: &Path) -> Result<(), String> { } #[test] -fn every_example_roundtrips_via_ailx() { +fn every_example_roundtrips_via_ail() { let examples = list_examples(); assert!( !examples.is_empty(), diff --git a/experiments/2026-05-12-cross-model-authoring/render/tests/splitter_unit.rs b/experiments/2026-05-12-cross-model-authoring/render/tests/splitter_unit.rs index b084751..14912ee 100644 --- a/experiments/2026-05-12-cross-model-authoring/render/tests/splitter_unit.rs +++ b/experiments/2026-05-12-cross-model-authoring/render/tests/splitter_unit.rs @@ -22,13 +22,13 @@ fn form_only_block_is_isolated() { } #[test] -fn ailx_form_only_block_uses_ailx_form() { - let input = "{form-only: ailx}\ngrammar rule\n{/form-only}\n"; +fn ail_form_only_block_uses_ail_form() { + let input = "{form-only: ail}\ngrammar rule\n{/form-only}\n"; let segments = split(input); assert_eq!( segments, vec![ - Segment::FormOnly { form: Form::Ailx, body: "grammar rule\n".to_string() }, + Segment::FormOnly { form: Form::Ail, body: "grammar rule\n".to_string() }, ] ); } @@ -49,7 +49,7 @@ fn example_marker_is_isolated() { #[test] fn multiple_directives_in_sequence() { - let input = "p1\n{example: a}\n{form-only: json}\njs\n{/form-only}\n{form-only: ailx}\nax\n{/form-only}\n{example: b}\np2\n"; + let input = "p1\n{example: a}\n{form-only: json}\njs\n{/form-only}\n{form-only: ail}\nax\n{/form-only}\n{example: b}\np2\n"; let segments = split(input); assert_eq!( segments, @@ -57,7 +57,7 @@ fn multiple_directives_in_sequence() { Segment::Prose("p1\n".to_string()), Segment::Example("a".to_string()), Segment::FormOnly { form: Form::Json, body: "js\n".to_string() }, - Segment::FormOnly { form: Form::Ailx, body: "ax\n".to_string() }, + Segment::FormOnly { form: Form::Ail, body: "ax\n".to_string() }, Segment::Example("b".to_string()), Segment::Prose("p2\n".to_string()), ] diff --git a/experiments/2026-05-12-cross-model-authoring/render/tests/token_balance.rs b/experiments/2026-05-12-cross-model-authoring/render/tests/token_balance.rs index a23aa91..732c8dc 100644 --- a/experiments/2026-05-12-cross-model-authoring/render/tests/token_balance.rs +++ b/experiments/2026-05-12-cross-model-authoring/render/tests/token_balance.rs @@ -1,7 +1,7 @@ //! Token-balance gate for master/spec.md form-only blocks. //! //! The mini-spec is the experimental treatment; if the JSON-cohort's -//! form-only scaffolding is meaningfully longer than the AILX-cohort's +//! form-only scaffolding is meaningfully longer than the AIL-cohort's //! (or vice versa), the experiment is biased before the model ever //! sees the prompts. This test catches gross imbalance. @@ -20,27 +20,27 @@ fn form_only_block_token_totals_within_five_percent() { let segments = splitter::split(&spec_text); let mut json_buf = String::new(); - let mut ailx_buf = String::new(); + let mut ail_buf = String::new(); for seg in &segments { if let Segment::FormOnly { form, body } = seg { match form { Form::Json => json_buf.push_str(body), - Form::Ailx => ailx_buf.push_str(body), + Form::Ail => ail_buf.push_str(body), } } } let bpe = tiktoken_rs::cl100k_base().expect("cl100k_base tokenizer should load"); let json_count = bpe.encode_with_special_tokens(&json_buf).len(); - let ailx_count = bpe.encode_with_special_tokens(&ailx_buf).len(); + let ail_count = bpe.encode_with_special_tokens(&ail_buf).len(); assert!(json_count > 0, "JSON-form blocks produced zero tokens"); - assert!(ailx_count > 0, "AILX-form blocks produced zero tokens"); + assert!(ail_count > 0, "AIL-form blocks produced zero tokens"); - let max = json_count.max(ailx_count) as f64; - let diff = (json_count as i64 - ailx_count as i64).unsigned_abs() as f64; + let max = json_count.max(ail_count) as f64; + let diff = (json_count as i64 - ail_count as i64).unsigned_abs() as f64; let ratio = diff / max; assert!( ratio <= 0.05, - "form-only block token imbalance: json={json_count}, ailx={ailx_count}, ratio={ratio:.4} > 0.05", + "form-only block token imbalance: json={json_count}, ail={ail_count}, ratio={ratio:.4} > 0.05", ); } diff --git a/experiments/2026-05-12-cross-model-authoring/rendered/ailx.md b/experiments/2026-05-12-cross-model-authoring/rendered/ail.md similarity index 97% rename from experiments/2026-05-12-cross-model-authoring/rendered/ailx.md rename to experiments/2026-05-12-cross-model-authoring/rendered/ail.md index be03eb5..b5620e4 100644 --- a/experiments/2026-05-12-cross-model-authoring/rendered/ailx.md +++ b/experiments/2026-05-12-cross-model-authoring/rendered/ail.md @@ -24,7 +24,7 @@ The schema of the on-disk form is fixed. The toolchain rejects any module that does not conform. -The AILX surface is parenthesised tagged-head Lisp-style notation. +The AIL surface is parenthesised tagged-head Lisp-style notation. Every form begins with `(` followed by a tag identifier; positional arguments follow; the form closes with `)`. There are no infix operators, no operator precedence, no implicit conversions. @@ -39,12 +39,12 @@ digit. String literals are double-quoted with `\"`, `\\`, `\n`, `\t` escapes; integer literals are signed decimal; the `(unit)` keyword denotes the unit value. -The AILX form is the printed dual of the JSON form: `ail parse -file.ailx` produces the same canonical bytes as the JSON form would, -and `ail render file.ail.json` produces the AILX text. Round-trip +The AIL form is the printed dual of the JSON form: `ail parse +file.ail` produces the same canonical bytes as the JSON form would, +and `ail render file.ail.json` produces the AIL text. Round-trip through this pair is gated by an integration test. -```ailx +```ail (module fn_returns_int (fn answer (doc "The simplest possible fn — no params, returns a fixed Int.") @@ -95,7 +95,7 @@ Type::Forall: `(forall (a b) BODY-TYPE)` quantifies over `a` and `b`. Class constraints attach as `(forall (a) (constraints (Eq a)) BODY-TYPE)`. -```ailx +```ail (module forall_polymorphic (fn id (doc "The polymorphic identity function. Exercises Type::Forall and Type::Var.") @@ -144,7 +144,7 @@ of the fn's signature and contributes to its content hash, so making the choice explicit at authoring time avoids surprise hash changes when the typechecker's defaults shift. -```ailx +```ail (module param_modes_all (fn f_implicit (doc "Implicit mode is the legacy default — `param_modes` is omitted from canonical JSON when every entry is Implicit, which is the case here. The visitor still observes ParamMode::Implicit via the (defaulted) ret_mode field on Type::Fn.") @@ -207,7 +207,7 @@ Inside any term position, an integer literal is a bare decimal number, a string is a double-quoted string, a bool is `true` or `false`, unit is `(unit)`. -```ailx +```ail (module fn_calls_prelude (fn add (doc "Add two Ints via the polymorphic prelude `+`. Also exercises TermLet by binding the sum to a local name before returning it, and TermClone by re-using the let-bound value.") @@ -216,7 +216,7 @@ number, a string is a double-quoted string, a bool is `true` or (body (let s (app + x y) (clone s))))) ``` -```ailx +```ail (module fn_with_lambda (fn make_adder (doc "Curried adder: takes an Int and returns an Int -> Int closure. Exercises TermLam and Type::Fn appearing as a return type.") @@ -258,7 +258,7 @@ need a separate registry to resolve overlapping constructor names across ADTs. Example: `(ctor List Cons 1 (ctor List Nil))` builds a single-element list. -```ailx +```ail (module data_simple (data Box (vars a) (doc "A unary box around a polymorphic value.") @@ -307,7 +307,7 @@ Pattern variables introduced by a `(case PAT BODY)` are in scope inside `BODY` and bind there only. They do not leak into sibling arms or into code outside the `match`. -```ailx +```ail (module data_with_match (data List (doc "Monomorphic singly-linked Int list — boxed, recursive.") @@ -329,7 +329,7 @@ arms or into code outside the `match`. (case (pat-ctor Cons _ t) (app + 1 (app go t))))) (in (app go xs)))))) ``` -```ailx +```ail (module match_literal_pattern (fn classify (doc "Classify an Int via literal patterns plus a wildcard fallback. Exercises PatternLit and PatternWild.") @@ -382,7 +382,7 @@ value; `X`'s value is discarded. Chains of sequencing are written nested: `(seq A (seq B C))` runs `A`, then `B`, then yields `C`'s value. -```ailx +```ail (module fn_with_do_seq (fn main (doc "Print two Ints in sequence. Exercises TermDo, TermSeq, and the IO effect on a fn type. The trailing unit return is implicit in the last Do (op returns Unit).") @@ -398,10 +398,10 @@ There are five literal shapes. An integer literal is a signed literal is a UTF-8 sequence in double quotes; AILang restricts the authored byte set to ASCII printable (Decision 6 Constraint 3) — non- ASCII bytes are an authoring error. A unit literal is the value of -type Unit, written `(unit)` in AILX and `{"kind": "unit"}` in JSON. +type Unit, written `(unit)` in AIL and `{"kind": "unit"}` in JSON. A Float literal is an IEEE-754 binary64 value. When you author -Floats, write the decimal form (`3.14`) — the AILX parser converts +Floats, write the decimal form (`3.14`) — the AIL parser converts to the 64-bit bit pattern and emits the canonical 16-character lowercase hex string into the JSON. You never author the hex encoding by hand; the toolchain owns that representation. NaN and @@ -431,7 +431,7 @@ There is no separate syntax for exponent-form floats in the v1 authoring surface; for non-finite values use the prelude constants `nan`, `inf`, `neg_inf`. -```ailx +```ail (module floats (const pi (doc "Pi as a Float constant. Exercises Def::Const and Literal::Float — the bit pattern below is f64::to_bits(3.14).") @@ -439,7 +439,7 @@ authoring surface; for non-finite values use the prelude constants (body 3.14))) ``` -```ailx +```ail (module bool_str (fn is_true (doc "Trivial Bool literal. Exercises Literal::Bool.") @@ -498,7 +498,7 @@ a)) BODY-TYPE)`. Each constraint is `(CLASS-NAME TYPE)` — usually the type is a single bound type variable but a concrete type is also legal (typically a typecheck error unless an instance exists). -```ailx +```ail (module class_def (class MyShow (param a) @@ -507,7 +507,7 @@ also legal (typically a typecheck error unless an instance exists). (type (fn-type (params a) (ret (con Str))))))) ``` -```ailx +```ail (module instance_def (class MyShow (param a) diff --git a/experiments/2026-05-12-cross-model-authoring/rendered/json.md b/experiments/2026-05-12-cross-model-authoring/rendered/json.md index b6ed548..59d1296 100644 --- a/experiments/2026-05-12-cross-model-authoring/rendered/json.md +++ b/experiments/2026-05-12-cross-model-authoring/rendered/json.md @@ -733,10 +733,10 @@ There are five literal shapes. An integer literal is a signed literal is a UTF-8 sequence in double quotes; AILang restricts the authored byte set to ASCII printable (Decision 6 Constraint 3) — non- ASCII bytes are an authoring error. A unit literal is the value of -type Unit, written `(unit)` in AILX and `{"kind": "unit"}` in JSON. +type Unit, written `(unit)` in AIL and `{"kind": "unit"}` in JSON. A Float literal is an IEEE-754 binary64 value. When you author -Floats, write the decimal form (`3.14`) — the AILX parser converts +Floats, write the decimal form (`3.14`) — the AIL parser converts to the 64-bit bit pattern and emits the canonical 16-character lowercase hex string into the JSON. You never author the hex encoding by hand; the toolchain owns that representation. NaN and @@ -761,7 +761,7 @@ Literal variants and their JSON shapes: serialisation drift across `serde_json` versions. Float bit patterns are computed by the toolchain on `ail parse`, -not by the author. When you write `3.14` in AILX, the parser emits +not by the author. When you write `3.14` in AIL, the parser emits `"bits": "40091eb851eb851f"` in the JSON form. diff --git a/skills/README.md b/skills/README.md index d1d841a..fed178f 100644 --- a/skills/README.md +++ b/skills/README.md @@ -19,7 +19,7 @@ The system was bootstrapped on 2026-05-09. See | [`implement`](implement/SKILL.md) | Plan exists | Code + tests + per-iter journal entry, all uncommitted in the working tree | Standard iteration path | | [`audit`](audit/SKILL.md) | Milestone closing OR baseline drift suspected | Drift report + bench-regression report | **Mandatory** at milestone close | | [`docwriter`](docwriter/SKILL.md) | API surface stabilized; rustdoc lag suspected | rustdoc updates in `///` and `//!`, uncommitted in the working tree | No — Boss-dispatched only | -| [`fieldtest`](fieldtest/SKILL.md) | Boss-dispatched post-audit field test on a milestone that touched user-visible surface | 2-4 `.ailx` example fixtures + `docs/specs/-fieldtest-.md`, all uncommitted in the working tree | No — Boss-dispatched only | +| [`fieldtest`](fieldtest/SKILL.md) | Boss-dispatched post-audit field test on a milestone that touched user-visible surface | 2-4 `.ail` example fixtures + `docs/specs/-fieldtest-.md`, all uncommitted in the working tree | No — Boss-dispatched only | | [`debug`](debug/SKILL.md) | Bug encountered (failing test, segfault, wrong stdout) | RED-test in the working tree (uncommitted) + cause analysis | **Mandatory RED-first** for any bug | | [`boss`](boss/SKILL.md) | User-invoked only (`/boss`) | Autonomous orchestration session — dispatches existing skills until done-state or bounce-back | No — user-gated | @@ -79,7 +79,7 @@ are no orphan agents (an anti-pattern after the 2026-05-09 build-out). | `ailang-bencher` | `audit/agents/` | `audit` (regression diagnostics — hypothesis-driven) | | `ailang-docwriter` | `docwriter/agents/` | `docwriter` (Boss-dispatched rustdoc sweep post-stability) | | `ailang-debugger` | `debug/agents/` | `debug` (RED-first; hands off GREEN to `implement` mini-mode) | -| `ailang-fieldtester` | `fieldtest/agents/` | `fieldtest` (writes real-world examples in `.ailx` Surface form against DESIGN.md only — never the compiler source) | +| `ailang-fieldtester` | `fieldtest/agents/` | `fieldtest` (writes real-world examples in `.ail` Surface form against DESIGN.md only — never the compiler source) | Each agent file has YAML frontmatter (`name`, `description`, `tools`) plus a system-prompt body. The `description` field is the one-sentence diff --git a/skills/audit/agents/ailang-bencher.md b/skills/audit/agents/ailang-bencher.md index 2b613e0..6184e36 100644 --- a/skills/audit/agents/ailang-bencher.md +++ b/skills/audit/agents/ailang-bencher.md @@ -130,7 +130,7 @@ is a side experiment, not the headline. ## What you DO ship -- New bench fixtures under `examples/bench_*.{ailx,ail.json}` when none of +- New bench fixtures under `examples/bench_*.ail*` when none of the existing ones exercise the hypothesis. Pair them (Implicit + explicit-mode variants) where the comparison demands it. - Edits to `bench/run.sh` (or a new harness alongside it) when the diff --git a/skills/fieldtest/SKILL.md b/skills/fieldtest/SKILL.md index 260c4a6..a6a3311 100644 --- a/skills/fieldtest/SKILL.md +++ b/skills/fieldtest/SKILL.md @@ -1,6 +1,6 @@ --- name: fieldtest -description: Boss-dispatched only, after audit closes clean (or with ratified drift only), when the orchestrator judges the iteration is complete and wants a field test. Picks 2-4 real-world programming tasks within the milestone's scope, implements each in the AIL Surface form (.ailx — not raw JSON), runs the resulting binaries, and writes a friction-and-bug spec to docs/specs/-fieldtest-.md. The spec feeds the next plan as a reference. Implementer simulates a downstream LLM that has only DESIGN.md plus the public examples — never the language's own implementation. +description: Boss-dispatched only, after audit closes clean (or with ratified drift only), when the orchestrator judges the iteration is complete and wants a field test. Picks 2-4 real-world programming tasks within the milestone's scope, implements each in the AIL Surface form (.ail — not raw JSON), runs the resulting binaries, and writes a friction-and-bug spec to docs/specs/-fieldtest-.md. The spec feeds the next plan as a reference. Implementer simulates a downstream LLM that has only DESIGN.md plus the public examples — never the language's own implementation. --- # fieldtest — LLM-usability field test for a shipped milestone @@ -24,7 +24,7 @@ specs at `docs/specs/-fieldtest-.md`. The substantive process — read DESIGN.md + JOURNAL + milestone spec, pick 2-4 real-world programming tasks per milestone axis, implement -each in `.ailx` Surface form, run via `ail check`/`build`/`run`, +each in `.ail` Surface form, run via `ail check`/`build`/`run`, classify findings, write the spec — lives in `agents/ailang-fieldtester.md`. That file also carries the spec template, the source-isolation discipline (no reading under @@ -65,7 +65,7 @@ routing table below applies in all cases. ``` THE FIELDTESTER WORKS FROM DESIGN.MD AND PUBLIC EXAMPLES — NOT FROM THE COMPILER SOURCE. -EVERY EXAMPLE IS WRITTEN IN .ailx (SURFACE) FIRST. RAW .ail.json IS NEVER HAND-AUTHORED. +EVERY EXAMPLE IS WRITTEN IN .ail (SURFACE) FIRST. RAW .ail.json IS NEVER HAND-AUTHORED. EVERY FRICTION POINT AND BUG IS RECORDED. NONE IS WORKED AROUND. ``` @@ -79,7 +79,7 @@ agent compiler-internal hints in the carrier. Dispatch `ailang-fieldtester` with the carrier from the Handoff Contract below. The agent picks 2-4 examples (one per axis the -milestone touched), implements them in `.ailx`, runs them through the +milestone touched), implements them in `.ail`, runs them through the public `ail` CLI, classifies findings, and writes the spec. All artefacts (fixtures + spec) stay in the working tree as unstaged changes; the Boss commits them after reviewing the report (suggested @@ -104,7 +104,7 @@ variation); five is too many for one report to stay readable. | Field | Content | |-------|---------| | `spec_path` | `docs/specs/-fieldtest-.md` | -| `examples_added` | list of `.ailx` paths committed | +| `examples_added` | list of `.ail` paths committed | | `findings` | list, each with class (`bug` / `friction` / `spec_gap` / `working`) + recommendation | | `status` | `clean` / `friction_found` / `bugs_found` / `infra_blocked` | diff --git a/skills/fieldtest/agents/ailang-fieldtester.md b/skills/fieldtest/agents/ailang-fieldtester.md index 84e4c89..3131c7e 100644 --- a/skills/fieldtest/agents/ailang-fieldtester.md +++ b/skills/fieldtest/agents/ailang-fieldtester.md @@ -1,6 +1,6 @@ --- name: ailang-fieldtester -description: Implements 2-4 real-world programming tasks in the AIL Surface form (.ailx) for a freshly closed milestone, runs them through the public `ail` CLI, and reports friction, bugs, and spec gaps as a structured spec. Simulates a downstream LLM author who has only DESIGN.md and the public examples — never the language's own implementation. Does NOT fix bugs and does NOT hand-write canonical JSON. +description: Implements 2-4 real-world programming tasks in the AIL Surface form (.ail) for a freshly closed milestone, runs them through the public `ail` CLI, and reports friction, bugs, and spec gaps as a structured spec. Simulates a downstream LLM author who has only DESIGN.md and the public examples — never the language's own implementation. Does NOT fix bugs and does NOT hand-write canonical JSON. tools: Read, Edit, Write, Bash, Glob, Grep --- @@ -46,7 +46,7 @@ Read in this order, before picking examples: 4. `docs/specs/.md` if one exists — the contract this milestone signed up for. 5. `examples/` — to learn the *form* of valid AIL. You may read any - `.ailx` and `.ail.json` under `examples/` (these are the public + `.ail` and `.ail.json` under `examples/` (these are the public corpus). You may NOT use them as a hint about how the compiler handles edge cases; only as a hint about the shape of the surface. @@ -66,7 +66,7 @@ spec; if both are also empty, return `NEEDS_CONTEXT`. ``` DESIGN.MD AND `examples/` ARE YOUR ONLY REFERENCE. CRATES/, RUNTIME/, BENCH/ ARE FORBIDDEN READS. -EVERY EXAMPLE IS WRITTEN IN .ailx FIRST. NO HAND-WRITTEN .ail.json. +EVERY EXAMPLE IS WRITTEN IN .ail FIRST. NO HAND-WRITTEN .ail.json. RECORD WHAT HAPPENS. DO NOT FIX. DO NOT WORK AROUND. ``` @@ -76,7 +76,7 @@ under `crates/`, `runtime/`, `bench/scripts/`, or `bench/reference/`, **stop**. The only file paths you may open are: - `CLAUDE.md`, `docs/**`, `examples/**`, `skills/**` -- the `.ailx` and `.ail.json` files YOU create under `examples/` +- the `.ail` and `.ail.json` files YOU create under `examples/` - the binaries YOU produce via `ail build` - the `.ll` files YOU produce via `ail emit-ir` if you want to inspect generated IR (the IR is part of the public surface per @@ -104,19 +104,19 @@ Each phase completes before the next starts. thin) or a 500-line numerics library (too thick). 3. Total: 2-4 examples. -### Phase 2 — Implement each example in `.ailx` +### Phase 2 — Implement each example in `.ail` For each example, in this order: -1. Draft the program in `.ailx` Surface form. Reach for the milestone's +1. Draft the program in `.ail` Surface form. Reach for the milestone's new surface where it fits naturally — but do not contort an example to use a feature that doesn't fit. -2. Save as `examples/fieldtest/__.ailx`, - e.g. `examples/fieldtest/22_1_eq_rational.ailx`. +2. Save as `examples/fieldtest/__.ail`, + e.g. `examples/fieldtest/22_1_eq_rational.ail`. 3. Run, in this order: ```bash - ail check examples/fieldtest/<...>.ailx - ail build examples/fieldtest/<...>.ailx -o /tmp/ft_ + ail check examples/fieldtest/<...>.ail + ail build examples/fieldtest/<...>.ail -o /tmp/ft_ /tmp/ft_ ``` Note: if your repo invokes `ail` as `cargo run -p ail --` instead, @@ -137,10 +137,10 @@ binary missing); in that case return `BLOCKED` with the cause. ### Phase 3 — Generate canonical JSON, only via tooling The canonical form is `.ail.json`. You do NOT hand-write it. After -each `.ailx` runs cleanly, generate the JSON via: +each `.ail` runs cleanly, generate the JSON via: ```bash -ail render --json examples/fieldtest/<...>.ailx > examples/fieldtest/<...>.ail.json +ail render --json examples/fieldtest/<...>.ail > examples/fieldtest/<...>.ail.json ``` (or whichever subcommand `ail --help` lists for the surface→json @@ -164,7 +164,7 @@ merged. ### Phase 5 — Write the spec, hand back Write `docs/specs/-fieldtest-.md` using the -spec structure below. Leave all artefacts (the `.ailx` files, the +spec structure below. Leave all artefacts (the `.ail` files, the `.ail.json` files, and the spec file) in the working tree as unstaged changes. You do NOT commit — the Boss commits after reading the end-report (suggested commit subject: @@ -187,7 +187,7 @@ What the milestone shipped. One paragraph. ## Examples Per example, one subsection: -### `examples/fieldtest/__.ailx` — +### `examples/fieldtest/__.ail` — - What it does - Why this task fits the milestone's scope - Outcome: compiles? runs? matches expected stdout? @@ -272,7 +272,7 @@ before committing. | "DESIGN.md is fuzzy on the typeclass instance ordering, I'll pick the natural reading and proceed" | Pick the reading, RUN the example, AND record `spec_gap` with the reading you picked and why another reading was equally plausible. | | "Bug found — I'll just fix it now, faster than handing off to debug" | Fix-in-place violates the skill split. The fix lands in a separate, RED-tested commit via `debug` → `implement`. | | "Two examples both ran clean, no findings — short report" | A clean run is itself a finding (`working`). Record what was reached for, what diagnostic showed up when wrong, what was easy. Wins protect the feature from drift. | -| "I'll skip the JSON file generation, the .ailx is enough" | The committed pair (.ailx + .ail.json) is the regression fixture. Without the JSON, future bench runs can't pick it up. If `ail render --json` doesn't exist, record it as a finding. | +| "I'll skip the JSON file generation, the .ail is enough" | The committed pair (.ail + .ail.json) is the regression fixture. Without the JSON, future bench runs can't pick it up. If `ail render --json` doesn't exist, record it as a finding. | | "Three examples is enough, I'll skip the fourth axis" | Each axis the milestone touched needs at least one example. Skipping an axis silently turns the field test into a partial signal, which is worse than no signal because the orchestrator will read it as full coverage. | ## Red Flags — STOP and re-read DESIGN.md