@chanmeng666/archlang-mcp 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,599 @@
1
+ <!-- GENERATED by scripts/gen-llms-full.ts — do not edit by hand. Run `npm run gen:llms`. -->
2
+
3
+ # ArchLang — full agent context (llms-full.txt)
4
+
5
+ This is the complete context for driving **ArchLang**, a tiny declarative language that compiles a
6
+ `.arch` floor-plan source file into a professional drawing (SVG/PNG/PDF/DXF). It follows the
7
+ [llms.txt](https://llmstxt.org/) convention: `llms.txt` is the concise project map, and this
8
+ `llms-full.txt` is the whole thing in one document — the language spec, the agent workflow, the CLI
9
+ reference, and every diagnostic code — sized to drop into a system prompt.
10
+
11
+ ArchLang is built for agents: **deterministic** (same source → byte-identical output), **pure** (no
12
+ runtime, no IO in the compiler), and **self-correcting** (every error carries a machine code and a
13
+ `fix`). Author, render, and verify entirely through the `arch` CLI — never hand-render SVG.
14
+
15
+ Contents:
16
+
17
+ 1. Language spec — the whole language in one page.
18
+ 2. Agent workflow — the compile → fix → describe → gate loop, and how to repair plan topology.
19
+ 3. CLI reference — every command, flag, and exit code.
20
+ 4. Diagnostic catalog — every error and warning, each with a fix.
21
+
22
+ ---
23
+
24
+ ## 1. Language spec
25
+
26
+ # ArchLang in one prompt
27
+
28
+ ArchLang is a tiny declarative language that compiles a `.arch` source file into a professional
29
+ floor-plan drawing (SVG/PNG/PDF/DXF). It is built for AI agents: deterministic (same source →
30
+ identical output), pure (no runtime/IO), and self-correcting (every error carries a machine code and
31
+ a `fix`). This page is everything you need to author it. Print it any time with `arch spec`.
32
+
33
+ ## The 7 rules that matter
34
+
35
+ 1. **Units are millimetres.** A 4-metre wall is `4000`, not `4`.
36
+ 2. **Origin is top-left; +x goes right, +y goes DOWN** (screen/SVG convention — *not* math y-up).
37
+ 3. **Coordinates are `(x, y)` tuples; sizes are `WxH`** (e.g. `4000x3000`) or `<expr> x <expr>` with spaces.
38
+ 4. **Doors and windows must lie ON a wall segment** (on its centerline), or you get a
39
+ `W_DOOR_OFF_WALL` / `W_WINDOW_OFF_WALL` warning.
40
+ 5. **String interpolation is `"{expr}"`** inside double quotes (e.g. `label "Unit {i}"`).
41
+ 6. **Ids must be unique.** Omit `id=` to auto-generate one; give an `id` only when you reference it.
42
+ 7. **Everything is expand-time and pure** — `let`/`for`/`if`/functions all evaluate during compile.
43
+
44
+ ## Structure
45
+
46
+ ```arch
47
+ plan "Title" {
48
+ units mm # required-ish settings come first
49
+ grid 50 # snap grid in mm
50
+ scale 1:50 # drawing scale (annotation only)
51
+ north up # up | down | left | right
52
+ # … elements and scripting …
53
+ title { project "…" drawn_by "…" date "…" }
54
+ }
55
+ ```
56
+
57
+ ## Elements
58
+
59
+ ```text
60
+ wall <category> thickness <mm> [material <name>] { (x,y) (x,y) … [close] } # category e.g. exterior/partition; `close` makes a loop
61
+ room [id=<name>] at (x,y) size <W>x<H> [label "…"] [uses living|kitchen|dining|bedroom|bath|wc|hall|circulation|storage|utility|office|entry …] # OR relational: room [id=…] (right-of|left-of|below|above) <roomId> [align top|middle|bottom|left|right] [gap <mm>] size <W>x<H> [label "…"]
62
+ door [id=<name>] at (x,y) width <mm> [wall <id|category>] [hinge left|right] [swing in|out] # must sit on a wall
63
+ window [id=<name>] at (x,y) width <mm> [wall <id|category>] # must sit on a wall
64
+ opening [id=<name>] at (x,y) width <mm> [wall <id|category>] # a leaf-less cased opening (gap in a wall) that still connects the two spaces
65
+ furniture <category> (at (x,y) | against wall <id> [segment <n>] [offset <mm>] [side left|right]) [size <W>x<H>] [label "…"] [rotate 0|90|180|270] [in <roomId>] # `at` size is plan W×H; `against` size is wall-relative along×depth and derives position+rotation, with `side` inferred from `in <roomId>` when omitted; a known fixture (wc/basin/shower/bathtub/kitchen_sink/counter/stove/fridge…) `against wall` may omit `size` to use its catalogued footprint
66
+ dim (x,y)->(x,y) offset <mm> [text "…"] # a dimension line
67
+ column [id=<name>] at (x,y) size <W>x<H>
68
+ ```
69
+
70
+ ## Scripting (all expand-time, deterministic)
71
+
72
+ - `let NAME = expr` — bind a constant. `NAME = expr` — reassign an existing binding.
73
+ - `let f(a, b) = expr` — a pure value-function. Built-ins: `min max abs sqrt floor ceil round len str`.
74
+ - `for i in lo..hi { … }` — loop over a half-open integer range (`0..3` → 0,1,2).
75
+ - `if cond { … } else { … }` · `while cond { … }`.
76
+ - `set <element>(attr: value)` — scoped default for following elements (e.g. `set door(swing: out)`).
77
+ - Arrays: `[a, b, c]`, indexed `arr[i]`. Operators: `+ - * / %`, `== != < > <= >=`, `&& ||`. Comments: `# …`.
78
+ - `import "lib/x.arch": name` and `component name(args) { … }` for reuse.
79
+
80
+ ## Keyword reference
81
+
82
+ - **Settings / control:** `plan`, `component`, `let`, `theme`, `title`, `style`, `import`, `for`, `if`, `while`, `else`, `set`, `strip`
83
+ - **Elements:** `wall`, `room`, `door`, `window`, `opening`, `furniture`, `dim`, `column`
84
+ - **Attributes:** `units`, `grid`, `scale`, `north`, `dims`, `accTitle`, `accDescr`, `material`, `angle`, `at`, `size`, `width`, `thickness`, `label`, `hinge`, `swing`, `offset`, `text`, `close`, `id`, `project`, `drawn_by`, `date`, `from`, `as`, `right-of`, `left-of`, `below`, `above`, `align`, `gap`, `uses`, `rotate`, `against`, `segment`, `side`, `on`, `into`, `near`, `anchor`, `inset`, `height`
85
+ - **Enums / values:** `up`, `down`, `left`, `right`, `in`, `out`, `mm`, `true`, `false`, `top`, `middle`, `bottom`, `center`, `centered`, `start`, `end`, `top-left`, `top-right`, `bottom-left`, `bottom-right`, `auto`, `living`, `kitchen`, `dining`, `bedroom`, `bath`, `wc`, `hall`, `circulation`, `storage`, `utility`, `office`, `entry`
86
+
87
+ ## CLI loop (how an agent drives it)
88
+
89
+ ```bash
90
+ arch spec # print this spec
91
+ arch manifest --json # the whole CLI API as data: commands, flags, formats, lint rules, error codes
92
+ arch compile plan.arch -o out.svg --json # render; JSON has { ok, diagnostics, summary }
93
+ echo '<source>' | arch compile - --json # compile from stdin (no temp file)
94
+ arch preview plan.arch -o out.png --json # render a PNG you can SHOW the user (zero-install where resvg is present; --install fetches it)
95
+ arch compile plan.arch -o walk.svg --overlay circulation # opt-in: draw the entrance→room walks + pinch markers on top (default output is unchanged)
96
+ arch describe plan.arch --json # semantic facts: rooms, areas, adjacency, what doors connect, + per-room circulation (walk distance, bottleneck width, detour)
97
+ arch lint plan.arch --json # architectural soundness warnings
98
+ arch validate plan.arch --strict --json # parse + lint, no render; --strict fails on warnings too
99
+ arch explain E_ROOM_SIZE --json # look up any error code
100
+ arch repair plan.arch -o fixed.arch # explicit corrector: new source w/ furniture out of walls/doorways/swings, overlaps separated, fixtures into their room + snapped to walls + change log
101
+ arch batch a.arch b.arch -f svg --json # render many variants at once → results[]
102
+ arch md notes.md -o out.md -f svg # render every fenced arch block in a Markdown file → image links
103
+ ```
104
+
105
+ **Self-correction loop:** compile/validate → if `ok` is false, read each `diagnostics[].fix` (and
106
+ `line`/`col`/`span`), edit the source, recompile. Exit code `2` means a deterministic
107
+ user-source error (fix it; don't blindly retry). Then `describe --json` to confirm the plan matches
108
+ intent (right room count, areas, adjacency) without rendering an image. **Before shipping, gate with
109
+ `arch validate --strict --json`** — it fails on advisory warnings too, so a plan that lint flags
110
+ (furniture through a wall, a fixture blocking a doorway, a room you can't step into, an unreachable
111
+ room, a walk that squeezes too narrow — `W_PATH_TOO_NARROW` — or wanders the long way round —
112
+ `W_CIRCUITOUS_PATH`) cannot pass silently.
113
+
114
+ **Place furniture so it's physically sound:** keep every piece inside its room and off the walls
115
+ (don't cross a wall centerline); back plumbing/kitchen fixtures onto a wall with `against wall <id>`
116
+ (+ `in <roomId>`) rather than guessing an `at`; give every room a `door`/`opening`; and leave
117
+ the doorway approach and the door's swing clear.
118
+
119
+ **Fix topology from facts, not guesses.** `arch repair` corrects furniture but never adds a door or
120
+ window (that is a design choice). When lint reports an unreachable room / no entrance / windowless
121
+ bedroom, read `describe --json` — `access.rooms[].reachable`, room `bbox`/`adjacent`, building
122
+ extent = min/max of room boxes — and add a `door`/`opening`/`window` on the right wall yourself
123
+ (an exterior entrance into a cut-off living space beats routing through a bedroom), then re-`repair`
124
+ and `validate --strict`. See SKILL.md for the exact arithmetic.
125
+
126
+ ## Common mistakes
127
+
128
+ | Mistake | Fix |
129
+ | --- | --- |
130
+ | Using metres (`size 4x3`) | Use millimetres (`size 4000x3000`). |
131
+ | Expecting +y to go up | +y goes **down**; a room below another has a larger y. |
132
+ | Door/window floating in space | Put its `at` on a wall segment's centerline. |
133
+ | `size 4000` (no height) | Sizes are `WxH`: `size 4000x3000` (or `W x H` with spaces). |
134
+ | Reusing an `id` | Ids are unique; omit `id=` to auto-generate. |
135
+ | String math without interpolation | Use `"{expr}"`, e.g. `label "{aream2(W,H)} m²"`. |
136
+
137
+ ## Worked examples
138
+
139
+ ### `examples/studio.arch`
140
+
141
+ ```arch
142
+ # A compact studio apartment — the canonical ArchLang example.
143
+ #
144
+ # Architecturally sound (passes `arch lint`): every room opens off a central hall,
145
+ # so the bath is never reached through the bedroom; the bath is fully enclosed and
146
+ # fitted with real fixtures; and no door leaf sweeps onto furniture. Self-contained
147
+ # (no imports) so it compiles from a single file.
148
+ plan "Studio 1BR" {
149
+ units mm
150
+ grid 50
151
+ scale 1:50
152
+ north up
153
+
154
+ # Exterior shell + partitions. The x=4000 divider runs the FULL height so the bath
155
+ # is walled off from the living space; the right column splits into bedroom / hall / bath.
156
+ wall exterior thickness 200 { (0,0) (7000,0) (7000,6000) (0,6000) close }
157
+ wall partition thickness 100 { (4000,0) (4000,6000) }
158
+ wall partition thickness 100 { (4000,3000) (7000,3000) }
159
+ wall partition thickness 100 { (4000,4400) (7000,4400) }
160
+
161
+ room id=r_living at (0,0) size 4000x6000 label "Living / Kitchen" uses living kitchen
162
+ room id=r_bed at (4000,0) size 3000x3000 label "Bedroom" uses bedroom
163
+ room id=r_hall at (4000,3000) size 3000x1400 label "Hall" uses hall
164
+ room id=r_bath at (4000,4400) size 3000x1600 label "Bath" uses bath
165
+
166
+ # Entrance into the living space; the hall links it to the bedroom and the bath.
167
+ # Living ↔ hall is a cased opening (circulation needs no door leaf); the bedroom
168
+ # and bath each get a real door off the hall.
169
+ door id=d_main at (3000,6000) width 1000 wall exterior hinge left swing in
170
+ opening id=o_living at (4000,3700) width 900 wall partition
171
+ door id=d_bed at (6400,3000) width 800 wall partition hinge right swing out
172
+ door id=d_bath at (4600,4400) width 800 wall partition hinge left swing out
173
+
174
+ window at (0,2000) width 1500 wall exterior
175
+ window at (7000,1500) width 1200 wall exterior
176
+ window at (7000,5200) width 700 wall exterior
177
+
178
+ # Kitchen run along the north wall: sink · counter · stove · fridge (drawn as symbols).
179
+ furniture kitchen_sink at (300,250) size 800x600
180
+ furniture counter at (1200,250) size 600x600
181
+ furniture stove at (1950,250) size 600x600
182
+ furniture fridge at (2700,250) size 600x650
183
+
184
+ # Living + bedroom furniture, kept clear of the door swings.
185
+ furniture sofa at (350,4300) size 2000x900 label "Sofa"
186
+ furniture bed at (4300,300) size 1500x2000 label "Bed"
187
+
188
+ # Bathroom fixtures, kept clear of the door's entry path (the door swings out into
189
+ # the hall): shower in the far corner, basin against the partition, WC on the south
190
+ # wall — the left third of the room stays open so you can actually step inside.
191
+ furniture shower at (6000,5000) size 900x900 # against the E + S walls (corner)
192
+ furniture basin at (5200,4450) size 600x450 # back to the hall partition
193
+ furniture wc at (5200,5200) size 400x700 # back to the south wall
194
+
195
+ # Dimension strings: room widths above, overall extents around. The reference
196
+ # (witness) points sit on the building's OUTER faces (x=-100/7100, y=-100/6100 for
197
+ # the 200mm shell) so the extension lines start at the wall face and read outward,
198
+ # never poking back into the building — while the spans still measure centerline to
199
+ # centerline (4000 · 3000 · 7000 · 6000).
200
+ dim (4000,-100)->(0,-100) offset 250 text "4000"
201
+ dim (7000,-100)->(4000,-100) offset 250 text "3000"
202
+ dim (0,6100)->(7000,6100) offset 500 text "7000"
203
+ dim (7100,6000)->(7100,0) offset 500 text "6000"
204
+
205
+ title {
206
+ project "Studio Apartment"
207
+ drawn_by "ArchCanvas"
208
+ date "2026-06-27"
209
+ }
210
+ }
211
+ ```
212
+
213
+ ### `examples/parametric.arch`
214
+
215
+ ```arch
216
+ # Parametric plan (v0.8 scripting): a row of studio units generated with a
217
+ # `for` loop over a range, a value-function, an array indexed per unit, a scoped
218
+ # `set` rule, an `if`, and string-interpolated labels. Everything is derived
219
+ # from the constants — change COUNT and the whole row regenerates.
220
+ plan "Parametric — Studio Row" {
221
+ units mm
222
+ grid 50
223
+ scale 1:100
224
+ north up
225
+
226
+ # Plan-level constants (visible everywhere below — plan scope is global).
227
+ let WALL = 200
228
+ let W = 4000 # unit width
229
+ let H = 5000 # unit depth
230
+ let DOOR = 900
231
+ let WIN = 1600
232
+ let COUNT = 3 # number of units
233
+
234
+ # A value-function (pure closure) — area in square metres.
235
+ let aream2(w, h) = w * h / 1000000
236
+
237
+ # Per-unit names, indexed by the loop variable.
238
+ let names = ["Studio A", "Studio B", "Studio C"]
239
+
240
+ # Entrance doors swing outward throughout this plan (scoped default).
241
+ set door(swing: out)
242
+
243
+ for i in 0..COUNT {
244
+ let x = i * W
245
+ wall exterior thickness WALL { (x, 0) (x + W, 0) (x + W, H) (x, H) close }
246
+ room at (x, 0) size W x H label "{names[i]}"
247
+ furniture bed at (x + 300, 300) size 1500x2000 label "Bed"
248
+ furniture kitch at (x + W - 1900, 300) size 1600x600 label "Kitchen"
249
+ door at (x + W / 2, H) width DOOR wall exterior hinge left
250
+ window at (x + W / 2, 0) width WIN wall exterior
251
+
252
+ # The end unit carries a per-unit area dimension (computed by the function).
253
+ # Referenced to the outer face (y = H + WALL/2) so the extension lines start at
254
+ # the wall and read downward, away from the building.
255
+ if i == COUNT - 1 {
256
+ dim (x, H + WALL / 2)->(x + W, H + WALL / 2) offset 600 text "{aream2(W, H)} m² each"
257
+ }
258
+ }
259
+
260
+ # Overall run, dimensioned above the building: right-to-left so the offset lands
261
+ # ABOVE the row (outside), and referenced to the outer top face (y = -WALL/2).
262
+ dim (W * COUNT, 0 - WALL / 2)->(0, 0 - WALL / 2) offset 1300 text "{COUNT} units"
263
+ }
264
+ ```
265
+
266
+ ---
267
+
268
+ ## 2. Agent workflow
269
+
270
+ # ArchLang — author floor plans as code
271
+
272
+ ArchLang turns a small `.arch` text file into a professional floor-plan drawing. It is built for
273
+ agents: deterministic, self-correcting (errors carry a machine code, a prose `fix`, and often a
274
+ **machine-applicable** fix `arch fix` can apply), and verifiable without ever looking at an image
275
+ (`arch describe`).
276
+
277
+ ## Setup (zero-install)
278
+
279
+ The CLI runs straight from npm — no clone, no build:
280
+
281
+ ```bash
282
+ npx @chanmeng666/archlang help
283
+ ```
284
+
285
+ (Or `npm i -g @chanmeng666/archlang` to get a persistent `arch` binary.)
286
+
287
+ ## The loop (always follow this)
288
+
289
+ 1. **Learn the language first.** Run `arch spec` and read it — the entire language in one page
290
+ (~2k tokens). (`arch context` prints *everything*: spec + this workflow + CLI reference + error
291
+ catalog.) Do this before writing any `.arch`.
292
+ 2. **Write the plan** to a `.arch` file (or pipe via stdin with `-`), preferring the **placement
293
+ sugar** below so you never hand-compute a coordinate.
294
+ 3. **Render it:** `arch compile plan.arch -o plan.svg --json`. The JSON is `{ ok, diagnostics,
295
+ summary }`.
296
+ 4. **Auto-fix the mechanical faults:** if `ok` is false, run `arch fix plan.arch --dry-run --json`
297
+ to preview the **machine-applicable** edits (off-wall opening → attachment form, out-of-range
298
+ position clamped, …), then re-run without `--dry-run` to apply. Anything `fix` can't resolve stays
299
+ in `diagnostics[].fix` for you to edit by hand. Exit code `2` means a deterministic user error —
300
+ fix it, don't blindly retry (`1` = IO/internal, `3` = bad usage).
301
+ 5. **See the plan without an image:** `arch compile plan.arch -f txt` (or `arch preview plan.arch
302
+ --ascii`) prints a zero-dependency ASCII floor plan you can read straight from stdout.
303
+ 6. **Verify intent:** `arch describe plan.arch --json` returns the rooms (areas, adjacency), what each
304
+ door connects, and totals. Confirm the room count, labels, and areas match what was asked.
305
+ 7. **Gate on soundness — don't ship a flagged plan.** `arch validate plan.arch --strict --json`
306
+ (parse + resolve + lint). `--strict` makes **every advisory warning fail** (exit `2`) — the gate a
307
+ generation pipeline runs before it ships. Add `--graph g.json` to also assert the intended
308
+ room-to-room adjacency (`{ "living": ["kitchen","hall"], … }`); a mismatch fails. Read each
309
+ `diagnostics[].fix`, edit, and re-run until it passes — or, if a warning is deliberate, say so.
310
+ 8. **Fix furniture geometry:** `arch repair plan.arch -o fixed.arch` pushes furniture out of
311
+ walls/doorways/swing arcs (the geometric corrector; distinct from `fix`).
312
+ 9. **Show the user:** `arch preview plan.arch -o plan.png` renders a PNG (`--install` fetches the
313
+ optional renderer if missing).
314
+
315
+ ## Write it right the first time (placement sugar — the preferred path)
316
+
317
+ A geometry-blind generator that emits absolute coordinates produces plans that render but are
318
+ physically wrong (openings off their wall, furniture through walls). Author by **attachment** instead
319
+ — the compiler computes the coordinate, and fails loudly if the reference is ambiguous:
320
+
321
+ - **Attach openings to a wall by position, not `at (x,y)`.** `door on <wall> at <pos> …` /
322
+ `window on <wall> at <pos> …` / `opening on <wall> at <pos> …`, where `<pos>` is millimetres along
323
+ the wall or a percentage (`50%`). `swing into <room>` picks the swing direction toward a named room;
324
+ `hinge near start|end` hinges at the segment end nearer a wall end. (Off-wall/ambiguous →
325
+ `E_ATTACH_WALL_REF`; past the wall → `E_ATTACH_POS_RANGE`.)
326
+ - **Lay rooms with `strip`.** `strip right at (0,0) gap 0 height 4000 { room … room … }` places a row
327
+ (or column, with `down`/`up` + `width`) of rooms end to end — no per-room `at`.
328
+ - **Place furniture by anchor.** `furniture <kind> in <room> anchor <corner|edge> [inset <mm>] …`
329
+ snaps a piece flush to a room corner/edge; `against wall <id>` backs plumbing/kitchen fixtures onto a
330
+ real wall face. Both are closed-form and never float or penetrate.
331
+ - **Every room still needs a way in** — put a `door` or cased `opening` on a wall of *every* room
332
+ (an open-plan space still needs a modeled opening), and keep furniture out of the doorway approach
333
+ (≥300 mm) and the leaf's swing.
334
+ - **Absolute `at (x,y)` is the fallback**, not the default — reach for it only when no attachment
335
+ expresses what you mean.
336
+
337
+ See `examples/attached.arch` for a full one-bedroom authored this way, and `arch spec` for the grammar.
338
+
339
+ ## Self-correct with data, not guesswork
340
+
341
+ `arch compile --json` returns every problem as a `Diagnostic` with a byte span, `line`/`col`, a
342
+ catalogued `E_*`/`W_*` code, and a prose `fix`. Where the correction is a mechanical text edit, the
343
+ diagnostic also carries **machine-applicable `fixes`**:
344
+
345
+ - **`arch fix`** applies them in a bounded, self-checking fixpoint — **only `machine-applicable` by
346
+ default** (`--unsafe` also applies `maybe-incorrect`; `--dry-run` previews; `--force` keeps a pass
347
+ that would otherwise roll back). Use it to clear the syntactic faults before you touch anything by
348
+ hand.
349
+ - **`arch fix` is syntactic; `arch repair` is geometric.** `fix` rewrites text where the right text is
350
+ known (e.g. an off-wall door → the attachment form); `repair` *moves furniture* to a position no
351
+ text edit could express. They compose — fix first, then repair.
352
+
353
+ ## Fix the topology: add doors & windows the room graph needs
354
+
355
+ `fix`/`repair` never add a door or a window — *where* to put one is a design choice the compiler must
356
+ not make. When lint reports `W_ROOM_UNREACHABLE`, `W_ROOM_DISCONNECTED`, `W_NO_ENTRANCE`,
357
+ `W_BATH_VIA_BEDROOM`, or `W_BEDROOM_NO_WINDOW`, ask ArchLang for candidates:
358
+
359
+ - **`arch suggest plan.arch --json`** returns ready-to-paste `door`/`window` statements (in the
360
+ **attachment form**) plus a rationale for each — for the unreachable room or windowless bedroom.
361
+ Choose one and insert it, then re-run the loop. This replaces hand-computing coordinates.
362
+ - **Manual fallback** (if `suggest` offers nothing that fits): from `describe().access`, connect each
363
+ unreachable room in priority — (1) a new **exterior entrance** `door on <exterior wall> at <pos>`
364
+ into a living/kitchen/hall with an exterior edge (avoids routing through a bedroom); else (2) a
365
+ `door on <shared wall> at <pos>` to an adjacent reachable, non-bedroom room; and give a windowless
366
+ bedroom a `window on <its exterior wall> at <pos> width 1200`. Never make a bathroom reachable only
367
+ through a bedroom. Then `arch repair` (a new door may pinch furniture) and re-gate.
368
+
369
+ > An *existing* opening `validate` reports **off its wall** (`W_DOOR_OFF_WALL` /
370
+ > `W_WINDOW_OFF_WALL` / `W_OPENING_OFF_WALL`) is a mis-coordinate, not a missing connector — run
371
+ > `arch fix` (it rewrites it to the attachment form) rather than adding a new one.
372
+
373
+ ## Structured authoring & constrained generation (optional)
374
+
375
+ - **Plan JSON.** Author or ingest the machine-native shape and compile it: `arch compile plan.json
376
+ --from-json -o out.svg`. The schema is served at
377
+ [`/plan.schema.json`](https://archlang-docs.vercel.app/plan.schema.json).
378
+ - **GBNF.** To force a local model to emit only parseable ArchLang, constrain decoding with
379
+ [`/archlang.gbnf`](https://archlang-docs.vercel.app/archlang.gbnf).
380
+
381
+ ## Commands
382
+
383
+ ```bash
384
+ arch spec # the whole language in one page — READ THIS FIRST
385
+ arch context # everything in one call: spec + this workflow + CLI reference + error catalog
386
+ arch manifest --json # the whole CLI API as data: commands, flags, formats, lint rules, error codes
387
+ arch compile plan.arch -o out.svg --json # render (also -f dxf|txt|pdf|png)
388
+ arch compile plan.arch -f txt # zero-dependency ASCII text plan on stdout (also `preview --ascii`)
389
+ arch compile plan.json --from-json -o out.svg # compile structured Plan JSON (see /plan.schema.json)
390
+ echo '<source>' | arch compile - -o - -f svg # compile stdin → SVG on stdout
391
+ arch fix plan.arch --dry-run --json # preview the machine-applicable diagnostic fixes (drop --dry-run to apply)
392
+ arch suggest plan.arch --json # advisory door/window statements for unreachable rooms / windowless bedrooms
393
+ arch describe plan.arch --json # semantic facts: rooms, areas, adjacency, door connections, circulation
394
+ arch lint plan.arch --json # architectural soundness warnings
395
+ arch validate plan.arch --strict --json # parse + resolve + lint; --strict fails on warnings (the ship gate)
396
+ arch validate plan.arch --graph g.json --json # also check interior-door adjacency against an intended graph
397
+ arch repair plan.arch -o fixed.arch # geometric corrector: furniture out of walls/doorways/swings + change log
398
+ arch fmt plan.arch --write # canonical formatting
399
+ arch batch a.arch b.arch -f svg --json # render many plans/variants at once → results[]
400
+ arch preview plan.arch -o plan.png # render a PNG to SHOW the user (--install fetches resvg if missing)
401
+ arch new -o plan.arch # scaffold a starter plan
402
+ arch explain E_ROOM_SIZE --json # look up any diagnostic code
403
+ ```
404
+
405
+ (An optional MCP server, `@chanmeng666/archlang-mcp`, wraps these same library functions for
406
+ MCP-native hosts — prefer the CLI when you have a shell; it costs nothing in context until called.)
407
+
408
+ ## Key rules (full detail in `arch spec`)
409
+
410
+ - **Units are millimetres** (a 4 m wall is `4000`); **origin top-left, +x right, +y DOWN**.
411
+ - **Attach openings to walls** (`on <wall> at <pos>`) so they always sit on a segment; a raw `at`
412
+ that lands off any wall warns (and `arch fix` rewrites it).
413
+ - **Fixtures draw real symbols:** `furniture wc|basin|shower|bathtub|kitchen_sink|counter|fridge|stove …`
414
+ renders a plan symbol; put fixtures in every bath and kitchen so lint stays quiet.
415
+ - **`dims auto`** draws dimension strings for you (`overall`, `rooms`, `walls`, or `all`).
416
+ - Edit is cheap: "make the bedroom 1 m wider" is a one-number change, then recompile.
417
+
418
+ Treat the CLI as the source of truth — author, render, and verify through it rather than reasoning
419
+ about SVG by hand.
420
+
421
+ ---
422
+
423
+ ## 3. CLI reference
424
+
425
+ The `arch` CLI is the agent interface. ArchLang compiler — agent-native CLI. Compile .arch floor-plan source to SVG/PNG/PDF/DXF.
426
+
427
+ Every command takes `--json` (structured result on stdout, messages on stderr) and reads source
428
+ from a file or stdin (`-`). There is intentionally no MCP server — this CLI is the whole API.
429
+
430
+ **Exit codes:** `0` ok · `1` internal / IO error · `2` user-source error (deterministic — fix it, don't blindly retry) · `3` bad usage
431
+
432
+ **Global flags:** `--json` structured result on stdout, messages on stderr · `--quiet|-q` suppress human messages on stderr
433
+
434
+ **Output formats (`-f`):** `svg` · `dxf` · `txt` · `pdf` (needs `pdfkit`) · `png` (needs `@resvg/resvg-js`)
435
+
436
+ ### Commands
437
+
438
+ **`arch compile`** — render a plan to SVG/DXF/PDF/PNG
439
+ - input: <file.arch|-> (Plan JSON with --from-json) → output: file (or stdout with -o -)
440
+ - flags: `--out|-o <file|->` output destination ('-' = stdout) · `--format|-f <svg|dxf|txt|pdf|png>` output format (default svg) · `--width|-w <px>` page width hint in pixels · `--cols <n>` text renderer (-f txt / preview --ascii) grid width in characters (default 80) · `--charset <unicode|ascii>` text renderer glyph set (default unicode) · `--overlay <circulation>` draw an opt-in diagnostic overlay (circulation walks + bottleneck markers); default output is unchanged · `--error-svg` on a broken plan, still emit a self-describing error-card image listing the diagnostics (exit code stays 2) · `--accessible` emit <title>/<desc>/role/aria accessibility metadata (the describe() caption) into the SVG; default output is unchanged · `--from-json` read the input as Plan JSON (RPLAN shape) instead of .arch, convert it, then compile · `--install` auto-install the optional dep for the chosen format if missing (PNG/PDF) · `--json` structured result on stdout, messages on stderr · `--quiet|-q` suppress human messages on stderr
441
+
442
+ **`arch batch`** — render many .arch files in one call, concurrently
443
+ - input: <a.arch> <b.arch> … → output: one file per input; --json gives a results[] array
444
+ - flags: `--out|-o <dir>` output directory (default: alongside each input) · `--format|-f <svg|dxf|txt|pdf|png>` output format (default svg) · `--jobs|-j <n>` max concurrent renders (default: CPU count) · `--json` structured result on stdout, messages on stderr · `--quiet|-q` suppress human messages on stderr
445
+
446
+ **`arch md`** (aliases: `markdown`) — render every ```arch block in a Markdown file and rewrite to image links
447
+ - input: <doc.md> → output: out.md + one image per block
448
+ - flags: `--out|-o <out.md>` rewritten Markdown destination · `--format|-f <svg|dxf|txt|pdf|png>` output format (default svg) · `--error-svg` on a broken plan, still emit a self-describing error-card image listing the diagnostics (exit code stays 2) · `--json` structured result on stdout, messages on stderr · `--quiet|-q` suppress human messages on stderr
449
+
450
+ **`arch preview`** — render a PNG you can look at (zero-install where the optional binary is present)
451
+ - input: <file.arch|-> → output: PNG file (or ASCII text on stdout with --ascii)
452
+ - flags: `--out|-o <out.png>` PNG destination (default: <name>.png) · `--scale|-s <n>` raster scale (default 2) · `--ascii` print a zero-dependency ASCII text plan to stdout instead of a PNG · `--cols <n>` text renderer (-f txt / preview --ascii) grid width in characters (default 80) · `--charset <unicode|ascii>` text renderer glyph set (default unicode) · `--error-svg` on a broken plan, still emit a self-describing error-card image listing the diagnostics (exit code stays 2) · `--install` auto-install @resvg/resvg-js if missing, then render · `--json` structured result on stdout, messages on stderr · `--quiet|-q` suppress human messages on stderr
453
+
454
+ **`arch watch`** — recompile on save (interactive)
455
+ - input: <file.arch> → output: file, rewritten on each save
456
+ - flags: `--out|-o <file|->` output destination ('-' = stdout) · `--format|-f <svg|dxf|txt|pdf|png>` output format (default svg) · `--width|-w <px>` page width hint in pixels
457
+
458
+ **`arch validate`** — parse + resolve + lint, no render (is it valid & sound?)
459
+ - input: <file.arch|-> → output: diagnostics (plus a graph{} report with --graph)
460
+ - flags: `--strict|--fail-on-warning` advisory warnings fail too (exit 2) · `--graph <graph.json>` also check the plan's interior-door adjacency against an intended graph (bare dict or {input_graph:{…}}); mismatch → exit 2 · `--json` structured result on stdout, messages on stderr · `--quiet|-q` suppress human messages on stderr
461
+
462
+ **`arch describe`** — semantic facts: rooms, areas, adjacency, what doors connect
463
+ - input: <file.arch|-> → output: facts (JSON or a summary)
464
+ - flags: `--json` structured result on stdout, messages on stderr · `--quiet|-q` suppress human messages on stderr
465
+
466
+ **`arch lint`** — architectural soundness warnings
467
+ - input: <file.arch|-> → output: W_* warnings
468
+ - flags: `--profile <residential-basic|accessibility-advisory>` advisory ruleset · `--strict|--fail-on-warning` warnings fail (exit 2) · `--json` structured result on stdout, messages on stderr · `--quiet|-q` suppress human messages on stderr
469
+
470
+ **`arch ast`** — parse only (no resolve/render) and print the span-bearing AST as JSON
471
+ - input: <file.arch|-> → output: AST JSON (scripting nodes unexpanded)
472
+ - flags: `--json` structured result on stdout, messages on stderr · `--quiet|-q` suppress human messages on stderr
473
+
474
+ **`arch complete`** — completion items in scope at a source byte offset (the LSP completion() core)
475
+ - input: <file.arch|-> → output: { items: [...] } completion items
476
+ - flags: `--at <byteOffset>` source byte offset to list completions at (required) · `--json` structured result on stdout, messages on stderr · `--quiet|-q` suppress human messages on stderr
477
+
478
+ **`arch fmt`** — canonical formatting
479
+ - input: <file.arch|-> → output: formatted source (or in place with --write)
480
+ - flags: `--write` format the file in place · `--json` structured result on stdout, messages on stderr · `--quiet|-q` suppress human messages on stderr
481
+
482
+ **`arch repair`** — explicit source-to-source corrector (furniture out of walls) + change log
483
+ - input: <file.arch|-> → output: corrected source + change log on stderr
484
+ - flags: `--out|-o <file|->` output destination ('-' = stdout) · `--json` structured result on stdout, messages on stderr · `--quiet|-q` suppress human messages on stderr
485
+
486
+ **`arch fix`** — apply the machine-applicable fix suggestions on a plan's diagnostics (bounded fixpoint)
487
+ - input: <file.arch|-> → output: fixed source (to the input file or -o) + change log on stderr
488
+ - flags: `--out|-o <file|->` output destination ('-' = stdout) · `--unsafe` also apply `maybe-incorrect` fixes (default: machine-applicable only) · `--dry-run` compute the result but do not write it · `--force` keep a pass even if it raises the error count · `--json` structured result on stdout, messages on stderr · `--quiet|-q` suppress human messages on stderr
489
+
490
+ **`arch suggest`** — advisory topology suggestions as data (door/window statements that resolve reachability/window faults)
491
+ - input: <file.arch|-> → output: suggestions (JSON or a summary)
492
+ - flags: `--json` structured result on stdout, messages on stderr · `--quiet|-q` suppress human messages on stderr
493
+
494
+ **`arch manifest`** (aliases: `capabilities`) — this document: the whole CLI API as structured data
495
+ - input: none → output: the manifest (JSON or a summary)
496
+ - flags: `--json` structured result on stdout, messages on stderr
497
+
498
+ **`arch spec`** — print the one-prompt language spec (spec.llm.md)
499
+ - input: none → output: the spec
500
+ - flags: `--json` structured result on stdout, messages on stderr
501
+
502
+ **`arch context`** — print the full bundled agent context (spec + workflow + CLI + errors)
503
+ - input: none → output: the full agent context (llms-full.txt)
504
+ - flags: `--json` structured result on stdout, messages on stderr
505
+
506
+ **`arch new`** (aliases: `init`) — scaffold a starter .arch
507
+ - input: none → output: starter source
508
+ - flags: `--out|-o <file>` write the starter here · `--force` overwrite an existing file · `--json` structured result on stdout, messages on stderr
509
+
510
+ **`arch explain`** — look up an error code (cause / fix / example)
511
+ - input: <CODE> → output: catalog entry
512
+ - flags: `--json` structured result on stdout, messages on stderr
513
+
514
+ ---
515
+
516
+ ## 4. Diagnostic catalog
517
+
518
+ Every diagnostic carries a stable code and a `fix`. Look one up with `arch explain <CODE>`.
519
+ **43 errors** (abort rendering) · **31 warnings** (advisory; `validate --strict` fails on them too).
520
+
521
+ ### Errors
522
+
523
+ - `E_ACC_PLACEMENT` — `accTitle`/`accDescr` used outside the plan level. **Fix:** Move the `accTitle`/`accDescr` line up to the plan body, alongside `units`/`north`.
524
+ - `E_ARGCOUNT` — Component called with the wrong number of arguments. **Fix:** Pass exactly one argument per declared parameter.
525
+ - `E_ARITY` — Built-in function called with the wrong number of arguments. **Fix:** Check the function's arity; most built-ins take one argument.
526
+ - `E_ASSIGN_UNDEF` — Assignment to an undeclared name. **Fix:** Declare it first with `let`, or fix a typo in the name.
527
+ - `E_ATTACH_POS_RANGE` — Opening attachment position is out of range. **Fix:** Use a percentage in 0–100%, a millimetre distance within the wall's run, or `center`.
528
+ - `E_ATTACH_WALL_REF` — Opening attached to an unknown or ambiguous wall. **Fix:** Reference an existing, unique wall id (add `id=` to the wall if needed).
529
+ - `E_CALL_DEPTH` — Value-function call stack too deep. **Fix:** Make the recursion terminate, or rewrite it iteratively with a bounded `while`.
530
+ - `E_COLUMN_SIZE` — Column must have a positive size. **Fix:** Give the column a positive `size W x H`.
531
+ - `E_DIV_ZERO` — Division or modulo by zero. **Fix:** Guard the divisor, or use a non-zero value.
532
+ - `E_DOMAIN` — Math domain error. **Fix:** Pass a value within the function's domain.
533
+ - `E_DOOR_WIDTH` — Door must have a positive width. **Fix:** Give the door a positive `width`.
534
+ - `E_DUP_ID` — Duplicate element id. **Fix:** Rename one of them, or drop the explicit id to auto-generate a unique one.
535
+ - `E_FURN_AGAINST` — Invalid `against wall` fixture placement. **Fix:** Name an existing wall id, add `segment <n>` for multi-segment walls, give `side left|right`, keep the segment axis-aligned, and drop any explicit `rotate`.
536
+ - `E_FURN_ROOM` — Furniture placed `in` an unknown room. **Fix:** Use the id of an existing `room id=…`, or drop the `in` clause.
537
+ - `E_FURN_ROTATE` — Furniture rotation must be a quarter-turn. **Fix:** Use a quarter-turn: `rotate 0|90|180|270`.
538
+ - `E_FURN_SIZE` — Furniture must have a positive size. **Fix:** Give the item a positive `size W x H`.
539
+ - `E_IMPORT_BAD_SPEC` — Malformed import spec. **Fix:** Use a relative path ("lib/x.arch") or a namespaced spec ("@scope/name:1.0.0").
540
+ - `E_IMPORT_CONFLICT` — Imported name conflicts with an existing component. **Fix:** Rename with `as`, or remove the duplicate.
541
+ - `E_IMPORT_CYCLE` — Cyclic import. **Fix:** Break the cycle so module dependencies form a tree.
542
+ - `E_IMPORT_NOT_EXPORTED` — Imported name is not exported by the module. **Fix:** Import a name the module actually defines (check its `component`s).
543
+ - `E_IMPORT_NOT_FOUND` — Import path could not be resolved. **Fix:** Check the path (relative to the importing file) and that the file exists.
544
+ - `E_IMPORT_PARSE` — Imported module has a parse error. **Fix:** Fix the syntax error in the imported module.
545
+ - `E_INDEX` — Array index out of range. **Fix:** Clamp or check the index against `len(arr)`.
546
+ - `E_JSON_KIND` — Unknown element kind in plan JSON. **Fix:** Use one of the supported kinds: opening `kind` must be `door` | `window` | `opening`.
547
+ - `E_JSON_SCHEMA` — Plan JSON does not match the schema. **Fix:** Fix the value at the reported JSON path (the message names it, e.g. `/rooms/0/width`); express geometry as concrete numbers, and author scripting/imports in `.arch` source instead.
548
+ - `E_LAYOUT_CYCLE` — Relational room placement forms a cycle. **Fix:** Break the cycle by giving one of the rooms absolute `at (x,y)` coordinates.
549
+ - `E_LAYOUT_REF` — Relational placement references an unknown room. **Fix:** Reference an existing room id, or fix the typo.
550
+ - `E_OPENING_WIDTH` — Opening must have a positive width. **Fix:** Give the opening a positive `width`.
551
+ - `E_PLACE_REF` — Furniture placed in an unknown or non-absolute room. **Fix:** Reference an existing room given absolute `at (x,y)` coordinates.
552
+ - `E_PNG_DEPENDENCY` — PNG/PDF export needs an optional dependency that is not installed. **Fix:** Install the optional dependency (`npm install @resvg/resvg-js`), or re-run with `--install` to fetch it automatically, or render to SVG/DXF (zero-dependency).
553
+ - `E_RANGE_LIMIT` — Range too large. **Fix:** Use a smaller range, or restructure to avoid materializing it.
554
+ - `E_RECURSION` — Component recursion too deep. **Fix:** Add a base case so the recursion terminates.
555
+ - `E_REDEF` — Name already defined in this scope. **Fix:** Rename one binding, or use `NAME = …` to reassign instead of redeclaring.
556
+ - `E_ROOM_SIZE` — Room must have a positive size. **Fix:** Give the room a positive `size W x H`.
557
+ - `E_STRIP_NEST` — Illegal `strip` nesting. **Fix:** Move the `strip` to the plan body, alongside the other elements.
558
+ - `E_STRIP_SIZE` — Room in a `strip` is missing a size. **Fix:** Give the room a `size <main>` (main-axis extent) plus either a strip `height`/`width` or its own `size <main>x<cross>`.
559
+ - `E_TYPE` — Type mismatch. **Fix:** Convert or supply the expected type.
560
+ - `E_UNKNOWN_COMPONENT` — Unknown component. **Fix:** Define the component, import it, or fix the name (see the suggestion hint).
561
+ - `E_UNKNOWN_FN` — Unknown function. **Fix:** Define it with `let f(…) = …`, or fix the name.
562
+ - `E_UNKNOWN_REF` — Unknown reference. **Fix:** Declare it with `let`, pass it as a parameter, or fix the typo.
563
+ - `E_WALL_THICKNESS` — Wall must have a positive thickness. **Fix:** Give the wall a positive `thickness`.
564
+ - `E_WHILE_LIMIT` — `while` exceeded its iteration cap. **Fix:** Ensure the loop body updates a binding so the condition eventually fails.
565
+ - `E_WINDOW_WIDTH` — Window must have a positive width. **Fix:** Give the window a positive `width`.
566
+
567
+ ### Warnings
568
+
569
+ - `W_BATH_VIA_BEDROOM` — Bathroom is reachable only through a bedroom. **Fix:** Add a door connecting the bathroom to a hall/living space, or route circulation so it is not reached only via a bedroom.
570
+ - `W_BEDROOM_NO_WINDOW` — Bedroom has no window. **Fix:** Add a `window` on an exterior wall of the room.
571
+ - `W_CIRCUITOUS_PATH` — A room is reached by a very roundabout path. **Fix:** Add a more direct connection — a door or a hall — so the room is not reached the long way round.
572
+ - `W_DOOR_CLEARANCE` — Door is narrower than the minimum clear width. **Fix:** Widen the door to at least the minimum clear width.
573
+ - `W_DOOR_OFF_WALL` — Door does not lie on any wall. **Fix:** Move the door onto a wall, or name its host with `wall <id|category>`. The diagnostic points at the nearest wall.
574
+ - `W_DOORWAY_BLOCKED` — A doorway's landing is blocked. **Fix:** Clear the space directly in front of and behind the door, or move the door.
575
+ - `W_DUP_ACC_METADATA` — Duplicate `accTitle`/`accDescr`. **Fix:** Keep a single `accTitle` and a single `accDescr`; delete the extra line(s).
576
+ - `W_EMPTY_PLAN` — Empty plan. **Fix:** Add at least one element (wall, room, …).
577
+ - `W_FIXTURE_FLOATING` — A plumbing/kitchen fixture is not against a wall. **Fix:** Move the fixture so one edge is against a wall (supply/waste/venting runs in the wall), or remove it.
578
+ - `W_FIXTURE_WRONG_ROOM` — Fixture sits outside its declared room. **Fix:** Move the fixture inside the named room, or correct the `in <roomId>`.
579
+ - `W_FURN_CLEARANCE` — A fixture's use-space is blocked. **Fix:** Leave the catalogued clearance clear in front of the fixture, or move the obstructing furniture.
580
+ - `W_FURNITURE_OVERLAP` — Two pieces of furniture overlap. **Fix:** Move or resize one so they no longer intersect; leave a walkway between them.
581
+ - `W_FURNITURE_WALL_COLLISION` — Furniture penetrates a wall. **Fix:** Move or resize the piece so it sits fully inside the room (against the wall face, not through it), or anchor it with `against wall <id>`.
582
+ - `W_HATCH_SCALE` — Hatch scale must be positive; using 1. **Fix:** Use a positive `scale`.
583
+ - `W_NO_ENTRANCE` — The plan has no exterior door. **Fix:** Add a `door` on an `exterior` wall.
584
+ - `W_OPENING_OFF_WALL` — Opening does not lie on any wall. **Fix:** Move the opening onto a wall, or name its host with `wall <id|category>`. The diagnostic points at the nearest wall.
585
+ - `W_PATH_TOO_NARROW` — The walk to a room squeezes below a passable width. **Fix:** Widen the tightest door/opening on the route, or move the furniture pinching it, so the whole path clears the minimum width.
586
+ - `W_ROOM_DISCONNECTED` — Room has no door — it can't be entered. **Fix:** Add a `door` on one of the room's walls.
587
+ - `W_ROOM_NO_CLEAR_PATH` — A room cannot be entered or crossed. **Fix:** Open up the layout: move or shrink the furniture nearest the door so there is a continuous walkable strip from each entrance into the room.
588
+ - `W_ROOM_NO_FIXTURE` — Bathroom or kitchen has no fixtures. **Fix:** Place the expected fixtures — e.g. import `lib/fixtures.arch` and add a `wc`, `basin`, `shower`, or `kitchen_sink`.
589
+ - `W_ROOM_NOT_ENCLOSED` — Bathroom is not fully enclosed. **Fix:** Extend the partition so the room's perimeter is walled on all sides (a door/window in the wall is fine — only a missing wall counts).
590
+ - `W_ROOM_OVERLAP` — Rooms overlap. **Fix:** Adjust positions/sizes if the overlap is unintended (it is allowed).
591
+ - `W_ROOM_TOO_SMALL` — Room is implausibly small. **Fix:** Increase its `size`, or merge it into an adjacent space.
592
+ - `W_ROOM_UNREACHABLE` — Room cannot be reached from the entrance. **Fix:** Add a door or cased `opening` linking it (directly or through a hall) to a space that reaches the entrance.
593
+ - `W_SANITIZED_CONFIG` — A disallowed config value was stripped. **Fix:** Use a plain colour/string value (no `<`, `>`, or `url(data:…)`).
594
+ - `W_SWING_OBSTRUCTED` — Door swing is obstructed. **Fix:** Move the door or the obstruction, flip the `hinge`/`swing`, or use a sliding door so the leaf clears.
595
+ - `W_SWING_ROOM_NOT_ADJACENT` — `swing into <room>` names a room the door does not border. **Fix:** Point `swing into` at a room the door actually opens onto, or use explicit `swing in|out`. The door falls back to its default swing.
596
+ - `W_UNKNOWN_MATERIAL` — Unknown wall material; using the default hatch. **Fix:** Use a known material (e.g. brick, concrete, insulation, tile) or omit it.
597
+ - `W_UNKNOWN_STYLE_KEY` — Unknown style key. **Fix:** Use a valid key (e.g. fill / stroke / label, depending on the kind).
598
+ - `W_UNKNOWN_THEME_KEY` — Unknown theme key. **Fix:** Use a known theme key (see the language reference / hover).
599
+ - `W_WINDOW_OFF_WALL` — Window does not lie on any wall. **Fix:** Move the window onto a wall, or name its host with `wall <id|category>`. The diagnostic points at the nearest wall.