The Koru Repo Cleanup: 12,716 Tracked Files, 182 of Them the Compiler
A review that was wrong on contact
Another session handed us an architecture review of koru — a read-only scan, twelve confident candidates, each with a diagram and a recommendation. We checked it against the tree. Most of it collapsed:
- its “backups in source control” were real at write time and purged within twenty-four hours — the ground had moved under the report;
- six “dead” modules were live on the second clock. Koru compiles in four
stages, and half the pipeline is wired in
koru_std/, not the top-levelzig build— a scan that checks only the first clock cannot see them; - the membrane corpus, the ceremony snapshot, the challenge ecology — all deliberate infrastructure — were filed as clutter.
The lesson became the rule: a claim about this repo from another session is a measurement at write time. It is unmeasured until you have checked it against the current tree, this session. Three verdicts: blocker, not a blocker, unmeasured — and unmeasured means stop talking.
What a clone actually contained
If claims about the repo couldn’t be trusted, the repo itself could be
measured. On 2026-08-31 the committed tree held 12,716 files. The
compiler — src/ and koru_std/ — was 182 of them.
The other 12,534:
- a compiled bevy dependency tree — cargo’s
target/, release and debug,.rliband.dylib, the largest objects in the entire repository; - a unikraft build tree — 3,248 files of
.unikraft/, clangd caches, a compiled archive; - 1,041 board snapshots — every test run’s JSON from January onward, committed forever;
- compiled benchmark binaries, a 10 MB release tarball, 14 generated AST dumps, a second benchmark suite with its own harness, and commit messages that read “The diff confirms it. Here’s the commit message:”.
The mechanism, named honestly
This wasn’t carelessness. It was a mechanism.
The publishing ceremony — the loop that posts the test board — had, as its own
documented instruction: git add -A. And koru’s .gitignore is a whitelist: /* ignores root files, but !*/ and !.* re-admit every directory and
every dotfile, so a new scratch directory is tracked until someone names it.
The gitignore names each artifact class only after that class has already
been caught once. The recipe: a new artifact class appears where the gitignore
hasn’t named it yet → the next ceremony runs git add -A → it ships, forever.
The hazard was documented twice. A near-miss (“a 2.8 MB .shell-probe nearly
swept into a publish commit”) and a firing (a staging directory’s backend.zig and *.err rode a ceremony commit into the published tree). Both were written
down. Neither stopped anything — because the wall was prose, and prose drifts.
The repo’s own law: running code is the memory.
The fix: remove the mechanism, wall the classes
- The ceremony no longer stages
git add -A. It stages by name, and refuses any status line it cannot account for. A ceremony commit stages nothing unseen. - The classes are deny-listed:
target/,*.rlib,.unikraft/,**/program.ast.json,test-results/2*.json,**/__pycache__/, probe compile output underprobes/**/. A class cannot come back by a deliberategit addof the directory that used to hold it. - Retention is walled: dated boards stay local; only the live snapshot ships.
- ~5,650 files purged across Aug 31–Sep 2, one class per commit, each with its deny-list in the same commit. Verified after each: the build, a filtered run of the suite, and the snapshot reader.
Prose alone wasn’t enough. scripts/git_wall.sh compiles .gitignore into
the commit boundary: pre-commit runs on staged paths, the suite runs on the
committed tree. Grandfather rows live in an allowlist that shrinks as
cleanup replays purge them — the wall never widens. A tracked path that .gitignore would refuse is a failed commit, not a note in a README.
The cleanup itself became a replayable frame — challenge 019, the repo-cleanup
pass — with the committed-tree procedure written down: inventory by size,
reader-verification before deletion (on both clocks: top-level build.zig and the metacircular backend graph), and resolve symlinks before removing
their targets (a snapshot link once pointed at a board we had just deleted).
The Sep 2 replay was thin — a tracked .pyc, a stale build log, probe caches
on disk — which is the honest shape once the big classes are gone.
The suite is why it’s safe
None of this is a language feature. But it runs on the same discipline the
language runs on: koru’s ground truth is the suite — 1,643 regression tests
in scope (~1,700 on disk) — and the suite pins behavior byte-for-byte. The
parser refactor that landed the same week (142 error-reporting sites collapsed
into one fail() helper) was verified by it: every diagnostic still refused
exactly where it was pinned, because the suite would have said so.
The repo is the product. The product is now 7,067 tracked files at ffda1004: 2.6% of them the compiler (src/ + koru_std/), and the rest
the suite that is the language’s spec and the ecosystem that keeps it honest.
Working-tree regenerables — zig-out-run-*, per-test backend builds — still
accumulate locally and get swept on replay; they are ignored, not shipped.