@wairon/cli 5.1.1-dev.11 → 5.1.1-dev.110
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/CONTRIBUTING.md +193 -0
- package/README.md +154 -41
- package/dist/cli/index.js +66474 -29775
- package/dist/cli/index.js.map +1 -1
- package/dist/index.d.ts +43671 -0
- package/dist/index.js +53581 -21797
- package/dist/index.js.map +1 -1
- package/dist/templates/architect.yaml +15 -13
- package/dist/templates/domain-owner.yaml +5 -4
- package/dist/templates/skills/sdd-architect.md +44 -26
- package/dist/templates/skills/sdd-auditor.md +5 -4
- package/dist/templates/skills/sdd-delegate.md +43 -4
- package/dist/templates/skills/sdd-implement.md +83 -13
- package/dist/templates/skills/sdd-narrative.md +7 -4
- package/dist/templates/variants/logic-shapes.yaml +59 -0
- package/dist/templates/variants/portal-shapes.yaml +13 -0
- package/dist/webapp.html +771 -0
- package/docs/architecture.md +83 -0
- package/docs/cli.md +1208 -0
- package/docs/design/approval-baseline.md +257 -0
- package/docs/design/connecting-agent-entrypoint.md +167 -0
- package/docs/design/execution-budgets.md +199 -0
- package/docs/design/pack-scoping.md +581 -0
- package/docs/design/supervisor-doctrine.md +110 -0
- package/docs/design-export.md +313 -0
- package/docs/extending-wairon.md +616 -0
- package/docs/network.md +109 -0
- package/docs/requirements.md +65 -0
- package/docs/roadmap.md +133 -0
- package/docs/standards/INDEX.md +23 -0
- package/docs/standards/architecture.md +1042 -0
- package/docs/standards/language-bindings.md +120 -0
- package/docs/templates.md +61 -0
- package/docs/vision.md +59 -0
- package/docs/wairon-feature-requests-hosted-mcp.md +74 -0
- package/package.json +18 -5
- package/schemas/design-export-2.json +1186 -0
package/docs/cli.md
ADDED
|
@@ -0,0 +1,1208 @@
|
|
|
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
|
+
**What to commit.** All of `.wai/` — the specs, `project.yaml`, `lock.json`,
|
|
29
|
+
`externals/` and `externals.lock.yaml`, vendored packs: the gate reads committed
|
|
30
|
+
files only (a migration's scratch folder, `.wai/transactions/`, ignores itself).
|
|
31
|
+
The generated `CLAUDE.md`/`GEMINI.md`, `.claude/` (or `.gemini/`) skills and
|
|
32
|
+
`.mcp.json` hold no machine path: commit them and every clone's assistant starts
|
|
33
|
+
with them, or ignore them and run `wairon generate` after a clone — either way
|
|
34
|
+
`wairon generate` / `wairon doctor --fix` refreshes them.
|
|
35
|
+
wairon writes no `.gitignore` — your toolchain's own entries (`node_modules/`,
|
|
36
|
+
`dist/`, …) are all it needs.
|
|
37
|
+
|
|
38
|
+
#### Which project a command acts on
|
|
39
|
+
Every command, the MCP server (`wairon mcp serve`) and `wairon dev` bind the same
|
|
40
|
+
project: from the folder you are in, the nearest folder whose `.wai/specs` holds
|
|
41
|
+
an L0 — but the walk **never climbs past the repository root** (the nearest folder
|
|
42
|
+
holding `.git`) unless a project above it declares the crossing as a member
|
|
43
|
+
(`members` naming the repository's folder, or a folder inside it). A stray `.wai`
|
|
44
|
+
in a parent folder therefore binds nothing in a child repository. Outside any
|
|
45
|
+
repository the walk is unbounded. Each command that acts on a project prints
|
|
46
|
+
`project <id> at <root>` on stderr first, so `wairon export` and every `--json`
|
|
47
|
+
output keep a pure stdout.
|
|
48
|
+
|
|
49
|
+
#### When a command refuses
|
|
50
|
+
A refusal is one `✖` line and a non-zero exit — never a Node stack trace, whatever
|
|
51
|
+
the input. A `.wai/project.yaml` that does not parse as YAML (most often a key
|
|
52
|
+
written twice, e.g. a second `rules:` block appended by hand) stops every command
|
|
53
|
+
the same way, naming the file, the line and the key —
|
|
54
|
+
`✖ .wai/project.yaml:33: duplicated mapping key "rules" — …` — and `wairon doctor`
|
|
55
|
+
diagnoses it too; no command runs on defaults instead. To report a bug, set
|
|
56
|
+
`WAIRON_DEBUG=1` and the stack is printed after the line.
|
|
57
|
+
|
|
58
|
+
#### Ids and names
|
|
59
|
+
Every id and name the design holds follows one grammar, and every writer, the
|
|
60
|
+
loader and the CLI judge it the same way. Spec ids (subsystems, components,
|
|
61
|
+
interfaces, implementations, types), project ids, and member and external
|
|
62
|
+
aliases, as well as method, parameter and field names, are never empty, at most
|
|
63
|
+
64 characters, never start with `-` (no command line could name one), hold no
|
|
64
|
+
control, NUL or zero-width character, and are never `__proto__`. Spec ids and
|
|
65
|
+
aliases are `[a-z0-9-_]+`, project ids `[a-z0-9-_.]` starting and ending
|
|
66
|
+
alphanumeric; these become folder and file names, so a name Windows reserves for
|
|
67
|
+
a device (`con`, `aux`, `nul`, `com1`, …) is refused on every platform, and a
|
|
68
|
+
project id or an alias is never `super`, the `::` grammar's namespace hop.
|
|
69
|
+
Method, parameter and field names are ASCII identifiers that never start with a
|
|
70
|
+
digit, and never a word the tree's `targetLanguage` reserves for that kind of
|
|
71
|
+
identifier: TypeScript and JavaScript allow a keyword as a method or a field but
|
|
72
|
+
not as a parameter, and no method named `constructor`; Rust, Python, Go, Java, C#,
|
|
73
|
+
C and Kotlin refuse their keywords everywhere (`RESERVED_IDENTIFIER`, naming the
|
|
74
|
+
language and an alternative). A display `name` is never blank and holds no
|
|
75
|
+
invisible character.
|
|
76
|
+
|
|
77
|
+
### `wairon status [--subsystem <id>] [--no-recursive] [--all]`
|
|
78
|
+
Print the SDD spec tree as a hierarchy. Its percentages measure **authoring
|
|
79
|
+
progress** — 80% once a component's component, contract and implementation specs
|
|
80
|
+
are written, 100% once its implementation names source files that exist, capped
|
|
81
|
+
at 50% while any of them is `draft` or `design` — and are
|
|
82
|
+
separate from **approval**, which is the lock record in `.wai/lock.json` and is
|
|
83
|
+
printed on its own line. `wairon lock` never rewrites a spec's status, so an
|
|
84
|
+
approved tree can still show draft specs. `--subsystem <id>` shows one
|
|
85
|
+
subsystem; `--no-recursive` shows this project without its members; `--all` lists
|
|
86
|
+
every spec that moved since the approval instead of the first few.
|
|
87
|
+
Each member project prints as `[Project] alias (id)` holding its own subsystems,
|
|
88
|
+
with its **approval state** computed at the member's own root (`approved`,
|
|
89
|
+
`drifted`, `never`) and how this project's lock pinned it (`matches`, `moved`,
|
|
90
|
+
`unpinned`). The report closes with this project's own state. Asked at the parent
|
|
91
|
+
or at the member, the answer is the same. When an external moved in its live
|
|
92
|
+
producer, drifted or could not be compared, an **Externals** section follows
|
|
93
|
+
the verdict (the same advisory comparison `validate` prints); it never changes
|
|
94
|
+
the exit code.
|
|
95
|
+
|
|
96
|
+
### `wairon validate [--ci] [--all] [--subsystem <id>] [--family] [--no-recursive]`
|
|
97
|
+
Run the architecture-conformance gate over the spec tree: reference integrity,
|
|
98
|
+
contract↔implementation method symmetry, narrative-call resolution, component
|
|
99
|
+
stereotype dependency rules, and dependency-cycle detection. `--ci` treats
|
|
100
|
+
warnings as errors (notices are printed and counted, never fatal).
|
|
101
|
+
|
|
102
|
+
- The first 100 findings per severity are printed; the rest are counted ("N
|
|
103
|
+
more not shown") and a per-code total follows. `--all` prints every finding.
|
|
104
|
+
- `--subsystem <id>` validates one subsystem.
|
|
105
|
+
- It ends with one **code-reading line**, the same sentence `sdd_validate_tree`
|
|
106
|
+
carries in its hint: the analysis grade the code was read at (the weakest any
|
|
107
|
+
file got — see `wairon lock` for the grades) and, at `exact`, the TypeScript
|
|
108
|
+
compiler that read it — `Code read at grade exact with TypeScript 5.9.3
|
|
109
|
+
(wairon's own copy)` on a project with TypeScript 7 or none, `(the project's
|
|
110
|
+
own)` on TypeScript 5 or 6 — or `Code: none read yet` before any file the design
|
|
111
|
+
names exists.
|
|
112
|
+
- At a project that declares members, `validate` is the **family run**: every
|
|
113
|
+
member's own gate, and this project's externals composed against their live
|
|
114
|
+
producers. `--no-recursive` runs this project's gate alone; `--family` runs
|
|
115
|
+
the family run from a member.
|
|
116
|
+
- **Where the network proofs run.** The proofs that need the whole family —
|
|
117
|
+
`ENTRY_UNPROVEN` (a `network` entry no modelled caller reaches), the gateway
|
|
118
|
+
rules for members, cross-project reach — are judged by the family run at the
|
|
119
|
+
**family root** (the project that declares the members). A member validated on
|
|
120
|
+
its own judges its own gate with its `network` entries counted as declared, and
|
|
121
|
+
says so in one line, so a green member run is never read as a proven one.
|
|
122
|
+
**In CI, run `wairon validate --ci` at the family root**, after installing
|
|
123
|
+
the project's dependencies (`npm ci` or your package manager's equivalent —
|
|
124
|
+
the gate reads TypeScript/JavaScript code with the project's own TypeScript
|
|
125
|
+
when it is 5 or 6, and with the copy wairon ships otherwise;
|
|
126
|
+
see [Using it in GitHub Actions](#using-it-in-github-actions)); a member in
|
|
127
|
+
its own repository adds the root's run to its pipeline or relies on the root
|
|
128
|
+
repository's.
|
|
129
|
+
- **Externals, compared live (advisory: the pin gates).** The gate judges each
|
|
130
|
+
external against its pin. Then every external the run did not compose is
|
|
131
|
+
compared with its **live** producer, offline, and printed in its own section:
|
|
132
|
+
`EXTERNAL_LIVE_INCOMPATIBLE` (warning: a used member changed at signature
|
|
133
|
+
level, was renamed — the new name is given — or is gone; it names the specs
|
|
134
|
+
that use it and the fix: adapt the uses, then `wairon externals pin <alias>`),
|
|
135
|
+
`EXTERNAL_DRIFTED` (notice: the producer moved, nothing used did) and
|
|
136
|
+
`EXTERNAL_LIVE_UNCOMPARED` (notice: the producer could not be read — a git
|
|
137
|
+
external is never fetched here; `wairon externals status` compares it). These
|
|
138
|
+
findings are **advisory**: printed and counted, but neither the exit code nor
|
|
139
|
+
`--ci` ever decides on them. A family run composes the externals in its reach
|
|
140
|
+
as its gate (`EXTERNAL_INCOMPATIBLE`, error) and gives them no second word.
|
|
141
|
+
- **Planned code is a notice.** An implementation whose `sourcePath` names a
|
|
142
|
+
file not written yet is `SOURCE_FILE_PLANNED`, and one naming no file is
|
|
143
|
+
`MISSING_SOURCE_PATH` — both notices — until realization begins (any file the
|
|
144
|
+
implementation names exists). From then on a missing file is
|
|
145
|
+
`MISSING_SOURCE_FILE` (error) and a contract method naming no file is
|
|
146
|
+
`METHOD_SOURCE_PATH_MISSING` (warning). `rules.conformance.requireCode: true`
|
|
147
|
+
in `project.yaml` reports the two notices as errors, for a CI that must say
|
|
148
|
+
every designed implementation has code.
|
|
149
|
+
- **Types are linked to code too.** A type spec without a `sourcePath` is
|
|
150
|
+
`MISSING_TYPE_SOURCE_PATH` — a notice before its subsystem has code, a
|
|
151
|
+
warning after. Plan a type's `sourcePath` when you plan the implementations'.
|
|
152
|
+
- **What the closing line says.** Plain `validate` passes with warnings and
|
|
153
|
+
says so — `Passed with N warning(s)` (`--ci` fails on them); `All checks
|
|
154
|
+
passed` means none. A tree holding no design yet (no L0, or an L0 with nothing
|
|
155
|
+
below it) says there is nothing to check.
|
|
156
|
+
- **Unknown keys are reported.** A key a spec's schema does not know (an
|
|
157
|
+
`exports:` table in the L0 — the export table is `publicInterfaces`; a
|
|
158
|
+
misspelt field) is `UNKNOWN_SPEC_KEY`, and an unknown setting in
|
|
159
|
+
`.wai/project.yaml` is `UNKNOWN_CONFIG_KEY` (both warnings): the parse would
|
|
160
|
+
otherwise drop them without a word.
|
|
161
|
+
- **Code that crosses the design.** A call through a constructor-injected
|
|
162
|
+
collaborator is collaboration whatever the import's form: when its type comes
|
|
163
|
+
from a component the caller never declared, even through `import type`, it is
|
|
164
|
+
`UNDECLARED_DEPENDENCY`; a Portal calling a write- or lifecycle-effect method
|
|
165
|
+
of a Repository, Index, Store or Registry is
|
|
166
|
+
`PORTAL_WRITE_SHORTCUT_IN_CODE` (error, the code twin of
|
|
167
|
+
`PORTAL_WRITE_SHORTCUT`). A Portal's calls are resolved by the TypeScript
|
|
168
|
+
type checker, so how the receiver is written does not matter: the call lands
|
|
169
|
+
on the method the checker resolves it to, and a call through an interface or
|
|
170
|
+
type — a port declared in the Portal's own contracts file included — lands on
|
|
171
|
+
every class of the project that realizes it (the ones that say `implements`
|
|
172
|
+
when any does, else every class the checker finds assignable). Taking a
|
|
173
|
+
method as a value counts as using it (`Reflect.apply`, a `Function`-typed
|
|
174
|
+
variable, `.bind`, a callback), and code no component maps — a helper in a
|
|
175
|
+
module no spec names, or a function that is none of its file's components'
|
|
176
|
+
modelled methods — is read as if it were inlined where it is called; the
|
|
177
|
+
finding names the path. A call the checker cannot resolve because its
|
|
178
|
+
receiver is typed `any` or `unknown`, cast to either or to an index
|
|
179
|
+
signature, picked by a computed key, or typed nothing, under the name of a
|
|
180
|
+
data component's write (or with a computed key on a receiver that was a data
|
|
181
|
+
component before the cast), is `PORTAL_CALL_UNRESOLVED` (a warning, so `--ci`
|
|
182
|
+
fails): give the receiver its type. An Orchestrator (or any component) whose
|
|
183
|
+
code calls a write- or lifecycle-effect method of another component that its
|
|
184
|
+
narrative never claims is `UNDECLARED_WRITE_CALL`, and so is a Portal calling
|
|
185
|
+
a workflow verb its narrative never names whose effect is not declared `read`
|
|
186
|
+
or `none`; the same unclaimed write through a receiver the checker cannot
|
|
187
|
+
follow is `CALL_ORIGIN_UNRESOLVED` (a warning). The narrative↔code and
|
|
188
|
+
declared-edge checks read the checker too, so a type-only `ports.ts` barrel,
|
|
189
|
+
`export *`, a port declared in a shared module and a dependency object typed
|
|
190
|
+
inline realize the calls and edges they carry. And a file importing a technology's package
|
|
191
|
+
outside the components that bind it is `TECH_LEAKAGE_IN_CODE`. A technology's
|
|
192
|
+
packages are its declared tokens (its name, or its `matches`) plus the common
|
|
193
|
+
packages wairon knows for it — see [Technologies and their
|
|
194
|
+
packages](#technologies-and-their-packages). An Adapter's call step to a verb
|
|
195
|
+
of a Portal that declares an out-of-process transport (HTTP, gRPC, a
|
|
196
|
+
database, a bus, a CLI…) is the link the design models: it is never resolved
|
|
197
|
+
to the remote Portal's file, so it never reads as `CALL_ORIGIN_UNRESOLVED`;
|
|
198
|
+
the call to the Adapter stays checked. Likewise the Adapter's `dependsOn` to
|
|
199
|
+
that Portal is realized by the link, never reported `UNREALIZED_DEPENDENCY`:
|
|
200
|
+
never add an import across a network boundary to satisfy a check.
|
|
201
|
+
|
|
202
|
+
#### Technologies and their packages
|
|
203
|
+
|
|
204
|
+
`TECH_LEAKAGE_IN_CODE` compares each import's package with the packages of the
|
|
205
|
+
technologies an implementation binds. A design writes the technology the way
|
|
206
|
+
people say it (`postgres`) while the code imports its driver (`pg`), so wairon
|
|
207
|
+
knows the common packages of the common technologies by default:
|
|
208
|
+
|
|
209
|
+
| Technology (any of these names) | Packages |
|
|
210
|
+
| --- | --- |
|
|
211
|
+
| `postgres`, `postgresql`, `pg` | `pg`, `postgres`, `pg-promise`, `@neondatabase/serverless`, `@vercel/postgres` |
|
|
212
|
+
| `mysql`, `mariadb` | `mysql2`, `mysql`, `mariadb` |
|
|
213
|
+
| `redis`, `valkey` | `redis`, `ioredis`, `@redis/client` |
|
|
214
|
+
| `mongodb`, `mongo` | `mongodb`, `mongoose` |
|
|
215
|
+
| `sqlite`, `sqlite3` | `better-sqlite3`, `sqlite3`, `sqlite` |
|
|
216
|
+
| `kafka` | `kafkajs`, `node-rdkafka` |
|
|
217
|
+
| `rabbitmq`, `amqp` | `amqplib`, `amqp-connection-manager` |
|
|
218
|
+
|
|
219
|
+
An HTTP client (`fetch`, `axios`, `got`, `undici`) is never a technology — it is
|
|
220
|
+
how an Adapter reaches anything — so no entry names one. To extend the list,
|
|
221
|
+
write the technology as `{ name, matches }` (each `matches` token is a package
|
|
222
|
+
too), or have a pack contribute `technologyPackages: { <technology>: [<package>,
|
|
223
|
+
…] }`.
|
|
224
|
+
|
|
225
|
+
A package of a technology that **no** implementation binds is reported too:
|
|
226
|
+
the code uses a technology the design never placed, so every importer is
|
|
227
|
+
outside its (missing) home. Bind it on the data-layer component that owns the
|
|
228
|
+
vendor call (`technologies: [redis]`), and that component becomes its home.
|
|
229
|
+
|
|
230
|
+
### `wairon generate [--target <name>] [--domain <id>] [--domains <ids>] [--root] [--family] [--no-prune] [--global] [--dry-run]`
|
|
231
|
+
Reconcile the generated guides, skills and context, and — only when the project
|
|
232
|
+
opts in — write the agent files from the spec-derived topology.
|
|
233
|
+
|
|
234
|
+
**Agent files are opt-in.** With `rules.materializeAgentFiles` off (the default
|
|
235
|
+
for a new project) no agent file is written, and a run removes the ones an
|
|
236
|
+
earlier version wrote; agents are served as live briefs instead
|
|
237
|
+
(`sdd_get_agent_brief`, `wairon agent brief <id>`). Set
|
|
238
|
+
`rules.materializeAgentFiles: true` in `.wai/project.yaml` to keep agent files
|
|
239
|
+
on disk (`rules.generateComponentImplementers: true` adds one implementer per
|
|
240
|
+
component).
|
|
241
|
+
|
|
242
|
+
- `--target <name>` limits generation to one of the project's configured
|
|
243
|
+
targets (`claude`, `agy`, …); an unknown name exits non-zero and lists the
|
|
244
|
+
targets the project configures.
|
|
245
|
+
- `--domain <id>` / `--domains <ids>` / `--root` limit it to some domains.
|
|
246
|
+
- `--no-prune` keeps wairon-managed agent files that left the topology.
|
|
247
|
+
- `--global` also writes a target whose output directory is outside the project
|
|
248
|
+
root, backing up each file it replaces.
|
|
249
|
+
- `--dry-run` previews without writing.
|
|
250
|
+
|
|
251
|
+
`generate` writes **only this project's** outputs. A parent's topology lists a
|
|
252
|
+
member's agents by reference (`delegatesTo: <alias>::<agentId>`) instead of
|
|
253
|
+
copying them, and a brief for `<alias>::<agent>` composes at the member's own
|
|
254
|
+
root. `--family` also generates each member's own layer, in its own root.
|
|
255
|
+
(`--no-recurse` is accepted for one release; not cascading is now the default.)
|
|
256
|
+
|
|
257
|
+
### `wairon lock [-y, --yes] [--subsystem <id>]`
|
|
258
|
+
Review and approve the design. Validates the spec tree **as if complete** (full
|
|
259
|
+
strictness, no draft-status relaxation) and — only if the **design** passes —
|
|
260
|
+
records the current tree as approved and refreshes this project's generated
|
|
261
|
+
outputs (agent files only when `rules.materializeAgentFiles` is on).
|
|
262
|
+
|
|
263
|
+
**It approves; it does not change statuses.** No spec's `status` is rewritten:
|
|
264
|
+
approval lives in `.wai/lock.json`. Implementation gates on that approval —
|
|
265
|
+
`wairon lock-check` in CI, `sdd_get_status` for an AI tool — not on
|
|
266
|
+
`status: complete`. Commit `.wai/lock.json` with the change it approves.
|
|
267
|
+
When any spec is still `draft` or `design`, the lock says how many of how many,
|
|
268
|
+
per kind (subsystems, components, contracts, implementations, types), and that
|
|
269
|
+
it approves them as they stand. Re-running it when nothing it records moved
|
|
270
|
+
keeps the record on file — its `lockedAt` included — so `.wai/lock.json` stays
|
|
271
|
+
unmodified; a change of approver, verdict, member pin or wairon release writes
|
|
272
|
+
a new record.
|
|
273
|
+
|
|
274
|
+
**What it certifies is the design.** Only design findings can refuse a lock.
|
|
275
|
+
Code-conformance findings (the code↔spec checks) are recorded **beside** the
|
|
276
|
+
claim in the record's `code` block, with the analyzer that produced them, and
|
|
277
|
+
printed as `code: N error(s), … recorded beside the claim`, with the analysis
|
|
278
|
+
**grade** — the weakest any analyzed file got: `exact` (read with the project's
|
|
279
|
+
TypeScript compiler API: declarations, imports and calls resolved), `pattern`
|
|
280
|
+
(read through the language's declaration and import patterns — recognized, not
|
|
281
|
+
resolved), `generic` (word-boundary identifiers only, the floor for any
|
|
282
|
+
language), or `none` (no file analyzed yet: no code, or none the specs name).
|
|
283
|
+
CI enforces them:
|
|
284
|
+
`wairon validate --ci` still fails on a code error. A design can be approved
|
|
285
|
+
before its code exists.
|
|
286
|
+
|
|
287
|
+
**It approves this project only.** The gate identity it records covers this
|
|
288
|
+
project's own specs, the design doctrine, its declared inputs, its `composition`
|
|
289
|
+
block, and each direct member's **composition subject** — the `stateId` in the
|
|
290
|
+
member's own lock record. The record (format 3) lists each direct member under
|
|
291
|
+
`members` with its subject and state (`approved`, `drifted`, `never`). Nothing is
|
|
292
|
+
written below the project: each member locks at its own root. With
|
|
293
|
+
`composition.requireApprovedMembers: true` in `project.yaml`, the lock refuses
|
|
294
|
+
while a direct member is drifted or never approved, naming each.
|
|
295
|
+
|
|
296
|
+
The identity is captured **before** validating and confirmed **before** writing:
|
|
297
|
+
if a spec, the doctrine, an input or a member's approval moves while the lock
|
|
298
|
+
runs, it refuses and writes nothing. (`--no-recursive` is accepted for one
|
|
299
|
+
release and changes nothing: a lock never reaches below its project.)
|
|
300
|
+
|
|
301
|
+
**Code linkage is not part of the approval.** The gate identity and the per-spec
|
|
302
|
+
digests read each spec's **design**: where it is realized — `sourcePath`,
|
|
303
|
+
`symbol`, `exportedVia`, `simPath`, `injectedParams`, conformance tiers, a Portal's
|
|
304
|
+
`router`, `externalLinks` — and the `createdAt`/`updatedAt` timestamps are left
|
|
305
|
+
out (`specsReading: design`). Linking code to an approved design, or a change and
|
|
306
|
+
its revert, never stales the lock; declare planned `sourcePath`s at design time.
|
|
307
|
+
|
|
308
|
+
**Readiness is not part of the approval either.** A spec's `status` (`draft`,
|
|
309
|
+
`design`, `complete`) says how far authoring is, never what the design says, so
|
|
310
|
+
it is left out of the design view: promoting a spec to `complete` after the lock
|
|
311
|
+
never reopens it. A format-3 record taken while the view still carried `status`
|
|
312
|
+
keeps passing when nothing but a status moved — it is judged in the reading it
|
|
313
|
+
was taken in, each spec's approved status recovered from its digest — and
|
|
314
|
+
`wairon doctor --fix` carries it into the current reading without a review.
|
|
315
|
+
|
|
316
|
+
A **format-2** record (written before code linkage left the approval) still passes
|
|
317
|
+
`lock-check` while nothing it covered moved. `wairon doctor --fix` re-expresses it
|
|
318
|
+
as format 3 in place — before any other repair touches a spec — keeping
|
|
319
|
+
`lockedAt`, `lockedBy`, the results and the member pins, and recording
|
|
320
|
+
`reexpressed: { at, fromAlgorithm, fromReading, by }`. When no spec file moved
|
|
321
|
+
but the record still does not match, what moved is the **gate** it was judged
|
|
322
|
+
under — the design rules of a newer wairon release, the project's rule tuning,
|
|
323
|
+
`composition`, the network declaration, consumed contracts or a member's
|
|
324
|
+
approval — never your design: `lock-check` and `doctor` say exactly that, `doctor
|
|
325
|
+
--fix` still carries the record into the design reading (its claim stays as
|
|
326
|
+
certified, so it reads stale for that stated cause), and one `wairon lock`
|
|
327
|
+
re-approves the unchanged design. A format-2 record whose specs moved reads stale
|
|
328
|
+
once; one `wairon lock` clears it for good.
|
|
329
|
+
|
|
330
|
+
It writes **nothing into your spec tree**. The approval is one sha256 per spec
|
|
331
|
+
(over its design view) on `.wai/lock.json`, the record that was always committed — so your
|
|
332
|
+
teammates, a fresh clone and CI all see the same approval you gave, and
|
|
333
|
+
`wairon status` elsewhere can name what has drifted from it. Keys are sorted, so
|
|
334
|
+
re-approving a one-spec change shows up as a two-line diff. A failed or
|
|
335
|
+
cancelled lock changes nothing at all.
|
|
336
|
+
|
|
337
|
+
`lockedBy` records who approved **and how that identity was established**: your
|
|
338
|
+
git author identity where there is one (so a reviewer can match it against the
|
|
339
|
+
author of the commit carrying the lock), else `user@hostname`. On a hosted
|
|
340
|
+
instance it is the authenticated subject. None of it is proof — the commit that
|
|
341
|
+
introduces `lock.json` is what carries that.
|
|
342
|
+
|
|
343
|
+
Before approving, it reports what moved since the last approval — the question
|
|
344
|
+
a human is actually answering:
|
|
345
|
+
|
|
346
|
+
```
|
|
347
|
+
3 spec(s) changed since the last approval:
|
|
348
|
+
~ sdd_core/spec_loader/.index.yaml
|
|
349
|
+
~ sdd_core/spec_loader/.interface.yaml
|
|
350
|
+
+ sdd_core/spec_index/.index.yaml
|
|
351
|
+
```
|
|
352
|
+
|
|
353
|
+
`--yes` skips the confirmation (for CI); `--subsystem`
|
|
354
|
+
limits the scope. A shell with no terminal and no `--yes` writes nothing.
|
|
355
|
+
|
|
356
|
+
### `wairon lock-check [--strict]`
|
|
357
|
+
The **merge gate**. One question, one exit code: *is the design in this working
|
|
358
|
+
tree the design that was approved?* It compares the tree's gate identity against
|
|
359
|
+
the one recorded in `.wai/lock.json` **as it is in the working tree**. In CI that
|
|
360
|
+
is the file committed in the checked-out revision; locally an uncommitted lock
|
|
361
|
+
record counts too, so commit it before you rely on a local pass.
|
|
362
|
+
|
|
363
|
+
| What it finds | Default | `--strict` |
|
|
364
|
+
| --- | --- | --- |
|
|
365
|
+
| **`locked`** — the approval still covers this design | pass (0) | pass (0) |
|
|
366
|
+
| **`stale`** — the design moved past its approval | **fail (1)** | **fail (1)** |
|
|
367
|
+
| **`unlocked`** — nothing was ever approved | pass, with a notice (0) | **fail (1)** |
|
|
368
|
+
| no `.wai/specs` in this directory at all | pass, saying so (0) | **fail (1)** |
|
|
369
|
+
| a member project has spec changes nobody approved at its own root | **fail (1)** | **fail (1)** |
|
|
370
|
+
| a member project was never approved by anyone | pass, naming it as *not gated* (0) | **fail (1)** |
|
|
371
|
+
| 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)** |
|
|
372
|
+
|
|
373
|
+
**What plain `lock-check` guarantees:** that a *committed* approval still covers the
|
|
374
|
+
design, and that no member project carries unapproved changes. Nothing more: a
|
|
375
|
+
project with no `.wai/lock.json` — never locked, *or the record deleted in the
|
|
376
|
+
pull request* — passes, because it reads exactly like one that never opted in.
|
|
377
|
+
**In CI, run `wairon lock-check --strict`** (or the reusable workflow with
|
|
378
|
+
`strict: true`) once the project has been locked: then a missing record, or a
|
|
379
|
+
member nobody ever approved, fails.
|
|
380
|
+
|
|
381
|
+
**Members.** The gate identity pins each direct member's *recorded* approval, so
|
|
382
|
+
the root reads every member project's own approval at its own root too. A member
|
|
383
|
+
whose specs moved past its own approval fails the root's check — run `wairon lock`
|
|
384
|
+
in that member's folder. A member that re-locked is expected (its own approver
|
|
385
|
+
signed it off), and the root is judged by whether its pin of the member is still
|
|
386
|
+
the member's approved one: after a member re-locks it is not, and the verdict says
|
|
387
|
+
exactly that — no own spec moved, run `wairon lock` here to pin the new approval.
|
|
388
|
+
|
|
389
|
+
A lock taken by a wairon release before 6.0.0 reads `stale` once, because the
|
|
390
|
+
gate identity gained inputs. The message says so — the approval *was taken under
|
|
391
|
+
an earlier gate identity* — and whether any own spec file changed since; re-lock
|
|
392
|
+
once and commit `.wai/lock.json`.
|
|
393
|
+
|
|
394
|
+
**It is optional by construction.** By default only a moved approval (or a
|
|
395
|
+
member's unapproved changes) refuses, and neither can happen in a project that
|
|
396
|
+
never locked — so adding this to an existing repository's CI cannot make it start
|
|
397
|
+
failing. `--strict` is what turns "never approved" into a failure, and a project
|
|
398
|
+
has to ask for it — which is why a project that has locked should run it strict.
|
|
399
|
+
|
|
400
|
+
It is **not** `wairon validate`. Validate asks whether the design is *legal* and
|
|
401
|
+
runs the whole rule set to answer; this asks whether it is *approved* and reads
|
|
402
|
+
one JSON file, which is why it is safe to run on every pull request. The two are
|
|
403
|
+
independent — a tree can be approved and illegal, or legal and unapproved — so
|
|
404
|
+
run both.
|
|
405
|
+
|
|
406
|
+
It gates on the **gate identity** (the hashed parsed tree plus the governing
|
|
407
|
+
doctrine), not on the per-spec content digests the same lock record carries.
|
|
408
|
+
Those answer "has this file changed since you approved it": a whitespace-only
|
|
409
|
+
edit moves them, and a gate that demands a re-lock for reformatting is one people
|
|
410
|
+
learn to bypass.
|
|
411
|
+
|
|
412
|
+
**What it proves:** that the design in the commit being merged is the design that
|
|
413
|
+
was approved. **What it does not prove:** *who* approved. `lockedBy` is a claim,
|
|
414
|
+
not an attestation — anyone who can run the CLI can write a lock record. Pull
|
|
415
|
+
request review is what establishes who reviewed; this establishes that the thing
|
|
416
|
+
merging is the thing that was reviewed.
|
|
417
|
+
|
|
418
|
+
#### Using it in GitHub Actions
|
|
419
|
+
|
|
420
|
+
This repository publishes it as a **reusable workflow**. Pin it — and the CLI it
|
|
421
|
+
installs — to **the version you lock with**: the one `wairon --version` prints
|
|
422
|
+
where you run `wairon lock`. Every published version has a git tag `v<version>`
|
|
423
|
+
and the same version on npm: a release `X.Y.Z` is `@vX.Y.Z` with
|
|
424
|
+
`wairon-version: 'X.Y.Z'`; a dev build `X.Y.Z-dev.N` is `@vX.Y.Z-dev.N` with
|
|
425
|
+
`wairon-version: 'X.Y.Z-dev.N'` (the npm `dev` dist-tag names the newest dev
|
|
426
|
+
build, but it moves — pin the exact version). Check that the version you pin is
|
|
427
|
+
on npm before you commit the job: `npm view @wairon/cli@<version> version`. The
|
|
428
|
+
workflow and the `lock-check` command first ship with 6.0.0 and its dev builds;
|
|
429
|
+
an older version has neither. Below, `<version>` stands for yours. Add one job
|
|
430
|
+
to your own workflow:
|
|
431
|
+
|
|
432
|
+
```yaml
|
|
433
|
+
# .github/workflows/ci.yml in YOUR repository
|
|
434
|
+
on: [pull_request]
|
|
435
|
+
|
|
436
|
+
jobs:
|
|
437
|
+
approved-design:
|
|
438
|
+
uses: SYW-Apps/Waffle-AIron/.github/workflows/lock-check.yml@v<version>
|
|
439
|
+
with:
|
|
440
|
+
wairon-version: '<version>'
|
|
441
|
+
strict: true # a deleted .wai/lock.json fails instead of switching the gate off
|
|
442
|
+
```
|
|
443
|
+
|
|
444
|
+
With inputs (all optional):
|
|
445
|
+
|
|
446
|
+
```yaml
|
|
447
|
+
approved-design:
|
|
448
|
+
uses: SYW-Apps/Waffle-AIron/.github/workflows/lock-check.yml@v<version>
|
|
449
|
+
with:
|
|
450
|
+
working-directory: packages/api # where the .wai/ tree lives (default: .)
|
|
451
|
+
wairon-version: '<version>' # the version you lock with, or an npm dist-tag (default: latest)
|
|
452
|
+
strict: true # fail when nothing was approved — use it once you have locked (default: false)
|
|
453
|
+
validate: true # also run `wairon validate --ci` there (default: true)
|
|
454
|
+
install: auto # the project's dependencies before validate: auto | none | <command> (default: auto)
|
|
455
|
+
node-version: '20' # (default: '20')
|
|
456
|
+
runs-on: ubuntu-latest # (default: ubuntu-latest)
|
|
457
|
+
```
|
|
458
|
+
|
|
459
|
+
Pin `@<ref>` to a **tag**, never to `main` or `dev`. A moving branch means the
|
|
460
|
+
check that gates your merges can change under you between two runs of the same
|
|
461
|
+
commit — and this one decides whether code merges. Pin `wairon-version` too, to
|
|
462
|
+
the version you lock with: the gate identity's algorithm can change between
|
|
463
|
+
releases, and the default `latest` moves.
|
|
464
|
+
|
|
465
|
+
On a `pull_request` event the default checkout is the merge commit, so what the
|
|
466
|
+
gate judges is literally the design that would land.
|
|
467
|
+
|
|
468
|
+
After the approval check the job runs `wairon validate --ci` in the same
|
|
469
|
+
directory (`validate: false` turns it off). Point `working-directory` at the
|
|
470
|
+
**family root** — the project that declares the members — so that run is the
|
|
471
|
+
family run, which judges the network proofs no member's own gate can.
|
|
472
|
+
|
|
473
|
+
**The project's dependencies come first.** The conformance gate reads
|
|
474
|
+
TypeScript/JavaScript code with the project's own TypeScript compiler when it is
|
|
475
|
+
TypeScript 5 or 6, and with the copy wairon ships otherwise — a fresh checkout,
|
|
476
|
+
a project without TypeScript, or one on TypeScript 7 (whose native compiler has
|
|
477
|
+
no JavaScript API) is still read at exact grade, and `CONFORMANCE_DEGRADED` is
|
|
478
|
+
left for a file the compiler could not read at all. Installing the project's
|
|
479
|
+
dependencies keeps the analysis on the project's own compiler. So before
|
|
480
|
+
`validate --ci` the job
|
|
481
|
+
installs them with the project's own package manager (`install: auto`, the
|
|
482
|
+
default): nothing without a `package.json`; otherwise the nearest lockfile from
|
|
483
|
+
`working-directory` up to the repository root decides — `package-lock.json` →
|
|
484
|
+
`npm ci`, `pnpm-lock.yaml` → `pnpm install --frozen-lockfile`, `yarn.lock` →
|
|
485
|
+
`yarn install` (`--immutable`, or `--frozen-lockfile` for Yarn 1). `install:
|
|
486
|
+
none` skips it; any other value is run as the install command. The approval
|
|
487
|
+
check alone (`validate: false`) installs nothing.
|
|
488
|
+
|
|
489
|
+
#### Without the reusable workflow
|
|
490
|
+
|
|
491
|
+
The same gate as plain steps — for another CI system, or a job of your own. For
|
|
492
|
+
a TypeScript/JavaScript project with code, **install the project's dependencies
|
|
493
|
+
before `wairon validate --ci`** (`npm ci`, `pnpm install --frozen-lockfile` or
|
|
494
|
+
`yarn install --immutable`, whichever your lockfile is for):
|
|
495
|
+
|
|
496
|
+
```yaml
|
|
497
|
+
# .github/workflows/design.yml in YOUR repository
|
|
498
|
+
on: [pull_request]
|
|
499
|
+
|
|
500
|
+
jobs:
|
|
501
|
+
design:
|
|
502
|
+
runs-on: ubuntu-latest
|
|
503
|
+
steps:
|
|
504
|
+
- uses: actions/checkout@v4
|
|
505
|
+
- uses: actions/setup-node@v4
|
|
506
|
+
with:
|
|
507
|
+
node-version: '20'
|
|
508
|
+
- run: npm install --global @wairon/cli@<version> # the version you lock with (wairon --version)
|
|
509
|
+
- run: wairon lock-check --strict
|
|
510
|
+
- run: npm ci # the project's dependencies: the gate reads your code with your TypeScript (5 or 6)
|
|
511
|
+
- run: wairon validate --ci
|
|
512
|
+
```
|
|
513
|
+
|
|
514
|
+
**A failing job does not block a merge on its own.** A workflow can only fail;
|
|
515
|
+
making a failing job stop a merge is a branch-protection / ruleset setting on
|
|
516
|
+
your repository ("Require status checks to pass" → add this job). No workflow
|
|
517
|
+
can declare that for itself.
|
|
518
|
+
|
|
519
|
+
### `wairon doctor [--fix] [-y, --yes] [--global] [--report <section>]`
|
|
520
|
+
Health check: flags stale generated guides/skills, an unregistered MCP server,
|
|
521
|
+
spec-tree issues and the member/reference migration still pending. `--fix`
|
|
522
|
+
regenerates stale in-project guides/context/skills, registers the MCP server and
|
|
523
|
+
applies the spec repairs, then the member migration once confirmed (`--yes`
|
|
524
|
+
answers that confirmation in a script). A write outside the project root (a
|
|
525
|
+
machine-wide MCP config) also needs `--global`; each file it replaces is backed
|
|
526
|
+
up beside itself. `--report <section>` prints one section's report and writes
|
|
527
|
+
nothing: `chaining` (the member/reference migration plan) or
|
|
528
|
+
`composed-validation` (what the family run changes about each member's
|
|
529
|
+
findings) or `reachability` (the reachability migration's plan); it never
|
|
530
|
+
combines with `--fix`.
|
|
531
|
+
|
|
532
|
+
Among its spec repairs, `--fix` rewrites every stored type position that is an
|
|
533
|
+
alias of its canonical spelling (`string[]` becomes `list<string>`, `boolean`
|
|
534
|
+
becomes `bool`, `T | null` becomes `T?`, `Promise<T>` becomes `async T`) — what
|
|
535
|
+
any later save would write. It never guesses: a `number` position whose name
|
|
536
|
+
says a whole number (`count`, `maxDepth`, `port`) gets `int` *proposed* and
|
|
537
|
+
listed, and every position no rewrite can settle (an inline function type, a
|
|
538
|
+
literal union, a union mixing in a primitive, a `number` with no proposal) is
|
|
539
|
+
listed with its replacement for an author. Plain `wairon doctor` prints the same
|
|
540
|
+
plan without writing it.
|
|
541
|
+
|
|
542
|
+
**The reachability migration.** `--fix` also moves the tree onto the
|
|
543
|
+
reachability model: `portalType` becomes `transport` (`HTTP_API` → `HTTP`, a
|
|
544
|
+
`Custom` Portal that binds no wire address → `InProcess`), a listener's `mounts`
|
|
545
|
+
become a Portal-level entry on each mounted Portal and its implementation's
|
|
546
|
+
`router` (the mount's `via`), `invokedBy: external` becomes `entry` on a Portal
|
|
547
|
+
verb and `runtime` elsewhere, an authored export `type` that agrees with the derived kind is dropped,
|
|
548
|
+
in-process `Custom` endpoint addresses are removed, and allows of retired codes
|
|
549
|
+
are dropped or renamed. It never invents design: a Portal that was neither a
|
|
550
|
+
listener nor mounted gets no entry, and a `sibling-subsystem` caller, an export
|
|
551
|
+
type that disagrees, an endpoint outside every old prefix and a router already
|
|
552
|
+
set otherwise are listed for an author. Plain `wairon doctor` prints the plan;
|
|
553
|
+
`--report reachability` lists every rewrite and every form left for an author.
|
|
554
|
+
The design changes, so one `wairon lock` follows. After it, declare the entries
|
|
555
|
+
the migration cannot know — a CLI, a stdio tool surface (`JSONRPC`), a library
|
|
556
|
+
(`InProcess`) — and model a caller or remove each verb still reported unreached.
|
|
557
|
+
|
|
558
|
+
`--fix` also re-expresses an earlier lock record — format 2, or format 3 taken
|
|
559
|
+
while `status` was still in the approval — in the current reading, first, before
|
|
560
|
+
any repair touches a spec (see `wairon lock`), keeping who approved it and when.
|
|
561
|
+
A format-2 record over a tree no file of which moved is carried into the design
|
|
562
|
+
reading even when the gate it was judged under moved; doctor then says the cause
|
|
563
|
+
(the gate, not the design) and that one `wairon lock` re-approves it.
|
|
564
|
+
|
|
565
|
+
### `wairon list` (alias `ls`) / `wairon show <id>`
|
|
566
|
+
List, or show full details of, the agents resolved from the spec tree
|
|
567
|
+
(`system-architect`, `<subsystem>-owner`, owners for free-standing domains, and
|
|
568
|
+
a `<component>-implementer` per component when
|
|
569
|
+
`rules.generateComponentImplementers` is on).
|
|
570
|
+
|
|
571
|
+
### `wairon agent brief <id>` / `wairon agent customize <id>`
|
|
572
|
+
`brief` prints the live delegation brief for one agent — the same composition
|
|
573
|
+
`sdd_get_agent_brief` returns, rendered from the current spec tree on every
|
|
574
|
+
call. `customize` scaffolds the user-owned guidance file `.wai/agents/<id>.md`,
|
|
575
|
+
which later briefs for that agent include.
|
|
576
|
+
|
|
577
|
+
### `wairon rules list`
|
|
578
|
+
List the SDD conformance rule registry — every rule group, the issue codes it
|
|
579
|
+
can emit, default severities, and any per-project overrides from
|
|
580
|
+
`rules.sddRuleSeverity`. The gate is a documented architecture linter: each
|
|
581
|
+
rule is a self-contained module in `src/core/rules/`. Rules injected by
|
|
582
|
+
extension packs are listed too, tagged with their pack.
|
|
583
|
+
|
|
584
|
+
### `wairon execution show` (alias `ls`) / `wairon execution set-tier <tier>`
|
|
585
|
+
Execution budgets — the resource axis of the derived topology. Where authority
|
|
586
|
+
says which paths an agent owns, a budget says what its work costs to do:
|
|
587
|
+
a capability tier, a reasoning effort, a turn ceiling, a tool class, and
|
|
588
|
+
whether it may delegate further or load MCP servers.
|
|
589
|
+
|
|
590
|
+
`show` lists every agent's allowance next to the rationale that produced it
|
|
591
|
+
(component stereotype, owned-path breadth, role), so a tier choice is auditable
|
|
592
|
+
rather than magic. It also flags any agent an override has pinned to the
|
|
593
|
+
`frontier` tier — derivation never selects it.
|
|
594
|
+
|
|
595
|
+
`set-tier` moves the aggressiveness dial and states what the new tier costs:
|
|
596
|
+
|
|
597
|
+
| Tier | What it does |
|
|
598
|
+
|------|--------------|
|
|
599
|
+
| `off` (default) | No budgets derived. Output is byte-identical to before this feature existed. |
|
|
600
|
+
| `free` | Structural constraints only — tool class, MCP access, nested delegation. No model or effort selection, so no quality tradeoff at all. |
|
|
601
|
+
| `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. |
|
|
602
|
+
| `trade` | Adds effort reduction on mechanical work and steps standard work down a tier. Real but bounded quality cost. |
|
|
603
|
+
| `aggressive` | Small tier for everything but deep reasoning, halved turn ceilings. Expect partial results and worse judgment. |
|
|
604
|
+
|
|
605
|
+
Raising the dial can only tighten a budget, so it is safe to turn without
|
|
606
|
+
auditing every agent. Per-agent overrides live in `.wai/project.yaml` under
|
|
607
|
+
`execution.overrides`.
|
|
608
|
+
|
|
609
|
+
### `wairon pack …`
|
|
610
|
+
Extension packs — plain config files (YAML, or a JS module for programmatic
|
|
611
|
+
rules) injecting custom profiles, language/platform tables, and conformance
|
|
612
|
+
rules (see [Extending wairon](extending-wairon.md)). `wairon packs` is a
|
|
613
|
+
deprecated alias of `wairon pack`.
|
|
614
|
+
|
|
615
|
+
| Command | Description |
|
|
616
|
+
|---------|-------------|
|
|
617
|
+
| `wairon pack init <name> [--kind declarative\|code] [--dir <path>] [--skill]` | Scaffold a new pack project |
|
|
618
|
+
| `wairon pack build [source] [--out <file>]` | Build an installable `.wpack` archive from a pack directory |
|
|
619
|
+
| `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 |
|
|
620
|
+
| `wairon pack uninstall <name>[@version]` | Remove a pack from the store |
|
|
621
|
+
| `wairon pack use <name>[@version] [--source <url>] [--pin] [--bundle] [-y]` | Select an installed pack for this project, recorded in `.wai/project.yaml` |
|
|
622
|
+
| `wairon pack unuse <name> [-y]` | Deselect a pack for this project (it stays installed) |
|
|
623
|
+
| `wairon pack impact <name>[@version]` | What a pack changes here, writing nothing (below) |
|
|
624
|
+
| `wairon pack sync` | Install every declared-but-missing pack from the source its selection records — what a fresh machine or CI runner needs |
|
|
625
|
+
| `wairon pack bundle [name] [--all]` | Commit a copy of a selected pack under `.wai/packs/`, so a clone and CI need no pack store |
|
|
626
|
+
| `wairon pack which <name>` | Which installed pack a name resolves to: version, path, digest, origin |
|
|
627
|
+
| `wairon pack list` | Global and project packs and what each provides, with load errors inline |
|
|
628
|
+
| `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 |
|
|
629
|
+
|
|
630
|
+
The vendoring commands in detail:
|
|
631
|
+
|
|
632
|
+
- `add <source>`: verify the pack loads, then vendor it into `.wai/packs/`
|
|
633
|
+
and register it in `project.yaml → extensions.packs` (commit `.wai/` so CI
|
|
634
|
+
and every clone enforce it). With `--global`, install machine-wide into
|
|
635
|
+
`WAIRON_PACKS_DIR` / `~/.wairon/packs` — auto-loaded for every project on
|
|
636
|
+
this machine (project packs win on collision; a project opts out via
|
|
637
|
+
`extensions.useGlobalPacks: false`). A source may be a file or a directory
|
|
638
|
+
with a pack entry file (`pack.yaml` | `pack.cjs` | `index.cjs` | …).
|
|
639
|
+
- `list`: global + project packs and what each provides (profiles,
|
|
640
|
+
languages, rules), with load errors inline.
|
|
641
|
+
- `remove <name>`: deregister by pack name and delete vendored files under
|
|
642
|
+
`.wai/packs/` (files elsewhere are left in place); `--global` removes a
|
|
643
|
+
machine-wide pack.
|
|
644
|
+
|
|
645
|
+
The store and selection commands (`install | uninstall | which | use | unuse |
|
|
646
|
+
impact | bundle | sync`) are described in
|
|
647
|
+
[Extending wairon](extending-wairon.md#installing-and-selecting-packs).
|
|
648
|
+
|
|
649
|
+
`wairon patterns list` lists the reusable, versioned architecture patterns the
|
|
650
|
+
loaded packs declare; `wairon variants list` lists the component variants (each
|
|
651
|
+
anchored on a base block, with its implementation guidance).
|
|
652
|
+
|
|
653
|
+
### `wairon pack impact <name>[@version]` — and confirm before every pack write
|
|
654
|
+
Packs exist to adjust wairon's checks and behaviour, and a pack may loosen or
|
|
655
|
+
remove them by design (an automation platform cannot hold every concept a full
|
|
656
|
+
backend can). So installing a pack is never judged — but it is always
|
|
657
|
+
intentional, and its impact is shown first.
|
|
658
|
+
|
|
659
|
+
`wairon pack impact` shows, writing nothing, what a pack changes on this
|
|
660
|
+
project:
|
|
661
|
+
|
|
662
|
+
- its **doctrine against wairon's defaults**, by the profile that carries each
|
|
663
|
+
change: rules loosened, raised, turned off or added; profiles added or a
|
|
664
|
+
builtin redefined; stereotypes removed or discouraged, edges licensed,
|
|
665
|
+
patterns, guarantee tokens and language tables added;
|
|
666
|
+
- the **profiles of it that would govern** here (the `projectType` and each
|
|
667
|
+
subsystem profile that names one);
|
|
668
|
+
- the **findings that change** on this project — introduced, resolved and
|
|
669
|
+
regraded, from a dry validate of the current and the candidate
|
|
670
|
+
configuration — with both totals.
|
|
671
|
+
|
|
672
|
+
A pack this project does not apply (or applies at another version) is measured
|
|
673
|
+
as applied; one it applies exactly as asked is measured as removed, so the
|
|
674
|
+
report reads as what the pack accounts for now. Nothing in the report is a
|
|
675
|
+
finding.
|
|
676
|
+
|
|
677
|
+
Every command that selects, updates or removes a project's pack — `pack use`,
|
|
678
|
+
`pack unuse`, `pack add`, `pack remove`, and `pack install` when it moves this
|
|
679
|
+
project's floating selection — **shows the same report and asks before it
|
|
680
|
+
writes**; anything but yes writes nothing. In a script, pass `-y, --yes`: the
|
|
681
|
+
write happens without the report, and the command says it applied without
|
|
682
|
+
showing it. A run with no terminal to ask on (CI, a pipe) behaves the same way.
|
|
683
|
+
|
|
684
|
+
### `wairon diagram [--format <fmt>] [--subsystem <id>] [--sequence <component:method>] [--depth <n>] [--all] [--out <path>] [--no-health]`
|
|
685
|
+
Generate architecture diagrams derived from the spec tree — living
|
|
686
|
+
documentation from the same source of truth as the conformance gate. Every
|
|
687
|
+
format writes a file (by default under `.wai/docs/diagrams/`; `--out` names
|
|
688
|
+
another file or directory); nothing opens automatically.
|
|
689
|
+
|
|
690
|
+
- **default (no flags): the interactive canvas** (`canvas.html`).
|
|
691
|
+
- `--format mermaid` (or `--subsystem <id>`): system-wide or scoped Mermaid
|
|
692
|
+
**component diagram** as a markdown file (subsystems as subgraphs,
|
|
693
|
+
`dependsOn` edges, thick edges for boundary hops, dashed `owns`
|
|
694
|
+
containment, bold border on the published surface). Use a `.mmd` `--out`
|
|
695
|
+
path for raw Mermaid.
|
|
696
|
+
- `--subsystem <id>`: scope to one subsystem plus its directly-connected
|
|
697
|
+
external neighbors.
|
|
698
|
+
- `--sequence <component:method>`: a **sequence diagram** derived from the
|
|
699
|
+
method's L5 narrative, recursively expanding `call` steps (cycle-guarded,
|
|
700
|
+
`--depth` limits expansion; default 3).
|
|
701
|
+
- `--canvas`: an **interactive HTML canvas** — the whole system on one page.
|
|
702
|
+
Subsystems as containers, pattern compounds nested inside, components laid
|
|
703
|
+
out in dependency layers (entrypoints left → data right), subsystems in
|
|
704
|
+
topological order (callers left of providers) with barycenter
|
|
705
|
+
crossing-reduction so links stay short and untangled. Pan/zoom,
|
|
706
|
+
double-click a boundary to collapse it (its external edges aggregate into
|
|
707
|
+
labeled "tubes"), click anything for a spec-derived detail panel
|
|
708
|
+
(description, interfaces, methods, endpoints, narratives, dependencies,
|
|
709
|
+
trusted links), search, and a validation-issue overlay. The computed
|
|
710
|
+
layout is locked by default — enable "rearrange" to drag, "reset layout"
|
|
711
|
+
to undo — and "export PNG" renders the full graph to a high-res image for
|
|
712
|
+
Miro/docs/slides. Fully self-contained (Cytoscape.js embedded inline,
|
|
713
|
+
works offline).
|
|
714
|
+
- `--format <mermaid|canvas|drawio|excalidraw>`: friendly alias for the flags below/above.
|
|
715
|
+
- `--drawio` / `--excalidraw`: **editable** exports in open formats
|
|
716
|
+
(draw.io / diagrams.net XML, Excalidraw scene JSON) with the exact same
|
|
717
|
+
computed layout as the canvas — import into diagrams.net, excalidraw.com,
|
|
718
|
+
VS Code extensions, or any whiteboard tool that accepts these formats.
|
|
719
|
+
- `--all`: write the full set (system, per-subsystem, one sequence per
|
|
720
|
+
entrypoint method with a narrative, plus `canvas.html`,
|
|
721
|
+
`architecture.drawio`, and `architecture.excalidraw`) into
|
|
722
|
+
`.wai/docs/diagrams/` (or `--out`).
|
|
723
|
+
- `--no-health`: skip comparing each consumption relation with its live
|
|
724
|
+
producer; the canvas draws those edges as not checked.
|
|
725
|
+
|
|
726
|
+
The canvas is a small single-page app with **C4-style scoped navigation**:
|
|
727
|
+
each view renders one scope's direct children (System → subsystems →
|
|
728
|
+
components → pattern members, infinitely deep by ownership); double-click
|
|
729
|
+
drills in, the breadcrumb navigates back, "Internals" previews children
|
|
730
|
+
inside boxes, "Externals" shows out-of-scope references as ghosts. Plus
|
|
731
|
+
SYW/light themes, presentation mode, per-view layout persistence,
|
|
732
|
+
narrative flowcharts with call drill-down, and an Export menu
|
|
733
|
+
(PNG / draw.io / Excalidraw) capturing the current view and layout.
|
|
734
|
+
|
|
735
|
+
### `wairon export [--out <file>]`
|
|
736
|
+
The whole design, resolved, as one JSON document for generators,
|
|
737
|
+
translators and documentation tools (format `wairon-design`; the
|
|
738
|
+
[format page](design-export.md) describes every field, the compatibility
|
|
739
|
+
promise and how a consumer follows renames).
|
|
740
|
+
|
|
741
|
+
- It first decides the approval verdict exactly as `wairon lock-check` does
|
|
742
|
+
(not strict) and stamps it as `source.approval` (`locked`, `stale` or
|
|
743
|
+
`unlocked`) and `source.approved`. An unapproved tree is exported too,
|
|
744
|
+
carrying its state.
|
|
745
|
+
- Without `--out` it prints the JSON to stdout and nothing else, so it pipes
|
|
746
|
+
cleanly (`wairon export | jq .components`). With `--out` it writes the file
|
|
747
|
+
and reports the path and the approval state.
|
|
748
|
+
- Member projects are listed under `dependencies`, never inlined: export each
|
|
749
|
+
from its own root. So the top of a family that holds no design of its own
|
|
750
|
+
exports no components. It names its members (on stderr without `--out`), so
|
|
751
|
+
that does not read as an empty design. A part's subsystems are the project's
|
|
752
|
+
own and are exported with it.
|
|
753
|
+
- Deterministic: the same tree and verdict give byte-identical output.
|
|
754
|
+
- A tree without an L0 is refused. The JSON Schema ships in the package as
|
|
755
|
+
`schemas/design-export-1.json`, and the library twin is `exportDesign()`.
|
|
756
|
+
|
|
757
|
+
### `wairon type rename-field <type> <field> <new-name>`
|
|
758
|
+
Renames a field of a type and respells every reference to it — each foreign
|
|
759
|
+
key `references: <type>.<field>` naming it, the type's own included. The field
|
|
760
|
+
keeps its place, type and description; its old name joins the field's rename
|
|
761
|
+
trace (`previousNames`), which `wairon export` shows as `formerly`, so a
|
|
762
|
+
consumer holding the old name reads a rename, not a removal and an addition.
|
|
763
|
+
Traces and prose are never rewritten. It refuses, writing nothing, a type or
|
|
764
|
+
field that does not exist, a name that is not an identifier or that the target
|
|
765
|
+
language reserves for a field (`fn` in Rust), and a name another field of the
|
|
766
|
+
type holds or retired. The MCP twin is `sdd_rename_field`. As with
|
|
767
|
+
any design edit, the next `wairon lock` approves it.
|
|
768
|
+
|
|
769
|
+
### `wairon method rename <component> <method> <new-name> [--dry-run] [--search <dirs...>] [--no-pin-symbol]`
|
|
770
|
+
Renames a contract method and retargets every reference to it in the bound
|
|
771
|
+
tree. The method moves on every interface of the component that declares it,
|
|
772
|
+
and on the implementations of those contracts with its narrative, source path
|
|
773
|
+
and symbol; call, register and dispatch steps, dispatch-table bindings,
|
|
774
|
+
lifecycle entrypoints, the lint allows keyed on it and the debt register
|
|
775
|
+
(`rules.conformance.carried`) follow. Its old name joins the method's rename
|
|
776
|
+
trace (`previousNames`), which `wairon export` shows as `formerly`, so a
|
|
777
|
+
consumer reads a rename, not a removal and an addition. Prose and a gRPC wire
|
|
778
|
+
method are never rewritten; they are listed as mentions.
|
|
779
|
+
|
|
780
|
+
- `--dry-run` prints what the rename would move, retarget and break — every
|
|
781
|
+
consumer that calls the method through an export, by project and spec — and
|
|
782
|
+
writes nothing. Run it first on a published verb.
|
|
783
|
+
- `--search <dirs...>` also scans these folders for consumer checkouts outside
|
|
784
|
+
the family (for example the folder holding sibling checkouts), so the dry run
|
|
785
|
+
names a sibling project the rename breaks.
|
|
786
|
+
- `--no-pin-symbol`: by default an implementation that declares no `symbol` is
|
|
787
|
+
pinned to the OLD name, so the function its code already holds keeps binding.
|
|
788
|
+
Pass it when the code is not written yet, or will be renamed with the
|
|
789
|
+
contract, so the implementation follows the new name.
|
|
790
|
+
|
|
791
|
+
It refuses, writing nothing: a component that does not exist, one in another
|
|
792
|
+
project (rename it from that project's root), a method the component does not
|
|
793
|
+
declare, a name a moving contract already declares or retired, and a new name
|
|
794
|
+
that is not an identifier in the tree's method casing (snake_case in a Rust or
|
|
795
|
+
Python tree, camelCase in a TypeScript one), that is longer than 64 characters
|
|
796
|
+
or starts with a digit, or that the target language reserves for a method
|
|
797
|
+
(`constructor` in TypeScript, `fn` or `type` in Rust — the refusal names the
|
|
798
|
+
language and offers an alternative). A contract that implements another
|
|
799
|
+
project's extension point takes its method names from that producer, so its
|
|
800
|
+
casing is not judged. The MCP twin is `sdd_rename_method` (with `dryRun`). The
|
|
801
|
+
next `wairon lock` approves the rename.
|
|
802
|
+
|
|
803
|
+
### `wairon method rename-param <component> <method> <param> <new-name>`
|
|
804
|
+
Renames a parameter of a contract method. The parameter moves on every
|
|
805
|
+
interface of the component that declares the method, keeping its place, type,
|
|
806
|
+
description and optionality; its old name joins the parameter's rename trace
|
|
807
|
+
(`previousNames`), which `wairon export` shows as `formerly`, so a generator or
|
|
808
|
+
consumer reads a rename, not a signature change. Each signature is re-derived
|
|
809
|
+
from the params (a method whose `signatureFrom` names this one follows by
|
|
810
|
+
itself), and an HTTP endpoint path placeholder `{old}` on the method's own
|
|
811
|
+
binding is respelled — the URL a caller sends is unchanged. Prose is never
|
|
812
|
+
rewritten. It refuses, writing nothing, a component, method or parameter that
|
|
813
|
+
does not exist (or a parameter the method takes from a `signatureFrom`), a name
|
|
814
|
+
that is not an identifier or that the target language reserves for a parameter
|
|
815
|
+
(`class` in TypeScript, `type` in Rust), and a name another parameter of the
|
|
816
|
+
method holds or retired. The MCP twin is `sdd_rename_param`. The next `wairon lock` approves it.
|
|
817
|
+
|
|
818
|
+
### `wairon network flows|policy|diagram|check|why|declare|undeclare`
|
|
819
|
+
Networking derived from the design: the reach the validator already models
|
|
820
|
+
(modelled calls, Portal entries and their scopes, declared networks and their
|
|
821
|
+
gateways), never addresses. Run at the family root.
|
|
822
|
+
|
|
823
|
+
- `flows [--format json|csv|markdown] [--out <file>]` prints the allowed-flows
|
|
824
|
+
matrix, narrowed to the modelled callers wherever the family proves a
|
|
825
|
+
`network` entry.
|
|
826
|
+
- `policy --bindings <file> [--out <file>]` generates Kubernetes
|
|
827
|
+
`NetworkPolicy` from the matrix and a bindings file you keep outside
|
|
828
|
+
`.wai/`. Names it cannot bind are listed, never guessed.
|
|
829
|
+
- `diagram [--out <file>]` prints a Mermaid flowchart of the networks as
|
|
830
|
+
trust boundaries, with the gateways, outside and the flows.
|
|
831
|
+
- `check --observed <file> [--bindings <file>]` compares observed live flows
|
|
832
|
+
(CSV or JSON) with the matrix. It exits 1 on an unexpected flow or an
|
|
833
|
+
unknown verb.
|
|
834
|
+
- `why <from> <to>` prints the modelled chain behind one flow. It exits 1
|
|
835
|
+
when nothing allows it.
|
|
836
|
+
- `declare [--description <text>]` writes the project's `network` declaration
|
|
837
|
+
into `.wai/project.yaml`; `undeclare` removes it (the assistant's twin is
|
|
838
|
+
`sdd_set_network`). It changes which rules fire: validate, then re-lock.
|
|
839
|
+
|
|
840
|
+
[Derived networking](network.md) describes every output, the bindings file
|
|
841
|
+
and the observed-flow format.
|
|
842
|
+
|
|
843
|
+
---
|
|
844
|
+
|
|
845
|
+
## Members
|
|
846
|
+
|
|
847
|
+
A project's **members** are declared in its `.wai/project.yaml`, each by one
|
|
848
|
+
location key:
|
|
849
|
+
|
|
850
|
+
```yaml
|
|
851
|
+
members:
|
|
852
|
+
scheduler: services/scheduler # contained
|
|
853
|
+
admin: ../admin # a sibling checkout
|
|
854
|
+
payments: git@host:acme/payments.git#<full commit> # a git repository at a commit
|
|
855
|
+
ledger: { source: services/ledger, description: The books, as: project } # long form
|
|
856
|
+
```
|
|
857
|
+
|
|
858
|
+
A member is a **part** or a **project**, and what it is follows from its
|
|
859
|
+
content — `as:` only asserts it (a contradiction is `MEMBER_KIND_MISMATCH`):
|
|
860
|
+
|
|
861
|
+
- A **part** (the default) is a piece of this project stored in another folder
|
|
862
|
+
or repository. Its subsystems are this project's own: written by local id,
|
|
863
|
+
judged by the ordinary subsystem rules, covered by this project's lock, and
|
|
864
|
+
generated and briefed with this project's agents. It holds only a specs folder
|
|
865
|
+
— plus, when it is not contained, a `project.yaml` holding just
|
|
866
|
+
`partOf: { project: <parent id>, path: <parent root> }`.
|
|
867
|
+
- A **project** is an independent boundary: its tree declares an id, holds an L0
|
|
868
|
+
or carries a lock. It is reached only as `alias::name` through its L0 exports,
|
|
869
|
+
judged by its own gate and locked at its own root. A project stored outside
|
|
870
|
+
this project (`../x`, git, `hosted:`) is a **referenced** project: this
|
|
871
|
+
project's gate judges it against its pin (`wairon externals pin`), and the
|
|
872
|
+
family run opens it where it is stored and composes it per use.
|
|
873
|
+
|
|
874
|
+
Boundaries are earned: grow a system from subsystems, to parts when a piece
|
|
875
|
+
needs its own folder or repository, to projects only when it needs its own team,
|
|
876
|
+
release, approval or public surface.
|
|
877
|
+
|
|
878
|
+
A git member is pinned at a full commit; it is read from the fetch cache
|
|
879
|
+
(`WAIRON_CACHE_DIR`, else the OS user cache directory), immutable and offline
|
|
880
|
+
once fetched. A git part is read-only here — change it in its own repository,
|
|
881
|
+
then move the pin with `wairon member update`. An uncached git part offline is
|
|
882
|
+
`PART_UNAVAILABLE`; an uncached git project falls back to its pin and is
|
|
883
|
+
`EXTERNAL_CHECK_UNAVAILABLE` — never a pass. Every cross-project reference is
|
|
884
|
+
`alias::name`: the alias is one of the referring project's project members or
|
|
885
|
+
declared `externals`, the name a public name in that project's L0 export table.
|
|
886
|
+
An id without `::` is local — a part's subsystems included.
|
|
887
|
+
|
|
888
|
+
**Authoring the L0 export table.** What another project may reference is the
|
|
889
|
+
project's **L0 export table**: the `publicInterfaces` list of the L0 spec
|
|
890
|
+
(`.wai/specs/.index.yaml`). It is authored in two steps, both through the MCP
|
|
891
|
+
tools (an assistant runs them; a human may edit the YAML the same way):
|
|
892
|
+
|
|
893
|
+
1. Each subsystem publishes its own surface — its inbound Portal, and the types
|
|
894
|
+
it shares — with `sdd_set_public_interfaces` (an L1 `publicInterfaces`).
|
|
895
|
+
2. The L0 **re-exports** from those subsystems with `sdd_update_spec` on kind
|
|
896
|
+
`system`, delta `publicInterfaces`. Each entry names a subsystem in `from`;
|
|
897
|
+
`from` alone re-exports everything it publishes, `component` (optionally
|
|
898
|
+
narrowed with `interface`) or `typeDef` picks one item, `as` renames it
|
|
899
|
+
(the public name consumers write after `alias::`), `role: implement` marks
|
|
900
|
+
an extension point consumers implement, and `audience` decides who may see
|
|
901
|
+
it (below).
|
|
902
|
+
|
|
903
|
+
```yaml
|
|
904
|
+
# .wai/specs/.index.yaml (excerpt)
|
|
905
|
+
publicInterfaces:
|
|
906
|
+
- from: distance # everything the distance subsystem publishes
|
|
907
|
+
- from: tiles
|
|
908
|
+
component: tile_portal
|
|
909
|
+
as: tiles # consumers write geo::tiles
|
|
910
|
+
audience: external
|
|
911
|
+
- from: shared
|
|
912
|
+
typeDef: coordinate # a shared type, as geo::coordinate
|
|
913
|
+
```
|
|
914
|
+
|
|
915
|
+
The key is `publicInterfaces` — an `exports:` (or any other key the schema does
|
|
916
|
+
not know) is reported as `UNKNOWN_SPEC_KEY`, never silently ignored, and an
|
|
917
|
+
unknown setting in `.wai/project.yaml` as `UNKNOWN_CONFIG_KEY`. A reference to a
|
|
918
|
+
name the producer does not export is `EXTERNAL_NOT_EXPORTED`.
|
|
919
|
+
|
|
920
|
+
**Export audiences.** Each L0 entry carries an `audience` (default `instance`)
|
|
921
|
+
that decides who may see the public name. Narrowest first:
|
|
922
|
+
|
|
923
|
+
| Audience | Who sees it |
|
|
924
|
+
|----------|-------------|
|
|
925
|
+
| `project` | Only this project's family: its members, its parent and their siblings. |
|
|
926
|
+
| `department` | The family, and — on a hosted instance — the projects of the owning department. A local sibling checkout or git consumer does **not** see it. |
|
|
927
|
+
| `instance` | The default. Any project on the instance, and any local consumer by path or git. |
|
|
928
|
+
| `partner` | Also consumers outside the instance that a partner grant admits. |
|
|
929
|
+
| `external` | Publicly consumable. |
|
|
930
|
+
|
|
931
|
+
A consumer is read at one audience: a family project at `project`, so it sees
|
|
932
|
+
everything; a path, git or hosted consumer outside the family at `instance`, so
|
|
933
|
+
it sees `instance`, `partner` and `external` entries only. A name exported to a
|
|
934
|
+
narrower audience is refused by `externals add` (and `externals use`), naming
|
|
935
|
+
the export's audience, the consumer's and this ranking. Because a project cannot
|
|
936
|
+
tell from its own tree whether its consumers are hosted department units,
|
|
937
|
+
`validate` notes every `department` export (`EXPORT_AUDIENCE_NARROW`, a
|
|
938
|
+
notice): widen it to `instance` when a sibling checkout or a git consumer uses
|
|
939
|
+
it, or keep it and allow the notice. A re-export can narrow an audience, never
|
|
940
|
+
widen it (`EXPORT_WIDENS_AUDIENCE`).
|
|
941
|
+
|
|
942
|
+
| Command | Description |
|
|
943
|
+
|---------|-------------|
|
|
944
|
+
| `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 |
|
|
945
|
+
| `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 |
|
|
946
|
+
| `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 |
|
|
947
|
+
| `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 |
|
|
948
|
+
| `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) |
|
|
949
|
+
| `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 |
|
|
950
|
+
| `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 |
|
|
951
|
+
| `wairon member adopt <alias>` | Make this project's external found by a path inside it a member again — detach's inverse |
|
|
952
|
+
| `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 |
|
|
953
|
+
| `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 |
|
|
954
|
+
| `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 |
|
|
955
|
+
| `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 |
|
|
956
|
+
|
|
957
|
+
On a hosted instance a project member is a project record of its own and
|
|
958
|
+
inherits access from its parent, while a **part is part of its parent's record**:
|
|
959
|
+
it gets no record, and a demote retires the member's record (disabled as "part of
|
|
960
|
+
`<parent>`", audited `member.retired`, its own-scope settings kept) and a promote
|
|
961
|
+
re-enables it; the record's `disabledReason` (shown in the web app and `host
|
|
962
|
+
project list`) says why. Hosted roots are isolated, so a member with a `../` or git source
|
|
963
|
+
is refused there; a `hosted:` project member is its own top-level record.
|
|
964
|
+
`sdd_detach_member` also **moves** the member to an isolated root of its own
|
|
965
|
+
(writing `source: { hosted: <id> }` for its consumers) and `sdd_adopt_member`
|
|
966
|
+
with `path` moves it back. A `source.hosted` producer outside a hosted server is
|
|
967
|
+
reported as "hosted-only producer `<id>`: available only through the hosted
|
|
968
|
+
server".
|
|
969
|
+
|
|
970
|
+
**A part on its own.** A part stored outside its parent can be validated in its
|
|
971
|
+
own repository's CI: run `wairon externals pin` at the part (with the parent on
|
|
972
|
+
disk at `partOf.path`) to pin the excerpt of the parent it uses; `wairon validate`
|
|
973
|
+
there then judges the part against that pin (`PART_JUDGED_ALONE`). Unpinned, it
|
|
974
|
+
says "part of `<parent>`; validate from the parent" (`PART_UNPINNED`).
|
|
975
|
+
|
|
976
|
+
**Family migrations.** `attach`, `detach`, `adopt`, `promote`, `demote`,
|
|
977
|
+
`rename-alias`, `internalize`, `project rename` and `subsystem externalize` all run
|
|
978
|
+
one flow: plan, print the plan (each project's edits, every refusal, the notes,
|
|
979
|
+
the file changes, the projects to re-lock), then apply it all or nothing.
|
|
980
|
+
`--report` prints the plan and writes nothing; otherwise the command asks —
|
|
981
|
+
`--yes` answers, and a shell with no terminal and no `--yes` writes nothing. A
|
|
982
|
+
refused plan exits non-zero and writes nothing. The plan is computed by running
|
|
983
|
+
the verb's writes on a private copy of the family's `.wai` trees (its parts'
|
|
984
|
+
included); applying it stages every change with a backup under each project's
|
|
985
|
+
`.wai/transactions/<id>/` (never committed) and swaps them in, restoring every
|
|
986
|
+
backup on any failure. A crash mid-swap leaves a journal: `wairon status` and
|
|
987
|
+
`wairon validate` show it as a notice, and `wairon doctor --fix` rolls it back.
|
|
988
|
+
No verb ever locks — each names the projects to re-lock. A promote (or `externalize --as project`) re-roots every
|
|
989
|
+
source path that moves with the new project: a path inside the new project's folder becomes
|
|
990
|
+
member-relative, and one outside it (source code you have not moved yet) becomes a member-relative
|
|
991
|
+
*planned* path — `SOURCE_FILE_PLANNED`, never a path that escapes the root — so the new project
|
|
992
|
+
validates on day one and you move the code when you are ready. A topic finding
|
|
993
|
+
(`UNCONSUMED_TOPIC`, `UNSOURCED_SUBSCRIPTION`) is sited at its topic: an allow names it
|
|
994
|
+
with `at: <topic>`. A member stored in a
|
|
995
|
+
`../` sibling checkout on the same volume joins the same transaction: its files
|
|
996
|
+
change all-or-nothing with the family's, the coordinator's journal names every
|
|
997
|
+
owner (so `wairon doctor --fix` recovers from either root), and the plan says
|
|
998
|
+
plainly "files: all-or-nothing; commits: one per repository" — commit each
|
|
999
|
+
repository yourself; no commit across repositories is ever atomic. A sibling on
|
|
1000
|
+
another volume is refused (`cross-volume`), and a git member always is (its files
|
|
1001
|
+
are a read-only cache). `member demote` names every L0 export entry it removes.
|
|
1002
|
+
|
|
1003
|
+
**Required packs.** A project may require packs of the members below it with
|
|
1004
|
+
`composition.requirePolicies` (see
|
|
1005
|
+
[Extending wairon](extending-wairon.md#governance--what-a-pack-changes-and-what-a-parent-requires)).
|
|
1006
|
+
`wairon member add --project` — and `wairon init` run in a subdirectory when you
|
|
1007
|
+
answer yes on a terminal, which creates a project member the same way — writes those packs into the new member's selection once,
|
|
1008
|
+
each pinned to the highest installed version its range admits, sets the
|
|
1009
|
+
`projectType` a requirement names, and prints what it applied and each
|
|
1010
|
+
requirement nothing installed satisfies. Scaffolding is an unattended pack write,
|
|
1011
|
+
so it shows no impact report; run `wairon pack impact <name>` at the member's
|
|
1012
|
+
root to see one.
|
|
1013
|
+
|
|
1014
|
+
**Deprecated forms.** For one release wairon still reads, and reports: a leading
|
|
1015
|
+
`::` (`::shared::money`), `super::` (`super::sibling`), a member path
|
|
1016
|
+
(`billing::invoice::invoice_portal`) and an L1 subsystem carrying
|
|
1017
|
+
`projectPath` (`DEPRECATED_MOUNT_FORM`), and a member's long-form `path` key
|
|
1018
|
+
(`{ path: services/x }`; the one location key is `source`). `wairon doctor
|
|
1019
|
+
--fix` rewrites them to `members`, the one location key and `alias::name`.
|
|
1020
|
+
|
|
1021
|
+
---
|
|
1022
|
+
|
|
1023
|
+
## Domains
|
|
1024
|
+
|
|
1025
|
+
A domain is a unit of agent ownership. Subsystem-derived domains come from the
|
|
1026
|
+
spec tree (read-only); free-standing domains live in `.wai/topology.yaml`.
|
|
1027
|
+
|
|
1028
|
+
| Command | Description |
|
|
1029
|
+
|---------|-------------|
|
|
1030
|
+
| `wairon domains list` | List all domains (subsystem-derived + free-standing) |
|
|
1031
|
+
| `wairon domains scan [--add]` | Detect physical directory candidates; `--add` adds selected ones as free-standing domains |
|
|
1032
|
+
| `wairon domains add [--path] [--id]` | Manually add a free-standing domain |
|
|
1033
|
+
| `wairon domains remove <id>` | Remove a free-standing domain (subsystem-derived domains cannot be removed here) |
|
|
1034
|
+
|
|
1035
|
+
---
|
|
1036
|
+
|
|
1037
|
+
## Skills
|
|
1038
|
+
|
|
1039
|
+
The five SDD skills (`sdd-architect`, `sdd-narrative`, `sdd-auditor`,
|
|
1040
|
+
`sdd-implement`, `sdd-delegate`) drive the spec-driven workflow inside your AI
|
|
1041
|
+
tool.
|
|
1042
|
+
|
|
1043
|
+
| Command | Description |
|
|
1044
|
+
|---------|-------------|
|
|
1045
|
+
| `wairon skills list` | List the built-in SDD skills |
|
|
1046
|
+
| `wairon skills install` (alias `sync`) | Install/refresh the skills into each active target's skills dir |
|
|
1047
|
+
|
|
1048
|
+
---
|
|
1049
|
+
|
|
1050
|
+
## MCP
|
|
1051
|
+
|
|
1052
|
+
The wairon MCP server exposes topology and `sdd_*` tools so AI tools can query
|
|
1053
|
+
and author specs directly.
|
|
1054
|
+
|
|
1055
|
+
| Command | Description |
|
|
1056
|
+
|---------|-------------|
|
|
1057
|
+
| `wairon mcp serve` | Start the MCP server (stdio transport) |
|
|
1058
|
+
| `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`) |
|
|
1059
|
+
| `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` |
|
|
1060
|
+
| `wairon mcp status` | Show whether the server is registered |
|
|
1061
|
+
|
|
1062
|
+
A project-local registration (`.mcp.json`, `.gemini/settings.json`) is committed with the
|
|
1063
|
+
project, so it holds nothing machine-specific: it runs `wairon mcp serve` from the PATH (or
|
|
1064
|
+
`node ./<path>` when the CLI lives inside the project, as in a checkout of wairon itself),
|
|
1065
|
+
and the server attaches to the project it is started in. A `--global` registration is
|
|
1066
|
+
machine-wide and names the running CLI by its absolute path.
|
|
1067
|
+
|
|
1068
|
+
The local server offers 47 tools:
|
|
1069
|
+
|
|
1070
|
+
| Group | Tools |
|
|
1071
|
+
|-------|-------|
|
|
1072
|
+
| Topology (read) | `listAgents`, `getAgent`, `listDomains`, `validateTopology`, `getProjectConfig` |
|
|
1073
|
+
| 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` |
|
|
1074
|
+
| Reading and checking | `sdd_get_spec`, `sdd_get_status`, `sdd_validate_tree` |
|
|
1075
|
+
| Renames and moves (in this tree) | `sdd_rename_component`, `sdd_rename_method`, `sdd_rename_param`, `sdd_rename_type`, `sdd_rename_field`, `sdd_move_methods` |
|
|
1076
|
+
| 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` |
|
|
1077
|
+
| Externals | `sdd_add_external`, `sdd_update_external`, `sdd_remove_external`, `sdd_pin_externals`, `sdd_get_externals_status` |
|
|
1078
|
+
| Public surface (the producer's side) | `sdd_list_consumers`, `sdd_surface_diff` |
|
|
1079
|
+
| Network | `sdd_set_network`, `sdd_get_network_flows`, `sdd_explain_flow` |
|
|
1080
|
+
| Packs | `sdd_pack_impact` |
|
|
1081
|
+
| Delegation | `sdd_get_agent_brief` (the live brief for one agent; also served as the `wairon-agent://` resource) |
|
|
1082
|
+
|
|
1083
|
+
A hosted server adds its own `sdd_host_*` and `sdd_landscape_*` tools; see the
|
|
1084
|
+
[hosted server guide](https://github.com/SYW-Apps/Waffle-AIron/blob/main/docs/design/hosted-mcp-server.md).
|
|
1085
|
+
|
|
1086
|
+
---
|
|
1087
|
+
|
|
1088
|
+
## Hosting (self-hosted server)
|
|
1089
|
+
|
|
1090
|
+
`wairon serve` runs wairon as an HTTP server that hosts many **fully-isolated**
|
|
1091
|
+
projects behind one endpoint — a public **data plane** (the `sdd_*` tools over
|
|
1092
|
+
streamable HTTP, per-project API-key scoped) and an admin **control plane**
|
|
1093
|
+
(project & key lifecycle, state-scoped lock). See the
|
|
1094
|
+
[hosted server guide](https://github.com/SYW-Apps/Waffle-AIron/blob/main/docs/design/hosted-mcp-server.md) for the architecture, Docker
|
|
1095
|
+
self-hosting, auth, and sizing.
|
|
1096
|
+
|
|
1097
|
+
### `wairon serve [--host <h>] [--port <p>] [--admin-host <h>] [--admin-port <p>] [--data-dir <path>] [--no-auth]`
|
|
1098
|
+
Start the hosting server. Data plane on `0.0.0.0:8080` (`POST /mcp`, `/healthz`,
|
|
1099
|
+
`/readyz`); admin plane on `127.0.0.1:8081` (`/admin/*`). Auth is **on by
|
|
1100
|
+
default** and requires `WAIRON_ADMIN_TOKEN` (the master credential) — the server
|
|
1101
|
+
refuses to start without it; `--no-auth` disables data-plane auth for a trusted
|
|
1102
|
+
network. `--data-dir` (or `WAIRON_DATA_DIR`, default `~/.wairon/data`) is the data
|
|
1103
|
+
root holding `projects/` and `auth/`. `WAIRON_AUDIT_POLICY` (optional) sets the
|
|
1104
|
+
durable audit policy as a JSON object of overrides laid over the secure default
|
|
1105
|
+
(capture `info` and above, keep events 90 days and `security` events 365, skip read
|
|
1106
|
+
events, redacted metadata) — e.g. `{"retentionDays":30,"includeReadEvents":true}`.
|
|
1107
|
+
Its fields are `enabled` and `includeReadEvents` (booleans), `retentionDays` and
|
|
1108
|
+
`securityRetentionDays` (non-negative numbers), `minimumLevel` (`debug`, `info`,
|
|
1109
|
+
`warning`, `error` or `security`) and `metadataMode` (`none` or `redacted`; the
|
|
1110
|
+
retired `full-redacted` still loads as `redacted`, with a deprecation warning); an unknown field, a wrong type or invalid JSON stops the server at
|
|
1111
|
+
startup with a message naming the variable. `WAIRON_QUOTA_POLICY` (optional) sets the
|
|
1112
|
+
advisory quota policy the same way, as a JSON object of overrides laid over the
|
|
1113
|
+
disabled default (`enabled: false`, mode `observe`, no limits) — e.g.
|
|
1114
|
+
`{"enabled":true,"mode":"warn","maxProjectsPerUser":20}`. Its fields are `enabled`
|
|
1115
|
+
(boolean), `mode` (`observe` or `warn`, the label an exceeded limit is reported
|
|
1116
|
+
with; quotas never block or throttle yet, so the retired `block` loads as `observe`,
|
|
1117
|
+
with a deprecation warning) and the non-negative integer
|
|
1118
|
+
limits `maxProjectsPerUser`, `maxMcpRequestsPerMinute`, `maxProjectBytes` and
|
|
1119
|
+
`maxAuditEventsPerDay`; it is refused at startup exactly as the audit policy is.
|
|
1120
|
+
|
|
1121
|
+
### `wairon host …` — control plane
|
|
1122
|
+
Runs **in-process** (no running server needed), so it works over SSH /
|
|
1123
|
+
`docker exec`. Reads the master credential from `WAIRON_ADMIN_TOKEN`.
|
|
1124
|
+
|
|
1125
|
+
Every project lives in an **organization unit**, and access is a grid of
|
|
1126
|
+
**permission assignments** (subject × scope × capability). An API key acts as its
|
|
1127
|
+
**owner's** live permissions, narrowed to the projects it names. The first
|
|
1128
|
+
project on a new instance:
|
|
1129
|
+
|
|
1130
|
+
```sh
|
|
1131
|
+
wairon host unit create --slug acme # a root unit (business_entity)
|
|
1132
|
+
wairon host project create --id shop --unit acme
|
|
1133
|
+
wairon host permission set --user alice --capability project:read --project shop
|
|
1134
|
+
wairon host permission set --user alice --capability project:write --project shop
|
|
1135
|
+
wairon host key mint --project shop --owner alice # → wk_… (shown once)
|
|
1136
|
+
```
|
|
1137
|
+
|
|
1138
|
+
`project:write` does not include `project:read`: a key whose owner can only
|
|
1139
|
+
write is refused every read tool. `host permission set` warns when it grants
|
|
1140
|
+
`project:write` to a user who cannot read that scope, and `host key mint --owner`
|
|
1141
|
+
warns when the owner cannot read the project — each naming the grant that fixes
|
|
1142
|
+
it. An owner needs no user record — an id with no record acts as a service
|
|
1143
|
+
principal.
|
|
1144
|
+
|
|
1145
|
+
| Command | Description |
|
|
1146
|
+
|---------|-------------|
|
|
1147
|
+
| `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 |
|
|
1148
|
+
| `wairon host project create --id <id> --unit <unitId>` | Provision a new isolated project (its own `.wai/` tree) in a unit |
|
|
1149
|
+
| `wairon host project list` | List hosted projects |
|
|
1150
|
+
| `wairon host project destroy --id <id>` | Remove a project and its tree |
|
|
1151
|
+
| `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 |
|
|
1152
|
+
| `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 |
|
|
1153
|
+
| `wairon host permission list [--user <userId>] [scope flags]` / `remove --id <assignmentId>` | List or remove assignments |
|
|
1154
|
+
| `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 |
|
|
1155
|
+
| `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, migrate every hosted project's spec tree onto the reachability model (each rewritten project is audited as `migration.reachability` and owes one re-lock by its approvers; the server never does this on its own when it binds a project, because the rewrite moves the approved design and a read-only request must not write), 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 |
|
|
1156
|
+
| `wairon host key list [--project <id>]` | List API keys |
|
|
1157
|
+
| `wairon host key revoke --id <id>` | Revoke a key |
|
|
1158
|
+
| `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 |
|
|
1159
|
+
| `wairon host git enable \| disable \| sync \| commit \| status \| sync-config` | Bind a hosted project to its real repository (wairon commits only `.wai/`) |
|
|
1160
|
+
| `wairon host producer configure \| produce \| remove \| list` | Project a hosted project to Notion or Miro |
|
|
1161
|
+
| `wairon host secret set \| list` | Set integration secrets at runtime (`git-token`, `notion-token`, `miro-token`, `signing-secret`) — no restart |
|
|
1162
|
+
| `wairon host packs list [--project <id>]` | List the server-global packs, or one hosted project's |
|
|
1163
|
+
| `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 |
|
|
1164
|
+
| `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 |
|
|
1165
|
+
|
|
1166
|
+
The hosted API and web UI preview the same way:
|
|
1167
|
+
`POST /admin/projects/{id}/packs/{name}/impact` (a declarative pack's YAML as
|
|
1168
|
+
`content`, or no body to preview adopting the server-global pack of that name)
|
|
1169
|
+
and `POST /admin/projects/{id}/packs/{name}/removal-impact`; both need
|
|
1170
|
+
`project:read` and write nothing. The hosted MCP data plane offers the same
|
|
1171
|
+
preview as `sdd_host_pack_impact`. The unattended hosted policy writes —
|
|
1172
|
+
reconcile, a project type change, and a policy-governed project creation —
|
|
1173
|
+
return the impact of every pack they applied in their results.
|
|
1174
|
+
|
|
1175
|
+
---
|
|
1176
|
+
|
|
1177
|
+
## Connecting to a hosted instance
|
|
1178
|
+
|
|
1179
|
+
| Command | Description |
|
|
1180
|
+
|---------|-------------|
|
|
1181
|
+
| `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`) |
|
|
1182
|
+
| `wairon logout [url]` | Forget a stored credential (local only — it does not revoke it), or list what is stored |
|
|
1183
|
+
| `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) |
|
|
1184
|
+
| `wairon remote attach \| detach \| status` | Bind this checkout to a hosted project; while attached, `wairon lock` locks the hosted project |
|
|
1185
|
+
|
|
1186
|
+
## Surfaces, externals and producers
|
|
1187
|
+
|
|
1188
|
+
| Command | Description |
|
|
1189
|
+
|---------|-------------|
|
|
1190
|
+
| `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. Text that is no location (braces, quotes, whitespace) is refused. The declaration is checked against the producer it reaches **before** anything is written — `--dry-run` included: one answering to another id, or not exporting a `use` name to this project, is refused naming the id, the closest exported names, or the narrower audience the name is exported to (see Export audiences). Pins by default; a producer that cannot be read leaves it declared and unpinned, saying why, and exits 2 (nothing was pinned). A refusal is one sentence naming the accepted form, and exits 1 |
|
|
1191
|
+
| `wairon externals use <alias> [--add a,b\|'*'] [--remove c] [--dry-run] [--json]` | Change one declared external's `use` imports, so the specs may name the producer's public names bare (e.g. so an `implements` of the producer's trait spells its types as the producer does — though `alias::name` and the bare imported name compare as one type either way). An added name the producer does not export to this project is refused. The pin is untouched. Exits 1 on a refusal |
|
|
1192
|
+
| `wairon externals remove <alias> [--dry-run] [--json]` | Remove one external: its declaration and its pin (`.wai/externals/<alias>.yaml` and its lock entry) together. An orphaned pin — one whose declaration was deleted by hand, which `externals list` shows — is removed too. Exits 1 when the alias is neither declared nor pinned |
|
|
1193
|
+
| `wairon externals consumers [--search <dirs...>] [--json]` | From a producer's root: the projects that consume it — each with the alias and section it declares it under and the public names its specs use — so a producer sees who breaks before it changes its surface. Without `--search`, the family read from the highest root in reach. `--search ..` (any folders) also scans those folders and the project roots directly under them for projects declaring this one as an external by a path that resolves here, or by git — a sibling checkout is found this way. A hosted consumer, or one on another machine, declares its dependency on its own side and is not visible here (`sdd_list_consumers` is the same answer for an assistant) |
|
|
1194
|
+
| `wairon externals pin [alias…] [--json]` | Pin declared externals into `.wai/externals/<alias>.yaml` and `.wai/externals.lock.yaml`. The snapshot is rewritten whenever anything it carries moved — not only the signatures the digest covers: a producer that added `abi: c`, changed a transport or a role, or recorded a rename is refreshed by a re-pin. Exits 1 when an alias could not be pinned (unresolved or unreachable — its previous pin stays) |
|
|
1195
|
+
| `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`); a pinned snapshot that no longer carries what the producer says (a stale `abi`, transport or role) is `drifted`, never `ok`, and names the stale facts. A use the lock does not hold is still compared with the live producer: gone from it, it is `removed` or `renamed`. 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 |
|
|
1196
|
+
| `wairon externals list [--json]` | The declared externals, how each resolves and what is pinned; a malformed declaration, and an orphaned pin whose declaration is gone, are listed with their problem, never hidden |
|
|
1197
|
+
| `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 3.1 document per portal), import one, or list them. OpenAPI defaults to the `project` audience, so every HTTP Portal of the project's own is described — one its L0 export table never exports included (a wider `--audience` narrows to what the table shares there); the native snapshot defaults to `instance`. In the OpenAPI document a path placeholder — `{name}`, or the Express spelling `:name`, which is rewritten to `{name}` — is a parameter `in: path`, the other params the query of a GET/DELETE or the JSON body of a POST/PUT/PATCH. The Portal's `basePath` is joined into every path (the document carries no `servers` entry: where it is served is deployment, not design). The parameter that carries the credential the Portal's `auth` binds (a bearer `token`, say) is left out of the parameters and the body — `security` describes it — and named under `x-wairon-credential-param` so a wairon reader can restore it. An unknown `--portal` is refused naming the portals the surface renders |
|
|
1198
|
+
| `wairon surface diff [--against <ref\|file>] [--json]` | The public-surface changelog: this project's export table now against the same table at its last **committed** approval (or at a git revision, or in a saved native snapshot from `surface export`) — every exported name and contract method `added`, `removed`, `renamed` (from its rename trace) or `changed` (signature or type shape), and how many a consumer may have to follow. What a producer writes release notes from before it re-locks; `wairon externals consumers --search <dir>` then says who uses what. Read-only. With no approval ever committed it says so (lock and commit first, or name `--against`). `sdd_surface_diff` is the same answer for an assistant |
|
|
1199
|
+
| `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) |
|
|
1200
|
+
|
|
1201
|
+
## Tooling
|
|
1202
|
+
|
|
1203
|
+
| Command | Description |
|
|
1204
|
+
|---------|-------------|
|
|
1205
|
+
| `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). It serves the same project over MCP (streamable HTTP) at `POST http://127.0.0.1:<port>/mcp?project=local` — the current project is registered as `local`; without `?project=local` the endpoint answers 403. For an AI tool on the same machine the stdio server (`wairon mcp serve`, what `wairon mcp install` registers) remains the usual route |
|
|
1206
|
+
| `wairon update [--check] [--channel <name>]` | Check/install the latest release; switch channel |
|
|
1207
|
+
| `wairon aliases list` | Show command aliases (`wai`) and their status |
|
|
1208
|
+
| `wairon aliases enable <name>` / `disable <name>` | Create / remove an alias |
|