`part`: Split a Koru Module into Files

· 4 min read

The standard library’s store grew to 11,985 lines — a quarter of koru_std in one file. Not because anyone chose it. The split rules were implicit: host facets joined by stem, and a misnamed sibling silently never joined; submodules needed a directory and changed the public surface. “Create a new file” was a guess about whether the compiler would honor it. Appending to the known-good file always compiled, so every author — human or agent — converged on the big file.

part replaces the guess with a declaration.

The declaration

A module’s own file names the sibling groups that join it:

// input.k
part impl

compute(x: 42): r |> show(v: r)
// input.impl.k
pub tor compute { x: u32 } -> u32

pub tor show { v: u32 }

part impl joins every input.impl.k* file — pure Koru, Zig host, JS host — and merges them into one module. The merge is the same machinery the host facets always used; the declaration’s only job is to make discovery loud. A tag with no file is an error (KORU201), never a silent miss — the failure mode that made misnamed files invisible is gone.

What the split keeps

The join is item-level, never text-level. Each part parses with its own path, so a diagnostic inside a part points into the part file — the C-preprocessor flattening never happens. Host lines route by their own file’s extension, so a .kjs part’s JavaScript never leaks into the Zig build. And a top-level declaration repeated across the boundary is an error, not a silent dedup — parts are same-kind files, so a duplicate is an accident.

Parts are flat by ruling: they split one file, and a part that outgrows its file promotes to a directory module — the existing surface, not a second module system.

The receipt

The same day the feature landed, the file that provoked it was split. store.kz became a 1,690-line primary declaring 12 part events — create, query, watch, stored, and the rest, each in its own store.<event>.kz. The split was verified byte-faithful before commit: reconstructing the merged module from primary and parts reproduces the pre-split file exactly. The store’s 236-test suite showed zero regressions.

The wall that makes it stick

A feature is adopted when the toolchain makes it the path of least resistance. The commit gate now pins every Koru file’s line count and refuses a staged file that grew past its pin, answering with the exact move: “store.kz pinned at 11,985; grew to N — add part <name> and move the code to store..k.” The wall caught its first violation within an hour of shipping — a comment reflow that grew a file by one line — and the commit that sanctioned the store split shrank the wall’s biggest row from 11,985 to 1,690.

The split rule is no longer implicit. It is a declaration the compiler enforces, and a wall that asks the question at every commit.