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.
772 lines
29 KiB
Markdown
772 lines
29 KiB
Markdown
# rpe.1 — Retire per-type print effect-ops — Implementation Plan
|
||
|
||
> **Parent spec:** `docs/specs/0027-retire-per-type-print-effects.md`
|
||
>
|
||
> **For agentic workers:** REQUIRED SUB-SKILL: use `skills/implement`
|
||
> to run this plan. Steps use `- [ ]` checkboxes for tracking.
|
||
|
||
**Goal:** Delete `io/print_int`, `io/print_bool`, `io/print_float`
|
||
from the compiler and migrate every corpus call site to the
|
||
polymorphic `print` helper shipped in iter 24.3.
|
||
|
||
**Architecture:** Four-site lockstep deletion (check builtins,
|
||
codegen lower_effect_op, codegen synth.rs effect_op_ret table,
|
||
plus three doc-comment touch-ups), preceded by exhaustive corpus
|
||
migration so that the deletion does not strand any live caller.
|
||
A RED test pins the deletion gate; per-task `cargo test --workspace`
|
||
gates each step. Bench-latency drift is expected per spec §C4 (a)
|
||
and is ratified at iter close.
|
||
|
||
**Tech Stack:** `ailang-check`, `ailang-codegen`, `ailang-surface`,
|
||
`ailang-prose`, `ailang-core`, `ail` CLI; the snapshot-regenerated
|
||
`.prose.txt` artefacts; `bench/run.sh` latency harness baselines.
|
||
|
||
---
|
||
|
||
## Errata vs. spec (incorporated below; spec is unchanged)
|
||
|
||
Recon surfaced four spec-vs-code mismatches; this plan absorbs
|
||
them without amending the spec:
|
||
|
||
1. **Spec §C2 says "three install_io_print_<T>_signature tests
|
||
(lines ~593–633)".** Only `install_io_print_float_signature`
|
||
exists (builtins.rs:600). The plan deletes the one that
|
||
exists, not three.
|
||
2. **Spec §D1 says "98 examples/*.ail files".** Actual: 92 `.ail`
|
||
+ 6 `.prose.txt` snapshot siblings = 98. The plan migrates
|
||
both, but a `.prose.txt` is regenerated, not text-substituted.
|
||
3. **`crates/ailang-codegen/src/synth.rs:215`** carries a parallel
|
||
effect-op `ret` table (`builtin_effect_op_ret`) the spec §C2
|
||
does not name. Lockstep delete with the codegen arms.
|
||
4. **Five test bodies + four doc-comment sites** carry
|
||
`io/print_int` as incidental scaffolding (`crates/ailang-check/src/lib.rs`,
|
||
`crates/ailang-core/tests/spec_drift.rs`, `design_schema_drift.rs`,
|
||
`crates/ailang-surface/{src/lex.rs, src/parse.rs}`,
|
||
`crates/ailang-prose/src/lib.rs`, `crates/ail/src/main.rs`,
|
||
`crates/ailang-core/specs/form_a.md`, `runtime/str.c`
|
||
comment). Plan covers them in Task 5 and Task 6.
|
||
|
||
The spec's §B/§C1/§D1 invariants are unaffected.
|
||
|
||
---
|
||
|
||
## Files this plan creates or modifies
|
||
|
||
**Create:**
|
||
|
||
- `crates/ailang-check/tests/no_per_type_print_ops.rs` — RED test
|
||
(Task 1). Model on `crates/ailang-check/tests/show_dispatch_pin.rs`.
|
||
|
||
**Modify — corpus (Task 2 + Task 3):**
|
||
|
||
- `examples/<name>.ail` × 92 — bulk substitution. Full list at
|
||
`/tmp/migration_targets.txt` (built by Task 2 Step 1; see Task 2
|
||
for the canonical query the LLM re-runs).
|
||
- `examples/<name>.prose.txt` × 6 — regenerate via `ail prose`.
|
||
Files: `bench_list_sum.prose.txt`, `ordering_match.prose.txt`,
|
||
`rc_app_let_partial_drop_leak.prose.txt`,
|
||
`rc_match_arm_partial_drop_leak.prose.txt`,
|
||
`rc_own_param_drop.prose.txt`,
|
||
`test_22b3_default_e2e.prose.txt`.
|
||
|
||
**Modify — compiler core (Task 4):**
|
||
|
||
- `crates/ailang-check/src/builtins.rs`:
|
||
- line 5 — module-doc-comment swap `io/print_int` → `io/print_str`.
|
||
- line 40 — `EffectOpSig.ret` doc swap.
|
||
- lines 269-276 — delete `env.effect_ops.insert("io/print_int", ...)`.
|
||
- lines 277-284 — delete `env.effect_ops.insert("io/print_bool", ...)`.
|
||
- lines 293-302 — delete floats-iter comment + `io/print_float` insert.
|
||
- line 349 — delete `io/print_int` row in `list()`.
|
||
- line 350 — delete `io/print_bool` row.
|
||
- line 352 — delete `io/print_float` row. (Line 351 — `io/print_str` — survives.)
|
||
- lines 593-635 — delete `install_io_print_float_signature` test fn (incl. its 7-line doc-comment).
|
||
- `crates/ailang-codegen/src/lib.rs`:
|
||
- lines 2302-2323 — delete `"io/print_int" => { ... }` arm.
|
||
- lines 2324-2345 — delete `"io/print_float" => { ... }` arm.
|
||
- lines 2373-2411 — delete `"io/print_bool" => { ... }` arm.
|
||
- lines 3580-3617 — delete `lowers_io_print_float` test (incl. doc-comment).
|
||
- format-string interns (`"fmt_int"`, `"%lld\n"`, `"fmt_float"`, `"%g\n"`, `"fmt_true"`, `"fmt_false"`) orphan and may be deleted in the same edit if they are no longer referenced. Verify by `grep` post-edit.
|
||
- `crates/ailang-codegen/src/synth.rs:215` — remove `"io/print_int" | "io/print_bool" | "io/print_float"` from the `match name` arm; keep `"io/print_str"`.
|
||
|
||
**Modify — incidental-scaffolding test bodies (Task 5):**
|
||
|
||
- `crates/ailang-check/src/lib.rs`:
|
||
- lines 3871-3877 (undeclared-effect-diagnostic test) — swap `op: "io/print_int"` → `op: "io/print_str"`; if the arg type was `Int`, change it to a `Str` literal that types as Str.
|
||
- lines 5047-5051 (tail-call-in-tail-position test) — same shape.
|
||
- `crates/ailang-core/tests/spec_drift.rs` lines 64-71 — swap to `io/print_str` with a `Str` arg.
|
||
- `crates/ailang-core/tests/design_schema_drift.rs` lines 87-94 — same.
|
||
- `crates/ailang-surface/src/lex.rs`:
|
||
- lines 18-20 — module-doc-comment swap.
|
||
- line 320 — `tokenize("+ == <= io/print_int std_list.map")` swap to `io/print_str`.
|
||
- line 328 — corresponding `assert_eq!` vector swap.
|
||
- `crates/ailang-prose/src/lib.rs` lines 1585-1593 — round-trip test asserting `render_term(&t) == "do io/print_int(5)"`: swap to `io/print_str` with a Str arg + matching assert.
|
||
|
||
**Modify — doc-comment touch-ups (Task 6):**
|
||
|
||
- `crates/ailang-surface/src/parse.rs:1377` — diagnostic example swap.
|
||
- `crates/ail/src/main.rs:30-31, 1419-1420` — CLI doc-comments swap.
|
||
- `runtime/str.c:117-118` — re-anchor on `%g` directly.
|
||
- `crates/ailang-core/specs/form_a.md:433-434` — surface-spec example: migrate to either `(do io/print_str s)` with a `Str` literal, or `(app print xs)` — pick the latter to advertise the post-iter idiom.
|
||
|
||
**Modify — DESIGN.md (Task 7):**
|
||
|
||
- `docs/DESIGN.md`:
|
||
- line 331 — Decision 11 example swap.
|
||
- lines 1990-1992 — past-tense rewrite ("retired 2026-05-14 in iter rpe.1").
|
||
- lines 2024-2025 — Heap-Str primitives output sentence rewrite per spec §C3.
|
||
- line 2331 — effect-op invocation comment example swap.
|
||
- lines 2565-2577 — Float NaN-rendering paragraph rewrite (anchor on `float_to_str`).
|
||
- line 2679 — "What is supported (effect ops)" list, keep only `io/print_str`.
|
||
- line 2699 — "(IO effect ops)" list, keep only `io/print_str`.
|
||
|
||
**Modify — incidental E2E test comments (Task 8):**
|
||
|
||
- `crates/ail/tests/eq_ord_e2e.rs:66, 76` — update comments to name `print` semantics.
|
||
- `crates/ail/tests/e2e.rs:2674-2683` (`int_arg_to_effect_op_does_not_rc_track`) — fixture migrates in Task 2; doc-comment migrates here.
|
||
- `crates/ail/tests/floats_e2e.rs:7` — module doc-comment swap.
|
||
|
||
**Modify — bench baselines (Task 9, conditional):**
|
||
|
||
- `bench/baseline.json` "latency" block (lines 70-92) — ratify if drift.
|
||
- `bench/baseline.json" throughput block (lines 1-69, 93) — ratify if drift.
|
||
- `bench/baseline_cross_lang.json` — ratify if drift.
|
||
- `bench/baseline_compile.json` — expected exit-0; ratify only if unexpected drift.
|
||
|
||
**Modify — roadmap (Task 10):**
|
||
|
||
- `docs/roadmap.md` P2 section lines 80-90 — strike entry with `- [x]` + date.
|
||
|
||
---
|
||
|
||
## Task 1: RED test — `no_per_type_print_ops.rs`
|
||
|
||
**Files:**
|
||
- Create: `crates/ailang-check/tests/no_per_type_print_ops.rs`
|
||
|
||
- [ ] **Step 1: Write the failing test**
|
||
|
||
```rust
|
||
//! Hard gate for iter rpe.1: the per-type print effect-ops
|
||
//! `io/print_int`, `io/print_bool`, `io/print_float` are retired
|
||
//! and must NOT appear in the builtin registry. The polymorphic
|
||
//! `print` helper (prelude, iter 24.3) is the canonical replacement;
|
||
//! `io/print_str` survives as the byte-channel primitive.
|
||
|
||
use ailang_check::build_check_env;
|
||
use ailang_surface::load_workspace;
|
||
use std::path::PathBuf;
|
||
|
||
fn fixture_dir() -> PathBuf {
|
||
let mut d = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
|
||
d.pop();
|
||
d.pop();
|
||
d.join("examples")
|
||
}
|
||
|
||
#[test]
|
||
fn per_type_print_effect_ops_are_retired() {
|
||
// Any workspace will do; we only inspect the post-install
|
||
// builtin registry. `hello.ail` is the smallest surviving
|
||
// fixture (uses `io/print_str` only).
|
||
let ws_path = fixture_dir().join("hello.ail");
|
||
let ws = load_workspace(&ws_path).expect("workspace loads");
|
||
let env = build_check_env(&ws);
|
||
|
||
for retired in ["io/print_int", "io/print_bool", "io/print_float"] {
|
||
assert!(
|
||
!env.effect_ops.contains_key(retired),
|
||
"{retired} must not appear in builtin registry post-rpe.1; \
|
||
use polymorphic `print` (Show-based) instead"
|
||
);
|
||
}
|
||
|
||
// Survivor pin: io/print_str stays.
|
||
assert!(
|
||
env.effect_ops.contains_key("io/print_str"),
|
||
"io/print_str must survive — it is the canonical byte-channel \
|
||
primitive and used internally by `print`"
|
||
);
|
||
}
|
||
```
|
||
|
||
- [ ] **Step 2: Verify the test fails RED**
|
||
|
||
Run: `cargo test --workspace -p ailang-check --test no_per_type_print_ops`
|
||
Expected: FAIL on the first `assert!` because `io/print_int`
|
||
still appears in `env.effect_ops` post-Task-2.
|
||
|
||
This gate flips to GREEN at Task 4 close.
|
||
|
||
---
|
||
|
||
## Task 2: Corpus `.ail` migration — 92 fixtures
|
||
|
||
**Files:**
|
||
- Modify: 92 `examples/<name>.ail` files (full list deterministic
|
||
from `grep -rl "io/print_int\|io/print_bool\|io/print_float"
|
||
examples/ | grep "\.ail$"`).
|
||
|
||
- [ ] **Step 1: Build the canonical target list**
|
||
|
||
Run:
|
||
```bash
|
||
grep -rl "io/print_int\|io/print_bool\|io/print_float" examples/ \
|
||
| grep "\.ail$" > /tmp/rpe1_targets.txt && \
|
||
wc -l /tmp/rpe1_targets.txt
|
||
```
|
||
Expected: `92 /tmp/rpe1_targets.txt`.
|
||
|
||
- [ ] **Step 2: Bulk-substitute each fixture**
|
||
|
||
For each path in `/tmp/rpe1_targets.txt`, transform every
|
||
occurrence of:
|
||
```
|
||
(do io/print_int <ARG>)
|
||
```
|
||
to:
|
||
```
|
||
(app print <ARG>)
|
||
```
|
||
And the same for `io/print_bool`, `io/print_float`.
|
||
|
||
The transformation is regular: parenthesis-balance is preserved
|
||
(both `do` and `app` consume their parentheses identically),
|
||
the head is replaced (`io/print_<T>` → `print`), and the leading
|
||
keyword is replaced (`do` → `app`). The argument expression is
|
||
copied verbatim.
|
||
|
||
Implementation: read each file, apply the regex
|
||
`\(do io/print_(int|bool|float) ` → `(app print `, write back.
|
||
Or process file-by-file with Edit-tool calls; either is fine.
|
||
|
||
- [ ] **Step 3: Verify each migrated fixture parses + round-trips**
|
||
|
||
Run:
|
||
```bash
|
||
cargo run -p ail -- check examples/<each-fixture> 2>&1
|
||
```
|
||
Expected: exit 0. The pre-iter compiler still knows
|
||
`io/print_int|bool|float`, so existing fixtures stay green; the
|
||
migrated form (using polymorphic `print`) ALSO type-checks because
|
||
`print` is in the prelude (iter 24.3). Both paths are green
|
||
simultaneously in this iter window.
|
||
|
||
Pragmatic shortcut: run `cargo test --workspace` at Step 4 instead
|
||
of per-fixture `ail check`.
|
||
|
||
- [ ] **Step 4: `cargo test --workspace` green**
|
||
|
||
Run: `cargo test --workspace`
|
||
Expected: 0 failures. The migrated fixtures' E2E test wrappers
|
||
still produce the same stdout (output bytes identical between
|
||
`(do io/print_<T> x)` and `(app print x)` per spec §"Data flow").
|
||
|
||
If any fixture has a stdout-comparison test that now produces
|
||
different bytes, that is a real divergence and the spec's
|
||
"output bytes identical" claim has a counterexample — STOP
|
||
and investigate before continuing.
|
||
|
||
---
|
||
|
||
## Task 3: Prose snapshot regeneration — 6 `.prose.txt` files
|
||
|
||
**Files:**
|
||
- Modify: `examples/bench_list_sum.prose.txt`,
|
||
`examples/ordering_match.prose.txt`,
|
||
`examples/rc_app_let_partial_drop_leak.prose.txt`,
|
||
`examples/rc_match_arm_partial_drop_leak.prose.txt`,
|
||
`examples/rc_own_param_drop.prose.txt`,
|
||
`examples/test_22b3_default_e2e.prose.txt`.
|
||
|
||
- [ ] **Step 1: Regenerate each prose snapshot**
|
||
|
||
For each fixture above, run:
|
||
```bash
|
||
cargo run -p ail -- prose examples/<stem>.ail \
|
||
> examples/<stem>.prose.txt
|
||
```
|
||
|
||
The `ail prose` subcommand emits Form-B; this file becomes the
|
||
committed snapshot used by `crates/ailang-prose/tests/snapshot.rs`.
|
||
|
||
- [ ] **Step 2: `cargo test --workspace -p ailang-prose` green**
|
||
|
||
Run: `cargo test --workspace -p ailang-prose`
|
||
Expected: 0 failures. The snapshot test will compare the
|
||
regenerated `.prose.txt` to the prose-renderer's current output
|
||
of the migrated fixture; they must match because we generated
|
||
them with the very command the test uses.
|
||
|
||
- [ ] **Step 3: `cargo test --workspace` green**
|
||
|
||
Run: `cargo test --workspace`
|
||
Expected: 0 failures (no regression in any other crate).
|
||
|
||
---
|
||
|
||
## Task 4: Compiler-side deletion (lockstep)
|
||
|
||
**Files:**
|
||
- Modify: `crates/ailang-check/src/builtins.rs`
|
||
(lines 5, 40, 269-302, 349, 350, 352, 593-635)
|
||
- Modify: `crates/ailang-codegen/src/lib.rs`
|
||
(lines 2302-2323, 2324-2345, 2373-2411, 3580-3617)
|
||
- Modify: `crates/ailang-codegen/src/synth.rs:215`
|
||
|
||
- [ ] **Step 1: Delete the three `env.effect_ops.insert` blocks**
|
||
|
||
In `crates/ailang-check/src/builtins.rs`, delete:
|
||
- lines 269-276 (`io/print_int` block)
|
||
- lines 277-284 (`io/print_bool` block)
|
||
- lines 293-302 (floats-iter comment + `io/print_float` block)
|
||
|
||
Keep the surviving `io/print_str` block (between or after; verify
|
||
by reading the file post-edit).
|
||
|
||
- [ ] **Step 2: Delete the three `list()` rows**
|
||
|
||
In `crates/ailang-check/src/builtins.rs`, delete:
|
||
- line 349 (`io/print_int` row)
|
||
- line 350 (`io/print_bool` row)
|
||
- line 352 (`io/print_float` row)
|
||
|
||
Keep line 351 (`io/print_str` row). Verify by re-reading.
|
||
|
||
- [ ] **Step 3: Delete the `install_io_print_float_signature` test**
|
||
|
||
In `crates/ailang-check/src/builtins.rs`, delete lines 593-635
|
||
(test fn + its 7-line doc-comment). This is the ONE test that
|
||
exists matching the spec's "three install_io_print_<T>_signature"
|
||
description — see Errata #1 above.
|
||
|
||
- [ ] **Step 4: Swap the module-doc-comments**
|
||
|
||
In `crates/ailang-check/src/builtins.rs`:
|
||
- line 5 — swap `io/print_int` reference to `io/print_str`.
|
||
- line 40 — same swap in `EffectOpSig.ret` doc.
|
||
|
||
- [ ] **Step 5: Delete the three codegen `lower_effect_op` arms**
|
||
|
||
In `crates/ailang-codegen/src/lib.rs`:
|
||
- lines 2302-2323 — delete `"io/print_int" => { ... }` arm.
|
||
- lines 2324-2345 — delete `"io/print_float" => { ... }` arm.
|
||
- lines 2373-2411 — delete `"io/print_bool" => { ... }` arm.
|
||
|
||
Keep the `"io/print_str"` arm (lines 2346-2372). Verify by
|
||
re-reading.
|
||
|
||
After deletion, search the file for orphaned format-string
|
||
interns (`"fmt_int"`, `"%lld\n"`, `"fmt_float"`, `"%g\n"`,
|
||
`"fmt_true"`, `"fmt_false"`). If they are no longer referenced
|
||
anywhere in the crate, delete the corresponding `out.push_str`
|
||
or `intern` calls too. If they ARE still referenced (e.g.
|
||
`"%g\n"` shared with another path), leave them.
|
||
|
||
- [ ] **Step 6: Delete the `lowers_io_print_float` test**
|
||
|
||
In `crates/ailang-codegen/src/lib.rs`, delete lines 3580-3617
|
||
(test + doc-comment).
|
||
|
||
- [ ] **Step 7: Update `synth.rs` builtin_effect_op_ret**
|
||
|
||
In `crates/ailang-codegen/src/synth.rs:215`, remove
|
||
`"io/print_int" | "io/print_bool" | "io/print_float"` from the
|
||
match arm pattern. Keep `"io/print_str"`. The match arm body
|
||
(returning `Type::unit()`) stays unchanged for the surviving
|
||
op.
|
||
|
||
- [ ] **Step 8: RED test flips GREEN**
|
||
|
||
Run: `cargo test --workspace -p ailang-check --test no_per_type_print_ops`
|
||
Expected: PASS. The Task-1 RED test is now satisfied because
|
||
the three retired ops no longer appear in `env.effect_ops`.
|
||
|
||
- [ ] **Step 9: `cargo test --workspace` green**
|
||
|
||
Run: `cargo test --workspace`
|
||
Expected: 0 failures. Any failure here means a stale `io/print_<T>`
|
||
reference survived in a fixture or test body — those are addressed
|
||
in Task 5; expect Task-5 sites to fail and proceed to Task 5.
|
||
|
||
---
|
||
|
||
## Task 5: In-tree test-body migration
|
||
|
||
**Files:**
|
||
- Modify: `crates/ailang-check/src/lib.rs:3871-3877, 5047-5051`
|
||
- Modify: `crates/ailang-core/tests/spec_drift.rs:64-71`
|
||
- Modify: `crates/ailang-core/tests/design_schema_drift.rs:87-94`
|
||
- Modify: `crates/ailang-surface/src/lex.rs:320, 328`
|
||
- Modify: `crates/ailang-prose/src/lib.rs:1585-1593`
|
||
- Modify: `crates/ail/tests/e2e.rs` — six IR-shape tests at lines 1408,
|
||
1628, 1710, 1800, 1936, 2013 (Cat B test-harness patch, see below).
|
||
|
||
**Cat B test-harness patch (added 2026-05-14 after the rpe.1 BLOCKED
|
||
orchestrator run):** Six IR-shape tests in `crates/ail/tests/e2e.rs`
|
||
build their own `lifted_ws` via a `lift_letrecs` loop and lower it
|
||
via `lower_workspace_with_alloc`, skipping `monomorphise_workspace`.
|
||
Pre-migration this was fine — the fixtures used `(do io/print_int x)`,
|
||
a directly-recognised effect-op. Post-migration they use `(app print x)`
|
||
(polymorphic free fn), which requires mono to resolve to
|
||
`print__Int`. Without the mono pass, lowering errors with
|
||
`UnknownVar("print")`. The fix is identical at all six sites: replace
|
||
the home-rolled `desugar+lift` loop with a single call to
|
||
`ailang_check::monomorphise_workspace(&ws)?` and lower the
|
||
resulting `MonomorphisedWorkspace`. Sites to patch:
|
||
|
||
- `reuse_as_demo_under_rc_uses_inplace_rewrite` (line 1408)
|
||
- `alloc_rc_borrow_only_recursive_list_drop` (line 1628)
|
||
- `alloc_rc_partial_drop_skips_moved_keeps_wildcarded` (line 1710)
|
||
- `alloc_rc_own_param_dec_at_fn_return` (line 1800)
|
||
- `iter18e_drop_iterative_emits_worklist_body_no_self_recursion` (line 1936)
|
||
- `iter18e_no_annotation_keeps_recursive_drop_body` (line 2013)
|
||
|
||
- [ ] **Step 1: Migrate `ailang-check/src/lib.rs:3871-3877`**
|
||
|
||
The site is an undeclared-effect-diagnostic test. The op name
|
||
is incidental — the test asserts "calling an unknown effect-op
|
||
produces diagnostic X". Swap `op: "io/print_int"` → `op:
|
||
"io/print_str"` AND change the arg from an `Int` literal to a
|
||
`Str` literal so the swapped op type-checks. Then change the
|
||
expected diagnostic's `effect_op` field if it's pinned by name.
|
||
|
||
If the test is asserting "unknown effect-op", the spirit is
|
||
preserved by inventing a never-registered name like
|
||
`"io/print_unknown"` and the test continues to assert "diagnostic
|
||
produced". Use whichever shape is closest to the original intent
|
||
— inspect the test body to decide.
|
||
|
||
- [ ] **Step 2: Migrate `ailang-check/src/lib.rs:5047-5051`**
|
||
|
||
Tail-call-in-tail-position test. Same shape as Step 1; the op
|
||
is incidental. Swap to `io/print_str` + `Str` arg.
|
||
|
||
- [ ] **Step 3: Migrate `spec_drift.rs:64-71`**
|
||
|
||
JSON-shape drift test on `Term::Do`. The op identity is the
|
||
serialised payload's key, not the load-bearing assertion.
|
||
Swap to `io/print_str` + `Str` arg.
|
||
|
||
- [ ] **Step 4: Migrate `design_schema_drift.rs:87-94`**
|
||
|
||
Same shape as Step 3.
|
||
|
||
- [ ] **Step 5: Migrate `lex.rs:320, 328`**
|
||
|
||
`tokenize("+ == <= io/print_int std_list.map")` exercises that
|
||
the lexer treats `io/print_int` as a single identifier-with-slash
|
||
token. The op name is incidental; the assertion is on the token
|
||
shape. Swap to `io/print_str`. Update the `assert_eq!` vector
|
||
at line 328 to match.
|
||
|
||
- [ ] **Step 6: Migrate `prose/src/lib.rs:1585-1593`**
|
||
|
||
`do_renders_with_keyword_and_op` round-trip test. Asserts
|
||
`render_term(&t) == "do io/print_int(5)"`. Swap to
|
||
`render_term(&t) == "do io/print_str(\"5\")"` (with `Str` literal
|
||
arg) OR pick a different surviving op if `io/print_str` makes
|
||
the test awkward.
|
||
|
||
- [ ] **Step 7: `cargo test --workspace` green**
|
||
|
||
Run: `cargo test --workspace`
|
||
Expected: 0 failures. Task 5 closes the test-body migration set.
|
||
|
||
---
|
||
|
||
## Task 6: Documentation-comment touch-ups
|
||
|
||
**Files:**
|
||
- Modify: `crates/ailang-surface/src/lex.rs:18-20`
|
||
- Modify: `crates/ailang-surface/src/parse.rs:1377`
|
||
- Modify: `crates/ail/src/main.rs:30-31, 1419-1420`
|
||
- Modify: `runtime/str.c:117-118`
|
||
- Modify: `crates/ailang-core/specs/form_a.md:433-434`
|
||
|
||
- [ ] **Step 1: Swap `lex.rs:18-20` module-doc-comment**
|
||
|
||
Replace the `io/print_int` example with `io/print_str` in the
|
||
qualified-name-tokenisation explanation.
|
||
|
||
- [ ] **Step 2: Swap `parse.rs:1377` diagnostic example**
|
||
|
||
`expect_ident("effect op (e.g. `io/print_int`)")` → swap to
|
||
`io/print_str`.
|
||
|
||
- [ ] **Step 3: Swap `main.rs:30-31` and `:1419-1420`**
|
||
|
||
`main.rs:30-31`: `builtins` subcommand doc — list a single
|
||
`io/print_str` (drop the per-type ops).
|
||
`main.rs:1419-1420`: `walk_term` Do-arm format-string comment
|
||
— swap to `io/print_str`.
|
||
|
||
- [ ] **Step 4: Re-anchor `runtime/str.c:117-118`**
|
||
|
||
Original: `matches io/print_float's libc rendering for IEEE
|
||
consistency`. Rewrite to: `matches the same %g format used
|
||
throughout the runtime for Float text rendering` (or similar —
|
||
the anchor is the format, not the now-retired effect-op).
|
||
|
||
- [ ] **Step 5: Migrate `form_a.md:433-434`**
|
||
|
||
The surface-spec example shows `(do io/print_int n)`. Migrate
|
||
to `(app print n)` to advertise the post-iter canonical idiom.
|
||
|
||
- [ ] **Step 6: `cargo test --workspace` green**
|
||
|
||
Run: `cargo test --workspace`
|
||
Expected: 0 failures. (Doc-comment changes can break tests if
|
||
they're embedded in doc-tests; verify.)
|
||
|
||
---
|
||
|
||
## Task 7: DESIGN.md sweep
|
||
|
||
**Files:**
|
||
- Modify: `docs/DESIGN.md` lines 331, 1990-1992, 2024-2025, 2331,
|
||
2565-2577, 2679, 2699.
|
||
|
||
- [ ] **Step 1: Line 331 — Decision 11 example**
|
||
|
||
Original (excerpt): "names like `io/print_int`". Swap to
|
||
`io/print_str`.
|
||
|
||
- [ ] **Step 2: Lines 1990-1992 — Polymorphic print past-tense rewrite**
|
||
|
||
Original:
|
||
> Routing through `print` replaces the ad-hoc `io/print_int|bool|float`
|
||
> idiom for new code; retiring the per-type effect-ops is queued
|
||
> as a P2 follow-up.
|
||
|
||
Rewrite:
|
||
> Routing through `print` replaces the ad-hoc per-type print
|
||
> effect-ops; the `io/print_int`, `io/print_bool`, `io/print_float`
|
||
> primitives were retired 2026-05-14 in iter rpe.1, leaving
|
||
> `io/print_str` as the only surviving direct-output effect-op.
|
||
|
||
- [ ] **Step 3: Lines 2024-2025 — Heap-Str primitives output sentence**
|
||
|
||
Original:
|
||
> Primitive output goes through `io/print_int` / `io/print_bool` /
|
||
> `io/print_str` directly.
|
||
|
||
Rewrite:
|
||
> Primitive output for `Str` values goes through `io/print_str`
|
||
> directly; values of other primitive types route through the
|
||
> polymorphic `print` helper (§"Polymorphic print"), which feeds
|
||
> the heap-Str result of `show x` into `io/print_str`. The
|
||
> per-type effect-ops `io/print_int`, `io/print_bool`,
|
||
> `io/print_float` were retired in iter rpe.1 (2026-05-14).
|
||
|
||
- [ ] **Step 4: Line 2331 — Effect-op invocation comment example**
|
||
|
||
Original: `// Effect-op invocation. \`op\` is "<eff>/<op>"
|
||
(e.g. "io/print_int").`
|
||
Rewrite: `// Effect-op invocation. \`op\` is "<eff>/<op>"
|
||
(e.g. "io/print_str").`
|
||
|
||
- [ ] **Step 5: Lines 2565-2577 — Float NaN-rendering paragraph**
|
||
|
||
The paragraph anchors NaN textual semantics on `io/print_float`
|
||
and contrasts with `do print x`. After iter rpe.1, both ends of
|
||
that contrast reach `%g` via `float_to_str` + `io/print_str`.
|
||
|
||
Rewrite the paragraph to anchor on `float_to_str` (the canonical
|
||
heap-Str producer for Float) rather than the now-retired
|
||
`io/print_float`. The `%g` semantics are unchanged; this is a
|
||
re-anchoring, not a semantic change.
|
||
|
||
- [ ] **Step 6: Line 2679 — "What is supported (effect ops)"**
|
||
|
||
Original: `(\`io/print_int\`, \`io/print_bool\`, \`io/print_str\`)`
|
||
Rewrite: `(\`io/print_str\`)`. Add a sentence: `Per-type print
|
||
ops (\`io/print_int\`, \`io/print_bool\`, \`io/print_float\`)
|
||
were retired in iter rpe.1; polymorphic \`print\` is the canonical
|
||
output path.`
|
||
|
||
- [ ] **Step 7: Line 2699 — "(IO effect ops)" list**
|
||
|
||
Original: `the IO effect ops (\`io/print_int|bool|str|float\`)`
|
||
Rewrite: `the IO effect op \`io/print_str\``.
|
||
|
||
- [ ] **Step 8: `cargo test --workspace` green**
|
||
|
||
Run: `cargo test --workspace`
|
||
Expected: 0 failures. The `design_schema_drift` test (mentioned
|
||
in Task 5) reads DESIGN.md; verify it still passes after these
|
||
swaps.
|
||
|
||
---
|
||
|
||
## Task 8: Incidental E2E test-comment touch-ups
|
||
|
||
**Files:**
|
||
- Modify: `crates/ail/tests/eq_ord_e2e.rs:66, 76`
|
||
- Modify: `crates/ail/tests/e2e.rs:2674-2683`
|
||
- Modify: `crates/ail/tests/floats_e2e.rs:7`
|
||
|
||
- [ ] **Step 1: Update `eq_ord_e2e.rs` comments**
|
||
|
||
Lines 66 and 76 reference `io/print_int` emitting `<int>\n`.
|
||
The asserted stdout is unchanged (post-migration `(app print x)`
|
||
at `x : Int` also emits `<int>\n`). Update the comments to
|
||
reference `print` semantics. The test body itself does not
|
||
change (it was migrated in Task 2 via the fixture migration).
|
||
|
||
- [ ] **Step 2: Update `e2e.rs:2674-2683` (int_arg_to_effect_op test)**
|
||
|
||
The test pins the RC-tracking property "Int args passed to an
|
||
effect-op are not consumed". The fixture `int_to_print_int_borrow.ail`
|
||
migrates in Task 2. The test name and doc-comment lose
|
||
grounding once `io/print_int` is gone.
|
||
|
||
Two options:
|
||
- Update the doc-comment to explain that the test now pins
|
||
"Int args passed to `print` (the polymorphic helper) are not
|
||
consumed" — the property is the same.
|
||
- Rename the test if Boss prefers (e.g.
|
||
`int_arg_to_print_does_not_rc_track`). Renaming changes a
|
||
test identity; lean toward keeping the name and updating the
|
||
doc.
|
||
|
||
Pick keep-the-name-and-update-the-doc. The test pins the
|
||
property, not the identifier.
|
||
|
||
- [ ] **Step 3: Update `floats_e2e.rs:7` module doc-comment**
|
||
|
||
Lists `io/print_float` as a covered op. Swap to `print` / Show
|
||
Float.
|
||
|
||
- [ ] **Step 4: `cargo test --workspace` green**
|
||
|
||
Run: `cargo test --workspace`
|
||
Expected: 0 failures.
|
||
|
||
---
|
||
|
||
## Task 9: Bench run + conditional ratification
|
||
|
||
**Files:**
|
||
- Conditional Modify: `bench/baseline.json`, `bench/baseline_cross_lang.json`,
|
||
`bench/baseline_compile.json`.
|
||
|
||
- [ ] **Step 1: Run `bench/check.py`**
|
||
|
||
Run: `python3 bench/check.py`
|
||
Expected exit code: `0` (compile-time-symmetric). If `1`
|
||
(drift), investigate before ratifying — the spec did not
|
||
anticipate a check-perf change.
|
||
|
||
- [ ] **Step 2: Run `bench/compile_check.py`**
|
||
|
||
Run: `python3 bench/compile_check.py`
|
||
Expected exit code: `0`. Same rationale.
|
||
|
||
- [ ] **Step 3: Run `bench/cross_lang.py`**
|
||
|
||
Run: `python3 bench/cross_lang.py`
|
||
Expected exit code: `0` OR `1` (legitimate drift if any bench
|
||
fixture's hot loop now allocates a heap-Str per print).
|
||
|
||
- [ ] **Step 4: Conditional ratification — if any script returned `1`**
|
||
|
||
For each script that returned `1`:
|
||
1. Inspect the per-fixture report to identify which fixtures
|
||
moved.
|
||
2. Run `python3 <script> --update-baseline` to write the new
|
||
baseline.
|
||
3. Note in the per-iter journal: which baseline was updated,
|
||
the magnitude of the drift, and the language reason
|
||
(post-iter `print` heap-alloc-per-iteration is the canonical
|
||
output path; old baseline reflected the retired direct-printf
|
||
path).
|
||
|
||
- [ ] **Step 5: All scripts green at final state**
|
||
|
||
After any ratification, re-run all three scripts; all must
|
||
return `0`.
|
||
|
||
---
|
||
|
||
## Task 10: Roadmap strike + per-iter journal + stats
|
||
|
||
**Files:**
|
||
- Modify: `docs/roadmap.md` P2 lines 80-90 (entry strike).
|
||
- Create: `docs/journals/2026-05-14-iter-rpe.1.md` (per-iter
|
||
journal).
|
||
- Create: `bench/orchestrator-stats/2026-05-14-iter-rpe.1.json`
|
||
(stats file per `implement/SKILL.md`).
|
||
|
||
- [ ] **Step 1: Strike the roadmap entry**
|
||
|
||
In `docs/roadmap.md` lines 80-90 (the
|
||
`Retire io/print_int / io/print_bool / io/print_float` P2
|
||
entry), change `- [ ]` → `- [x]` and append a one-line tail
|
||
naming this iter:
|
||
|
||
```markdown
|
||
- [x] **\[milestone\]** Retire `io/print_int` / `io/print_bool` /
|
||
`io/print_float` effect-ops + migrate example corpus to `print`.
|
||
Shipped 2026-05-14 as iter rpe.1.
|
||
- context: per-iter journal `docs/journals/2026-05-14-iter-rpe.1.md`.
|
||
```
|
||
|
||
- [ ] **Step 2: Write the per-iter journal**
|
||
|
||
Standard journal shape: `# iter rpe.1 — Retire per-type print
|
||
effect-ops`, sections: Summary / Decisions / Files touched /
|
||
Verification / Concerns / Known debt.
|
||
|
||
The journal records the §C4 (a) decision (migrate all,
|
||
including bench-latency fixtures) and any bench-baseline
|
||
ratifications from Task 9.
|
||
|
||
- [ ] **Step 3: Write the stats file**
|
||
|
||
JSON file at `bench/orchestrator-stats/2026-05-14-iter-rpe.1.json`
|
||
recording: tasks completed, retries per task, blocked count,
|
||
duration, `cargo test --workspace` final count.
|
||
|
||
- [ ] **Step 4: Final `cargo test --workspace` + clippy + doc**
|
||
|
||
Run, in parallel:
|
||
- `cargo test --workspace`
|
||
- `cargo clippy --workspace --all-targets -- -D warnings`
|
||
- `cargo doc --workspace --no-deps 2>&1 | grep -c '^warning:'`
|
||
|
||
Expected: tests green; clippy zero (the iter-clippy-sweep
|
||
baseline holds); doc zero (the rustdoc-sweep baseline holds).
|
||
|
||
Any clippy / rustdoc regression introduced by this iter is
|
||
itself a Task-N debt; fix in-task before declaring DONE.
|
||
|
||
---
|
||
|
||
## Self-review
|
||
|
||
1. **Spec coverage:** every §C/§D/§G/§I-named element has a
|
||
task — RED test (Task 1, spec §G2), corpus migration (Task 2,
|
||
spec §C1 + §D1), prose snapshot regen (Task 3, §D1 extension
|
||
via Errata #2), four-site deletion (Task 4, spec §C2 + Errata
|
||
#3), test-body migration (Task 5, Errata #4), doc-comment
|
||
touch-ups (Task 6, Errata #4), DESIGN.md sweep (Task 7, spec
|
||
§C3), E2E-comment touch-ups (Task 8, partial Errata #4), bench
|
||
ratification (Task 9, spec §C4 (a) + §G3), roadmap strike +
|
||
journal + stats (Task 10, spec §I task 7).
|
||
|
||
2. **Placeholder scan:** zero "TBD" / "TODO" / "similar to Task".
|
||
|
||
3. **Type consistency:** `env.effect_ops` (the BTreeMap on `Env`),
|
||
`lower_effect_op` (the codegen dispatcher), `build_check_env`
|
||
(entry point used by the RED test) all match real symbol names
|
||
in the workspace.
|
||
|
||
4. **Step granularity:** every numbered step is 2–5 minutes.
|
||
Task 2 Step 2 is "92 file edits" but they are mechanical and
|
||
the LLM can stream them with one Edit-tool call per file.
|
||
|
||
5. **No commit steps:** zero `git commit` calls in any task
|
||
template. The orchestrator-agent (or implement-skill) leaves
|
||
the working tree dirty; Boss commits.
|