TypeScript API
@kensnzk/koyu は .muro を読み、検査し、問いに答え、形を導き、図を吐くライブラリである。CLI が答えるものはすべてこの API が答える。koyu コマンド・koyu-mcp サーバー・この API は同じ導出の三つの入口であり、どれかにしか無い答えというものは無い。
npm install @kensnzk/koyu
実行時依存はゼロである。パッケージが引くのは Node 標準モジュールだけで、それも @kensnzk/koyu/node の中だけに閉じている。動作環境は Node 22 以上 (engines.node が >=22)。
四つの入口
import { parse, checkDiagnostics, derive } from "@kensnzk/koyu";
import { parseFile, parseFileWith } from "@kensnzk/koyu/node";
import { validate, VALIDATION_RULES } from "@kensnzk/koyu/validate";
import { svgPlan, svgAxo } from "@kensnzk/koyu/draw";
| 入口 | 中身 | node:fs |
|---|---|---|
@kensnzk/koyu | 面の全部 — 解析・診断・問い・導出・生成・差分・検証 | 引かない |
@kensnzk/koyu/node | parseFile parseFileWith の二つだけ | 引く |
@kensnzk/koyu/validate | validate VALIDATION_RULES と型 Finding ValidationRule | 引かない |
@kensnzk/koyu/draw | svgPlan svgAxo と型 PlanOptions AxoOptions | 引かない |
ルートは node:fs も node:path も引かない。ブラウザ・Web Worker・エッジランタイムでそのまま動く。ファイルシステムを触る入口だけが /node に分離してある。分けてあるのはパーサ本体を純粋に保つためで、合成 (import の解決) は「レイヤーをどう読むか」という関数を外から受け取る形になっており、fs はその実装の一つでしかない。ブラウザは仮想ファイル群 (parseFiles) か独自ローダー (parseWith) を渡す — 解析と合成。
/validate と /draw はルートが再輸出している面の一部でもある。領域を混ぜないための別入口であって、別の実装ではない。ルートから validate を呼んでも @kensnzk/koyu/validate から呼んでも同じ関数である。(/validate のモジュールには Finding を組み立てる補助 finding も出ているが、これはルートが再輸出していない — 下の一覧に無い名は約束の外にある。)
面は書き下されている
パッケージのルートは export * を使わない。モジュールに export を足した瞬間に、誰も宣言していない約束が凍る面に増えてしまうからである。約束は書き下されていなければならない。
したがってこの面の全部は、src/index.ts に一つずつ書かれた名の集合である — 実行時の値が 59、型が 77。ここに無い名は、ソースの中にあっても約束ではない。
面に載る基準は四つある。
- パッケージの外 (ビューワー・評価ハーネス・スクリプト・エディタ拡張) が実際に呼ぶ
- CLI か MCP が答えるものを API からも答えるために要る
- 導出として名指しで約束されている
- テストが契約として固定している
core のモジュール同士が引き合うだけの配管は面ではない。型は、載せた値の署名を書き下すのに要るものだけが載る。
最初のプログラム
読み込み、検査し、面積を出す。これだけで一巡している。
import { checkDiagnostics, areaM2 } from "@kensnzk/koyu";
import { parseFile } from "@kensnzk/koyu/node";
const model = parseFile("examples/two-rooms.muro");
const diags = checkDiagnostics(model);
console.log(`${model.name} — spaces ${model.spaces.size} / diagnostics ${diags.length}`);
for (const d of diags) console.log(`${d.severity} ${d.code} ${d.message}`);
for (const s of model.spaces.values()) {
console.log(`${s.path}\t${s.type}\t${areaM2(s) ?? "-"}`);
}
二室 — spaces 3 / diagnostics 0
/L1/a room 16.2
/L1/b room 16.2
/out exterior -
model.spaces は Map<string, Space>、model.boundaries は Boundary[] である。パスが空間の同一性であり、境界はどちらの空間にも属さない第一級の関係として配列に並ぶ — Model と構成型。
診断が空でも建物が使えるとは限らない。checkDiagnostics が言うのは「書かれたものがデータとして矛盾していない」までである。扉を一枚も書かない二階建ては、診断が空のまま完全に密封される。建築の側の判断は validate が別に言う。
頁の地図
| 頁 | 何を引くか |
|---|---|
| Model と構成型 | Model Space Boundary Zone Level Opening ほか、書かれた構成の型 |
| 解析と合成 | parse parseFiles parseWith parseFile parseFileWith tokenize LayerLoader |
| 診断 | checkDiagnostics check DIAGNOSTIC_CODES Diagnostic CheckResult |
| 検証 | validate VALIDATION_RULES Finding ValidationRule |
| モデルへの問い | doorsBetween neighbors areaM2 siteReport daylightInputs ほか |
| 形の導出 | derive と Form の全構成型、DERIVATION_CONSTANTS TOLERANCES |
| 実体と生成物 | thicken band columnRect runPrism slabs verticalRuns ほか |
| 図の生成 | svgPlan svgAxo とその選択肢 |
| 正準JSON | toCanonical |
| 意味差分 | semanticDiff renderDiff ModelDiff |
| 同一性の生成 | newUids |
| 幾何の小物 | polygonAreaM2 pointInPolygon polyBounds rectToPoly envelopeGaps |
| エラー | SourceError srcRef |
| 言語版 | SUPPORTED_LANGUAGE_VERSIONS DEFAULT_LANGUAGE_VERSION |
三つの領域
面の中身は三つに割れていて、割れ方そのものが約束の一部である。
| 領域 | 何を言うか | 凍るか |
|---|---|---|
| core | 構成の整合と、そこから導かれる数と形 | 凍る — 意味論を変える変更は言語版を上げる |
| 検証 | 建築的な判定 (Finding) | 凍らない — 増える・精度が上がる・捨てられる |
| 生成 | SVG の中身 | 凍らない — 見た目は自由に変わる |
core は合否を言わない。面積・線分・凸片・立体を返すところまでが core であり、「足りているか」「守られているか」は検証が言う。この分離は型に現れている — core は Diagnostic { code, severity } を返し、検証は Finding { rule, level } を返す。フィールド名から違うので、二つの配列は取り違えようがない。
生成 (svgPlan / svgAxo) が返す SVG の中身は約束の外にある。同じ入力から同じ形が出ることは約束されるが、同じバイトが出ることは約束されない。色・線種・書体・記号の見た目は断りなく変わる。図を機械で比べるなら toCanonical か derive の返り値を比べる。