Files
AILang/docs/roadmap.md
T
Brummel 7580d434f0 iter docs-honesty-lint.1: canonical docs present-tense-honest + wrap-robust pin + Sweep 5
Strips Wunschdenken (forward intent stated as fact) and non-citation
post-mortem / doc-archaeology from docs/DESIGN.md (14 corrections incl.
1 Step-14 procedural-catch-all site the spec's illustrative regex would
have missed) + docs/PROSE_ROUNDTRIP.md; genuine forward intent (LLM
tool-use / MCP / LSP) relocated to roadmap P3. Codifies the
tense+modality discriminator as a present-tense DESIGN.md meta-
subsection. Guards two ways: wrap-robust enumerated absent/present pin
(crates/ailang-core/tests/docs_honesty_pin.rs, norm() whitespace-
collapse helper — structurally discharges the recurring grep/contains
line-wrap failure family) + wrap-robust advisory Sweep 5 in
architect_sweeps.sh (contiguous-fragment regex) with full sweep-count
lockstep + new ailang-architect.md "DESIGN.md honesty drift" bullet.
KEEPs preserved (Diverge-reserved, regions-rejected, self-labelled
tiebreaker). No language/checker/codegen/runtime change — sole crates/
change is the additive pin; ail check/run/build byte-unchanged.
Workspace 609/0 (delta +4 pin), sentinel trio green, build clean.
2026-05-18 12:30:03 +02:00

