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

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