The Koru Repo Cleanup: 12,716 Tracked Files, 182 of Them the Compiler

· 7 min read

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-level zig 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, .rlib and .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 under probes/**/. A class cannot come back by a deliberate git add of 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.