Skip to main content

Gating CI

If your .muro files live in git, you can run koyu on every commit. What this page settles is which command fails the build, at which exit code.

A check-only CI quietly stops looking at the judgement

This is the first thing anyone reaches for.

koyu check building/main.muro

It is not enough. What check guarantees stops at "what is written holds together as data". It says nothing about whether the building is usable.

The default between touching spaces is a wall, and a wall is impassable without a door. So a building with no doors at all comes out green.

koyu 1.0
name 密封
unit mm
grid X 0 3600 7200
grid Y 0 4500
level L1 0 h:2400 slab:150
space /L1/a room X1..X2 Y1..Y2 name:居室A daylight:1
space /L1/b room X2..X3 Y1..Y2 name:居室B
space /out exterior name:外部
npx tsx src/cli.ts check sealed.muro
✔ Consistent — 3 spaces / 1 boundary
  Structural consistency only — architectural validity is what koyu validate says, separately

Exit 0. Hand the same file to koyu validate:

npx tsx src/cli.ts validate sealed.muro
✖ [daylight.ratio] <absolute path>/sealed.muro:line 7: Insufficient daylight: /L1/a — effective window 0.00 m2 < required 2.31 m2 (1/7 of the 16.20 m2 floor)
✖ [access.unreachable] <absolute path>/sealed.muro:line 7: Cannot reach the exterior: /L1/a (no passable boundary leads out — write a door)
✖ [access.unreachable] <absolute path>/sealed.muro:line 8: Cannot reach the exterior: /L1/b (no passable boundary leads out — write a door)
Validation — 3 violations / 0 cautions

Exit 1. Put only check in CI and those three findings are never seen by anyone.

Run the two together

The minimum gate is this.

koyu check building/main.muro --strict
koyu validate building/main.muro

--strict is there because warnings otherwise pass through green. "Not one floor is generated on this storey" and "the vertical run's form is not generated" are warnings, so without it they exit 0.

npx tsx src/cli.ts check warn.muro
⚠ <absolute path>/warn.muro:line 6: Level L1 has no slab:, so not one floor is generated on this storey
✔ Consistent — 3 spaces / 2 boundaries (1 warning)
  Structural consistency only — architectural validity is what koyu validate says, separately

Exit 0; with --strict it becomes 1. Same file, same warning, different exit code.

Which command fails at which exit code

Command012Put it in CI?
checkNo errors (nor warnings under --strict)Errors / warnings under --strict / unreadableMissing argumentAlways, with --strict
validateNo violations (cautions stay 0)Violations / unreadableMissing argumentAlways
doorsReachableUnreachableTwo paths not givenWhen you want to protect one specific escape route
lightAll rooms meet 1/7 / nothing in scopeSome fall shortMissing argumentRedundant if validate is there
siteThe site report came outNo siteMissing argumentWhen the site declaration must exist
levelsIt came outNo level definedMissing argumentRarely useful (check fails first)
diffNo differencesThere are differencesThe input is brokenWhen you want to freeze a generated file
plan / axoWrittenCould not be drawnUndeclared level name and the likeWhen the drawings must keep generating
graph / stats / runs / layers / jsonAlways 0UnreadableMissing argumentNot gates — these commands pass no judgement

Three traps.

diff's 0/1 reads the other way round. check's 0 is "it holds together"; diff's 0 is "they are the same". You put diff in CI to protect "this generated file has not changed", and the sense inverts.

light returns 0 with nothing in scope. Put light in CI for a model that writes no daylight:1 and green comes back having looked at nothing. To protect daylight, validate is safer — daylight.ratio is a violation and exits 1.

validate does not fail on cautions. envelope.gap, stair.proportion and site.area are all cautions, so the exit code stays 0. To fail on cautions too, read --json and count them yourself.

koyu validate building/main.muro --json | node -e '
const f = JSON.parse(require("fs").readFileSync(0, "utf8"));
if (f.length) { console.error(f.map(x => `${x.level} [${x.rule}] ${x.message}`).join("\n")); process.exit(1); }
'

Running several buildings

With several entries, stop as soon as one fails.

for f in building/*/main.muro; do
koyu check "$f" --strict || exit 1
koyu validate "$f" || exit 1
done

A shell for returns only the last command's exit code, so put || exit 1 on each line.

In GitHub Actions

name: koyu
on: [push, pull_request]
jobs:
gate:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 22
- run: npm ci
- run: |
for f in building/*/main.muro; do
npx koyu check "$f" --strict || exit 1
npx koyu validate "$f" || exit 1
done

Node 22 or later is required.

Getting the codes when it fails

The CI log carries the human-facing output. Codes never appear in the human output. To look one up, add --json.

koyu check building/main.muro --json
[
 {
  "code": "BND04",
  "severity": "error",
  "message": "The spaces do not touch, so no boundary can be derived: /L1/a | /L1/b",
  "line": 6,
  "file": "<absolute path>/bad.muro",
  "path": [
   "/L1/a",
   "/L1/b"
  ]
 }
]

A file that could not be read because of a syntax error still returns valid JSON under --json (copied into a single SYN01), so a machine reader built on top of the CI log does not break.

See also