The koyu command
koyu takes one .muro file, composes it, and answers a question about it. All 14 subcommands share the same derivations; the CLI, the MCP server, and the public API are different entrances to the same answers.
Running it
koyu check examples/two-rooms.muro
Installing the package (@kensnzk/koyu) gives you two executables, koyu and koyu-mcp. It needs Node 22 or later.
From inside the repository the following two are equivalent. Every piece of output on this page and on the per-command pages below was obtained by actually running the command at the root of the repository.
npx tsx src/cli.ts check examples/two-rooms.muro
npm run koyu -- check examples/two-rooms.muro
The human-facing output is in English. It uses the same words as the machine-facing surfaces (diagnostics, findings, MCP), and there is no argument to switch locale — so that the same wording is never maintained in two ledgers.
The common shape
koyu <check|validate|layers|diff|plan|axo|doors|graph|stats|levels|runs|light|site|json> <entry.muro> [args...]
What you pass is always one path, the entry. Even for a building split into layers with import, pass only the base layer's file (examples/house/main.muro, say). Composition happens from scratch on every run, and no intermediate state is stored anywhere.
diff is the one exception: it takes a second file path after the first.
The entry, and how import resolves
An import path resolves relative to the file the import line is written in — not relative to the entry, and not relative to the working directory. So copying just the base layer's file somewhere else will not compose; you get Cannot read file: ./assets.muro.
Passing one of the split layers on its own dies, because that layer has neither grid nor level.
npx tsx src/cli.ts check examples/house/L1.muro
✖ <absolute path>/examples/house/L1.muro:line 3: Undeclared level: level:L1
(<absolute path> stands in for the resolved absolute path; the real output prints it in full.)
koyu layers shows which layers took part and in what strength order.
Reading the exit codes
| Exit code | Meaning |
|---|---|
| 0 | Success — the answer to the question is yes |
| 1 | Failure — there are errors, something is missing, it cannot be reached, or the input could not be read because of a syntax or composition error |
| 2 | You called it wrong — a missing argument, an unknown subcommand, an undeclared level name, an unreadable number |
2 is a problem with the command you typed, not with the model you wrote. What 0 and 1 mean concretely differs per subcommand, so read the exit-code table on each page. diff in particular has its own convention: 0 = no difference, 1 = differences, 2 = the input is broken.
Never letting a calling mistake pass with exit 0 is deliberate. Handing in an unreadable scale does not produce a width="NaN" SVG announced as "generated".
There is no --help
No --help flag is implemented. A call that omits the subcommand name or the file path prints usage, but that is the "you called it wrong" path, and the exit code is 2. Typing --help takes the same path, because --help does not fill the subcommand and file-path positions.
npx tsx src/cli.ts --help
Usage: koyu <check|validate|layers|diff|plan|axo|doors|graph|stats|levels|runs|light|site|json> <file.muro> [args...]
check: --json (emit Diagnostic[] as JSON) / --strict (exit 1 if there are warnings) — structural consistency only
validate: --json (emit Finding[] as JSON) — architectural judgement (not what check guarantees)
layers: the layers that took part in composition, weakest first. --attrs for the provenance of each attribute
diff: koyu diff <a.muro> <b.muro> [--json] — the difference in the language of composition (0=no difference / 1=differences / 2=the input is broken)
That usage text is not exhaustive. It mentions four subcommands only. plan's -l / -o, axo's five flags, and doors's two path arguments are all absent from it. Each command's page below writes out every flag it has.
An unknown subcommand is also exit 2.
npx tsx src/cli.ts frobnicate examples/two-rooms.muro
Unknown command: frobnicate
The 14 subcommands
| Command | What it answers | Flags | Exit codes |
|---|---|---|---|
check | Does what is written hold together as data | --json --strict | 0 / 1 |
validate | Is it sound as architecture (not what check guarantees) | --json | 0 / 1 |
layers | Which layers composed, and which layer gave which value | --attrs | 0 |
diff | What did this edit change about the composition | --json | 0 / 1 / 2 |
plan | The plan drawing (SVG) | -l -o | 0 / 1 / 2 |
axo | The axonometric (SVG) | -o -d -l -s --no-walls --ceilings | 0 / 1 / 2 |
doors | How many doors from here to there | — | 0 / 1 / 2 |
graph | What is this space next to, and how | — | 0 |
stats | What are the areas | — | 0 |
levels | How do the heights stack up | — | 0 / 1 |
runs | How was the vertical circulation derived | — | 0 |
light | Do the rooms in daylight scope meet 1/7 | — | 0 / 1 |
site | Site area, frontage, coverage, floor area ratio | — | 0 / 1 |
json | The canonical JSON a machine reads | — | 0 |
Every command prints usage and returns exit 2 if the subcommand name or the file path is missing. The table above omits that shared 2.
Do not conflate the two greens
A green check and a usable building are different things. The default between touching spaces is a wall, so a two-storey building that declares no door at all stays green in check while being perfectly sealed. check says only that what is written holds together as data; architectural soundness is what validate says, separately.
They differ down to the type. check returns Diagnostic { code, severity }; validate returns Finding { rule, level }. The spellings differ and the two arrays cannot be concatenated. Put both in CI — how to wire them up is on Gating CI.
See also
- Gating CI — which command to fail the build on, and at which exit code
- The VS Code extension — running
checkon every save - koyu-mcp — the same derivations for agents
- The public API — the same derivations from a program
- .muro reference — how to write the file you pass in