Skip to main content

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 codeMeaning
0Success — the answer to the question is yes
1Failure — there are errors, something is missing, it cannot be reached, or the input could not be read because of a syntax or composition error
2You 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

CommandWhat it answersFlagsExit codes
checkDoes what is written hold together as data--json --strict0 / 1
validateIs it sound as architecture (not what check guarantees)--json0 / 1
layersWhich layers composed, and which layer gave which value--attrs0
diffWhat did this edit change about the composition--json0 / 1 / 2
planThe plan drawing (SVG)-l -o0 / 1 / 2
axoThe axonometric (SVG)-o -d -l -s --no-walls --ceilings0 / 1 / 2
doorsHow many doors from here to there0 / 1 / 2
graphWhat is this space next to, and how0
statsWhat are the areas0
levelsHow do the heights stack up0 / 1
runsHow was the vertical circulation derived0
lightDo the rooms in daylight scope meet 1/70 / 1
siteSite area, frontage, coverage, floor area ratio0 / 1
jsonThe canonical JSON a machine reads0

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