663 lines
38 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# AILang Roadmap
Priority-ordered list of upcoming work — milestones, features, todos,
and ideas. The orchestrator maintains this file. The user can request
additions; the orchestrator chooses what to remove or reprioritise as
work progresses.
## Conventions
- One checkbox per entry. `- [ ]` is open; `- [~]` is in progress
(work has started — a plan exists, a branch is open, or commits are
landing); `- [x]` is done. A finished entry may stay briefly for
context, then is removed (with a one-line mirror in
`docs/journals/`).
- Each entry is tagged by **kind** and lives under a **priority**
bucket:
- **\[milestone\]** — big chunk that will get a `docs/specs/<milestone>.md`.
- **\[feature\]** — smaller addition inside a milestone, no full
spec.
- **\[todo\]** — concrete task that can run without a brainstorm
(cleanup, doc fix, mechanical refactor, test backfill).
- **\[idea\]** — not yet decision-ready, no commitment.
- Optional `depends on:` line names another entry that has to land
first.
- Optional `context:` line points to the journal entry (per-iter
file under `docs/journals/` or, for pre-2026-05-11 entries, the
archived `docs/journal-archive.md`) where the rationale lives.
The roadmap is intentionally terse; rationale stays in the
journals.
- Priority buckets:
- **P0** — in flight. Spec or plan already exists.
- **P1** — next up. Decision made; not yet started.
- **P2** — medium-term. Decided in principle, scheduled later.
- **P3** — ideas. No commitment; may be cut.
## P0 — In flight
- [x] **\[milestone\]** Remove `mut` / `var` / `assign` — CLOSED
2026-05-18. The local-mutable-state construct removed entirely
and atomically (`Term::Mut`/`Term::Assign`/`MutVar`, the 3 Form-A
keywords, the 4 `mut` `CheckError` variants, `mut_scope_stack`,
every exhaustive `Term` arm across 17 src files) in lockstep with
DESIGN.md + fixtures + drift trio + carve-out + roadmap; `loop`/
`recur` + `let`/`if` are the surviving forms. Justified by the
feature-acceptance criterion applied inverted (clause 2 redundant,
clause 3 IS the iterated-mutable-state bug class). Approach A: no
deprecation window, JSON tags fail closed; shared codegen alloca
machinery kept (loop reuses it, renamed `binder_allocas`) + the
`Term::Lam` escape-guard loop half byte-equivalent. spec/plan/
iter (`d1ad50e`/`f355899`/`07f0802`), milestone-close `audit`
clean (architect `[high]` form_a.md fixed in `.tidy` `4837871`;
bench causally exonerated — HEAD vs pre-milestone bench binaries
`cmp`-byte-identical, no ratify), `fieldtest` clean (4 fresh
programs, all worked first try without mutable locals — the
removal thesis empirically confirmed). Decoupled from
Stateful-islands. Stays here briefly for context; remove once
stale (full record in `docs/journals/INDEX.md`).
- context: `docs/specs/2026-05-18-remove-mut-var-assign.md`;
`docs/specs/2026-05-18-fieldtest-remove-mut-var-assign.md`;
`docs/journals/2026-05-18-iter-remove-mut-var-assign.1.md`;
`docs/journals/2026-05-18-audit-remove-mut-var-assign.md`
- [x] **\[milestone\]** Prose `loop` binders — projection redesign —
CLOSED 2026-05-18. Form-B prose now renders loop binders as a
parenthesised init-list on the keyword (`loop(acc = 0, i = 1)
{ … }`, positionally isomorphic to `recur(...)`) instead of bare
`name = init;` statements inside the body block (which misread as
C/Rust re-init-every-iteration). Single-iteration, projection-only:
one `ailang-prose` `write_term` `Term::Loop` arm + 2 committed
byte-equality `.prose.txt` snapshots/tests; loop/recur AST, Form A,
JSON-AST, typecheck, codegen, and the Form-A↔JSON round-trip
invariant all byte-unchanged. brainstorm→plan→implement
(`c657e74`/`6533134`/`c9355d7`); milestone-close `audit` clean
(architect zero drift; bench carry-on — sole firing is the tracked
P2 `*.bump_s` environmental staleness, latency-tail proven sampling
noise by re-run); no fieldtest (projection-only, no authoring-
surface change). Stays here briefly for context; remove once stale.
- context: `docs/specs/2026-05-18-prose-loop-binders.md`;
`docs/journals/2026-05-18-audit-prose-loop-binders.md`; surfaced
from the loop/recur prose review (2026-05-18 chat).
- [x] **\[milestone\]** Standalone `loop` / `recur` — CLOSED 2026-05-18.
Strictly-additive strict-iteration surface, `recur` tail-position-only,
no totality claim. Shipped: iter 1 `a179ec3` (additive AST nodes),
iter 2 `1566ce0` (typecheck), iter 3 `edd2558` (codegen + run-to-value
E2E), tidy `39380d3`/`2ee9794` (lambda-captures-loop-binder rejected
at check). Milestone-close `audit` clean (architect drift resolved,
bench pristine 25/0 carry-on); `fieldtest` 2026-05-18 clean on all
four milestone axes (0 bugs; diagnostics point-exact+self-fixing,
recur-tail through match/let, loop-as-value-subexpr byte-stable,
no-termination exact). Two orthogonal non-blocking findings routed
to P2 todos (niladic `(app f)` — re-confirms mut-local F3; module
`(doc)` diagnostic hint). Stays here briefly for context; remove
once stale (full record in `docs/journals/INDEX.md`).
- context: `docs/specs/2026-05-17-loop-recur.md`;
`docs/specs/2026-05-18-fieldtest-loop-recur.md`; principles entry
`docs/specs/2026-05-17-llm-surface-discipline.md` §6.2.
- [~] **\[milestone\]** DESIGN.md / docs honesty lint — spec'd +
approved 2026-05-18 (`aff25cd`), open as P0 in-flight; dependency
(Remove `mut`/`var`/`assign`) closed 2026-05-18; next `planner`.
Sweep the canonical docs so they mirror ONLY the actual current
state, present-tense. Single iteration (mirror effect-doc-honesty):
a two-pronged tense+modality discriminator codified present-tense
as a DESIGN.md meta-subsection (Approach A); procedural enumeration
= discriminator applied to the full `architect_sweeps.sh` + new
Sweep 5 output (not a frozen list — the sweep already surfaces
post-mortem sites the architect waved through as legitimate
quotes); anti-regrowth = enumerated `docs_honesty_pin.rs` (hard
cargo-test gate, incl. protected-exception present-anchors against
over-correction) + Sweep 5 + `ailang-architect.md` "DESIGN.md
honesty drift" bullet (advisory standing forward check). No
language/checker/codegen/runtime change.
- context: `docs/specs/2026-05-18-docs-honesty-lint.md`
(grounding-check PASS 10/10); 2026-05-18 user directive;
reserved-vs-vapour precedent
`docs/journals/2026-05-16-iter-effect-doc-honesty.md`
## P1 — Next
- [x] **\[milestone\]** Heap-`Str` ABI — runtime infrastructure for
malloc-backed, refcounted `Str` values alongside the existing
static `@.str_*` globals. Today the `Str` path is static-only
(DESIGN.md §"Float semantics" notes this explicitly), so any
primitive that needs to produce a `Str` at runtime — `int_to_str`,
`float_to_str` (currently type-installed but `CodegenError::Internal`
on call), eventual `Show.show`, future `++` on strings — cannot
ship. Scope: pick the representation (likely a `{rc_header, len,
bytes…}` slab consistent with the rest of the RC runtime), teach
codegen to accept both static and heap `Str` at the same ABI slot,
wire RC `inc`/`drop`/`clone`/`compare`/`eq` on the heap form, and
ship `int_to_str` + `float_to_str` as the first two callers so the
ABI gets exercised end-to-end. Unblocks Show + print rewire below.
- context: DESIGN.md §"Float semantics" (`float_to_str` is type-
installed, codegen-deferred); spec 2026-05-11-23-eq-ord-prelude
§"Show. Defers behind a heap-Str-ABI milestone"; spec
2026-05-10-fieldtest-floats §F4 ("dynamic Str allocation in the
runtime"); spec 2026-05-09-22-typeclasses §22b.4b ("Show#Int
needs an `int_to_str` primitive returning heap-allocated Str").
- [x] **\[milestone\]** Post-22 Prelude — Show + print rewire — shipped
2026-05-13 as iters 24.1 (heap-Str runtime + codegen for `bool_to_str`
+ `str_clone`, f38bad8), 24.2 (prelude `class Show` + four primitive
instances Int/Bool/Str/Float + 22b TShow/tshow migration), and 24.3
(polymorphic `fn print : forall a. Show a => (a borrow) -> () !IO`
+ positive 4-prim E2E + user-ADT E2E + Show-aware NoInstance
diagnostic + DESIGN.md amendments to §"Prelude (built-in) classes"
and §"Float semantics"). The `MethodNameCollision` workaround that
blocked the original spec retired in mq.3 (2026-05-13); spec
`docs/specs/2026-05-13-24-show-print.md` re-derived 24.2 + 24.3
against the post-mq architecture.
- context: spec `docs/specs/2026-05-13-24-show-print.md`; per-iter
journals 2026-05-12-iter-24.1, 2026-05-13-iter-24.2, 2026-05-13-iter-24.3.
## P2 — Medium-term
- [ ] **\[milestone\]** Iteration-totality story — structural +
Int-bounded total recursion with *enforced* non-negativity.
AILang's iteration story stays as-is (structural / tail recursion;
`tail-app` intact). The genuine ambition — make `f(n-1)`-family
recursion (incl. branching tree builders) total *by construction*
with the non-negative-entry precondition **enforced**, not merely
documented — is deferred here because doing it without a purity-
pillar concession requires refinement/`Nat` type machinery the
language has not built (Decision 4 keeps refinements opaque, no
SMT). Not abandoned; correctly sequenced after the type machinery.
- depends on: a future `Nat`/refinement-types milestone (no spec
yet).
- context: `docs/specs/2026-05-16-iteration-discipline-revert.md`
(why the 2026-05 attempt was reverted) and
`docs/journals/2026-05-15-iter-it.3.md` (the branching-builder
counter-example that surfaced the gap).
- [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`.
- [x] **\[todo\]** Author `examples/prelude.ail` alongside
`examples/prelude.ail.json`. (Satisfied 2026-05-13 by iter
form-a.0 — `examples/prelude.ail` rendered via `ail render`,
116 lines / 6386 bytes, round-trip-CI green.)
- [x] **\[milestone\]** Form-A as the default authoring surface for
examples and docs. (Closed 2026-05-13 by iter form-a.1.) Render every `examples/*.ail.json` to its
`.ail` sibling via `ail render`, **delete the now-redundant
`.ail.json`**, and regenerate the JSON-AST per `ail parse` at
build / test time. Same for inline JSON-AST blocks in `docs/`
markdown (~7 in DESIGN.md plus ~29 other md files) — convert to
Form-A snippets where the snippet's purpose is to show "what the
language looks like", not "what the schema is".
After this milestone the working tree contains exactly one
representation per program: the `.ail` source. The JSON-AST is a
build artefact, not a checked-in twin. Tests and benches that
currently glob `*.ail.json` either parse-then-consume or are
rewired to point at `.ail` directly. The round-trip invariant
flips role: today it gates that the two forms agree; afterwards
it gates that `parse` is deterministic.
Carve-outs that MUST stay JSON-AST (no Form-A counterpart, and
no `.ail` sibling shall be created — these are the only seven
files that remain `.ail.json`-only post-milestone):
- Fixtures that test canonical-form rejection — Form A would
reject them at parse, defeating the test:
`test_ct1_bare_xmod_rejected.ail.json`,
`test_ct1_qualified_class_rejected.ail.json`,
`test_ct1_bad_qualifier.ail.json`, `broken_unbound.ail.json`,
`test_22b2_invalid_superclass_param.ail.json`,
`test_22b2_kind_mismatch.ail.json`,
`test_22b2_unbound_constraint_var.ail.json`.
- DESIGN.md schema documentation blocks where the JSON-AST shape
*is* the point (Decision 11, ParamMode, canonical-form
invariants).
- Any other case where the structured form is the artefact under
discussion, not a vehicle for a program.
Touches CLAUDE.md ("source of truth is structured data") — the
language doctrine doesn't change (JSON-AST is still the canonical
hashable form), but the *authoring* doctrine does: authors write
`.ail`, the build derives JSON-AST. Sentence in CLAUDE.md needs
rewording in the same milestone.
Mechanical for the bulk; per-fixture judgement call only for the
carve-out list. Run as one milestone so the corpus flip-over
happens at a single, audit-gated point.
- context: post-Form-A-as-canonical-authoring corollary of the
`examples/prelude.ail` entry above. Generalises the same idea
to the rest of the corpus and follows through on the cross-
model authoring data (textual form cheaper, more first-try
hits) by treating Form A as the privileged surface in the
working tree, not just in flavour text.
- [ ] **\[feature\]** Operator routing through `Eq` / `Ord``==`,
`<` etc. resolved via the typeclass instead of the built-in
primitive comparators. No commitment; gated on bench re-baselining
to make sure the indirection doesn't tank latency.
- context: JOURNAL 2026-05-09
- depends on: Post-22 Prelude — Eq/Ord (shipped 23.5)
- [x] **\[todo\]** `types` / `ctor_index` overlay shape question —
decide whether the env's two parallel ctor maps should collapse
into one overlay, or stay split. Surfaced during the
env-construction unify audit.
- context: JOURNAL 2026-05-10 ("Audit close: env-construction unify"); closed by iter ctt.1 — DESIGN.md §"Env construction" anchors the split decision.
- [x] **\[todo\]** CLI human-mode diagnostic surface for `WorkspaceLoadError`.
Shipped 2026-05-14 as iter cli-diag-human — a new `load_workspace_human`
helper in `crates/ail/src/main.rs` routes 9 non-JSON
`ailang_surface::load_workspace(&path)?` call sites through
`workspace_error_to_diagnostic`, so the bracketed `[code]` prefix is
preserved across `ail check`, `build`, `run`, `emit-ir`, `prose`,
`describe`, `deps`, `diff`, `manifest`.
- context: per-iter journal `docs/journals/2026-05-14-iter-cli-diag-human.md`.
- [x] **\[todo\]** Retire dead `KindMismatch` arm — `validate_classdefs`'s
`walk_kind_mismatch` path is structurally unreachable through
well-formed schema post-ct.1 (the canonical-form validator catches
the malformed `Type::Con { name: param }` shape earlier). The
enum variant + `walk_kind_mismatch` helper stay as dead-but-defensive
code; a future tidy can delete both.
- context: JOURNAL 2026-05-11 ("Iteration ct.1"); closed by iter ctt.3 — variant + helper + dispatch + Display arm all deleted; canonical-form-rejection test stays green asserting `BareCrossModuleTypeRef`.
- [x] **\[todo\]** Re-key `Registry.type_def_module` to handle
bare-name-collision-across-modules — `BTreeMap<String, String>` keyed
by bare type name silently overwrites when two modules each define
`type Foo`; `normalize_type_for_registry` would then collapse `M.Foo`
and `N.Foo` to whichever insert won. Acceptable for current corpus
(distinct bare type names across modules), but the proper fix is to
key by `(owning_module, bare_name) → defining_module`.
- context: JOURNAL 2026-05-11 ("Iteration ct.1") — flagged by ct.1.5a quality reviewer.
- [ ] **\[feature\]** 22c — typeclass corpus expansion. User-defined
classes beyond the prelude four; multi-parameter classes; superclass
chains; richer instance bodies. Deferred from milestone 22.
- context: JOURNAL 2026-05-09
- [x] **\[milestone\]** Module-qualified class names + type-driven
method dispatch — retired the `MethodNameCollision` workaround;
shipped 2026-05-13 as iters mq.1 (canonical-form extension for
class-ref fields + workspace-internal qualification), mq.2 (type-
driven dispatch mechanism installed: `method_to_candidate_classes`
inverse index, multi-candidate `ResidualConstraint`,
`resolve_method_dispatch` 5-step rule, `AmbiguousMethodResolution` +
`UnknownClass` diagnostics), and mq.3 (retirement + multi-class E2E
+ `class-method-shadowed-by-fn` warning + DESIGN.md sync). Two
libraries can now each declare `class Eq` with their own `eq`;
cross-class method ambiguity is resolved at the call site via
type-driven dispatch with `<module>.<Class>.<method>` as the
disambiguation form.
- context: `docs/specs/2026-05-10-canonical-type-names.md` "Out of
scope: Class names" — the workaround was named there so it
stayed visible until this milestone retired it.
- [ ] **\[todo\]** Boehm full retirement — remove the transitional
Boehm GC path now that RC + uniqueness is the canonical memory
story.
- context: JOURNAL pre-22, "Boehm transitional"
- [ ] **\[feature\]** Closure-pair slab / pool — codegen tweak to
pool the env+code closure pairs instead of one-shot heap allocs.
Bench-gated.
- [ ] **\[todo\]** `FnDef::synthetic` flag — formalise the
monomorphiser-emitted FnDefs so downstream passes can tell
user-authored from synthesised at a glance. Currently inferred from
symbol naming.
- [ ] **\[todo\]** 21'h iteration — final 21' carry-over (latency
methodology pass). Numbering kept for continuity with the 21' arc.
- [x] **\[todo\]** DESIGN.md effect-prose is fiction — standalone
documentation-honesty tidy. Shipped 2026-05-16 as iter
effect-doc-honesty (a29700c): the three false effect-system claims
(row-polymorphic `![IO | r]`; `IO`+`Diverge` both wired up;
`Term::Do` "effect-handler table at link time") reconciled to the
real flat-closed-set / IO-only / `IndexMap`+codegen-`match`
mechanism, satellite lockstep done, guarded by a 4-test
doc-presence pin. No language/checker/codegen change.
- context: per-iter journal
`docs/journals/2026-05-16-iter-effect-doc-honesty.md`.
- [ ] **\[todo\]** `io/print_float` always-emit-`.0` — surface
printer always emits `.` or `e/E` so re-lex routes to Float;
the runtime printer (`printf("%g\n", v)`) doesn't, so `2.0`
prints as `2` (Int-shaped). Asymmetric. Either switch the
runtime path to a `.0`-fallback printer (matching surface) or
document the `%g` contract in DESIGN.md §"Float semantics" so
the LLM-author knows `io/print_float`'s output is for-humans
not round-trip.
- context: `docs/specs/2026-05-10-fieldtest-floats.md` finding F1.
- [ ] **\[todo\]** Rustdoc warning sweep — `cargo doc --no-deps`
reports 16 pre-existing warnings (15 in `ailang-check`, 1 in
`ailang-core`: private-item links from public doc, unresolved
intra-crate links). All predate the design-md-consolidation
milestone; treat as a one-off sweep.
- context: JOURNAL 2026-05-10 ("Audit close: design-md-consolidation").
- [ ] **\[todo\]** `ailang-plan-recon` site-undercount countermeasure
(P2, **escalated** — now a structural recon-contract defect, no
longer a single-milestone curiosity) — the recon agent hand-lists
the change blast radius and has under-counted it **four times across
two milestones, through a different site type each time**:
loop-recur.1 walker arms, loop-recur.2 cross-module `synth` callers,
loop-recur.tidy implicit, and now remove-mut-var-assign.1 (in-source
`mod tests` fns + a drift-pin fn + 5 orphaned `.ail.json` carve-outs
+ a non-enumerated `codegen_import_map_fallback_pin.rs` E0599) **plus
a spec-named doc the recon was told did not exist**
(`crates/ailang-core/specs/form_a.md` — the spec listed it for
deletion; a Boss plan-time grep checked only `docs/form_a.md`,
concluded "no such file", and propagated that into the recon brief;
caught only at milestone-close audit). Two distinct gaps: (a) the
compile-driven sweep catches exhaustive-`match`/caller misses but
**not** non-compile-checked sites (docs, fixtures, drift pins) —
those need a separate "every spec-named path is verified to exist
and is handled" cross-check; (b) a recon brief must never inherit an
unverified "X does not exist" — existence claims feeding a recon are
themselves load-bearing and must be tree-wide, not dir-scoped.
Tighten `skills/planner/agents/ailang-plan-recon.md`: for any
signature-change / enum-variant / removal scope the recon REPORTS
the compile-driven enumeration as the authoritative code-site set
(hand-list advisory) AND emits a spec-named-path existence table
(every path the spec names, `ls`-verified, with its handling task).
No language change; an agent-definition discipline fix.
- context: loop/recur close audit (architect `[low]`, 2026-05-18) +
remove-mut-var-assign close audit (architect `[high]` form_a.md +
`[medium]` process-drift, 2026-05-18); pairs with planner Step-5
items 7+8 (those scrub the *plan*; this scrubs the *recon* and the
*recon brief*).
- [ ] **\[todo\]** `design_schema_drift.rs` fidelity widening —
current test checks anchor *presence* anywhere in DESIGN.md;
the audit found that `[high]` schema gaps in §"Data model"
are invisible because anchors live in Decision 11 instead.
Constrain the test to scan only §"Data model" + ParamMode
block, or extract JSON-schema blocks into a machine-readable
file the test consumes.
- context: JOURNAL 2026-05-10 ("Audit close").
- [ ] **\[todo\]** Split `BadCrossModuleTypeRef` into two diagnostics —
the current single shape collapses unknown-owner and
known-owner / unknown-type-in-owner into one message. The two
cases suggest different fixes (add `(import <owner>)` vs. fix the
type name). In the known-owner branch, list the owner's available
type defs as candidates the way `bare-cross-module-type-ref`
lists candidates from imports.
- context: fieldtest 2026-05-11 — `examples/ct_3*.ail` exhibits both branches with identical-shape diagnostics.
- [ ] **\[todo\]** Zero-arg `(app f)` rejected at parse — the Form-A
parser refuses an application with an empty argument list, so a
nullary call has no surface form. Surfaced by the mut-local
fieldtest (F3) AND **independently re-confirmed by the loop/recur
fieldtest** (2026-05-18, finding spec_gap): the maximally-natural
infinite-event-loop shape is a niladic `run_forever : fn() -> Int`
called `(app run_forever)`, which dies at parse before reaching
loop/recur semantics. Two independent fieldtests hitting the same
gap raises the priority signal. Decide: accept `(app f)` as the
nullary-call surface, or ratify in DESIGN.md that nullary functions
are expressed differently (and say how). DESIGN.md's `Term::App`
`args:[Term...]` states no minimum, so the surface "expected at
least one argument" rule is unbacked by spec — a genuine design
fork, deliberately NOT auto-ratified under autonomous orchestration.
Not a blocker; no current corpus program needs it, but an LLM
author reaches for it.
- context: `docs/specs/2026-05-15-fieldtest-mut-local.md` finding F3;
`docs/specs/2026-05-18-fieldtest-loop-recur.md` spec_gap.
- [ ] **\[todo\]** Module-level `(doc …)` diagnostic omits where `doc`
belongs — a `(module NAME (doc "…") (fn …))` is correctly rejected
(`unknown def head \`doc\``) but the message lists valid def heads
without saying doc strings attach *inside* `fn`/`data` defs; an
author can read it and conclude doc strings are unsupported. One-line
tidy: append a hint like "(doc strings attach inside `fn`/`data`
defs, not at module level)". Low cost; verbose-diagnostic-philosophy
gap, not a bug.
- context: `docs/specs/2026-05-18-fieldtest-loop-recur.md` friction.
- [ ] **\[todo\]** Workspace search beyond entry-module's directory —
`load_workspace` only finds sibling `.ail.json` files in the same
directory as the entry module, so any consumer of prelude/std in a
subdirectory has no way to resolve cross-module imports. Add either
a `--workspace-root` flag on `ail check` / `ail build` / `ail run`,
or upward-search from the entry module's directory. Alternatively
ratify the flat-workspace assumption in DESIGN.md if intentional.
- context: fieldtest 2026-05-11 — fieldtest fixtures could not be
placed under `examples/fieldtest/` because of this; predates the
canonical-type-names milestone but surfaces every time.
- [ ] **\[todo\]** `*.bump_s` throughput baseline is stale vs current
hardware — `bench/check.py`'s `throughput.*.bump_s` family (the
fastest, most jitter-prone metrics) reads ~+513% over
`bench/baseline.json` on the current machine. Localised at the
iteration-discipline-revert audit (2026-05-16): an interleaved
3×60-run measurement of `bench_list_sum` bump_s built from the
byte-oracle commit `1ff7e81` shows the *same* ~+11% elevation as
HEAD, and the two bump binaries are `cmp`-identical — so this is
environmental drift relative to the 2026-05-09 baseline-capture
machine state, **not** a codegen regression. Re-capture the full
`*.bump_s` set on current hardware from a known-clean commit and
recalibrate the `bump_s` baseline+tolerance pair (or widen the
tolerance) so the noise floor stops tripping `check.py` exit 1.
Pure bench-harness recalibration; no language change.
- [ ] **\[todo\]** `check.py` RC-latency `*.max_us` is a structural
false-positive at `-n 5` — **distinct** from the `*.bump_s`
staleness above (different metric family, different root cause).
`latency.{explicit,implicit}_at_rc.max_us` is the single worst of
~5000 samples over only 5 runs, dominated by OS scheduling / THP /
IRQ jitter on a shared host, not allocator behaviour. It has fired
a false `REGRESSION` on **three consecutive no-runtime-change
milestones** (iteration-discipline-revert, prose-loop-binders,
remove-mut-var-assign) and vanished on identical-code re-run every
time (remove-mut-var-assign close: HEAD vs `48e7774` bench binaries
`cmp`-byte-identical, `explicit_at_rc.max_us` collapsed
+108%→-2.30% ok by rerun3) — a ~3-for-3 false-positive rate on null
changes, while the median/p99/p99.9 of the same benchmarks held
across all re-runs. Pick one mitigation: drop `*.max_us` from the
gating set (keep median/p99/p99.9, which are the trustworthy
signal), or raise `-n` substantially for the tail metrics, or pin
the latency arm to an isolated cpuset. Pure bench-harness change;
no language change.
- context: remove-mut-var-assign close audit, bencher localisation
(2026-05-18) — byte-identical-binary causal exoneration + the
3-milestone false-positive history.
- context: `docs/journals/2026-05-16-audit-iteration-discipline-revert.md`
(the bencher localisation evidence).
- [x] **\[todo\]** `check_in_workspace` per-module overlay narrowing —
`crates/ailang-check/src/lib.rs:1234` still clears+rebuilds
`env.ctor_index` per-module; ct.3.2 narrowed the analogous mono
overlay to types-only. The typecheck-side overlay's `env.ctor_index`
half serves the duplicate-detection diagnostic at workspace-build
time, not the runtime ctor lookup (which is type-driven post-ct.2.2).
Narrowing is mechanical but needs a careful read to confirm no
other consumer survives.
- context: JOURNAL 2026-05-11 ("Iteration ct.4") — milestone close
follow-up; closed by iter ctt.1 — recon confirmed the rebuild is the load-bearing consumer of in-band DuplicateCtor (pinned by `crates/ailang-check/tests/duplicate_ctor_pin.rs`); the asymmetry with the mono side is intentional.
- [x] **\[feature\]** `str_concat : (borrow Str, borrow Str) -> Str` —
heap-Str concatenation primitive. Shipped 2026-05-13 as iter
str-concat (closes fieldtest-form-a friction #4). Symmetric to the
iter 24.1 `str_clone` / `int_to_str` / `bool_to_str` plumbing:
runtime C helper (`ailang_str_concat`), `ailang-check` builtin
registration, `ailang-codegen` extern + `lower_app` arm, plus a
fresh `examples/show_user_adt_with_label.ail` corpus fixture
exercising the LLM-natural Show-body shape.
- context: per-iter journal 2026-05-13-iter-str-concat.md.
- [~] **\[milestone\]** Stateful islands — bounded mutation for
streaming workloads (`Stateful a b` + `!Mut` effect + `mut`
syntactic block). Adds a sealed mutable-state layer on top of
the pure core: a `Stateful a b` first-class type whose interior
is mutable, whose exterior is a typed callable with `!Mut` in
its effect set, and whose state non-aliasing is enforced by
uniqueness inference at the block boundary. Targets the
online / streaming workload class — rolling indicators, IIR
filters, online aggregates, sensor fusion, online learning —
whose mathematics is "new sample + old state → new state +
output" per step, and which today's pure-functional state-
threading makes ergonomically expensive at scale (multi-record
explicit threading for the canonical sliding-window SMA, no
zero-allocation pipe combinator, growing tuple-state types as
pipelines lengthen).
**Sub-milestone sequencing is UNDER RE-THINK with the user
(2026-05-16) — no further sub-milestone is planned or brainstormed
until that conversation happens.** Why: the 2026-05-15 decomposition
named "(2) effect-handler infrastructure as a prerequisite for any
non-IO/non-Diverge effect" as the next step. A read-only recon of
the live effect subsystem (2026-05-16) showed that premise is false:
the effect raise / declared-set / `UndeclaredEffect`-subset / call-
propagation machinery is already fully generic over an arbitrary
effect string; `!Mut` needs **no** effect-handler machinery. The
only real code prerequisite the recon found is one hard-coded
assumption (`linearity.rs` walks every `Term::Do` arg as `Borrow`;
`EffectOpSig` has no per-arg mode field) — a single struct field,
not a milestone. A brainstorm spec that tried to make sub-ms-2 a
standalone deliverable bundled that speculative one-field capability
with an unrelated DESIGN.md honesty fix; the user correctly rejected
the bundle as incoherent and the build-ahead-of-consumer half as the
iteration-discipline trap repeated. **Resolution:** (a) the DESIGN.md
effect-honesty correction is split out and runs now as a standalone
documentation tidy (see the P2 `[todo]` "DESIGN.md effect-prose is
fiction" below); (b) the `arg_modes` finding is recorded as recon
context for the future `!Mut` design, NOT built ahead — it is
first-iteration material of whatever the `!Mut` milestone turns out
to be, validated there against a real consuming op, never a
synthetic test op; (c) the old "(2)…(5)" sequence (effect-handler
infra → `!Mut`+`ref a` → `MutArray a` → `Stateful a b`+`pipe`) is
no longer treated as settled — the whole ordering and granularity
is what the pending user conversation re-decides.
**Motivation.** AILang's signature-as-contract thesis is *better*
served by an explicit `Stateful a b` + `!Mut` annotation than by
the implicit-closure-mutation idiom of myc / Lua / JS factory
patterns: the signature tells the truth about time-identity
without the body needing to be read — exactly the LLM-author
correctness affordance AILang exists to deliver, extended to a
workload class it does not yet serve. Decision 10's constraint #3
forbids *shared* mutable refs (DESIGN.md:1082); it does not
forbid uniqueness-bounded mutation. Lean 4 (`ST`), Roc (`Task`),
Haskell (`ST`/`IO`), and Koka (effects) all use a layered design
of this shape to host exactly this workload. AILang's existing
`own` / `borrow` mode machinery plus its effect-slot architecture
are the foundation; this milestone supplies the layer that sits
on top.
**Scope (subject to brainstorm refinement).**
- `Stateful a b` as a first-class type — a sealed callable with a
hidden mutable env, externally a typed callable whose effect
set includes `!Mut`.
- `!Mut` effect, the first non-IO / non-Diverge effect; forces
the effect-handler infrastructure forward.
- `mut` block as the syntactic boundary in Form A — outside,
AILang's pure self; inside, `var` / `assign` / mutable arrays
legal. (Iteration is *not* part of this boundary: AILang has no
`while`/`for` and this milestone does not introduce one —
repetition stays recursion, exactly as the language has it today
(structural / tail recursion); a `mut` block is a sealed
expression, never a loop over mutable state.)
- `var x = expr` + `assign x expr` AST nodes, legal only inside
`mut`.
- Mutable array primitive (`MutArray a`, O(1) index/update under
uniqueness) — co-developed because the ring-buffer use case
that motivates the whole effort has no carrier today.
- `pipe : Stateful a b → Stateful b c → Stateful a c` as a
built-in combinator with zero-allocation lowering — composition
is fusion at codegen, not closure-pair layering at runtime.
- Decision 12 (or amendment to Decision 10) that names
uniqueness-bounded mutation as the legitimate exception to
"no shared mutable refs", and the pure / mutable-island layering
as the architectural shape.
**Blockers (hard prerequisites + open design questions).**
- *Effect handlers absent.* DESIGN.md:2663 — "No effect
handlers — only the built-in IO and Diverge ops." `!Mut` is the
first non-trivial effect and forces handler infrastructure to
ship. May warrant lifting out as its own prerequisite milestone;
brainstorm decides.
- *Uniqueness inference through `var` captures.* Current
inference operates over the immutable RC graph; mutable
bindings captured by closures need a more powerful pass. Lean 4
has a known algorithm; AILang does not implement it.
- *No mutable-array primitive.* No `Array a`, no slab container
of any kind in the language today. Separate spec needed inside
this milestone (representation, `own`-only or `borrow`-able,
bounds-checking discipline).
- *`runST`-equivalent escape discharge.* Producing a `Stateful`
that legitimately escapes its `mut` block while proving the
inner state doesn't leak. Haskell's rank-2 trick
(`runST :: (forall s. ST s a) -> a`) is unavailable —
DESIGN.md:1930 confirms higher-rank polymorphism is not
supported. Need an alternative — likely sealed-by-construction
at the factory boundary, or an effect-mode tag that gates
escape.
- *Form A surface design.* Decision 1 forbids macros (source =
data, not text), so `mut` / `var` / `assign` are genuine AST
nodes with round-trip-invariant coverage. Surface needs LLM-
utility validation (does the author reach for `var` / `mut`
unprompted?) before implementation.
- *Codegen for in-place struct-field writes.* `Term::ReuseAs`
today lowers ADT in-place rewrite; no direct mutable struct-
field-write path exists. `crates/ailang-codegen/src/escape.rs`
plus the lowering passes need extension.
- *Boundary escape analysis.* "Interior state doesn't leak" is an
escape analysis specifically over `var` / `ref` values; the
existing pass covers allocations, not mutable cells.
- *Decision-10 status.* Whether to amend Decision 10 inline or
add a Decision 12 that names the mutable-island layer alongside
the pure layer. The latter is probably cleaner — Decision 10
stays load-bearing for the pure layer, Decision 12 names the
extension and its discipline.
- *Streaming bench corpus.* Current corpus (list_sum, tree_walk,
closure_chain, hof_pipeline) is all pure. Streaming-specific
benches (SMA pipeline, IIR filter, online stats) plus an
external baseline (myc, hand-C, Python/NumPy) need to exist to
validate that the layered design hits the zero-alloc
performance target that myc demonstrates.
- *LLM-utility test.* DESIGN.md §"Feature-acceptance criterion"
is the gate: the surface must produce code an LLM author
naturally writes. Fieldtest after spec, before any code
commits.
- context: 2026-05-15 chat on the `~/sma_factory.myc` analysis —
myc's stateful-closure-plus-pipe idiom delivers a streaming
workload pattern (3-line SMA factory, 2-line pipeline, zero
runtime allocation per tick) that AILang's pure-only model
cannot match without this layered extension. Pending brainstorm
will produce `docs/specs/<date>-stateful-islands.md` and decide
the effect-handler-prerequisite question.
## P3 — Ideas
- [x] **\[todo\]** `compare_primitives_smoke.ail` counterpart.
Satisfied 2026-05-13 by milestone form-a-default-authoring —
`examples/compare_primitives_smoke.ail` is the canonical
authoring form for that fixture. Form A is now the default
authoring surface across the entire corpus.
- context: closed incidentally by form-a iter form-a.1.
- [ ] **\[todo\]** Codegen `lookup_ctor_in_pattern` type-anchoring —
`crates/ailang-codegen/src/lib.rs:1790` still walks every module's
ctor_index by bare ctor name. Plumbing the scrutinee's
qualified `Type::Con` through pattern lowering would type-anchor
it symmetric to the ct.2.2 typecheck-side fix. Not load-bearing
(uniqueness is enforced at typecheck), but cleaner.
- context: JOURNAL 2026-05-11 ("Iteration ct.3" + ct.4 close).
- [ ] **\[todo\]** `compare_primitives_smoke` IR-shape assertion —
observe `compare__Int` / `compare__Bool` / `compare__Str` symbols
in the emitted IR. Blocked on `emit-ir` CLI not running mono;
resolution paths include extending the CLI to run mono, using
library APIs directly in e2e.rs, or adding `--dump-ir` to `ail
build`. The E2E stdout assertion already covers correctness; the
IR-shape test would catch refactor regressions that rename
mono symbols.
- context: JOURNAL 2026-05-11 ("Iteration ct.4") — dropped from
ct.4.4 when the BLOCKED condition surfaced.
- [ ] **\[idea\]** Latency methodology rework — switch from per-run
timing to a histogram-based approach so tail-latency regressions
are visible without re-running. Queued from 21'g.
- [ ] **\[idea\]** Parser-test backfill for 22b.4a-era duplicate-clause
sites (class × 3, class method × 2, instance × 3, instance method
× 1). No regression pin today; only worth it if a 22b.4a-era
diagnostic needs to change.
- context: JOURNAL 2026-05-10 ("Iteration 22-tidy.7")
- [ ] **\[idea\]** `write_type` `Type::Forall` arm in
`crates/ailang-prose/src/lib.rs` silently drops constraints in
inline-type rendering. Dormant — surface forms today only carry
forall at fn-signature top level. Fix only if a future feature
carries forall + constraints inline.
- context: JOURNAL 2026-05-10 ("Iteration 22-tidy.6")
- [ ] **\[idea\]** Richer integration paths between RC and
uniqueness — deferred from the 21' arc; revisit once the
uniqueness inference covers more program shapes.
- [ ] **\[idea\]** LLM tool-use schema / MCP server / LSP front-end
over the prose-roundtrip cycle — additive layers on top of the
static-prompt `ail merge-prose | client | ail parse | ail check`
path (LLM calls back into `ail parse`/`ail check` mid-turn; an
MCP-compatible client discovers AILang's resources/tools/prompts).
Relocated here from DESIGN.md §"prose roundtrip" + PROSE_ROUNDTRIP.md
by the docs-honesty-lint milestone (forward intent does not live in
the canonical docs).
- context: `docs/specs/2026-05-18-docs-honesty-lint.md`