@wairon/cli 5.1.1-dev.102 → 5.1.1-dev.104

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.
package/docs/cli.md ADDED
@@ -0,0 +1,814 @@
1
+ # wairon — CLI Reference
2
+
3
+ All commands operate on the `.wai/` directory in the current project. Global
4
+ flags: `--verbose`, `--silent`, `-v`/`--version`.
5
+
6
+ ---
7
+
8
+ ## Project
9
+
10
+ ### `wairon init [-y, --yes] [--pack <source>]`
11
+ Bootstrap `.wai/` in the current project: project config, the SDD spec tree
12
+ (an L0 `.index.yaml` is seeded), the shared `.wai/context/` and the AI guide
13
+ (`CLAUDE.md` / `GEMINI.md`), the project-local MCP registration, and the SDD
14
+ skills installed into each selected target tool. No agent files are written:
15
+ agents are served as live briefs (`sdd_get_agent_brief`, `wairon agent brief
16
+ <id>`) unless the project opts into `rules.materializeAgentFiles: true`. `--yes`
17
+ uses defaults without prompts (a shell with no terminal needs it): the project name from the folder, profile backend, target claude only — another tool is one interactive answer or one `targets` entry away;
18
+ `--pack <source>` (repeatable) vendors + registers an extension pack right after
19
+ init (see `wairon pack`). Re-running on an
20
+ initialized project is a no-op that points you back to the SDD flow. Run in a
21
+ folder an existing project binds (see *Which project a command acts on* below),
22
+ it asks on a terminal whether to make that folder a **member** of the parent —
23
+ which edits the parent's `project.yaml` — and with `--yes` it refuses, writing
24
+ nothing and printing the exact `wairon member add <alias> <path> --project` to
25
+ run from the parent (or: make the folder its own repository with `git init` and
26
+ re-run `wairon init` for an independent project).
27
+
28
+ #### Which project a command acts on
29
+ Every command, the MCP server (`wairon mcp serve`) and `wairon dev` bind the same
30
+ project: from the folder you are in, the nearest folder whose `.wai/specs` holds
31
+ an L0 — but the walk **never climbs past the repository root** (the nearest folder
32
+ holding `.git`) unless a project above it declares the crossing as a member
33
+ (`members` naming the repository's folder, or a folder inside it). A stray `.wai`
34
+ in a parent folder therefore binds nothing in a child repository. Outside any
35
+ repository the walk is unbounded. Each command that acts on a project prints
36
+ `project <id> at <root>` on stderr first, so `wairon export` and every `--json`
37
+ output keep a pure stdout.
38
+
39
+ ### `wairon status [--subsystem <id>] [--no-recursive] [--all]`
40
+ Print the SDD spec tree as a hierarchy. Its percentages measure **authoring
41
+ progress** — 80% once a component's component, contract and implementation specs
42
+ are written, 100% once its implementation names source files that exist, capped
43
+ at 50% while any of them is `draft` or `design` — and are
44
+ separate from **approval**, which is the lock record in `.wai/lock.json` and is
45
+ printed on its own line. `wairon lock` never rewrites a spec's status, so an
46
+ approved tree can still show draft specs. `--subsystem <id>` shows one
47
+ subsystem; `--no-recursive` shows this project without its members; `--all` lists
48
+ every spec that moved since the approval instead of the first few.
49
+ Each member project prints as `[Project] alias (id)` holding its own subsystems,
50
+ with its **approval state** computed at the member's own root (`approved`,
51
+ `drifted`, `never`) and how this project's lock pinned it (`matches`, `moved`,
52
+ `unpinned`). The report closes with this project's own state. Asked at the parent
53
+ or at the member, the answer is the same. When an external moved in its live
54
+ producer, drifted or could not be compared, an **Externals** section follows
55
+ the verdict (the same advisory comparison `validate` prints); it never changes
56
+ the exit code.
57
+
58
+ ### `wairon validate [--ci] [--all] [--subsystem <id>] [--family] [--no-recursive]`
59
+ Run the architecture-conformance gate over the spec tree: reference integrity,
60
+ contract↔implementation method symmetry, narrative-call resolution, component
61
+ stereotype dependency rules, and dependency-cycle detection. `--ci` treats
62
+ warnings as errors (notices are printed and counted, never fatal).
63
+
64
+ - The first 100 findings per severity are printed; the rest are counted ("N
65
+ more not shown") and a per-code total follows. `--all` prints every finding.
66
+ - `--subsystem <id>` validates one subsystem.
67
+ - At a project that declares members, `validate` is the **family run**: every
68
+ member's own gate, and this project's externals composed against their live
69
+ producers. `--no-recursive` runs this project's gate alone; `--family` runs
70
+ the family run from a member.
71
+ - **Externals, compared live (advisory: the pin gates).** The gate judges each
72
+ external against its pin. Then every external the run did not compose is
73
+ compared with its **live** producer, offline, and printed in its own section:
74
+ `EXTERNAL_LIVE_INCOMPATIBLE` (warning: a used member changed at signature
75
+ level, was renamed — the new name is given — or is gone; it names the specs
76
+ that use it and the fix: adapt the uses, then `wairon externals pin <alias>`),
77
+ `EXTERNAL_DRIFTED` (notice: the producer moved, nothing used did) and
78
+ `EXTERNAL_LIVE_UNCOMPARED` (notice: the producer could not be read — a git
79
+ external is never fetched here; `wairon externals status` compares it). These
80
+ findings are **advisory**: printed and counted, but neither the exit code nor
81
+ `--ci` ever decides on them. A family run composes the externals in its reach
82
+ as its gate (`EXTERNAL_INCOMPATIBLE`, error) and gives them no second word.
83
+ - **Planned code is a notice.** An implementation whose `sourcePath` names a
84
+ file not written yet is `SOURCE_FILE_PLANNED`, and one naming no file is
85
+ `MISSING_SOURCE_PATH` — both notices — until realization begins (any file the
86
+ implementation names exists). From then on a missing file is
87
+ `MISSING_SOURCE_FILE` (error) and a contract method naming no file is
88
+ `METHOD_SOURCE_PATH_MISSING` (warning). `rules.conformance.requireCode: true`
89
+ in `project.yaml` reports the two notices as errors, for a CI that must say
90
+ every designed implementation has code.
91
+
92
+ ### `wairon generate [--target <name>] [--domain <id>] [--domains <ids>] [--root] [--family] [--no-prune] [--global] [--dry-run]`
93
+ Reconcile the generated guides, skills and context, and — only when the project
94
+ opts in — write the agent files from the spec-derived topology.
95
+
96
+ **Agent files are opt-in.** With `rules.materializeAgentFiles` off (the default
97
+ for a new project) no agent file is written, and a run removes the ones an
98
+ earlier version wrote; agents are served as live briefs instead
99
+ (`sdd_get_agent_brief`, `wairon agent brief <id>`). Set
100
+ `rules.materializeAgentFiles: true` in `.wai/project.yaml` to keep agent files
101
+ on disk (`rules.generateComponentImplementers: true` adds one implementer per
102
+ component).
103
+
104
+ - `--target <name>` limits generation to one of the project's configured
105
+ targets (`claude`, `agy`, …); an unknown name exits non-zero and lists the
106
+ targets the project configures.
107
+ - `--domain <id>` / `--domains <ids>` / `--root` limit it to some domains.
108
+ - `--no-prune` keeps wairon-managed agent files that left the topology.
109
+ - `--global` also writes a target whose output directory is outside the project
110
+ root, backing up each file it replaces.
111
+ - `--dry-run` previews without writing.
112
+
113
+ `generate` writes **only this project's** outputs. A parent's topology lists a
114
+ member's agents by reference (`delegatesTo: <alias>::<agentId>`) instead of
115
+ copying them, and a brief for `<alias>::<agent>` composes at the member's own
116
+ root. `--family` also generates each member's own layer, in its own root.
117
+ (`--no-recurse` is accepted for one release; not cascading is now the default.)
118
+
119
+ ### `wairon lock [-y, --yes] [--subsystem <id>]`
120
+ Review and approve the design. Validates the spec tree **as if complete** (full
121
+ strictness, no draft-status relaxation) and — only if the **design** passes —
122
+ records the current tree as approved and refreshes this project's generated
123
+ outputs (agent files only when `rules.materializeAgentFiles` is on).
124
+
125
+ **It approves; it does not change statuses.** No spec's `status` is rewritten:
126
+ approval lives in `.wai/lock.json`. Implementation gates on that approval —
127
+ `wairon lock-check` in CI, `sdd_get_status` for an AI tool — not on
128
+ `status: complete`. Commit `.wai/lock.json` with the change it approves.
129
+
130
+ **What it certifies is the design.** Only design findings can refuse a lock.
131
+ Code-conformance findings (the code↔spec checks) are recorded **beside** the
132
+ claim in the record's `code` block, with the analyzer that produced them, and
133
+ printed as `code: N error(s), … recorded beside the claim`. CI enforces them:
134
+ `wairon validate --ci` still fails on a code error. A design can be approved
135
+ before its code exists.
136
+
137
+ **It approves this project only.** The gate identity it records covers this
138
+ project's own specs, the design doctrine, its declared inputs, its `composition`
139
+ block, and each direct member's **composition subject** — the `stateId` in the
140
+ member's own lock record. The record (format 3) lists each direct member under
141
+ `members` with its subject and state (`approved`, `drifted`, `never`). Nothing is
142
+ written below the project: each member locks at its own root. With
143
+ `composition.requireApprovedMembers: true` in `project.yaml`, the lock refuses
144
+ while a direct member is drifted or never approved, naming each.
145
+
146
+ The identity is captured **before** validating and confirmed **before** writing:
147
+ if a spec, the doctrine, an input or a member's approval moves while the lock
148
+ runs, it refuses and writes nothing. (`--no-recursive` is accepted for one
149
+ release and changes nothing: a lock never reaches below its project.)
150
+
151
+ **Code linkage is not part of the approval.** The gate identity and the per-spec
152
+ digests read each spec's **design**: where it is realized — `sourcePath`,
153
+ `symbol`, `exportedVia`, `simPath`, `injectedParams`, conformance tiers, a mount's
154
+ `via`, `externalLinks` — and the `createdAt`/`updatedAt` timestamps are left
155
+ out (`specsReading: design`). Linking code to an approved design, or a change and
156
+ its revert, never stales the lock; declare planned `sourcePath`s at design time.
157
+
158
+ A **format-2** record (written before this reading) still passes `lock-check`
159
+ while nothing it covered moved. `wairon doctor --fix` re-expresses it as format 3
160
+ in place — keeping `lockedAt`, `lockedBy`, the results and the member pins, and
161
+ recording `reexpressed: { at, fromAlgorithm, fromReading, by }`. A format-2 record
162
+ that no longer matches reads stale once; one `wairon lock` clears it for good.
163
+
164
+ It writes **nothing into your spec tree**. The approval is one sha256 per spec
165
+ (over its design view) on `.wai/lock.json`, the record that was always committed — so your
166
+ teammates, a fresh clone and CI all see the same approval you gave, and
167
+ `wairon status` elsewhere can name what has drifted from it. Keys are sorted, so
168
+ re-approving a one-spec change shows up as a two-line diff. A failed or
169
+ cancelled lock changes nothing at all.
170
+
171
+ `lockedBy` records who approved **and how that identity was established**: your
172
+ git author identity where there is one (so a reviewer can match it against the
173
+ author of the commit carrying the lock), else `user@hostname`. On a hosted
174
+ instance it is the authenticated subject. None of it is proof — the commit that
175
+ introduces `lock.json` is what carries that.
176
+
177
+ Before approving, it reports what moved since the last approval — the question
178
+ a human is actually answering:
179
+
180
+ ```
181
+ 3 spec(s) changed since the last approval:
182
+ ~ sdd_core/spec_loader/.index.yaml
183
+ ~ sdd_core/spec_loader/.interface.yaml
184
+ + sdd_core/spec_index/.index.yaml
185
+ ```
186
+
187
+ `--yes` skips the confirmation (for CI); `--subsystem`
188
+ limits the scope. A shell with no terminal and no `--yes` writes nothing.
189
+
190
+ ### `wairon lock-check [--strict]`
191
+ The **merge gate**. One question, one exit code: *is the design in this working
192
+ tree the design that was approved?* It compares the tree's gate identity against
193
+ the one recorded in `.wai/lock.json` **as it is in the working tree**. In CI that
194
+ is the file committed in the checked-out revision; locally an uncommitted lock
195
+ record counts too, so commit it before you rely on a local pass.
196
+
197
+ | What it finds | Default | `--strict` |
198
+ | --- | --- | --- |
199
+ | **`locked`** — the approval still covers this design | pass (0) | pass (0) |
200
+ | **`stale`** — the design moved past its approval | **fail (1)** | **fail (1)** |
201
+ | **`unlocked`** — nothing was ever approved | pass, with a notice (0) | **fail (1)** |
202
+ | no `.wai/specs` in this directory at all | pass, saying so (0) | **fail (1)** |
203
+ | a member project has spec changes nobody approved at its own root | **fail (1)** | **fail (1)** |
204
+ | a member project was never approved by anyone | pass, naming it as *not gated* (0) | **fail (1)** |
205
+ | only a member's own approval moved (it re-locked) | **fail (1)**, naming the member: re-lock here to pin its new approval | **fail (1)** |
206
+
207
+ **What plain `lock-check` guarantees:** that a *committed* approval still covers the
208
+ design, and that no member project carries unapproved changes. Nothing more: a
209
+ project with no `.wai/lock.json` — never locked, *or the record deleted in the
210
+ pull request* — passes, because it reads exactly like one that never opted in.
211
+ **In CI, run `wairon lock-check --strict`** (or the reusable workflow with
212
+ `strict: true`) once the project has been locked: then a missing record, or a
213
+ member nobody ever approved, fails.
214
+
215
+ **Members.** The gate identity pins each direct member's *recorded* approval, so
216
+ the root reads every member project's own approval at its own root too. A member
217
+ whose specs moved past its own approval fails the root's check — run `wairon lock`
218
+ in that member's folder. A member that re-locked is expected (its own approver
219
+ signed it off), and the root is judged by whether its pin of the member is still
220
+ the member's approved one: after a member re-locks it is not, and the verdict says
221
+ exactly that — no own spec moved, run `wairon lock` here to pin the new approval.
222
+
223
+ A lock taken by a wairon release before 6.0.0 reads `stale` once, because the
224
+ gate identity gained inputs. The message says so — the approval *was taken under
225
+ an earlier gate identity* — and whether any own spec file changed since; re-lock
226
+ once and commit `.wai/lock.json`.
227
+
228
+ **It is optional by construction.** By default only a moved approval (or a
229
+ member's unapproved changes) refuses, and neither can happen in a project that
230
+ never locked — so adding this to an existing repository's CI cannot make it start
231
+ failing. `--strict` is what turns "never approved" into a failure, and a project
232
+ has to ask for it — which is why a project that has locked should run it strict.
233
+
234
+ It is **not** `wairon validate`. Validate asks whether the design is *legal* and
235
+ runs the whole rule set to answer; this asks whether it is *approved* and reads
236
+ one JSON file, which is why it is safe to run on every pull request. The two are
237
+ independent — a tree can be approved and illegal, or legal and unapproved — so
238
+ run both.
239
+
240
+ It gates on the **gate identity** (the hashed parsed tree plus the governing
241
+ doctrine), not on the per-spec content digests the same lock record carries.
242
+ Those answer "has this file changed since you approved it": a whitespace-only
243
+ edit moves them, and a gate that demands a re-lock for reformatting is one people
244
+ learn to bypass.
245
+
246
+ **What it proves:** that the design in the commit being merged is the design that
247
+ was approved. **What it does not prove:** *who* approved. `lockedBy` is a claim,
248
+ not an attestation — anyone who can run the CLI can write a lock record. Pull
249
+ request review is what establishes who reviewed; this establishes that the thing
250
+ merging is the thing that was reviewed.
251
+
252
+ #### Using it in GitHub Actions
253
+
254
+ This repository publishes it as a **reusable workflow** (from v6.0.0 — the
255
+ workflow and the `lock-check` command do not exist in earlier releases). Add one
256
+ job to your own workflow:
257
+
258
+ ```yaml
259
+ # .github/workflows/ci.yml in YOUR repository
260
+ on: [pull_request]
261
+
262
+ jobs:
263
+ approved-design:
264
+ uses: SYW-Apps/Waffle-AIron/.github/workflows/lock-check.yml@v6.0.0
265
+ with:
266
+ wairon-version: '6.0.0'
267
+ strict: true # a deleted .wai/lock.json fails instead of switching the gate off
268
+ ```
269
+
270
+ With inputs (all optional):
271
+
272
+ ```yaml
273
+ approved-design:
274
+ uses: SYW-Apps/Waffle-AIron/.github/workflows/lock-check.yml@v6.0.0
275
+ with:
276
+ working-directory: packages/api # where the .wai/ tree lives (default: .)
277
+ wairon-version: '6.0.0' # version or npm dist-tag, 6.0.0 or later (default: latest)
278
+ strict: true # fail when nothing was approved — use it once you have locked (default: false)
279
+ node-version: '20' # (default: '20')
280
+ runs-on: ubuntu-latest # (default: ubuntu-latest)
281
+ ```
282
+
283
+ Pin `@<ref>` to a **tag**, never to `main` or `dev`. A moving branch means the
284
+ check that gates your merges can change under you between two runs of the same
285
+ commit — and this one decides whether code merges. Pin `wairon-version` too, to
286
+ the version you lock with: the gate identity's algorithm can change between
287
+ releases, and the default `latest` moves.
288
+
289
+ On a `pull_request` event the default checkout is the merge commit, so what the
290
+ gate judges is literally the design that would land.
291
+
292
+ **A failing job does not block a merge on its own.** A workflow can only fail;
293
+ making a failing job stop a merge is a branch-protection / ruleset setting on
294
+ your repository ("Require status checks to pass" → add this job). No workflow
295
+ can declare that for itself.
296
+
297
+ ### `wairon doctor [--fix] [-y, --yes] [--global] [--report <section>]`
298
+ Health check: flags stale generated guides/skills, an unregistered MCP server,
299
+ spec-tree issues and the member/reference migration still pending. `--fix`
300
+ regenerates stale in-project guides/context/skills, registers the MCP server and
301
+ applies the spec repairs, then the member migration once confirmed (`--yes`
302
+ answers that confirmation in a script). A write outside the project root (a
303
+ machine-wide MCP config) also needs `--global`; each file it replaces is backed
304
+ up beside itself. `--report <section>` prints one section's report and writes
305
+ nothing: `chaining` (the member/reference migration plan) or
306
+ `composed-validation` (what the family run changes about each member's
307
+ findings); it never combines with `--fix`.
308
+
309
+ Among its spec repairs, `--fix` rewrites every stored type position that is an
310
+ alias of its canonical spelling (`string[]` becomes `list<string>`, `boolean`
311
+ becomes `bool`, `T | null` becomes `T?`, `Promise<T>` becomes `async T`) — what
312
+ any later save would write. It never guesses: a `number` position whose name
313
+ says a whole number (`count`, `maxDepth`, `port`) gets `int` *proposed* and
314
+ listed, and every position no rewrite can settle (an inline function type, a
315
+ literal union, a union mixing in a primitive, a `number` with no proposal) is
316
+ listed with its replacement for an author. Plain `wairon doctor` prints the same
317
+ plan without writing it.
318
+
319
+ `--fix` also re-expresses a format-2 lock record that still provably covers the
320
+ tree as format 3 (see `wairon lock`), keeping who approved it and when; when it
321
+ no longer covers the tree, doctor says that one `wairon lock` is needed.
322
+
323
+ ### `wairon list` (alias `ls`) / `wairon show <id>`
324
+ List, or show full details of, the agents resolved from the spec tree
325
+ (`system-architect`, `<subsystem>-owner`, owners for free-standing domains, and
326
+ a `<component>-implementer` per component when
327
+ `rules.generateComponentImplementers` is on).
328
+
329
+ ### `wairon agent brief <id>` / `wairon agent customize <id>`
330
+ `brief` prints the live delegation brief for one agent — the same composition
331
+ `sdd_get_agent_brief` returns, rendered from the current spec tree on every
332
+ call. `customize` scaffolds the user-owned guidance file `.wai/agents/<id>.md`,
333
+ which later briefs for that agent include.
334
+
335
+ ### `wairon rules list`
336
+ List the SDD conformance rule registry — every rule group, the issue codes it
337
+ can emit, default severities, and any per-project overrides from
338
+ `rules.sddRuleSeverity`. The gate is a documented architecture linter: each
339
+ rule is a self-contained module in `src/core/rules/`. Rules injected by
340
+ extension packs are listed too, tagged with their pack.
341
+
342
+ ### `wairon execution show` (alias `ls`) / `wairon execution set-tier <tier>`
343
+ Execution budgets — the resource axis of the derived topology. Where authority
344
+ says which paths an agent owns, a budget says what its work costs to do:
345
+ a capability tier, a reasoning effort, a turn ceiling, a tool class, and
346
+ whether it may delegate further or load MCP servers.
347
+
348
+ `show` lists every agent's allowance next to the rationale that produced it
349
+ (component stereotype, owned-path breadth, role), so a tier choice is auditable
350
+ rather than magic. It also flags any agent an override has pinned to the
351
+ `frontier` tier — derivation never selects it.
352
+
353
+ `set-tier` moves the aggressiveness dial and states what the new tier costs:
354
+
355
+ | Tier | What it does |
356
+ |------|--------------|
357
+ | `off` (default) | No budgets derived. Output is byte-identical to before this feature existed. |
358
+ | `free` | Structural constraints only — tool class, MCP access, nested delegation. No model or effort selection, so no quality tradeoff at all. |
359
+ | `default` | Adds capability-tier selection per role and turn ceilings. Mechanical work (Store, Index, Registry, Adapter) runs smaller; work carrying decisions (Orchestrator, Supervisor) keeps the capable tier. |
360
+ | `trade` | Adds effort reduction on mechanical work and steps standard work down a tier. Real but bounded quality cost. |
361
+ | `aggressive` | Small tier for everything but deep reasoning, halved turn ceilings. Expect partial results and worse judgment. |
362
+
363
+ Raising the dial can only tighten a budget, so it is safe to turn without
364
+ auditing every agent. Per-agent overrides live in `.wai/project.yaml` under
365
+ `execution.overrides`.
366
+
367
+ ### `wairon pack …`
368
+ Extension packs — plain config files (YAML, or a JS module for programmatic
369
+ rules) injecting custom profiles, language/platform tables, and conformance
370
+ rules (see [Extending wairon](extending-wairon.md)). `wairon packs` is a
371
+ deprecated alias of `wairon pack`.
372
+
373
+ | Command | Description |
374
+ |---------|-------------|
375
+ | `wairon pack init <name> [--kind declarative\|code] [--dir <path>] [--skill]` | Scaffold a new pack project |
376
+ | `wairon pack build [source] [--out <file>]` | Build an installable `.wpack` archive from a pack directory |
377
+ | `wairon pack install <source> [-y]` | Install a pack (`.wpack`/`.zip` or a directory) into this machine's pack store. It applies to nothing until a project selects it |
378
+ | `wairon pack uninstall <name>[@version]` | Remove a pack from the store |
379
+ | `wairon pack use <name>[@version] [--source <url>] [--pin] [--bundle] [-y]` | Select an installed pack for this project, recorded in `.wai/project.yaml` |
380
+ | `wairon pack unuse <name> [-y]` | Deselect a pack for this project (it stays installed) |
381
+ | `wairon pack impact <name>[@version]` | What a pack changes here, writing nothing (below) |
382
+ | `wairon pack sync` | Install every declared-but-missing pack from the source its selection records — what a fresh machine or CI runner needs |
383
+ | `wairon pack bundle [name] [--all]` | Commit a copy of a selected pack under `.wai/packs/`, so a clone and CI need no pack store |
384
+ | `wairon pack which <name>` | Which installed pack a name resolves to: version, path, digest, origin |
385
+ | `wairon pack list` | Global and project packs and what each provides, with load errors inline |
386
+ | `wairon pack add <source> [--global]` / `wairon pack remove <name> [--global]` | Vendor a pack file or directory into `.wai/packs/` (or install it machine-wide), or remove it |
387
+
388
+ The vendoring commands in detail:
389
+
390
+ - `add <source>`: verify the pack loads, then vendor it into `.wai/packs/`
391
+ and register it in `project.yaml → extensions.packs` (commit `.wai/` so CI
392
+ and every clone enforce it). With `--global`, install machine-wide into
393
+ `WAIRON_PACKS_DIR` / `~/.wairon/packs` — auto-loaded for every project on
394
+ this machine (project packs win on collision; a project opts out via
395
+ `extensions.useGlobalPacks: false`). A source may be a file or a directory
396
+ with a pack entry file (`pack.yaml` | `pack.cjs` | `index.cjs` | …).
397
+ - `list`: global + project packs and what each provides (profiles,
398
+ languages, rules), with load errors inline.
399
+ - `remove <name>`: deregister by pack name and delete vendored files under
400
+ `.wai/packs/` (files elsewhere are left in place); `--global` removes a
401
+ machine-wide pack.
402
+
403
+ The store and selection commands (`install | uninstall | which | use | unuse |
404
+ impact | bundle | sync`) are described in
405
+ [Extending wairon](extending-wairon.md#installing-and-selecting-packs).
406
+
407
+ `wairon patterns list` lists the reusable, versioned architecture patterns the
408
+ loaded packs declare; `wairon variants list` lists the component variants (each
409
+ anchored on a base block, with its implementation guidance).
410
+
411
+ ### `wairon pack impact <name>[@version]` — and confirm before every pack write
412
+ Packs exist to adjust wairon's checks and behaviour, and a pack may loosen or
413
+ remove them by design (an automation platform cannot hold every concept a full
414
+ backend can). So installing a pack is never judged — but it is always
415
+ intentional, and its impact is shown first.
416
+
417
+ `wairon pack impact` shows, writing nothing, what a pack changes on this
418
+ project:
419
+
420
+ - its **doctrine against wairon's defaults**, by the profile that carries each
421
+ change: rules loosened, raised, turned off or added; profiles added or a
422
+ builtin redefined; stereotypes removed or discouraged, edges licensed,
423
+ patterns, guarantee tokens and language tables added;
424
+ - the **profiles of it that would govern** here (the `projectType` and each
425
+ subsystem profile that names one);
426
+ - the **findings that change** on this project — introduced, resolved and
427
+ regraded, from a dry validate of the current and the candidate
428
+ configuration — with both totals.
429
+
430
+ A pack this project does not apply (or applies at another version) is measured
431
+ as applied; one it applies exactly as asked is measured as removed, so the
432
+ report reads as what the pack accounts for now. Nothing in the report is a
433
+ finding.
434
+
435
+ Every command that selects, updates or removes a project's pack — `pack use`,
436
+ `pack unuse`, `pack add`, `pack remove`, and `pack install` when it moves this
437
+ project's floating selection — **shows the same report and asks before it
438
+ writes**; anything but yes writes nothing. In a script, pass `-y, --yes`: the
439
+ write happens without the report, and the command says it applied without
440
+ showing it. A run with no terminal to ask on (CI, a pipe) behaves the same way.
441
+
442
+ ### `wairon diagram [--format <fmt>] [--subsystem <id>] [--sequence <component:method>] [--depth <n>] [--all] [--out <path>] [--no-health]`
443
+ Generate architecture diagrams derived from the spec tree — living
444
+ documentation from the same source of truth as the conformance gate. Every
445
+ format writes a file (by default under `.wai/docs/diagrams/`; `--out` names
446
+ another file or directory); nothing opens automatically.
447
+
448
+ - **default (no flags): the interactive canvas** (`canvas.html`).
449
+ - `--format mermaid` (or `--subsystem <id>`): system-wide or scoped Mermaid
450
+ **component diagram** as a markdown file (subsystems as subgraphs,
451
+ `dependsOn` edges, thick edges for boundary hops, dashed `owns`
452
+ containment, bold border on the published surface). Use a `.mmd` `--out`
453
+ path for raw Mermaid.
454
+ - `--subsystem <id>`: scope to one subsystem plus its directly-connected
455
+ external neighbors.
456
+ - `--sequence <component:method>`: a **sequence diagram** derived from the
457
+ method's L5 narrative, recursively expanding `call` steps (cycle-guarded,
458
+ `--depth` limits expansion; default 3).
459
+ - `--canvas`: an **interactive HTML canvas** — the whole system on one page.
460
+ Subsystems as containers, pattern compounds nested inside, components laid
461
+ out in dependency layers (entrypoints left → data right), subsystems in
462
+ topological order (callers left of providers) with barycenter
463
+ crossing-reduction so links stay short and untangled. Pan/zoom,
464
+ double-click a boundary to collapse it (its external edges aggregate into
465
+ labeled "tubes"), click anything for a spec-derived detail panel
466
+ (description, interfaces, methods, endpoints, narratives, dependencies,
467
+ trusted links), search, and a validation-issue overlay. The computed
468
+ layout is locked by default — enable "rearrange" to drag, "reset layout"
469
+ to undo — and "export PNG" renders the full graph to a high-res image for
470
+ Miro/docs/slides. Fully self-contained (Cytoscape.js embedded inline,
471
+ works offline).
472
+ - `--format <mermaid|canvas|drawio|excalidraw>`: friendly alias for the flags below/above.
473
+ - `--drawio` / `--excalidraw`: **editable** exports in open formats
474
+ (draw.io / diagrams.net XML, Excalidraw scene JSON) with the exact same
475
+ computed layout as the canvas — import into diagrams.net, excalidraw.com,
476
+ VS Code extensions, or any whiteboard tool that accepts these formats.
477
+ - `--all`: write the full set (system, per-subsystem, one sequence per
478
+ entrypoint method with a narrative, plus `canvas.html`,
479
+ `architecture.drawio`, and `architecture.excalidraw`) into
480
+ `.wai/docs/diagrams/` (or `--out`).
481
+ - `--no-health`: skip comparing each consumption relation with its live
482
+ producer; the canvas draws those edges as not checked.
483
+
484
+ The canvas is a small single-page app with **C4-style scoped navigation**:
485
+ each view renders one scope's direct children (System → subsystems →
486
+ components → pattern members, infinitely deep by ownership); double-click
487
+ drills in, the breadcrumb navigates back, "Internals" previews children
488
+ inside boxes, "Externals" shows out-of-scope references as ghosts. Plus
489
+ SYW/light themes, presentation mode, per-view layout persistence,
490
+ narrative flowcharts with call drill-down, and an Export menu
491
+ (PNG / draw.io / Excalidraw) capturing the current view and layout.
492
+
493
+ ### `wairon export [--out <file>]`
494
+ The whole design, resolved, as one JSON document for generators,
495
+ translators and documentation tools (format `wairon-design`; the
496
+ [format page](design-export.md) describes every field, the compatibility
497
+ promise and how a consumer follows renames).
498
+
499
+ - It first decides the approval verdict exactly as `wairon lock-check` does
500
+ (not strict) and stamps it as `source.approval` (`locked`, `stale` or
501
+ `unlocked`) and `source.approved`. An unapproved tree is exported too,
502
+ carrying its state.
503
+ - Without `--out` it prints the JSON to stdout and nothing else, so it pipes
504
+ cleanly (`wairon export | jq .components`). With `--out` it writes the file
505
+ and reports the path and the approval state.
506
+ - Member projects are listed under `dependencies`, never inlined: export each
507
+ from its own root. So the top of a family that holds no design of its own
508
+ exports no components. It names its members (on stderr without `--out`), so
509
+ that does not read as an empty design. A part's subsystems are the project's
510
+ own and are exported with it.
511
+ - Deterministic: the same tree and verdict give byte-identical output.
512
+ - A tree without an L0 is refused. The JSON Schema ships in the package as
513
+ `schemas/design-export-1.json`, and the library twin is `exportDesign()`.
514
+
515
+ ---
516
+
517
+ ## Members
518
+
519
+ A project's **members** are declared in its `.wai/project.yaml`, each by one
520
+ location key:
521
+
522
+ ```yaml
523
+ members:
524
+ scheduler: services/scheduler # contained
525
+ admin: ../admin # a sibling checkout
526
+ payments: git@host:acme/payments.git#<full commit> # a git repository at a commit
527
+ ledger: { source: services/ledger, description: The books, as: project } # long form
528
+ ```
529
+
530
+ A member is a **part** or a **project**, and what it is follows from its
531
+ content — `as:` only asserts it (a contradiction is `MEMBER_KIND_MISMATCH`):
532
+
533
+ - A **part** (the default) is a piece of this project stored in another folder
534
+ or repository. Its subsystems are this project's own: written by local id,
535
+ judged by the ordinary subsystem rules, covered by this project's lock, and
536
+ generated and briefed with this project's agents. It holds only a specs folder
537
+ — plus, when it is not contained, a `project.yaml` holding just
538
+ `partOf: { project: <parent id>, path: <parent root> }`.
539
+ - A **project** is an independent boundary: its tree declares an id, holds an L0
540
+ or carries a lock. It is reached only as `alias::name` through its L0 exports,
541
+ judged by its own gate and locked at its own root. A project stored outside
542
+ this project (`../x`, git, `hosted:`) is a **referenced** project: this
543
+ project's gate judges it against its pin (`wairon externals pin`), and the
544
+ family run opens it where it is stored and composes it per use.
545
+
546
+ Boundaries are earned: grow a system from subsystems, to parts when a piece
547
+ needs its own folder or repository, to projects only when it needs its own team,
548
+ release, approval or public surface.
549
+
550
+ A git member is pinned at a full commit; it is read from the fetch cache
551
+ (`WAIRON_CACHE_DIR`, else the OS user cache directory), immutable and offline
552
+ once fetched. A git part is read-only here — change it in its own repository,
553
+ then move the pin with `wairon member update`. An uncached git part offline is
554
+ `PART_UNAVAILABLE`; an uncached git project falls back to its pin and is
555
+ `EXTERNAL_CHECK_UNAVAILABLE` — never a pass. Every cross-project reference is
556
+ `alias::name`: the alias is one of the referring project's project members or
557
+ declared `externals`, the name a public name in that project's L0 export table.
558
+ An id without `::` is local — a part's subsystems included.
559
+
560
+ | Command | Description |
561
+ |---------|-------------|
562
+ | `wairon member add <alias> <source> [--project] [--description <text>]` | Create a member at `<source>` (`path`, `../path` or `git-url[#commit]`) and declare it by the shorthand. A **part** by default: a specs folder (and, outside this project, its `partOf`). With `--project` a project: its `project.yaml` declaring `<alias>` as its id and its L0, each only when absent. A git member is never scaffolded: it is pinned at the commit given, else the default branch head, and its content decides what it is |
563
+ | `wairon member promote <alias> [--id <id>]` | Make a part an independent project **in place**: its id, an L0 exporting exactly what this project uses of it, references across the new boundary respelled `alias::name` (`<parent id>::name` the other way), the parent declared as its external, pins on both sides. Refused for a part fetched from git, a trustedLink that would cross the boundary, a component used across it that its subsystem does not publish, or an id already taken |
564
+ | `wairon member demote <alias> [--home <subsystem>] [--packs adopt\|drop]` | Make a project member a part **in place** — promote's inverse: its own metadata goes home (as `internalize` sends it), its L0, lock and pins end, references across the old boundary become local ids. Refused while another family project consumes it, or for a member fetched from git |
565
+ | `wairon member update <alias> [--ref <ref> \| --commit <sha>] [--report]` | Move a git member's pinned commit to its ref's head (or the ref or commit given), printing the spec files it adds, changes and removes; `--report` writes nothing. The only way a git member's content changes; this project's lock reads stale until re-locked when the content moved |
566
+ | `wairon member move <alias> <path>` | Move a contained member's directory and point its `members` entry there (a legacy L1 mount is moved into `members` first) |
567
+ | `wairon member attach <alias> <path> [--description <text>]` | Make the **existing** project at `<path>` a member, keeping its L0, subsystems, packs and lock; its id is declared when it only defaulted one (the id its lock approved, else its effective id). Refused when its id collides with a family project's |
568
+ | `wairon member detach <alias> [--widen]` | Take a member out of the family: this project and every family consumer reach it as an external by `source.path`, this project's pinned. Refused (`audience-too-narrow`, each export named with its users) while a family project uses a name the member exports only to the family; `--widen` instead widens exactly those used exports to `instance` in the member's L0, each shown in the plan |
569
+ | `wairon member adopt <alias>` | Make this project's external found by a path inside it a member again — detach's inverse |
570
+ | `wairon member rename-alias <old> <new>` | Rename one alias of this project (a member or an external) and respell this project's references through it; no member or sibling changes |
571
+ | `wairon member internalize <alias> [--into <subsystem>] [--packs adopt\|drop] [--export <name>…]` | Fold a member into this project's own specs folder. A **part** is a storage move: its subsystems move in and nothing else changes. A **project** is demoted first — its own metadata goes to a home (its L0 vision onto the `--into` subsystem, its boundaries, requirements and databases into this L0, its language, profile and depth onto the moved subsystems, its members and externals into this configuration, its packs adopted or dropped), what has no home (its lock, pins, derived outputs) is deleted and listed, and every family project that consumed it is re-pointed here |
572
+ | `wairon project rename <new-id> [--project <alias path>]` | Move a project's id — this project's, or a member's named by its alias path — and every reference to the old id family-wide; the old id is kept in `previousIds`. Lists every project it writes to re-lock |
573
+ | `wairon subsystem externalize <id> --path <dir> [--as part\|project]` | Move an internal subsystem's specs into a **part** at `<dir>` (a new one, declared under the subsystem id, or the existing part there) — a storage move: no reference, export or pin changes, the same verdict. With `--as project` the move is followed by a promote, family-wide: references across the new boundary become `alias::name`, what crosses it is exported and imported, and every other family project's names are checked to keep resolving. You move the source code |
574
+
575
+ On a hosted instance a project member is a project record of its own and
576
+ inherits access from its parent, while a **part is part of its parent's record**:
577
+ it gets no record, and a demote retires the member's record (disabled as "part of
578
+ `<parent>`", audited `member.retired`, its own-scope settings kept) and a promote
579
+ re-enables it; the record's `disabledReason` (shown in the web app and `host
580
+ project list`) says why. Hosted roots are isolated, so a member with a `../` or git source
581
+ is refused there; a `hosted:` project member is its own top-level record.
582
+ `sdd_detach_member` also **moves** the member to an isolated root of its own
583
+ (writing `source: { hosted: <id> }` for its consumers) and `sdd_adopt_member`
584
+ with `path` moves it back. A `source.hosted` producer outside a hosted server is
585
+ reported as "hosted-only producer `<id>`: available only through the hosted
586
+ server".
587
+
588
+ **A part on its own.** A part stored outside its parent can be validated in its
589
+ own repository's CI: run `wairon externals pin` at the part (with the parent on
590
+ disk at `partOf.path`) to pin the excerpt of the parent it uses; `wairon validate`
591
+ there then judges the part against that pin (`PART_JUDGED_ALONE`). Unpinned, it
592
+ says "part of `<parent>`; validate from the parent" (`PART_UNPINNED`).
593
+
594
+ **Family migrations.** `attach`, `detach`, `adopt`, `promote`, `demote`,
595
+ `rename-alias`, `internalize`, `project rename` and `subsystem externalize` all run
596
+ one flow: plan, print the plan (each project's edits, every refusal, the notes,
597
+ the file changes, the projects to re-lock), then apply it all or nothing.
598
+ `--report` prints the plan and writes nothing; otherwise the command asks —
599
+ `--yes` answers, and a shell with no terminal and no `--yes` writes nothing. A
600
+ refused plan exits non-zero and writes nothing. The plan is computed by running
601
+ the verb's writes on a private copy of the family's `.wai` trees (its parts'
602
+ included); applying it stages every change with a backup under each project's
603
+ `.wai/transactions/<id>/` (never committed) and swaps them in, restoring every
604
+ backup on any failure. A crash mid-swap leaves a journal: `wairon status` and
605
+ `wairon validate` show it as a notice, and `wairon doctor --fix` rolls it back.
606
+ No verb ever locks — each names the projects to re-lock. A member stored in a
607
+ `../` sibling checkout on the same volume joins the same transaction: its files
608
+ change all-or-nothing with the family's, the coordinator's journal names every
609
+ owner (so `wairon doctor --fix` recovers from either root), and the plan says
610
+ plainly "files: all-or-nothing; commits: one per repository" — commit each
611
+ repository yourself; no commit across repositories is ever atomic. A sibling on
612
+ another volume is refused (`cross-volume`), and a git member always is (its files
613
+ are a read-only cache). `member demote` names every L0 export entry it removes.
614
+
615
+ **Required packs.** A project may require packs of the members below it with
616
+ `composition.requirePolicies` (see
617
+ [Extending wairon](extending-wairon.md#governance--what-a-pack-changes-and-what-a-parent-requires)).
618
+ `wairon member add --project` — and `wairon init` run in a subdirectory when you
619
+ answer yes on a terminal, which creates a project member the same way — writes those packs into the new member's selection once,
620
+ each pinned to the highest installed version its range admits, sets the
621
+ `projectType` a requirement names, and prints what it applied and each
622
+ requirement nothing installed satisfies. Scaffolding is an unattended pack write,
623
+ so it shows no impact report; run `wairon pack impact <name>` at the member's
624
+ root to see one.
625
+
626
+ **Deprecated forms.** For one release wairon still reads, and reports: a leading
627
+ `::` (`::shared::money`), `super::` (`super::sibling`), a member path
628
+ (`billing::invoice::invoice_portal`) and an L1 subsystem carrying
629
+ `projectPath` (`DEPRECATED_MOUNT_FORM`), and a member's long-form `path` key
630
+ (`{ path: services/x }`; the one location key is `source`). `wairon doctor
631
+ --fix` rewrites them to `members`, the one location key and `alias::name`.
632
+
633
+ ---
634
+
635
+ ## Domains
636
+
637
+ A domain is a unit of agent ownership. Subsystem-derived domains come from the
638
+ spec tree (read-only); free-standing domains live in `.wai/topology.yaml`.
639
+
640
+ | Command | Description |
641
+ |---------|-------------|
642
+ | `wairon domains list` | List all domains (subsystem-derived + free-standing) |
643
+ | `wairon domains scan [--add]` | Detect physical directory candidates; `--add` adds selected ones as free-standing domains |
644
+ | `wairon domains add [--path] [--id]` | Manually add a free-standing domain |
645
+ | `wairon domains remove <id>` | Remove a free-standing domain (subsystem-derived domains cannot be removed here) |
646
+
647
+ ---
648
+
649
+ ## Skills
650
+
651
+ The five SDD skills (`sdd-architect`, `sdd-narrative`, `sdd-auditor`,
652
+ `sdd-implement`, `sdd-delegate`) drive the spec-driven workflow inside your AI
653
+ tool.
654
+
655
+ | Command | Description |
656
+ |---------|-------------|
657
+ | `wairon skills list` | List the built-in SDD skills |
658
+ | `wairon skills install` (alias `sync`) | Install/refresh the skills into each active target's skills dir |
659
+
660
+ ---
661
+
662
+ ## MCP
663
+
664
+ The wairon MCP server exposes topology and `sdd_*` tools so AI tools can query
665
+ and author specs directly.
666
+
667
+ | Command | Description |
668
+ |---------|-------------|
669
+ | `wairon mcp serve` | Start the MCP server (stdio transport) |
670
+ | `wairon mcp install [--global] [--config-dir <path>] [--backend claude\|gemini]` | Register the server. Default: project-local. `--global` uses the home config (respects `CLAUDE_CONFIG_DIR`/`GEMINI_CONFIG_DIR`); `--config-dir` installs into an explicit, validated config dir (requires `--backend`) |
671
+ | `wairon mcp install --hosted <url> [--project <id>] [--token <token>]` | Register a **hosted** entry against an instance instead of the local stdio server; the token defaults to the credential stored by `wairon login` |
672
+ | `wairon mcp status` | Show whether the server is registered |
673
+
674
+ A project-local registration (`.mcp.json`, `.gemini/settings.json`) is committed with the
675
+ project, so it holds nothing machine-specific: it runs `wairon mcp serve` from the PATH (or
676
+ `node ./<path>` when the CLI lives inside the project, as in a checkout of wairon itself),
677
+ and the server attaches to the project it is started in. A `--global` registration is
678
+ machine-wide and names the running CLI by its absolute path.
679
+
680
+ The local server offers 38 tools:
681
+
682
+ | Group | Tools |
683
+ |-------|-------|
684
+ | Topology (read) | `listAgents`, `getAgent`, `listDomains`, `validateTopology`, `getProjectConfig` |
685
+ | Authoring | `sdd_initialize_system`, `sdd_add_subsystem`, `sdd_set_public_interfaces`, `sdd_add_component`, `sdd_define_interface`, `sdd_set_endpoints`, `sdd_write_narrative`, `sdd_add_type`, `sdd_update_spec`, `sdd_delete_spec` |
686
+ | Reading and checking | `sdd_get_spec`, `sdd_get_status`, `sdd_validate_tree` |
687
+ | Renames and moves (in this tree) | `sdd_rename_component`, `sdd_rename_method`, `sdd_rename_type`, `sdd_move_methods` |
688
+ | Members and family migrations (each takes `dryRun`) | `sdd_add_member`, `sdd_move_member`, `sdd_externalize_subsystem`, `sdd_promote_member`, `sdd_demote_member`, `sdd_internalize_member`, `sdd_attach_member`, `sdd_detach_member`, `sdd_adopt_member`, `sdd_rename_project`, `sdd_rename_member_alias` |
689
+ | Externals | `sdd_add_external`, `sdd_pin_externals`, `sdd_get_externals_status` |
690
+ | Packs | `sdd_pack_impact` |
691
+ | Delegation | `sdd_get_agent_brief` (the live brief for one agent; also served as the `wairon-agent://` resource) |
692
+
693
+ A hosted server adds its own `sdd_host_*` and `sdd_landscape_*` tools; see the
694
+ [hosted server guide](design/hosted-mcp-server.md).
695
+
696
+ ---
697
+
698
+ ## Hosting (self-hosted server)
699
+
700
+ `wairon serve` runs wairon as an HTTP server that hosts many **fully-isolated**
701
+ projects behind one endpoint — a public **data plane** (the `sdd_*` tools over
702
+ streamable HTTP, per-project API-key scoped) and an admin **control plane**
703
+ (project & key lifecycle, state-scoped lock). See the
704
+ [hosted server guide](design/hosted-mcp-server.md) for the architecture, Docker
705
+ self-hosting, auth, and sizing.
706
+
707
+ ### `wairon serve [--host <h>] [--port <p>] [--admin-host <h>] [--admin-port <p>] [--data-dir <path>] [--no-auth]`
708
+ Start the hosting server. Data plane on `0.0.0.0:8080` (`POST /mcp`, `/healthz`,
709
+ `/readyz`); admin plane on `127.0.0.1:8081` (`/admin/*`). Auth is **on by
710
+ default** and requires `WAIRON_ADMIN_TOKEN` (the master credential) — the server
711
+ refuses to start without it; `--no-auth` disables data-plane auth for a trusted
712
+ network. `--data-dir` (or `WAIRON_DATA_DIR`, default `~/.wairon/data`) is the data
713
+ root holding `projects/` and `auth/`. `WAIRON_AUDIT_POLICY` (optional) sets the
714
+ durable audit policy as a JSON object of overrides laid over the secure default
715
+ (capture `info` and above, keep events 90 days and `security` events 365, skip read
716
+ events, redacted metadata) — e.g. `{"retentionDays":30,"includeReadEvents":true}`.
717
+ Its fields are `enabled` and `includeReadEvents` (booleans), `retentionDays` and
718
+ `securityRetentionDays` (non-negative numbers), `minimumLevel` (`debug`, `info`,
719
+ `warning`, `error` or `security`) and `metadataMode` (`none` or `redacted`; the
720
+ retired `full-redacted` still loads as `redacted`, with a deprecation warning); an unknown field, a wrong type or invalid JSON stops the server at
721
+ startup with a message naming the variable. `WAIRON_QUOTA_POLICY` (optional) sets the
722
+ advisory quota policy the same way, as a JSON object of overrides laid over the
723
+ disabled default (`enabled: false`, mode `observe`, no limits) — e.g.
724
+ `{"enabled":true,"mode":"warn","maxProjectsPerUser":20}`. Its fields are `enabled`
725
+ (boolean), `mode` (`observe` or `warn`, the label an exceeded limit is reported
726
+ with; quotas never block or throttle yet, so the retired `block` loads as `observe`,
727
+ with a deprecation warning) and the non-negative integer
728
+ limits `maxProjectsPerUser`, `maxMcpRequestsPerMinute`, `maxProjectBytes` and
729
+ `maxAuditEventsPerDay`; it is refused at startup exactly as the audit policy is.
730
+
731
+ ### `wairon host …` — control plane
732
+ Runs **in-process** (no running server needed), so it works over SSH /
733
+ `docker exec`. Reads the master credential from `WAIRON_ADMIN_TOKEN`.
734
+
735
+ Every project lives in an **organization unit**, and access is a grid of
736
+ **permission assignments** (subject × scope × capability). An API key acts as its
737
+ **owner's** live permissions, narrowed to the projects it names. The first
738
+ project on a new instance:
739
+
740
+ ```sh
741
+ wairon host unit create --slug acme # a root unit (business_entity)
742
+ wairon host project create --id shop --unit acme
743
+ wairon host permission set --user alice --capability project:read --project shop
744
+ wairon host permission set --user alice --capability project:write --project shop
745
+ wairon host key mint --project shop --owner alice # → wk_… (shown once)
746
+ ```
747
+
748
+ `project:write` does not include `project:read`: a key whose owner can only
749
+ write is refused every read tool. `host permission set` warns when it grants
750
+ `project:write` to a user who cannot read that scope, and `host key mint --owner`
751
+ warns when the owner cannot read the project — each naming the grant that fixes
752
+ it. An owner needs no user record — an id with no record acts as a service
753
+ principal.
754
+
755
+ | Command | Description |
756
+ |---------|-------------|
757
+ | `wairon host unit create --slug <slug> [--kind <kind>] [--name <name>] [--parent <unitId>]` | Create an organization unit. A root unit (no `--parent`) must be a `business_entity`, which is its default kind; under `--parent` the default is `team`, and `department`, … are allowed where the hierarchy admits them |
758
+ | `wairon host project create --id <id> --unit <unitId>` | Provision a new isolated project (its own `.wai/` tree) in a unit |
759
+ | `wairon host project list` | List hosted projects |
760
+ | `wairon host project destroy --id <id>` | Remove a project and its tree |
761
+ | `wairon host demo [--id <id>] [--unit <unitId>] [--force]` | Provision a project seeded with an example spec tree (default id and unit `demo`; the unit is created if absent), so the canvas has content |
762
+ | `wairon host permission set --user <userId> --capability <cap> [--value yes\|approval\|no\|inherit] [--project <id> [--subsystem <id>] \| --unit <unitId> \| --instance]` | Set one assignment. Capabilities: `project:read`, `project:create`, `project:write`, `project:admin`, `approval:decide`. The scope defaults to the instance |
763
+ | `wairon host permission list [--user <userId>] [scope flags]` / `remove --id <assignmentId>` | List or remove assignments |
764
+ | `wairon host key mint --project <id\|*> --owner <userId> [--label <label>]` | Mint an API key acting as the owner's live permissions (plaintext shown once). The project must exist (`*` aside); a token naming a project covers its members. Without `--owner` (the legacy `--role editor\|admin` mint) the key resolves to **zero** permissions and the command warns |
765
+ | `wairon host doctor [--fix]` | Inspect the data dir and, with `--fix`, migrate it: roll back a transaction a crash left unfinished there, apply the permission-model migration, then register every hosted family's members as records of their own (no grant written — access is inherited through the parent chain — and every member-qualified key entry rewritten to a record id), all or nothing, audited |
766
+ | `wairon host key list [--project <id>]` | List API keys |
767
+ | `wairon host key revoke --id <id>` | Revoke a key |
768
+ | `wairon host lock --project <id>` | The same lock flow as `wairon lock` (design gate, `members`, `code` beside the claim, format 3) against the hosted project |
769
+ | `wairon host git enable \| disable \| sync \| commit \| status \| sync-config` | Bind a hosted project to its real repository (wairon commits only `.wai/`) |
770
+ | `wairon host producer configure \| produce \| remove \| list` | Project a hosted project to Notion or Miro |
771
+ | `wairon host secret set \| list` | Set integration secrets at runtime (`git-token`, `notion-token`, `miro-token`, `signing-secret`) — no restart |
772
+ | `wairon host packs list [--project <id>]` | List the server-global packs, or one hosted project's |
773
+ | `wairon host packs install --file <pack.yaml> [--name <n>] [--project <id>] [-y]` | Install a declarative pack server-wide, or into a hosted project. Into a project it first shows the pack's impact on that project (measured by the host, writing nothing) and asks; `--yes`, or no terminal, installs without the report and says so |
774
+ | `wairon host packs remove --name <n> [--project <id>] [-y]` | Remove a pack; from a project it first shows what the pack accounts for there and asks, as install does |
775
+
776
+ The hosted API and web UI preview the same way:
777
+ `POST /admin/projects/{id}/packs/{name}/impact` (a declarative pack's YAML as
778
+ `content`, or no body to preview adopting the server-global pack of that name)
779
+ and `POST /admin/projects/{id}/packs/{name}/removal-impact`; both need
780
+ `project:read` and write nothing. The hosted MCP data plane offers the same
781
+ preview as `sdd_host_pack_impact`. The unattended hosted policy writes —
782
+ reconcile, a project type change, and a policy-governed project creation —
783
+ return the impact of every pack they applied in their results.
784
+
785
+ ---
786
+
787
+ ## Connecting to a hosted instance
788
+
789
+ | Command | Description |
790
+ |---------|-------------|
791
+ | `wairon login <url> [--token <token>] [--project <id>]` | Store a bearer credential for a hosted instance on this machine (`--project` verifies it against that project first; the token defaults to `WAIRON_REMOTE_TOKEN`) |
792
+ | `wairon logout [url]` | Forget a stored credential (local only — it does not revoke it), or list what is stored |
793
+ | `wairon remote push \| pull [--url] [--project] [--token] [--unit <id>] [--force] …` | Migrate a spec tree to or from a hosted project (`--unit` creates the destination project first) |
794
+ | `wairon remote attach \| detach \| status` | Bind this checkout to a hosted project; while attached, `wairon lock` locks the hosted project |
795
+
796
+ ## Surfaces, externals and producers
797
+
798
+ | Command | Description |
799
+ |---------|-------------|
800
+ | `wairon externals add <alias> [<source>] [--project <id>] [--ref <ref>] [--dir <dir>] [--use a,b\|'*'] [--description <text>] [--no-pin] [--dry-run] [--json]` | Declare one external in `.wai/project.yaml`. The source is the location grammar members use: `../sibling`, `hosted:<id>`, `<git url>` or `<git url>#<commit>` (the commit is the ref the pin follows, fixed there); omit it when the family provides the producer. It is checked against the producer it reaches: one answering to another id, or not exporting a `use` name to this project, takes the declaration back out and names the id or the closest exported names. Pins by default; a producer that cannot be read leaves it declared and unpinned, saying why. A refusal is one sentence naming the accepted form, and exits 1 |
801
+ | `wairon externals pin [alias…] [--json]` | Pin declared externals into `.wai/externals/<alias>.yaml` and `.wai/externals.lock.yaml`; exits 1 when an alias could not be pinned (unresolved or unreachable — its previous pin stays) |
802
+ | `wairon externals status [--json]` | Each pin compared with its live producer per used member — `unchanged`, `changed`, `renamed` (with the new name), `removed`, `unlocked`, `unavailable` — and each external's health (`incompatible`, `not compared`, `drifted`, `ok`). Git producers are fetched. The opt-in **live** gate: exits 1 when any external is incompatible, 2 when nothing is incompatible but something could not be compared (never a pass), 0 otherwise |
803
+ | `wairon externals list [--json]` | The declared externals, how each resolves and what is pinned; a malformed declaration is listed with its problem, never hidden |
804
+ | `wairon surface export \| import \| list [--audience <level>] [--format native\|openapi] [--portal <id>] [--out <path>] [--source <path>]` | Exchange a public surface document: export this project's (native snapshot or one OpenAPI document per portal), import one, or list them |
805
+ | `wairon produce <notion\|miro> [--page <id>] [--token <token>]` | Project the local spec tree to Notion or Miro (the token comes from `--token`, the environment, else a prompt; nothing is stored) |
806
+
807
+ ## Tooling
808
+
809
+ | Command | Description |
810
+ |---------|-------------|
811
+ | `wairon dev [--port <port>] [--open]` | A local single-project dev server: the wairon web UI over the current project, no login or tenancy (loopback only) |
812
+ | `wairon update [--check] [--channel <name>]` | Check/install the latest release; switch channel |
813
+ | `wairon aliases list` | Show command aliases (`wai`) and their status |
814
+ | `wairon aliases enable <name>` / `disable <name>` | Create / remove an alias |