832375f2ac
All 176 files in the four accumulating directories now use a zero-padded 4-digit counter prefix that reflects creation order (`NNNN-slug.md`). The counter is assigned per directory in strict git-log creation order; ties broken alphabetically by original name. The old `YYYY-MM-DD-` prefix on docs/specs/ and docs/plans/ files is dropped — the date is recoverable from git log and the counter carries the ordering. A file's counter is stable for the life of the file: never reassigned, never reused, never compacted. Deleted files retire their counter; subsequent files do not fill the gap. This is the property that lets cross-references stay literal — refs use the full filename including the counter (`design/contracts/0007-honesty-rule.md`) so they grep cleanly and resolve directly without a glob step. 313 cross-references updated across .md/.rs/.toml/.c/.json files (test pins, include_str! paths, design-INDEX entries, baseline notes, runtime C comments, inter-contract markdown links incl. bare basename and `../models/foo.md` forms). CLAUDE.md gets a new "File-naming convention" section spelling out the rule and rationale. skills/brainstorm/SKILL.md and skills/planner/SKILL.md updated so new spec/plan creation produces counter-prefixed names from the start. The full test suite (cargo test --workspace) passes.
202 lines
9.4 KiB
Markdown
202 lines
9.4 KiB
Markdown
# Iter form-a.0 — Prelude Pilot — Implementation Plan
|
|
|
|
> **Parent spec:** `docs/specs/0025-form-a-default-authoring.md`
|
|
>
|
|
> **For agentic workers:** REQUIRED SUB-SKILL: use `skills/implement`
|
|
> to run this plan. Steps use `- [ ]` checkboxes for tracking.
|
|
|
|
**Goal:** Render `examples/prelude.ail.json` to its Form-A sibling
|
|
`examples/prelude.ail` and prove that the existing roundtrip-CI
|
|
gate (`every_ail_fixture_matches_its_json_counterpart`) plus the
|
|
parallel idempotency test both pass on the new file, validating
|
|
the migration mechanism on the hardest single file before iter 1
|
|
commits to the bulk test-infrastructure refactor.
|
|
|
|
**Architecture:** One CLI invocation (`ail render`) produces the
|
|
Form-A text from the canonical JSON-AST. The two relevant tests
|
|
in `crates/ailang-surface/tests/round_trip.rs` auto-discover
|
|
fixtures via `read_dir(examples/)`; the freshly added `.ail`
|
|
lands in their corpus on the next `cargo test` run with no test-
|
|
code changes. Iter 0 deliberately keeps `prelude.ail.json` on
|
|
disk alongside the new `.ail` — the JSON counterpart is the
|
|
byte-witness the gate uses, and that gate is the safety net that
|
|
makes the pilot worth running.
|
|
|
|
**Tech Stack:** `ail render` CLI subcommand (Form-A printer in
|
|
`ailang-surface::print`), `crates/ailang-surface/tests/round_trip.rs`
|
|
(auto-discovering gate + idempotency tests), `docs/journals/`
|
|
append convention.
|
|
|
|
**Files this plan creates or modifies:**
|
|
|
|
- Create: `examples/prelude.ail` — Form-A rendering of `examples/prelude.ail.json`, ~6386 bytes / 116 lines, produced verbatim by `ail render`.
|
|
- Create: `docs/journals/2026-05-13-iter-form-a.0.md` — per-iter journal entry.
|
|
- Modify: `docs/journals/INDEX.md:53` (append-only, one new line at line 54).
|
|
|
|
No source-code edits, no test edits, no CLAUDE.md / DESIGN.md
|
|
edits. The two existing roundtrip tests pick up the new fixture
|
|
automatically because both use `read_dir(examples/)` discovery.
|
|
|
|
---
|
|
|
|
### Task 1: Render `examples/prelude.ail` and verify both auto-discovering roundtrip tests pass
|
|
|
|
**Files:**
|
|
- Create: `examples/prelude.ail`
|
|
- Test (existing, no edit): `crates/ailang-surface/tests/round_trip.rs:120-204` (`every_ail_fixture_matches_its_json_counterpart`) and `crates/ailang-surface/tests/round_trip.rs:216-277` (`parse_then_print_then_parse_is_idempotent_on_every_ail_fixture`).
|
|
|
|
- [ ] **Step 1: Render the prelude via `ail render`**
|
|
|
|
Run from the workspace root:
|
|
|
|
```
|
|
cargo run -q --bin ail -- render examples/prelude.ail.json > examples/prelude.ail
|
|
```
|
|
|
|
The CLI dispatch lives at `crates/ail/src/main.rs:424-427`:
|
|
```rust
|
|
Cmd::Render { path } => {
|
|
let m = ailang_surface::load_module(&path)?;
|
|
print!("{}", ailang_surface::print(&m));
|
|
}
|
|
```
|
|
|
|
`render` takes a single positional path argument and writes Form-A
|
|
text to stdout; shell redirection captures it to the target file.
|
|
|
|
Expected: exit 0, no stderr output, `examples/prelude.ail` created.
|
|
|
|
- [ ] **Step 2: Sanity-check the rendered file's shape**
|
|
|
|
Run:
|
|
|
|
```
|
|
wc -lc examples/prelude.ail
|
|
```
|
|
|
|
Expected output (verified during recon): approximately `116 6386 examples/prelude.ail` — exactly 116 lines, 6386 bytes. A different count by more than ±1 line is a signal that the renderer drifted between recon and execution; investigate before continuing.
|
|
|
|
Then verify the file begins with the expected `(module prelude` header by reading the first 5 lines:
|
|
|
|
```
|
|
head -n 5 examples/prelude.ail
|
|
```
|
|
|
|
Expected first line: `(module prelude`. Expected second line: ` (data Ordering`. If the file does not start this way, the render is wrong; do not continue.
|
|
|
|
- [ ] **Step 3: Run the gate test in isolation**
|
|
|
|
This is the spec's named witness for iter 0 (§A2).
|
|
|
|
Run:
|
|
|
|
```
|
|
cargo test -p ailang-surface --test round_trip every_ail_fixture_matches_its_json_counterpart
|
|
```
|
|
|
|
Expected: PASS. The test (`crates/ailang-surface/tests/round_trip.rs:120-204`) walks every `.ail` file under `examples/`, looks up the same-stem `.ail.json` counterpart, and asserts that parsing the `.ail` produces canonical bytes byte-equal to loading the JSON. The freshly added `prelude.ail` (with its retained `prelude.ail.json` counterpart) is the new corpus element this iter introduces; the test must report at least one more fixture passing than the previous run (or the same count if the test summary doesn't print per-file).
|
|
|
|
If the test FAILS on the new prelude: the failure mode is E1 from the spec (a fixture round-trips before migration but the migrated `.ail` fails to parse, or parses to different bytes). The bug is in `ail render` or `ailang_surface::parse`; this iter halts and the bug fix takes precedence. Do NOT delete or alter `prelude.ail` to make the test pass.
|
|
|
|
- [ ] **Step 4: Run the parallel idempotency test**
|
|
|
|
The recon flagged this test as the second one that auto-discovers `.ail` fixtures and would react to the new file. It checks a different property (idempotency under print) and must also pass.
|
|
|
|
Run:
|
|
|
|
```
|
|
cargo test -p ailang-surface --test round_trip parse_then_print_then_parse_is_idempotent_on_every_ail_fixture
|
|
```
|
|
|
|
Expected: PASS. The test (`crates/ailang-surface/tests/round_trip.rs:216-277`) walks every `.ail` file and asserts `canonical(parse(t)) == canonical(parse(print(parse(t))))`. If this fails on the new prelude while the gate test passes, the Form-A printer is non-idempotent on some feature prelude uses — a deeper render bug that the gate test does not catch. Same halt rule: do not alter the rendered file.
|
|
|
|
- [ ] **Step 5: Run the full workspace test suite for regression safety**
|
|
|
|
Run:
|
|
|
|
```
|
|
cargo test --workspace
|
|
```
|
|
|
|
Expected: PASS, identical pass count to pre-iter baseline plus zero (no new tests are added in this iter). The recon explicitly verified that no `.ail.json`-walking test (`print_then_parse_round_trips_every_fixture`, `every_ast_variant_is_observed_in_the_fixture_corpus`, `cli_render_then_parse_preserves_canonical_bytes_on_every_fixture`) has its corpus changed by adding a sibling `.ail`, so the only tests that should react are the two run in Steps 3-4; the full suite is the sanity check that nothing unexpected fires.
|
|
|
|
---
|
|
|
|
### Task 2: Write the per-iter journal entry and append the INDEX line
|
|
|
|
**Files:**
|
|
- Create: `docs/journals/2026-05-13-iter-form-a.0.md`
|
|
- Modify: `docs/journals/INDEX.md` (append one line at the end of file — current last line is 53).
|
|
|
|
- [ ] **Step 1: Write the journal file**
|
|
|
|
Create `docs/journals/2026-05-13-iter-form-a.0.md` with the
|
|
following content. The `116 lines / 6386 bytes` figures are the
|
|
recon-observed values; if Task 1 Step 2 observed different values
|
|
(it should not, the renderer is byte-stable), use the actual `wc
|
|
-lc` output and adjust the INDEX line in Step 2 to match.
|
|
|
|
```markdown
|
|
# Iter form-a.0 — Prelude Pilot
|
|
|
|
**Date:** 2026-05-13
|
|
**Parent spec:** `docs/specs/0025-form-a-default-authoring.md`
|
|
**Parent plan:** `docs/plans/0059-iter-form-a.0.md`
|
|
|
|
## Outcome
|
|
|
|
`examples/prelude.ail` rendered from `examples/prelude.ail.json` via
|
|
`ail render` (116 lines / 6386 bytes). The two auto-discovering
|
|
roundtrip tests in `crates/ailang-surface/tests/round_trip.rs` both
|
|
pass on the new fixture without test-code changes:
|
|
|
|
- `every_ail_fixture_matches_its_json_counterpart` — green; the
|
|
`prelude.ail` parses to canonical bytes byte-equal to
|
|
`prelude.ail.json`.
|
|
- `parse_then_print_then_parse_is_idempotent_on_every_ail_fixture`
|
|
— green; the Form-A printer is idempotent over prelude.
|
|
|
|
`prelude.ail.json` is retained on disk per spec §A2; iter 1 deletes
|
|
it alongside the bulk test-infrastructure refactor. This is the
|
|
only iteration in the milestone where a fixture is dual-form.
|
|
|
|
## Why this iter is the pilot
|
|
|
|
Spec §A2 names prelude as the pilot because it exercises the widest
|
|
cross-section of language features in a single file: typeclasses
|
|
with superclasses (`Ord extends Eq`), polymorphic free functions
|
|
with constraints (`forall<a> where Show a fn print`), IO-effect
|
|
signatures, primitive ctor patterns, and the loader's auto-injection
|
|
path. A green roundtrip on prelude is strong evidence the bulk
|
|
migration in iter 1+ will not surface render or parse bugs.
|
|
|
|
## Verifications run
|
|
|
|
- `cargo test -p ailang-surface --test round_trip every_ail_fixture_matches_its_json_counterpart` — PASS
|
|
- `cargo test -p ailang-surface --test round_trip parse_then_print_then_parse_is_idempotent_on_every_ail_fixture` — PASS
|
|
- `cargo test --workspace` — PASS, no regression
|
|
|
|
## Scope NOT touched
|
|
|
|
Per spec §"Iteration scope": no test-infrastructure changes, no
|
|
CLAUDE.md / DESIGN.md edits, no deletion of `prelude.ail.json`,
|
|
no migration script yet. Those land in iter 1.
|
|
```
|
|
|
|
- [ ] **Step 2: Append the INDEX line**
|
|
|
|
The last existing line in `docs/journals/INDEX.md` is line 53
|
|
(iter `24.tidy`). Append one new line at the end of file:
|
|
|
|
```markdown
|
|
- 2026-05-13 — iter form-a.0: prelude pilot for Form-A-as-default-authoring milestone — examples/prelude.ail rendered (116 lines / 6386 bytes) via `ail render`; two auto-discovering roundtrip tests (every_ail_fixture_matches_its_json_counterpart + parse_then_print_then_parse_is_idempotent_on_every_ail_fixture) green on the new fixture without test-code changes; prelude.ail.json retained per spec §A2 (singular dual-form iter, deletion lands iter 1 with bulk test-infra refactor); cargo test --workspace green → 2026-05-13-iter-form-a.0.md
|
|
```
|
|
|
|
Adjust the byte/line counts in the line if Task 1 Step 2 observed
|
|
different numbers. Convention (per INDEX.md header lines 1-7):
|
|
append-only, one line per iter, newest at the bottom, em-dash
|
|
between iter id and summary, arrow before the journal filename.
|
|
|
|
No other files modify in this step; INDEX append is the terminal
|
|
working-tree edit of this iter.
|