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:
@@ -1,6 +1,6 @@
|
|||||||
// Hand-C reference for bench_compute_collatz.
|
// 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.
|
// starting value in [1..N], count Collatz steps to reach 1, sum.
|
||||||
// Three sizes: 10k / 100k / 500k starting values.
|
// Three sizes: 10k / 100k / 500k starting values.
|
||||||
//
|
//
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
// Hand-C reference for bench_compute_intsum.
|
// 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.
|
// `i * 7` for i in [n, n-1, ..., 1], printing the final acc.
|
||||||
// Three sizes: 1M / 10M / 50M iterations.
|
// Three sizes: 1M / 10M / 50M iterations.
|
||||||
//
|
//
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
// Hand-C reference for bench_list_sum.
|
// 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.
|
// of [0, 1, ..., N-1] via prepending, then sum by linear traversal.
|
||||||
// Three workload sizes: 100k / 1M / 3M cells, matching the AILang
|
// Three workload sizes: 100k / 1M / 3M cells, matching the AILang
|
||||||
// fixture exactly so the AILang/C wall-time ratio is fair.
|
// fixture exactly so the AILang/C wall-time ratio is fair.
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
//
|
//
|
||||||
// Companion to list_sum.c (which leaks); this version adds explicit
|
// Companion to list_sum.c (which leaks); this version adds explicit
|
||||||
// `free()` calls to mirror AILang's explicit-mode RC dec-tax. Pairs
|
// `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
|
// 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
|
// genuinely O(N) work that bench_list_sum.c never paid; the rc/c
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
// Hand-C reference for bench_tree_walk.
|
// 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.
|
// binary tree of given depth (every value = 1) and sum every node.
|
||||||
// Three depths: 16 / 18 / 20 (= 65535 / 262143 / 1048575 nodes).
|
// Three depths: 16 / 18 / 20 (= 65535 / 262143 / 1048575 nodes).
|
||||||
//
|
//
|
||||||
|
|||||||
@@ -190,7 +190,7 @@ enum Cmd {
|
|||||||
#[arg(long)]
|
#[arg(long)]
|
||||||
json: bool,
|
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`.
|
/// `.ail.json`. Iter 14c addition; symmetric to `render`.
|
||||||
///
|
///
|
||||||
/// The form-(A) projection is one of potentially many producers of
|
/// 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.
|
"You are integrating prose edits back into an AILang module.
|
||||||
|
|
||||||
ROLE
|
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
|
file) that reflects the human's prose edits while preserving the
|
||||||
load-bearing semantic detail from the original module.
|
load-bearing semantic detail from the original module.
|
||||||
|
|
||||||
@@ -826,7 +826,7 @@ fn main() -> Result<()> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
Cmd::Parse { path, output } => {
|
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).
|
// JSON. Symmetric to `render` (which goes the other way).
|
||||||
let src = std::fs::read_to_string(&path)
|
let src = std::fs::read_to_string(&path)
|
||||||
.with_context(|| format!("reading {}", path.display()))?;
|
.with_context(|| format!("reading {}", path.display()))?;
|
||||||
|
|||||||
@@ -429,8 +429,8 @@ fn render_parse_round_trip_canonical() {
|
|||||||
.expect("run ail render");
|
.expect("run ail render");
|
||||||
assert!(render.status.success(), "ail render failed");
|
assert!(render.status.success(), "ail render failed");
|
||||||
|
|
||||||
let tmp = std::env::temp_dir().join(format!("ailang_render_e2e_{}.ailx", std::process::id()));
|
let tmp = std::env::temp_dir().join(format!("ailang_render_e2e_{}.ail", std::process::id()));
|
||||||
std::fs::write(&tmp, &render.stdout).expect("write rendered ailx");
|
std::fs::write(&tmp, &render.stdout).expect("write rendered ail");
|
||||||
|
|
||||||
let parsed = Command::new(ail_bin())
|
let parsed = Command::new(ail_bin())
|
||||||
.args(["parse", tmp.to_str().unwrap()])
|
.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.
|
/// not leak the LCons outer cells.
|
||||||
///
|
///
|
||||||
/// Background: 18f.2's tail-latency bench found that
|
/// 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
|
/// implicit-mode variant despite carrying mode annotations
|
||||||
/// throughout the hot path. Diagnosis: in
|
/// throughout the hot path. Diagnosis: in
|
||||||
///
|
///
|
||||||
|
|||||||
@@ -62,7 +62,7 @@ fn roundtrip_one(fixture: &Path, tmpdir: &Path) -> Result<(), String> {
|
|||||||
let bytes_orig = ailang_core::canonical::to_bytes(&original_module);
|
let bytes_orig = ailang_core::canonical::to_bytes(&original_module);
|
||||||
let h_orig = blake3::hash(&bytes_orig);
|
let h_orig = blake3::hash(&bytes_orig);
|
||||||
|
|
||||||
// Step B: `ail render <fixture>` → captured stdout = ailx text.
|
// Step B: `ail render <fixture>` → captured stdout = ail text.
|
||||||
let render_out = Command::new(ail_bin())
|
let render_out = Command::new(ail_bin())
|
||||||
.args(["render", fixture.to_str().unwrap()])
|
.args(["render", fixture.to_str().unwrap()])
|
||||||
.output()
|
.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
|
let stem = fixture
|
||||||
.file_name()
|
.file_name()
|
||||||
.and_then(|n| n.to_str())
|
.and_then(|n| n.to_str())
|
||||||
.and_then(|n| n.strip_suffix(".ail.json"))
|
.and_then(|n| n.strip_suffix(".ail.json"))
|
||||||
.unwrap_or("fixture");
|
.unwrap_or("fixture");
|
||||||
let tmp_ailx = tmpdir.join(format!("{stem}.round.ailx"));
|
let tmp_ail = tmpdir.join(format!("{stem}.round.ail"));
|
||||||
std::fs::write(&tmp_ailx, &render_out.stdout)
|
std::fs::write(&tmp_ail, &render_out.stdout)
|
||||||
.map_err(|e| format!("write tempfile {}: {e}", tmp_ailx.display()))?;
|
.map_err(|e| format!("write tempfile {}: {e}", tmp_ail.display()))?;
|
||||||
|
|
||||||
// Step D: `ail parse <tmp_ailx>` → captured stdout = canonical
|
// Step D: `ail parse <tmp_ail>` → captured stdout = canonical
|
||||||
// bytes + trailing newline.
|
// bytes + trailing newline.
|
||||||
let parse_out = Command::new(ail_bin())
|
let parse_out = Command::new(ail_bin())
|
||||||
.args(["parse", tmp_ailx.to_str().unwrap()])
|
.args(["parse", tmp_ail.to_str().unwrap()])
|
||||||
.output()
|
.output()
|
||||||
.map_err(|e| format!("spawn ail parse: {e}"))?;
|
.map_err(|e| format!("spawn ail parse: {e}"))?;
|
||||||
if !parse_out.status.success() {
|
if !parse_out.status.success() {
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
Form-A is the canonical textual surface of AILang. It is the form that
|
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
|
LLMs generate when asked to produce or edit AILang code, and the form
|
||||||
that `ail parse <file>.ailx` reads. The inverse direction — printing
|
that `ail parse <file>.ail` reads. The inverse direction — printing
|
||||||
JSON-AST as Form-A — is `ail render <file>.ail.json`. Round-trip
|
JSON-AST as Form-A — is `ail render <file>.ail.json`. Round-trip
|
||||||
through this pair is the gating contract: `parse(render(m)) == m`
|
through this pair is the gating contract: `parse(render(m)) == m`
|
||||||
for every well-formed module.
|
for every well-formed module.
|
||||||
@@ -26,7 +26,7 @@ Lisp-style S-expression dress that:
|
|||||||
- omits structural noise (no field tags; positions carry meaning)
|
- omits structural noise (no field tags; positions carry meaning)
|
||||||
- has a real parser with positional error messages
|
- has a real parser with positional error messages
|
||||||
- round-trips through `ail render` ↔ `ail parse` losslessly
|
- 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.
|
LLMs generate Form-A; the toolchain converts to JSON.
|
||||||
|
|
||||||
@@ -48,7 +48,7 @@ LLMs generate Form-A; the toolchain converts to JSON.
|
|||||||
DEF*)
|
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 ...)`).
|
`(module bench_list_sum ...)`).
|
||||||
|
|
||||||
## Imports
|
## Imports
|
||||||
@@ -269,11 +269,11 @@ significantly.
|
|||||||
|
|
||||||
## Few-shot corpus
|
## 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
|
parseable and typechecks clean. Pattern-match against them when
|
||||||
generating new code.
|
generating new code.
|
||||||
|
|
||||||
### 1 — `hello.ailx`: minimal IO program
|
### 1 — `hello.ail`: minimal IO program
|
||||||
|
|
||||||
```
|
```
|
||||||
(module hello
|
(module hello
|
||||||
@@ -283,7 +283,7 @@ generating new code.
|
|||||||
(body (do io/print_str "Hello, AILang."))))
|
(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
|
(module borrow_own_demo
|
||||||
@@ -333,7 +333,7 @@ generating new code.
|
|||||||
(do io/print_int (app sum_list xs)))))))
|
(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
|
(module lit_pat
|
||||||
|
|||||||
@@ -10,18 +10,18 @@
|
|||||||
//! For every `examples/*.ail.json` fixture, load → `print` →
|
//! For every `examples/*.ail.json` fixture, load → `print` →
|
||||||
//! `parse` → canonical bytes; assert byte-equal to original.
|
//! `parse` → canonical bytes; assert byte-equal to original.
|
||||||
//!
|
//!
|
||||||
//! 2. `every_ailx_fixture_matches_its_json_counterpart`: hand-
|
//! 2. `every_ail_fixture_matches_its_json_counterpart`: hand-
|
||||||
//! authored ground-truth check. For every `examples/*.ailx`
|
//! authored ground-truth check. For every `examples/*.ail`
|
||||||
//! fixture, parse → canonical bytes; if a same-stem `.ail.json`
|
//! fixture, parse → canonical bytes; if a same-stem `.ail.json`
|
||||||
//! counterpart exists, assert canonical-byte equality against
|
//! 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.
|
//! 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
|
//! 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
|
//! `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};
|
use std::path::{Path, PathBuf};
|
||||||
|
|
||||||
@@ -100,7 +100,7 @@ fn round_trip_one(path: &Path) -> Result<(), String> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn list_ailx_fixtures() -> Vec<PathBuf> {
|
fn list_ail_fixtures() -> Vec<PathBuf> {
|
||||||
let dir = examples_dir();
|
let dir = examples_dir();
|
||||||
let mut paths: Vec<PathBuf> = std::fs::read_dir(&dir)
|
let mut paths: Vec<PathBuf> = std::fs::read_dir(&dir)
|
||||||
.unwrap_or_else(|e| panic!("read_dir({}): {e}", dir.display()))
|
.unwrap_or_else(|e| panic!("read_dir({}): {e}", dir.display()))
|
||||||
@@ -109,7 +109,7 @@ fn list_ailx_fixtures() -> Vec<PathBuf> {
|
|||||||
.filter(|p| {
|
.filter(|p| {
|
||||||
p.file_name()
|
p.file_name()
|
||||||
.and_then(|n| n.to_str())
|
.and_then(|n| n.to_str())
|
||||||
.map(|n| n.ends_with(".ailx"))
|
.map(|n| n.ends_with(".ail"))
|
||||||
.unwrap_or(false)
|
.unwrap_or(false)
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
@@ -118,11 +118,11 @@ fn list_ailx_fixtures() -> Vec<PathBuf> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn every_ailx_fixture_matches_its_json_counterpart() {
|
fn every_ail_fixture_matches_its_json_counterpart() {
|
||||||
let fixtures = list_ailx_fixtures();
|
let fixtures = list_ail_fixtures();
|
||||||
assert!(
|
assert!(
|
||||||
!fixtures.is_empty(),
|
!fixtures.is_empty(),
|
||||||
"no .ailx fixtures found under {}",
|
"no .ail fixtures found under {}",
|
||||||
examples_dir().display()
|
examples_dir().display()
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -130,32 +130,32 @@ fn every_ailx_fixture_matches_its_json_counterpart() {
|
|||||||
let mut paired = 0usize;
|
let mut paired = 0usize;
|
||||||
let mut parse_only = 0usize;
|
let mut parse_only = 0usize;
|
||||||
|
|
||||||
for ailx_path in &fixtures {
|
for ail_path in &fixtures {
|
||||||
let text = match std::fs::read_to_string(ailx_path) {
|
let text = match std::fs::read_to_string(ail_path) {
|
||||||
Ok(t) => t,
|
Ok(t) => t,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
failures.push(format!("{}: read failed: {e}", ailx_path.display()));
|
failures.push(format!("{}: read failed: {e}", ail_path.display()));
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
let parsed = match ailang_surface::parse(&text) {
|
let parsed = match ailang_surface::parse(&text) {
|
||||||
Ok(m) => m,
|
Ok(m) => m,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
failures.push(format!("{}: parse failed: {e}", ailx_path.display()));
|
failures.push(format!("{}: parse failed: {e}", ail_path.display()));
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Same-stem counterpart lookup. `<stem>.ailx` → `<stem>.ail.json`.
|
// Same-stem counterpart lookup. `<stem>.ail` → `<stem>.ail.json`.
|
||||||
let stem = ailx_path
|
let stem = ail_path
|
||||||
.file_name()
|
.file_name()
|
||||||
.and_then(|n| n.to_str())
|
.and_then(|n| n.to_str())
|
||||||
.and_then(|n| n.strip_suffix(".ailx"))
|
.and_then(|n| n.strip_suffix(".ail"))
|
||||||
.unwrap_or("");
|
.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() {
|
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.)
|
// without a JSON counterpart. (Today: none expected.)
|
||||||
parse_only += 1;
|
parse_only += 1;
|
||||||
continue;
|
continue;
|
||||||
@@ -166,7 +166,7 @@ fn every_ailx_fixture_matches_its_json_counterpart() {
|
|||||||
Err(e) => {
|
Err(e) => {
|
||||||
failures.push(format!(
|
failures.push(format!(
|
||||||
"{}: load_module({}) failed: {e}",
|
"{}: load_module({}) failed: {e}",
|
||||||
ailx_path.display(),
|
ail_path.display(),
|
||||||
json_path.display()
|
json_path.display()
|
||||||
));
|
));
|
||||||
continue;
|
continue;
|
||||||
@@ -179,7 +179,7 @@ fn every_ailx_fixture_matches_its_json_counterpart() {
|
|||||||
let s_round = String::from_utf8_lossy(&bytes_parsed).into_owned();
|
let s_round = String::from_utf8_lossy(&bytes_parsed).into_owned();
|
||||||
failures.push(format!(
|
failures.push(format!(
|
||||||
"{} vs {}: canonical bytes differ.\noriginal: {s_orig}\nparsed: {s_round}",
|
"{} vs {}: canonical bytes differ.\noriginal: {s_orig}\nparsed: {s_round}",
|
||||||
ailx_path.display(),
|
ail_path.display(),
|
||||||
json_path.display()
|
json_path.display()
|
||||||
));
|
));
|
||||||
continue;
|
continue;
|
||||||
@@ -189,7 +189,7 @@ fn every_ailx_fixture_matches_its_json_counterpart() {
|
|||||||
|
|
||||||
if !failures.is_empty() {
|
if !failures.is_empty() {
|
||||||
panic!(
|
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(),
|
failures.len(),
|
||||||
fixtures.len(),
|
fixtures.len(),
|
||||||
paired,
|
paired,
|
||||||
@@ -198,27 +198,27 @@ fn every_ailx_fixture_matches_its_json_counterpart() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
eprintln!(
|
eprintln!(
|
||||||
".ailx cross-check ok ({} paired, {} parse-only)",
|
".ail cross-check ok ({} paired, {} parse-only)",
|
||||||
paired, parse_only
|
paired, parse_only
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Direction 2 of the Roundtrip Invariant (DESIGN.md §"Roundtrip
|
/// 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.
|
/// 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`
|
/// 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
|
/// 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.
|
/// Direction-2 claim has a dedicated enforcement point.
|
||||||
#[test]
|
#[test]
|
||||||
fn parse_then_print_then_parse_is_idempotent_on_every_ailx_fixture() {
|
fn parse_then_print_then_parse_is_idempotent_on_every_ail_fixture() {
|
||||||
let fixtures = list_ailx_fixtures();
|
let fixtures = list_ail_fixtures();
|
||||||
assert!(
|
assert!(
|
||||||
!fixtures.is_empty(),
|
!fixtures.is_empty(),
|
||||||
"no .ailx fixtures found under {}",
|
"no .ail fixtures found under {}",
|
||||||
examples_dir().display()
|
examples_dir().display()
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -266,12 +266,12 @@ fn parse_then_print_then_parse_is_idempotent_on_every_ailx_fixture() {
|
|||||||
|
|
||||||
if !failures.is_empty() {
|
if !failures.is_empty() {
|
||||||
panic!(
|
panic!(
|
||||||
"{} of {} .ailx fixture(s) failed idempotency check (passed: {}):\n{}",
|
"{} of {} .ail fixture(s) failed idempotency check (passed: {}):\n{}",
|
||||||
failures.len(),
|
failures.len(),
|
||||||
fixtures.len(),
|
fixtures.len(),
|
||||||
passed,
|
passed,
|
||||||
failures.join("\n\n")
|
failures.join("\n\n")
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
eprintln!("parse→print→parse idempotency ok for {passed} .ailx fixtures");
|
eprintln!("parse→print→parse idempotency ok for {passed} .ail fixtures");
|
||||||
}
|
}
|
||||||
|
|||||||
+25
-25
@@ -175,7 +175,7 @@ Trade-off: no inline optimisations through the LLVM API. We rely on
|
|||||||
Form (A) is implemented as
|
Form (A) is implemented as
|
||||||
the `ailang-surface` crate (parser + printer). Form-A is
|
the `ailang-surface` crate (parser + printer). Form-A is
|
||||||
gated against drift by `ailang-surface/tests/round_trip.rs`, which
|
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
|
canonical-byte equality. `ail render` and both branches
|
||||||
of `ail describe` were rewired to use `ailang_surface::print`, making
|
of `ail describe` were rewired to use `ailang_surface::print`, making
|
||||||
form (A) the **sole** text projection of a module — the legacy
|
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
|
semantic indentation, maximal-munch lexing, context-sensitive
|
||||||
reductions.
|
reductions.
|
||||||
2. **AST-isomorphic.** Every surface form maps to exactly one AST
|
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,
|
directions, BLAKE3-stable hashing, Float-bits-hex encoding,
|
||||||
workspace-CI enforcement points) is anchored as the top-level
|
workspace-CI enforcement points) is anchored as the top-level
|
||||||
§"Roundtrip Invariant" — this constraint records that Decision
|
§"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
|
continue to consume `ailang-core::ast::Module` values regardless
|
||||||
of which projection produced them.
|
of which projection produced them.
|
||||||
- Round-trip test: for every `examples/*.ail.json`, parse the
|
- 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
|
hash-equivalence to the original. Hash equivalence is the truth
|
||||||
check; the surface ships only if every fixture round-trips
|
check; the surface ships only if every fixture round-trips
|
||||||
identically.
|
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**
|
existing `ail render`. **`.ail.json` remains a first-class input**
|
||||||
to every existing subcommand; the parser is a producer, not a
|
to every existing subcommand; the parser is a producer, not a
|
||||||
gatekeeper.
|
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
|
the 30-production ceiling of constraint 1 is intact (the parser
|
||||||
implements ~28 named productions). All 17 `examples/*.ail.json`
|
implements ~28 named productions). All 17 `examples/*.ail.json`
|
||||||
fixtures round-trip identically through `print → parse → canonical
|
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.
|
JSON identical to their corresponding `.ail.json` files.
|
||||||
|
|
||||||
3. **Tail-call surface.** Decision 8 ships two new
|
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
|
- The LLM emits **Form-A** (the canonical authoring surface), not
|
||||||
JSON. JSON-AST stays the only hashable artefact, but it is 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.
|
before `ail check` to produce the canonical JSON.
|
||||||
- `crates/ailang-core/specs/form_a.md` is the complete LLM-targeted
|
- `crates/ailang-core/specs/form_a.md` is the complete LLM-targeted
|
||||||
Form-A specification — grammar, every term / pattern / type /
|
Form-A specification — grammar, every term / pattern / type /
|
||||||
def keyword, schema invariants, pitfall catalogue, four
|
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
|
as `ailang_core::FORM_A_SPEC` and embedded verbatim in every
|
||||||
`merge-prose` prompt.
|
`merge-prose` prompt.
|
||||||
- `crates/ailang-core/tests/spec_drift.rs` walks every variant of
|
- `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` —
|
- `meta-llama/CodeLlama-13b-Instruct-hf` —
|
||||||
`experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-9197fd/`
|
`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 |
|
| reached green | 1/4 | 1/4 | 0/4 | 2/4 |
|
||||||
| first-attempt 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) |
|
| top error class | check (×3) | parse (×3) | check (×2) | parse (×2) |
|
||||||
|
|
||||||
Both subjects show the same direction on every metric the table
|
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
|
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;
|
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
|
CodeLlama strictly dominates, 2/4 vs 0/4). The failure-class
|
||||||
symmetry observed on the original Qwen baseline holds for both
|
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
|
the parse stage — each form's front-of-pipeline check. Three of the
|
||||||
four first-attempt-green cells observed across the two subjects are
|
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
|
fourth is Qwen-JSON t3, the same task the original baseline already
|
||||||
flagged as the JSON-form's only first-attempt success. Two CodeLlama
|
flagged as the JSON-form's only first-attempt success. Two CodeLlama
|
||||||
JSON-cohort cells (`t2_length`, `t4_count_zeros`) terminated as
|
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
|
IONOS-side terminal error, not a model-output failure; CodeLlama
|
||||||
JSON-cohort numbers therefore average over 2 informative cells, not
|
JSON-cohort numbers therefore average over 2 informative cells, not
|
||||||
4. The Qwen re-run also shifted by one cell against the cma.3
|
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
|
temperature=0 inputs; this is IONOS-side state-of-day noise on a
|
||||||
single deterministic re-run, not an effect of the ms.1
|
single deterministic re-run, not an effect of the ms.1
|
||||||
feedback-formatting fix (which only changes JSON-cohort prompt
|
feedback-formatting fix (which only changes JSON-cohort prompt
|
||||||
@@ -650,10 +650,10 @@ content).
|
|||||||
|
|
||||||
**Scope of this addendum:** two subjects, n=1 each, deterministic.
|
**Scope of this addendum:** two subjects, n=1 each, deterministic.
|
||||||
This is a second data point pointing in the same direction as the
|
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
|
is the AI authoring projection") would need ≥3 subjects with
|
||||||
statistical robustness to ratify; both current points point the
|
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
|
the sample size nor the cross-call noise observed in the Qwen
|
||||||
re-run is enough to close out the Decision.
|
re-run is enough to close out the Decision.
|
||||||
|
|
||||||
@@ -1813,7 +1813,7 @@ would invoke literal-defaulting which axis-7 already excluded.
|
|||||||
## Roundtrip Invariant
|
## Roundtrip Invariant
|
||||||
|
|
||||||
Every well-formed AILang module has both a canonical `.ail.json`
|
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
|
are exact projections of the same AST. Concretely, both directions
|
||||||
of the bijection hold:
|
of the bijection hold:
|
||||||
|
|
||||||
@@ -1822,7 +1822,7 @@ of the bijection hold:
|
|||||||
`canonical::to_bytes(load_module(J))`. The textual surface is
|
`canonical::to_bytes(load_module(J))`. The textual surface is
|
||||||
the inverse of the loader composed with the printer; no
|
the inverse of the loader composed with the printer; no
|
||||||
information is lost across the round-trip.
|
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))`
|
`parse(t)` is a complete AST, and re-printing `print(parse(t))`
|
||||||
produces a text that re-parses to the same AST (modulo
|
produces a text that re-parses to the same AST (modulo
|
||||||
formatting). The parser is total over the well-formed surface
|
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`
|
- `crates/ailang-surface/tests/round_trip.rs::print_then_parse_round_trips_every_fixture`
|
||||||
— for every `.ail.json`, `print` then `parse` produces canonical-
|
— for every `.ail.json`, `print` then `parse` produces canonical-
|
||||||
byte-equal output. Direction 1 above.
|
byte-equal output. Direction 1 above.
|
||||||
- `crates/ailang-surface/tests/round_trip.rs::parse_then_print_then_parse_is_idempotent_on_every_ailx_fixture`
|
- `crates/ailang-surface/tests/round_trip.rs::parse_then_print_then_parse_is_idempotent_on_every_ail_fixture`
|
||||||
— for every `.ailx` text `t`, `parse(t)` and `parse(print(parse(t)))`
|
— for every `.ail` text `t`, `parse(t)` and `parse(print(parse(t)))`
|
||||||
produce canonical-byte-equal AST. Direction 2 above.
|
produce canonical-byte-equal AST. Direction 2 above.
|
||||||
- `crates/ailang-surface/tests/round_trip.rs::every_ailx_fixture_matches_its_json_counterpart`
|
- `crates/ailang-surface/tests/round_trip.rs::every_ail_fixture_matches_its_json_counterpart`
|
||||||
— for every `.ailx` with a same-stem `.ail.json` counterpart,
|
— for every `.ail` with a same-stem `.ail.json` counterpart,
|
||||||
`parse` of the text yields canonical bytes equal to the JSON
|
`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.
|
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`
|
- `crates/ailang-core/tests/schema_coverage.rs::every_ast_variant_is_observed_in_the_fixture_corpus`
|
||||||
— every variant of `Def`, `Term`, `Pattern`, `Literal`, `Type`,
|
— every variant of `Def`, `Term`, `Pattern`, `Literal`, `Type`,
|
||||||
@@ -1882,7 +1882,7 @@ never by relaxing the test.
|
|||||||
### Why this is anchored at top level
|
### Why this is anchored at top level
|
||||||
|
|
||||||
The invariant is a property of the language identity, not of any
|
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
|
surface and lists round-trip-as-property as one of its
|
||||||
constraints, but the property is load-bearing for every
|
constraints, but the property is load-bearing for every
|
||||||
downstream concern that treats the two forms as exchangeable:
|
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 manifest <module.ail.json> — table: name :: type !effects [hash]
|
||||||
ail describe <module> <name> — detail of a definition (form-A body)
|
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 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 prose <module.ail.json> — JSON-AST → form-B (lossy human prose, no parser)
|
||||||
ail merge-prose <m.ail.json> <m.prose.txt>
|
ail merge-prose <m.ail.json> <m.prose.txt>
|
||||||
— compose the LLM-mediator prompt for the prose round-trip
|
— 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)`
|
`(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`)
|
inside that scrutinee. Std-library demos (`examples/std_*_demo.ail.json`)
|
||||||
exercise this end-to-end.
|
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
|
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
|
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
|
sole text projection; `ail parse` is the inverse direction. Round-trip
|
||||||
|
|||||||
@@ -19,14 +19,14 @@ modes to watch for.
|
|||||||
## What the LLM produces
|
## What the LLM produces
|
||||||
|
|
||||||
Iter 20f revised a load-bearing piece of this cycle: the LLM emits
|
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
|
fixed by Decision 6), not JSON-AST. JSON-AST is the canonical
|
||||||
hashable artefact, but it is not a writing surface — every type
|
hashable artefact, but it is not a writing surface — every type
|
||||||
reference wraps in `{"k": "con", ...}`, every term in
|
reference wraps in `{"k": "con", ...}`, every term in
|
||||||
`{"t": "...", ...}`, and a single missing `param_modes` entry is a
|
`{"t": "...", ...}`, and a single missing `param_modes` entry is a
|
||||||
schema error rather than a parse error with a position.
|
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
|
`ail render` produces, and the form `ail parse` consumes. The full
|
||||||
LLM-targeted specification is shipped inside the binary as
|
LLM-targeted specification is shipped inside the binary as
|
||||||
`ailang_core::FORM_A_SPEC` (sourced from
|
`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
|
1. ail prose foo.ail.json > foo.prose.txt
|
||||||
2. $EDITOR foo.prose.txt # human edits freely
|
2. $EDITOR foo.prose.txt # human edits freely
|
||||||
3. ail merge-prose foo.ail.json foo.prose.txt > prompt.txt
|
3. ail merge-prose foo.ail.json foo.prose.txt > prompt.txt
|
||||||
4. cat prompt.txt | <your-llm-cli> > foo.new.ailx
|
4. cat prompt.txt | <your-llm-cli> > foo.new.ail
|
||||||
5. ail parse foo.new.ailx > foo.new.ail.json
|
5. ail parse foo.new.ail > foo.new.ail.json
|
||||||
6. ail check foo.new.ail.json
|
6. ail check foo.new.ail.json
|
||||||
7. mv foo.new.ail.json foo.ail.json # if check is clean
|
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.
|
You are integrating prose edits back into an AILang module.
|
||||||
|
|
||||||
ROLE
|
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
|
file) that reflects the human's prose edits while preserving the
|
||||||
load-bearing semantic detail from the original module.
|
load-bearing semantic detail from the original module.
|
||||||
|
|
||||||
|
|||||||
@@ -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.
|
||||||
@@ -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.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 — 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 — 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
|
||||||
|
|||||||
+9
-9
@@ -171,7 +171,7 @@ context. Pick the next milestone from P1.)_
|
|||||||
type name). In the known-owner branch, list the owner's available
|
type name). In the known-owner branch, list the owner's available
|
||||||
type defs as candidates the way `bare-cross-module-type-ref`
|
type defs as candidates the way `bare-cross-module-type-ref`
|
||||||
lists candidates from imports.
|
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 —
|
- [ ] **\[todo\]** Workspace search beyond entry-module's directory —
|
||||||
`load_workspace` only finds sibling `.ail.json` files in the same
|
`load_workspace` only finds sibling `.ail.json` files in the same
|
||||||
directory as the entry module, so any consumer of prelude/std in a
|
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
|
- context: fieldtest 2026-05-11 — fieldtest fixtures could not be
|
||||||
placed under `examples/fieldtest/` because of this; predates the
|
placed under `examples/fieldtest/` because of this; predates the
|
||||||
canonical-type-names milestone but surfaces every time.
|
canonical-type-names milestone but surfaces every time.
|
||||||
- [ ] **\[todo\]** `ail check`/`build`/`run` accept `.ailx` extension —
|
- [ ] **\[todo\]** `ail check`/`build`/`run` accept `.ail` extension —
|
||||||
today `ail check foo.ailx` produces a misleading JSON-parse error
|
today `ail check foo.ail` produces a misleading JSON-parse error
|
||||||
(`json: expected value at line 1 column 1`) because the loader only
|
(`json: expected value at line 1 column 1`) because the loader only
|
||||||
accepts `.ail.json`. Either teach the CLI subcommands to auto-parse
|
accepts `.ail.json`. Either teach the CLI subcommands to auto-parse
|
||||||
`.ailx` internally, or detect the extension and emit a
|
`.ail` internally, or detect the extension and emit a
|
||||||
"did you mean `ail parse foo.ailx`?" hint. The LLM-author's natural
|
"did you mean `ail parse foo.ail`?" hint. The LLM-author's natural
|
||||||
first command should not be the one that produces a misleading
|
first command should not be the one that produces a misleading
|
||||||
diagnostic.
|
diagnostic.
|
||||||
- context: fieldtest 2026-05-11 — orthogonal to canonical-type-names
|
- context: fieldtest 2026-05-11 — orthogonal to canonical-type-names
|
||||||
@@ -205,13 +205,13 @@ context. Pick the next milestone from P1.)_
|
|||||||
|
|
||||||
## P3 — Ideas
|
## P3 — Ideas
|
||||||
|
|
||||||
- [ ] **\[todo\]** `compare_primitives_smoke.ailx` counterpart —
|
- [ ] **\[todo\]** `compare_primitives_smoke.ail` counterpart —
|
||||||
the canonical happy-path exhibit for canonical-type-names ships
|
the canonical happy-path exhibit for canonical-type-names ships
|
||||||
only as JSON. Per Decision 6, Surface is the LLM-author surface;
|
only as JSON. Per Decision 6, Surface is the LLM-author surface;
|
||||||
the milestone should have a `.ailx` counterpart. Two paths: author
|
the milestone should have a `.ail` counterpart. Two paths: author
|
||||||
`examples/compare_primitives_smoke.ailx` to round-trip into the
|
`examples/compare_primitives_smoke.ail` to round-trip into the
|
||||||
existing JSON, OR retire the JSON fixture in favour of
|
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.
|
happy-path exhibit.
|
||||||
- context: fieldtest 2026-05-11 (spec_gap).
|
- context: fieldtest 2026-05-11 (spec_gap).
|
||||||
- [ ] **\[todo\]** Codegen `lookup_ctor_in_pattern` type-anchoring —
|
- [ ] **\[todo\]** Codegen `lookup_ctor_in_pattern` type-anchoring —
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
; Bench fixture: explicit-mode pair of bench_list_sum.
|
; 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
|
; 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
|
; on every fn-param signature in the hot path. Under --alloc=rc the
|
||||||
; codegen now emits `inc`/`dec` instructions: each cell allocated by
|
; codegen now emits `inc`/`dec` instructions: each cell allocated by
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
# Cross-model authoring-form test
|
# 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
|
foreign LLM author reaches for and succeeds with. Single subject for
|
||||||
v1: Qwen3-Coder-Next via IONOS. Two blind cohorts; same four tasks.
|
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.
|
harness. **Authored in cma.2**, not cma.1.
|
||||||
- `render/` — standalone Cargo crate, outside the root workspace,
|
- `render/` — standalone Cargo crate, outside the root workspace,
|
||||||
builds the renderer binary.
|
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.
|
checked into the repo for review.
|
||||||
- `harness/` — **Authored in cma.2**.
|
- `harness/` — **Authored in cma.2**.
|
||||||
- `runs/<date>-<hash>/` — populated by `harness` during a live run.
|
- `runs/<date>-<hash>/` — 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,
|
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
|
(every AST variant in `ailang_core::ast` is exercised by at least
|
||||||
one example), `token_balance` (form-only blocks balanced within ±5%
|
one example), `token_balance` (form-only blocks balanced within ±5%
|
||||||
across the two rendered files).
|
across the two rendered files).
|
||||||
|
|||||||
@@ -42,8 +42,8 @@ fn main() -> Result<()> {
|
|||||||
|
|
||||||
let rendered_json = std::fs::read_to_string(args.rendered.join("json.md"))
|
let rendered_json = std::fs::read_to_string(args.rendered.join("json.md"))
|
||||||
.with_context(|| format!("reading {}", args.rendered.join("json.md").display()))?;
|
.with_context(|| format!("reading {}", args.rendered.join("json.md").display()))?;
|
||||||
let rendered_ailx = std::fs::read_to_string(args.rendered.join("ailx.md"))
|
let rendered_ail = std::fs::read_to_string(args.rendered.join("ail.md"))
|
||||||
.with_context(|| format!("reading {}", args.rendered.join("ailx.md").display()))?;
|
.with_context(|| format!("reading {}", args.rendered.join("ail.md").display()))?;
|
||||||
let tasks = tasks::load_all(&args.tasks)?;
|
let tasks = tasks::load_all(&args.tasks)?;
|
||||||
if tasks.is_empty() { bail!("no tasks found in {}", args.tasks.display()); }
|
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 run_status = "ok";
|
||||||
|
|
||||||
let mut completed: std::collections::BTreeSet<(&'static str, String)> = Default::default();
|
let mut completed: std::collections::BTreeSet<(&'static str, String)> = Default::default();
|
||||||
'outer: for cohort in [Cohort::Json, Cohort::Ailx] {
|
'outer: for cohort in [Cohort::Json, Cohort::Ail] {
|
||||||
let system_prompt = match cohort { Cohort::Json => &rendered_json, Cohort::Ailx => &rendered_ailx };
|
let system_prompt = match cohort { Cohort::Json => &rendered_json, Cohort::Ail => &rendered_ail };
|
||||||
for t in &tasks {
|
for t in &tasks {
|
||||||
let (row, consumed) = run_one(
|
let (row, consumed) = run_one(
|
||||||
&backend, &args, cohort, system_prompt, t,
|
&backend, &args, cohort, system_prompt, t,
|
||||||
@@ -75,7 +75,7 @@ fn main() -> Result<()> {
|
|||||||
// walking every (cohort, task) pair. The pairs we never reached
|
// walking every (cohort, task) pair. The pairs we never reached
|
||||||
// still need a row, with final_status = budget_abort.
|
// still need a row, with final_status = budget_abort.
|
||||||
if run_status == "budget_exceeded" {
|
if run_status == "budget_exceeded" {
|
||||||
for cohort in [Cohort::Json, Cohort::Ailx] {
|
for cohort in [Cohort::Json, Cohort::Ail] {
|
||||||
for t in &tasks {
|
for t in &tasks {
|
||||||
if !completed.contains(&(cohort.as_str(), t.id.clone())) {
|
if !completed.contains(&(cohort.as_str(), t.id.clone())) {
|
||||||
rows.push(ScoreRow {
|
rows.push(ScoreRow {
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
//! "t1_add_three": { "1": { "content": "...", "usage": {...} } },
|
//! "t1_add_three": { "1": { "content": "...", "usage": {...} } },
|
||||||
//! "t2_length": { "1": {...}, "2": {...} }
|
//! "t2_length": { "1": {...}, "2": {...} }
|
||||||
//! },
|
//! },
|
||||||
//! "ailx": { ... }
|
//! "ail": { ... }
|
||||||
//! }
|
//! }
|
||||||
//! ```
|
//! ```
|
||||||
//! Where each `"<turn>"` entry has `"content"` (the program the
|
//! Where each `"<turn>"` entry has `"content"` (the program the
|
||||||
|
|||||||
@@ -12,20 +12,20 @@ use std::time::Duration;
|
|||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
pub enum Cohort {
|
pub enum Cohort {
|
||||||
Json,
|
Json,
|
||||||
Ailx,
|
Ail,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Cohort {
|
impl Cohort {
|
||||||
pub fn extension(self) -> &'static str {
|
pub fn extension(self) -> &'static str {
|
||||||
match self {
|
match self {
|
||||||
Cohort::Json => "ail.json",
|
Cohort::Json => "ail.json",
|
||||||
Cohort::Ailx => "ailx",
|
Cohort::Ail => "ail",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn as_str(self) -> &'static str {
|
pub fn as_str(self) -> &'static str {
|
||||||
match self {
|
match self {
|
||||||
Cohort::Json => "json",
|
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()));
|
let prog_path = workdir.join(format!("prog.{}", cohort.extension()));
|
||||||
std::fs::write(&prog_path, program).with_context(|| format!("writing {}", prog_path.display()))?;
|
std::fs::write(&prog_path, program).with_context(|| format!("writing {}", prog_path.display()))?;
|
||||||
|
|
||||||
// For AILX cohort, parse to JSON first.
|
// For AIL cohort, parse to JSON first.
|
||||||
let json_path_initial = if matches!(cohort, Cohort::Ailx) {
|
let json_path_initial = if matches!(cohort, Cohort::Ail) {
|
||||||
let out = workdir.join("prog.ail.json");
|
let out = workdir.join("prog.ail.json");
|
||||||
let parse_out = Command::new(ail_bin())
|
let parse_out = Command::new(ail_bin())
|
||||||
.arg("parse").arg(&prog_path)
|
.arg("parse").arg(&prog_path)
|
||||||
|
|||||||
@@ -68,7 +68,7 @@ pub fn write_summary_md(rows: &[ScoreRow], path: &Path) -> Result<()> {
|
|||||||
let mut f = std::fs::File::create(path)
|
let mut f = std::fs::File::create(path)
|
||||||
.with_context(|| format!("creating {}", path.display()))?;
|
.with_context(|| format!("creating {}", path.display()))?;
|
||||||
writeln!(f, "# Cross-model authoring-form test — run summary\n")?;
|
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")?;
|
writeln!(f, "## Cohort: {cohort}\n")?;
|
||||||
let cohort_rows: Vec<&ScoreRow> = rows.iter().filter(|r| r.cohort == cohort).collect();
|
let cohort_rows: Vec<&ScoreRow> = rows.iter().filter(|r| r.cohort == cohort).collect();
|
||||||
if cohort_rows.is_empty() {
|
if cohort_rows.is_empty() {
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
//! The intent (parent spec §strip_locations) is **symmetric
|
//! The intent (parent spec §strip_locations) is **symmetric
|
||||||
//! degradation**: both cohorts lose the localisation information
|
//! degradation**: both cohorts lose the localisation information
|
||||||
//! their compiler natively produces. The JSON cohort would
|
//! 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.
|
//! `at byte N` offsets. Neither survives this pass.
|
||||||
|
|
||||||
use regex::Regex;
|
use regex::Regex;
|
||||||
|
|||||||
+1
-1
@@ -49,7 +49,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"ailx": {
|
"ail": {
|
||||||
"t1_add_three": {
|
"t1_add_three": {
|
||||||
"1": {
|
"1": {
|
||||||
"content": "(module garbage",
|
"content": "(module garbage",
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
//! End-to-end mock-mode test (parent spec §Testing strategy).
|
//! End-to-end mock-mode test (parent spec §Testing strategy).
|
||||||
//! Runs the harness binary against a canned response file that
|
//! Runs the harness binary against a canned response file that
|
||||||
//! makes (json, t3_main_prints) green on turn 2 and
|
//! 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)
|
//! scores.csv is well-formed and the right per-(cohort,task)
|
||||||
//! artefacts land on disk.
|
//! 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!(scores.starts_with("cohort,task_id,first_attempt_green,"));
|
||||||
assert_eq!(scores.lines().count(), 1 + 8, "header + 8 rows expected");
|
assert_eq!(scores.lines().count(), 1 + 8, "header + 8 rows expected");
|
||||||
assert!(scores.contains("json,t3_main_prints,false,2"));
|
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.
|
// Spot-check one per-cohort artefact tree.
|
||||||
let pc = run_dir.join("per_cohort").join("ailx").join("t1_add_three");
|
let pc = run_dir.join("per_cohort").join("ail").join("t1_add_three");
|
||||||
assert!(pc.join("turn_1_program.ailx").exists());
|
assert!(pc.join("turn_1_program.ail").exists());
|
||||||
assert!(pc.join("turn_5_program.ailx").exists());
|
assert!(pc.join("turn_5_program.ail").exists());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -46,8 +46,8 @@ pattern object carries a `p` discriminator. Every literal object
|
|||||||
carries a `kind` discriminator.
|
carries a `kind` discriminator.
|
||||||
{/form-only}
|
{/form-only}
|
||||||
|
|
||||||
{form-only: ailx}
|
{form-only: ail}
|
||||||
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
|
Every form begins with `(` followed by a tag identifier; positional
|
||||||
arguments follow; the form closes with `)`. There are no infix
|
arguments follow; the form closes with `)`. There are no infix
|
||||||
operators, no operator precedence, no implicit conversions.
|
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
|
escapes; integer literals are signed decimal; the `(unit)` keyword
|
||||||
denotes the unit value.
|
denotes the unit value.
|
||||||
|
|
||||||
The AILX form is the printed dual of the JSON form: `ail parse
|
The AIL form is the printed dual of the JSON form: `ail parse
|
||||||
file.ailx` produces the same canonical bytes as the JSON form would,
|
file.ail` produces the same canonical bytes as the JSON form would,
|
||||||
and `ail render file.ail.json` produces the AILX text. Round-trip
|
and `ail render file.ail.json` produces the AIL text. Round-trip
|
||||||
through this pair is gated by an integration test.
|
through this pair is gated by an integration test.
|
||||||
{/form-only}
|
{/form-only}
|
||||||
|
|
||||||
@@ -110,7 +110,7 @@ Type::Forall: `{"k": "forall", "vars": [...], "body": ...}` with an
|
|||||||
optional `constraints` array for class constraints (see section 10).
|
optional `constraints` array for class constraints (see section 10).
|
||||||
{/form-only}
|
{/form-only}
|
||||||
|
|
||||||
{form-only: ailx}
|
{form-only: ail}
|
||||||
Type::Con: `Int`, `Bool`, `Str`, `Float`, `Unit`, or a user type
|
Type::Con: `Int`, `Bool`, `Str`, `Float`, `Unit`, or a user type
|
||||||
name. For parameterised types: `(con List Int)` reads "List of Int";
|
name. For parameterised types: `(con List Int)` reads "List of Int";
|
||||||
nested: `(con Map (con Str) (con 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"`.
|
`"param_modes": ["own"]`, `"ret_mode": "own"`.
|
||||||
{/form-only}
|
{/form-only}
|
||||||
|
|
||||||
{form-only: ailx}
|
{form-only: ail}
|
||||||
Modes are surface wrappers around the type in parameter and return
|
Modes are surface wrappers around the type in parameter and return
|
||||||
position. Inside a `(fn-type ...)`:
|
position. Inside a `(fn-type ...)`:
|
||||||
|
|
||||||
@@ -235,7 +235,7 @@ use snake_case. This is historical and the canonical bytes preserve
|
|||||||
the difference.
|
the difference.
|
||||||
{/form-only}
|
{/form-only}
|
||||||
|
|
||||||
{form-only: ailx}
|
{form-only: ail}
|
||||||
Function definition: `(fn NAME (doc STRING)? (type TYPE) (params
|
Function definition: `(fn NAME (doc STRING)? (type TYPE) (params
|
||||||
NAME*) (body TERM))`. The `doc` clause is optional but recommended.
|
NAME*) (body TERM))`. The `doc` clause is optional but recommended.
|
||||||
|
|
||||||
@@ -295,7 +295,7 @@ constructor's declared `fields` in order; nullary constructors
|
|||||||
carry `"args": []`.
|
carry `"args": []`.
|
||||||
{/form-only}
|
{/form-only}
|
||||||
|
|
||||||
{form-only: ailx}
|
{form-only: ail}
|
||||||
Type definition: `(data NAME (vars TYVAR*)? (doc STRING)? (ctor
|
Type definition: `(data NAME (vars TYVAR*)? (doc STRING)? (ctor
|
||||||
CTOR-NAME ARG-TYPE*)*)`. The `vars` clause is optional; nullary
|
CTOR-NAME ARG-TYPE*)*)`. The `vars` clause is optional; nullary
|
||||||
constructors have an empty argument list.
|
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.
|
single pattern. Repeating a name is a typecheck error.
|
||||||
{/form-only}
|
{/form-only}
|
||||||
|
|
||||||
{form-only: ailx}
|
{form-only: ail}
|
||||||
Match expression: `(match SCRUT (case PAT BODY) (case PAT BODY)
|
Match expression: `(match SCRUT (case PAT BODY) (case PAT BODY)
|
||||||
...)`. Arms are introduced by `(case ...)`; the order matters (top-
|
...)`. Arms are introduced by `(case ...)`; the order matters (top-
|
||||||
to-bottom).
|
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.
|
discarded but its effects count toward the enclosing fn's row.
|
||||||
{/form-only}
|
{/form-only}
|
||||||
|
|
||||||
{form-only: ailx}
|
{form-only: ail}
|
||||||
Effect row on a fn type: `(effects E1 E2 ...)` inside the
|
Effect row on a fn type: `(effects E1 E2 ...)` inside the
|
||||||
`(fn-type ...)`. Empty row: omit the `(effects)` clause or write
|
`(fn-type ...)`. Empty row: omit the `(effects)` clause or write
|
||||||
`(effects)`. Common effectful row: `(effects IO)`. Order is sorted
|
`(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
|
literal is a UTF-8 sequence in double quotes; AILang restricts the
|
||||||
authored byte set to ASCII printable (Decision 6 Constraint 3) — non-
|
authored byte set to ASCII printable (Decision 6 Constraint 3) — non-
|
||||||
ASCII bytes are an authoring error. A unit literal is the value of
|
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
|
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
|
to the 64-bit bit pattern and emits the canonical 16-character
|
||||||
lowercase hex string into the JSON. You never author the hex
|
lowercase hex string into the JSON. You never author the hex
|
||||||
encoding by hand; the toolchain owns that representation. NaN and
|
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.
|
serialisation drift across `serde_json` versions.
|
||||||
|
|
||||||
Float bit patterns are computed by the toolchain on `ail parse`,
|
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.
|
`"bits": "40091eb851eb851f"` in the JSON form.
|
||||||
{/form-only}
|
{/form-only}
|
||||||
|
|
||||||
{form-only: ailx}
|
{form-only: ail}
|
||||||
Literal surface forms:
|
Literal surface forms:
|
||||||
|
|
||||||
- Int: bare signed decimal, e.g. `42`, `-7`, `0`.
|
- 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.
|
constraints; omitted from canonical bytes when empty.
|
||||||
{/form-only}
|
{/form-only}
|
||||||
|
|
||||||
{form-only: ailx}
|
{form-only: ail}
|
||||||
Class declaration: `(class NAME (param TYVAR) (method NAME (type
|
Class declaration: `(class NAME (param TYVAR) (method NAME (type
|
||||||
METHOD-TYPE) (default BODY)?)*)`. The `default` clause is
|
METHOD-TYPE) (default BODY)?)*)`. The `default` clause is
|
||||||
optional; methods without a default must be supplied by every
|
optional; methods without a default must be supplied by every
|
||||||
|
|||||||
@@ -2,8 +2,8 @@
|
|||||||
//!
|
//!
|
||||||
//! - JSON form: read the on-disk canonical bytes verbatim, emit a
|
//! - JSON form: read the on-disk canonical bytes verbatim, emit a
|
||||||
//! ```json``` fenced block.
|
//! ```json``` fenced block.
|
||||||
//! - AILX form: load the example via ailang_core::load_module, print
|
//! - AIL form: load the example via ailang_core::load_module, print
|
||||||
//! via ailang_surface::print, emit a ```ailx``` fenced block.
|
//! via ailang_surface::print, emit a ```ail``` fenced block.
|
||||||
|
|
||||||
use anyhow::{anyhow, Context, Result};
|
use anyhow::{anyhow, Context, Result};
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
@@ -11,7 +11,7 @@ use std::path::Path;
|
|||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
pub enum Form {
|
pub enum Form {
|
||||||
Json,
|
Json,
|
||||||
Ailx,
|
Ail,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn render_example(examples_dir: &Path, id: &str, form: Form) -> Result<String> {
|
pub fn render_example(examples_dir: &Path, id: &str, form: Form) -> Result<String> {
|
||||||
@@ -28,11 +28,11 @@ pub fn render_example(examples_dir: &Path, id: &str, form: Form) -> Result<Strin
|
|||||||
.with_context(|| format!("reading {}", path.display()))?;
|
.with_context(|| format!("reading {}", path.display()))?;
|
||||||
Ok(format!("```json\n{}\n```\n", bytes.trim_end()))
|
Ok(format!("```json\n{}\n```\n", bytes.trim_end()))
|
||||||
}
|
}
|
||||||
Form::Ailx => {
|
Form::Ail => {
|
||||||
let module = ailang_core::load_module(&path)
|
let module = ailang_core::load_module(&path)
|
||||||
.with_context(|| format!("load_module on {}", path.display()))?;
|
.with_context(|| format!("load_module on {}", path.display()))?;
|
||||||
let text = ailang_surface::print(&module);
|
let text = ailang_surface::print(&module);
|
||||||
Ok(format!("```ailx\n{}\n```\n", text.trim_end()))
|
Ok(format!("```ail\n{}\n```\n", text.trim_end()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 anyhow::{anyhow, Context, Result};
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
@@ -15,17 +15,17 @@ fn main() -> Result<()> {
|
|||||||
let segments = splitter::split(&spec_text);
|
let segments = splitter::split(&spec_text);
|
||||||
|
|
||||||
let json_out = render_for(&segments, &examples_dir, examples::Form::Json, splitter::Form::Json)?;
|
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)
|
std::fs::create_dir_all(&rendered_dir)
|
||||||
.with_context(|| format!("creating {}", rendered_dir.display()))?;
|
.with_context(|| format!("creating {}", rendered_dir.display()))?;
|
||||||
let json_path = rendered_dir.join("json.md");
|
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)
|
std::fs::write(&json_path, json_out)
|
||||||
.with_context(|| format!("writing {}", json_path.display()))?;
|
.with_context(|| format!("writing {}", json_path.display()))?;
|
||||||
std::fs::write(&ailx_path, ailx_out)
|
std::fs::write(&ail_path, ail_out)
|
||||||
.with_context(|| format!("writing {}", ailx_path.display()))?;
|
.with_context(|| format!("writing {}", ail_path.display()))?;
|
||||||
eprintln!("xmodel-render: wrote {} and {}", json_path.display(), ailx_path.display());
|
eprintln!("xmodel-render: wrote {} and {}", json_path.display(), ail_path.display());
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,20 +2,20 @@
|
|||||||
//!
|
//!
|
||||||
//! Recognises three directives, one per line, no nesting:
|
//! Recognises three directives, one per line, no nesting:
|
||||||
//! {form-only: json} … {/form-only}
|
//! {form-only: json} … {/form-only}
|
||||||
//! {form-only: ailx} … {/form-only}
|
//! {form-only: ail} … {/form-only}
|
||||||
//! {example: <id>}
|
//! {example: <id>}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
pub enum Form {
|
pub enum Form {
|
||||||
Json,
|
Json,
|
||||||
Ailx,
|
Ail,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Form {
|
impl Form {
|
||||||
fn parse(name: &str) -> Option<Form> {
|
fn parse(name: &str) -> Option<Form> {
|
||||||
match name {
|
match name {
|
||||||
"json" => Some(Form::Json),
|
"json" => Some(Form::Json),
|
||||||
"ailx" => Some(Form::Ailx),
|
"ail" => Some(Form::Ail),
|
||||||
_ => None,
|
_ => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ fn round_trip_one(path: &Path) -> Result<(), String> {
|
|||||||
let bytes_parsed = ailang_core::canonical::to_bytes(&parsed);
|
let bytes_parsed = ailang_core::canonical::to_bytes(&parsed);
|
||||||
if bytes_original != bytes_parsed {
|
if bytes_original != bytes_parsed {
|
||||||
return Err(format!(
|
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(),
|
path.display(),
|
||||||
text,
|
text,
|
||||||
bytes_original.len(),
|
bytes_original.len(),
|
||||||
@@ -50,7 +50,7 @@ fn round_trip_one(path: &Path) -> Result<(), String> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn every_example_roundtrips_via_ailx() {
|
fn every_example_roundtrips_via_ail() {
|
||||||
let examples = list_examples();
|
let examples = list_examples();
|
||||||
assert!(
|
assert!(
|
||||||
!examples.is_empty(),
|
!examples.is_empty(),
|
||||||
|
|||||||
@@ -22,13 +22,13 @@ fn form_only_block_is_isolated() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn ailx_form_only_block_uses_ailx_form() {
|
fn ail_form_only_block_uses_ail_form() {
|
||||||
let input = "{form-only: ailx}\ngrammar rule\n{/form-only}\n";
|
let input = "{form-only: ail}\ngrammar rule\n{/form-only}\n";
|
||||||
let segments = split(input);
|
let segments = split(input);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
segments,
|
segments,
|
||||||
vec![
|
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]
|
#[test]
|
||||||
fn multiple_directives_in_sequence() {
|
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);
|
let segments = split(input);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
segments,
|
segments,
|
||||||
@@ -57,7 +57,7 @@ fn multiple_directives_in_sequence() {
|
|||||||
Segment::Prose("p1\n".to_string()),
|
Segment::Prose("p1\n".to_string()),
|
||||||
Segment::Example("a".to_string()),
|
Segment::Example("a".to_string()),
|
||||||
Segment::FormOnly { form: Form::Json, body: "js\n".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::Example("b".to_string()),
|
||||||
Segment::Prose("p2\n".to_string()),
|
Segment::Prose("p2\n".to_string()),
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
//! Token-balance gate for master/spec.md form-only blocks.
|
//! Token-balance gate for master/spec.md form-only blocks.
|
||||||
//!
|
//!
|
||||||
//! The mini-spec is the experimental treatment; if the JSON-cohort's
|
//! 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
|
//! (or vice versa), the experiment is biased before the model ever
|
||||||
//! sees the prompts. This test catches gross imbalance.
|
//! 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 segments = splitter::split(&spec_text);
|
||||||
|
|
||||||
let mut json_buf = String::new();
|
let mut json_buf = String::new();
|
||||||
let mut ailx_buf = String::new();
|
let mut ail_buf = String::new();
|
||||||
for seg in &segments {
|
for seg in &segments {
|
||||||
if let Segment::FormOnly { form, body } = seg {
|
if let Segment::FormOnly { form, body } = seg {
|
||||||
match form {
|
match form {
|
||||||
Form::Json => json_buf.push_str(body),
|
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 bpe = tiktoken_rs::cl100k_base().expect("cl100k_base tokenizer should load");
|
||||||
let json_count = bpe.encode_with_special_tokens(&json_buf).len();
|
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!(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 max = json_count.max(ail_count) as f64;
|
||||||
let diff = (json_count as i64 - ailx_count as i64).unsigned_abs() as f64;
|
let diff = (json_count as i64 - ail_count as i64).unsigned_abs() as f64;
|
||||||
let ratio = diff / max;
|
let ratio = diff / max;
|
||||||
assert!(
|
assert!(
|
||||||
ratio <= 0.05,
|
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",
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
+19
-19
@@ -24,7 +24,7 @@ The schema of the on-disk form is fixed. The toolchain rejects any
|
|||||||
module that does not conform.
|
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
|
Every form begins with `(` followed by a tag identifier; positional
|
||||||
arguments follow; the form closes with `)`. There are no infix
|
arguments follow; the form closes with `)`. There are no infix
|
||||||
operators, no operator precedence, no implicit conversions.
|
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
|
escapes; integer literals are signed decimal; the `(unit)` keyword
|
||||||
denotes the unit value.
|
denotes the unit value.
|
||||||
|
|
||||||
The AILX form is the printed dual of the JSON form: `ail parse
|
The AIL form is the printed dual of the JSON form: `ail parse
|
||||||
file.ailx` produces the same canonical bytes as the JSON form would,
|
file.ail` produces the same canonical bytes as the JSON form would,
|
||||||
and `ail render file.ail.json` produces the AILX text. Round-trip
|
and `ail render file.ail.json` produces the AIL text. Round-trip
|
||||||
through this pair is gated by an integration test.
|
through this pair is gated by an integration test.
|
||||||
|
|
||||||
```ailx
|
```ail
|
||||||
(module fn_returns_int
|
(module fn_returns_int
|
||||||
(fn answer
|
(fn answer
|
||||||
(doc "The simplest possible fn — no params, returns a fixed Int.")
|
(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))
|
`b`. Class constraints attach as `(forall (a) (constraints (Eq a))
|
||||||
BODY-TYPE)`.
|
BODY-TYPE)`.
|
||||||
|
|
||||||
```ailx
|
```ail
|
||||||
(module forall_polymorphic
|
(module forall_polymorphic
|
||||||
(fn id
|
(fn id
|
||||||
(doc "The polymorphic identity function. Exercises Type::Forall and Type::Var.")
|
(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
|
making the choice explicit at authoring time avoids surprise
|
||||||
hash changes when the typechecker's defaults shift.
|
hash changes when the typechecker's defaults shift.
|
||||||
|
|
||||||
```ailx
|
```ail
|
||||||
(module param_modes_all
|
(module param_modes_all
|
||||||
(fn f_implicit
|
(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.")
|
(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
|
number, a string is a double-quoted string, a bool is `true` or
|
||||||
`false`, unit is `(unit)`.
|
`false`, unit is `(unit)`.
|
||||||
|
|
||||||
```ailx
|
```ail
|
||||||
(module fn_calls_prelude
|
(module fn_calls_prelude
|
||||||
(fn add
|
(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.")
|
(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)))))
|
(body (let s (app + x y) (clone s)))))
|
||||||
```
|
```
|
||||||
|
|
||||||
```ailx
|
```ail
|
||||||
(module fn_with_lambda
|
(module fn_with_lambda
|
||||||
(fn make_adder
|
(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.")
|
(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
|
across ADTs. Example: `(ctor List Cons 1 (ctor List Nil))` builds a
|
||||||
single-element list.
|
single-element list.
|
||||||
|
|
||||||
```ailx
|
```ail
|
||||||
(module data_simple
|
(module data_simple
|
||||||
(data Box (vars a)
|
(data Box (vars a)
|
||||||
(doc "A unary box around a polymorphic value.")
|
(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
|
inside `BODY` and bind there only. They do not leak into sibling
|
||||||
arms or into code outside the `match`.
|
arms or into code outside the `match`.
|
||||||
|
|
||||||
```ailx
|
```ail
|
||||||
(module data_with_match
|
(module data_with_match
|
||||||
(data List
|
(data List
|
||||||
(doc "Monomorphic singly-linked Int list — boxed, recursive.")
|
(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))))))
|
(case (pat-ctor Cons _ t) (app + 1 (app go t))))) (in (app go xs))))))
|
||||||
```
|
```
|
||||||
|
|
||||||
```ailx
|
```ail
|
||||||
(module match_literal_pattern
|
(module match_literal_pattern
|
||||||
(fn classify
|
(fn classify
|
||||||
(doc "Classify an Int via literal patterns plus a wildcard fallback. Exercises PatternLit and PatternWild.")
|
(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
|
nested: `(seq A (seq B C))` runs `A`, then `B`, then yields `C`'s
|
||||||
value.
|
value.
|
||||||
|
|
||||||
```ailx
|
```ail
|
||||||
(module fn_with_do_seq
|
(module fn_with_do_seq
|
||||||
(fn main
|
(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).")
|
(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
|
literal is a UTF-8 sequence in double quotes; AILang restricts the
|
||||||
authored byte set to ASCII printable (Decision 6 Constraint 3) — non-
|
authored byte set to ASCII printable (Decision 6 Constraint 3) — non-
|
||||||
ASCII bytes are an authoring error. A unit literal is the value of
|
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
|
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
|
to the 64-bit bit pattern and emits the canonical 16-character
|
||||||
lowercase hex string into the JSON. You never author the hex
|
lowercase hex string into the JSON. You never author the hex
|
||||||
encoding by hand; the toolchain owns that representation. NaN and
|
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
|
authoring surface; for non-finite values use the prelude constants
|
||||||
`nan`, `inf`, `neg_inf`.
|
`nan`, `inf`, `neg_inf`.
|
||||||
|
|
||||||
```ailx
|
```ail
|
||||||
(module floats
|
(module floats
|
||||||
(const pi
|
(const pi
|
||||||
(doc "Pi as a Float constant. Exercises Def::Const and Literal::Float — the bit pattern below is f64::to_bits(3.14).")
|
(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)))
|
(body 3.14)))
|
||||||
```
|
```
|
||||||
|
|
||||||
```ailx
|
```ail
|
||||||
(module bool_str
|
(module bool_str
|
||||||
(fn is_true
|
(fn is_true
|
||||||
(doc "Trivial Bool literal. Exercises Literal::Bool.")
|
(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
|
the type is a single bound type variable but a concrete type is
|
||||||
also legal (typically a typecheck error unless an instance exists).
|
also legal (typically a typecheck error unless an instance exists).
|
||||||
|
|
||||||
```ailx
|
```ail
|
||||||
(module class_def
|
(module class_def
|
||||||
(class MyShow
|
(class MyShow
|
||||||
(param a)
|
(param a)
|
||||||
@@ -507,7 +507,7 @@ also legal (typically a typecheck error unless an instance exists).
|
|||||||
(type (fn-type (params a) (ret (con Str)))))))
|
(type (fn-type (params a) (ret (con Str)))))))
|
||||||
```
|
```
|
||||||
|
|
||||||
```ailx
|
```ail
|
||||||
(module instance_def
|
(module instance_def
|
||||||
(class MyShow
|
(class MyShow
|
||||||
(param a)
|
(param a)
|
||||||
@@ -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
|
literal is a UTF-8 sequence in double quotes; AILang restricts the
|
||||||
authored byte set to ASCII printable (Decision 6 Constraint 3) — non-
|
authored byte set to ASCII printable (Decision 6 Constraint 3) — non-
|
||||||
ASCII bytes are an authoring error. A unit literal is the value of
|
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
|
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
|
to the 64-bit bit pattern and emits the canonical 16-character
|
||||||
lowercase hex string into the JSON. You never author the hex
|
lowercase hex string into the JSON. You never author the hex
|
||||||
encoding by hand; the toolchain owns that representation. NaN and
|
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.
|
serialisation drift across `serde_json` versions.
|
||||||
|
|
||||||
Float bit patterns are computed by the toolchain on `ail parse`,
|
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.
|
`"bits": "40091eb851eb851f"` in the JSON form.
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
+2
-2
@@ -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 |
|
| [`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 |
|
| [`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 |
|
| [`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/<date>-fieldtest-<milestone>.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/<date>-fieldtest-<milestone>.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 |
|
| [`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 |
|
| [`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-bencher` | `audit/agents/` | `audit` (regression diagnostics — hypothesis-driven) |
|
||||||
| `ailang-docwriter` | `docwriter/agents/` | `docwriter` (Boss-dispatched rustdoc sweep post-stability) |
|
| `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-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`)
|
Each agent file has YAML frontmatter (`name`, `description`, `tools`)
|
||||||
plus a system-prompt body. The `description` field is the one-sentence
|
plus a system-prompt body. The `description` field is the one-sentence
|
||||||
|
|||||||
@@ -130,7 +130,7 @@ is a side experiment, not the headline.
|
|||||||
|
|
||||||
## What you DO ship
|
## 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
|
the existing ones exercise the hypothesis. Pair them (Implicit + explicit-mode
|
||||||
variants) where the comparison demands it.
|
variants) where the comparison demands it.
|
||||||
- Edits to `bench/run.sh` (or a new harness alongside it) when the
|
- Edits to `bench/run.sh` (or a new harness alongside it) when the
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
---
|
---
|
||||||
name: fieldtest
|
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/<date>-fieldtest-<milestone>.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/<date>-fieldtest-<milestone>.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
|
# fieldtest — LLM-usability field test for a shipped milestone
|
||||||
@@ -24,7 +24,7 @@ specs at `docs/specs/<date>-fieldtest-<milestone>.md`.
|
|||||||
|
|
||||||
The substantive process — read DESIGN.md + JOURNAL + milestone spec,
|
The substantive process — read DESIGN.md + JOURNAL + milestone spec,
|
||||||
pick 2-4 real-world programming tasks per milestone axis, implement
|
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
|
classify findings, write the spec — lives in
|
||||||
`agents/ailang-fieldtester.md`. That file also carries the spec
|
`agents/ailang-fieldtester.md`. That file also carries the spec
|
||||||
template, the source-isolation discipline (no reading under
|
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.
|
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.
|
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
|
Dispatch `ailang-fieldtester` with the carrier from the Handoff
|
||||||
Contract below. The agent picks 2-4 examples (one per axis the
|
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
|
public `ail` CLI, classifies findings, and writes the spec. All
|
||||||
artefacts (fixtures + spec) stay in the working tree as unstaged
|
artefacts (fixtures + spec) stay in the working tree as unstaged
|
||||||
changes; the Boss commits them after reviewing the report (suggested
|
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 |
|
| Field | Content |
|
||||||
|-------|---------|
|
|-------|---------|
|
||||||
| `spec_path` | `docs/specs/<date>-fieldtest-<milestone>.md` |
|
| `spec_path` | `docs/specs/<date>-fieldtest-<milestone>.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 |
|
| `findings` | list, each with class (`bug` / `friction` / `spec_gap` / `working`) + recommendation |
|
||||||
| `status` | `clean` / `friction_found` / `bugs_found` / `infra_blocked` |
|
| `status` | `clean` / `friction_found` / `bugs_found` / `infra_blocked` |
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
---
|
---
|
||||||
name: ailang-fieldtester
|
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
|
tools: Read, Edit, Write, Bash, Glob, Grep
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -46,7 +46,7 @@ Read in this order, before picking examples:
|
|||||||
4. `docs/specs/<milestone>.md` if one exists — the contract this
|
4. `docs/specs/<milestone>.md` if one exists — the contract this
|
||||||
milestone signed up for.
|
milestone signed up for.
|
||||||
5. `examples/` — to learn the *form* of valid AIL. You may read any
|
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
|
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.
|
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.
|
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.
|
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:
|
**stop**. The only file paths you may open are:
|
||||||
|
|
||||||
- `CLAUDE.md`, `docs/**`, `examples/**`, `skills/**`
|
- `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 binaries YOU produce via `ail build`
|
||||||
- the `.ll` files YOU produce via `ail emit-ir` if you want to
|
- 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
|
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).
|
thin) or a 500-line numerics library (too thick).
|
||||||
3. Total: 2-4 examples.
|
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:
|
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
|
new surface where it fits naturally — but do not contort an
|
||||||
example to use a feature that doesn't fit.
|
example to use a feature that doesn't fit.
|
||||||
2. Save as `examples/fieldtest/<milestone-short>_<n>_<slug>.ailx`,
|
2. Save as `examples/fieldtest/<milestone-short>_<n>_<slug>.ail`,
|
||||||
e.g. `examples/fieldtest/22_1_eq_rational.ailx`.
|
e.g. `examples/fieldtest/22_1_eq_rational.ail`.
|
||||||
3. Run, in this order:
|
3. Run, in this order:
|
||||||
```bash
|
```bash
|
||||||
ail check examples/fieldtest/<...>.ailx
|
ail check examples/fieldtest/<...>.ail
|
||||||
ail build examples/fieldtest/<...>.ailx -o /tmp/ft_<n>
|
ail build examples/fieldtest/<...>.ail -o /tmp/ft_<n>
|
||||||
/tmp/ft_<n>
|
/tmp/ft_<n>
|
||||||
```
|
```
|
||||||
Note: if your repo invokes `ail` as `cargo run -p ail --` instead,
|
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
|
### Phase 3 — Generate canonical JSON, only via tooling
|
||||||
|
|
||||||
The canonical form is `.ail.json`. You do NOT hand-write it. After
|
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
|
```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
|
(or whichever subcommand `ail --help` lists for the surface→json
|
||||||
@@ -164,7 +164,7 @@ merged.
|
|||||||
### Phase 5 — Write the spec, hand back
|
### Phase 5 — Write the spec, hand back
|
||||||
|
|
||||||
Write `docs/specs/<YYYY-MM-DD>-fieldtest-<milestone>.md` using the
|
Write `docs/specs/<YYYY-MM-DD>-fieldtest-<milestone>.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
|
`.ail.json` files, and the spec file) in the working tree as
|
||||||
unstaged changes. You do NOT commit — the Boss commits after
|
unstaged changes. You do NOT commit — the Boss commits after
|
||||||
reading the end-report (suggested commit subject:
|
reading the end-report (suggested commit subject:
|
||||||
@@ -187,7 +187,7 @@ What the milestone shipped. One paragraph.
|
|||||||
|
|
||||||
## Examples
|
## Examples
|
||||||
Per example, one subsection:
|
Per example, one subsection:
|
||||||
### `examples/fieldtest/<milestone>_<n>_<slug>.ailx` — <task name>
|
### `examples/fieldtest/<milestone>_<n>_<slug>.ail` — <task name>
|
||||||
- What it does
|
- What it does
|
||||||
- Why this task fits the milestone's scope
|
- Why this task fits the milestone's scope
|
||||||
- Outcome: compiles? runs? matches expected stdout?
|
- 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. |
|
| "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`. |
|
| "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. |
|
| "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. |
|
| "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
|
## Red Flags — STOP and re-read DESIGN.md
|
||||||
|
|||||||
Reference in New Issue
Block a user