Skip to main content

The koyu command

koyu takes one .muro file, composes it, and answers a question about it. Every subcommand shares 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|section|elevation|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".

--version​

--version (or -v) is the one flag that takes no file. It says which implementation you are running and which language it speaks, because those are two separate versions and only one of them is on npm.

npx tsx src/cli.ts --version

The exit code is 0 — it is an answer, not a calling mistake.

Read it as three facts, and note that the last two are separate. The first number is this implementation. reads is every language version it accepts, so a file declaring one of those opens here. newest is the version to declare to get everything the language has.

a file with no version line is read as 1.1 is frozen and does not follow newest (the version line). They no longer agree, and that gap is the fact a reader most needs: an old file that names no version does not quietly become a file at the newest version.

When a file will not open because it declares something newer, this line is what tells you the file is fine and the tool is behind.

The same three facts are on package.json as the muro field (reads / newest / undeclared), for anything that needs them without running the binary, and in the MCP server's serverInfo.

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|section|elevation|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: --profile <id> --as-of <YYYY-MM-DD> [--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. plan's -l / -o, axo's flags, and doors's path arguments are 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

Subcommands​

CommandWhat it answersFlagsExit codes
checkDoes what is written hold together as data--json --strict0 / 1
validateIs it sound as architecture, under a named profile (not what check guarantees)--profile --as-of --json0 / 1 / 2
layersWhich layers composed, and which layer gave which value--attrs0 / 1
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
sectionThe section cut at a grid reference (SVG)--at --look -s -o0 / 1 / 2
elevationThe elevation of one face (SVG)--face -s -o0 / 1 / 2
doorsHow many doors from here to there—0 / 1 / 2
graphWhat is this space next to, and how—0 / 1
statsWhat are the areas--by0 / 1 / 2
levelsHow do the heights stack up—0 / 1
runsHow was the vertical circulation derived—0 / 1
lightDo the rooms in daylight scope meet 1/7—0 / 1
siteSite area, frontage, coverage, floor area ratio—0 / 1
jsonThe canonical JSON a machine reads—0 / 1

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 an AssessmentReport whose findings carry { rule, level }. The spellings differ and the two cannot be concatenated. Put both in CI — how to wire them up is on Gating CI.

validate also names its grounds: --profile and --as-of are required, and leaving one out is a usage error (exit 2) rather than a verdict.

See also​