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.
Resolves Gitea #12 — the design fork from the 2026-05-20 /boss
session, surfaced independently by two prior fieldtests
(mut-local F3 + loop-recur `run_forever`).
Mechanics, layer by layer:
Parser — `crates/ailang-surface/src/parse.rs:1322-1331` drops
the 4-line `expected at least one argument` guard in
`parse_app_body`. Grammar comments at file top change `term+`
to `term*` on both `app-term` and `tail-app-term`; doc comment
on `parse_app_body` changes `1+ args` to `0+ args`. Inline
comment cites #12 and notes which layers below already accept
empty args.
Serde — `crates/ailang-core/src/ast.rs:419-425` annotates
`Term::App.args` with `#[serde(default)]`, mirroring
`Term::Ctor.args`'s *actual* attrs verbatim (no
`skip_serializing_if`). Write behaviour unchanged — canonical
JSON still emits `"args":[]` for nullary calls; only read
behaviour gains tolerance for the `args` key being absent.
Verified hash-impact-free at plan time:
`grep -rn '"args":\[\]' examples/*.ail.json` returns zero
matches, so no existing fixture's canonical bytes shift.
Doc-honesty — `design/contracts/data-model.md`:
(a) the `app` jsonc shape gains an explicit "args may be
empty / read-tolerant" note;
(b) the existing `ctor` comment claiming "args omitted when
empty" was factually false (Ctor.args has no
`skip_serializing_if`; a serde probe at plan time
confirmed writes always emit `"args":[]`). Rewritten to
describe the actual write/read asymmetry, with a
cross-reference to the new `app` note.
E2E pin — `crates/ail/tests/nullary_app_e2e.rs` +
`examples/nullary_app_smoke.ail`. Defines `greet : fn()
-> Unit !IO` and calls it as `(app greet)`; asserts stdout
`"hello\n"`. RED→GREEN pin for the parser change AND the
milestone-protecting E2E for nullary call surface going
forward. Fixture literal is `"hello"` (no `\n`) because
`io/print_str` lowers via `puts` which appends a newline —
the plan body had a `"hello\n"` literal which would have
yielded `"hello\n\n"`; the implementer caught and aligned to
the canonical pattern in `examples/hello.ail` while writing
the fixture, fix scoped to that single file.
Layers below parser untouched: typechecker's arity check at
`crates/ailang-check/src/lib.rs:3300` is `args.len() !=
params.len()` which is `0 != 0 → false` for nullary; codegen's
`args.iter().zip(sig.params.iter())` at
`crates/ailang-codegen/src/lib.rs:2410` is an empty loop;
LLVM `call @ail_<mod>_<name>()` with empty arglist is valid;
surface printer's arg-emit loop at
`crates/ailang-surface/src/print.rs:430-434` writes nothing for
empty args, producing exactly `(app f)`.
Verification: full workspace `cargo test --workspace --quiet`
green (0 failed across all crates); drift pins
`design_index_pin 5/5` + `design_schema_drift 8/8`; round-trip
`2/2`; new `nullary_app_e2e 1/1`. Stats file:
`bench/orchestrator-stats/2026-05-20-iter-nullary-app.1.json`.
closes#12