@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.
Files changed (37) hide show
  1. package/CONTRIBUTING.md +193 -0
  2. package/README.md +154 -41
  3. package/dist/cli/index.js +66474 -29775
  4. package/dist/cli/index.js.map +1 -1
  5. package/dist/index.d.ts +43671 -0
  6. package/dist/index.js +53581 -21797
  7. package/dist/index.js.map +1 -1
  8. package/dist/templates/architect.yaml +15 -13
  9. package/dist/templates/domain-owner.yaml +5 -4
  10. package/dist/templates/skills/sdd-architect.md +44 -26
  11. package/dist/templates/skills/sdd-auditor.md +5 -4
  12. package/dist/templates/skills/sdd-delegate.md +43 -4
  13. package/dist/templates/skills/sdd-implement.md +83 -13
  14. package/dist/templates/skills/sdd-narrative.md +7 -4
  15. package/dist/templates/variants/logic-shapes.yaml +59 -0
  16. package/dist/templates/variants/portal-shapes.yaml +13 -0
  17. package/dist/webapp.html +771 -0
  18. package/docs/architecture.md +83 -0
  19. package/docs/cli.md +1208 -0
  20. package/docs/design/approval-baseline.md +257 -0
  21. package/docs/design/connecting-agent-entrypoint.md +167 -0
  22. package/docs/design/execution-budgets.md +199 -0
  23. package/docs/design/pack-scoping.md +581 -0
  24. package/docs/design/supervisor-doctrine.md +110 -0
  25. package/docs/design-export.md +313 -0
  26. package/docs/extending-wairon.md +616 -0
  27. package/docs/network.md +109 -0
  28. package/docs/requirements.md +65 -0
  29. package/docs/roadmap.md +133 -0
  30. package/docs/standards/INDEX.md +23 -0
  31. package/docs/standards/architecture.md +1042 -0
  32. package/docs/standards/language-bindings.md +120 -0
  33. package/docs/templates.md +61 -0
  34. package/docs/vision.md +59 -0
  35. package/docs/wairon-feature-requests-hosted-mcp.md +74 -0
  36. package/package.json +18 -5
  37. package/schemas/design-export-2.json +1186 -0
@@ -0,0 +1,616 @@
1
+ # Extending wairon — packs & wrapper products
2
+
3
+ Wairon core is a spec engine and a rule enforcer. Everything platform- or
4
+ product-specific — custom architectural profiles, language/platform tables,
5
+ extra conformance rules — is injected from outside through **extension
6
+ packs**: plain configuration files (YAML, or a JS module for programmatic
7
+ rules) that wairon reads and imports. A product built this way (a
8
+ *wrapper*) is just packs + an installer on top of the user's existing
9
+ wairon installation: no fork, no build step, and wairon core never learns
10
+ the platform exists.
11
+
12
+ A working wrapper lives at [`examples/wrapper/`](https://github.com/SYW-Apps/Waffle-AIron/blob/main/examples/wrapper/) and
13
+ is guarded by `tests/examples/wrapper-example.test.ts`, so it cannot
14
+ silently rot.
15
+
16
+ ## Installing and selecting packs
17
+
18
+ > **New model (preferred).** Installing a pack makes it *available*; a project
19
+ > then *selects* it. Installing no longer grants a pack authority over every
20
+ > project on the machine — see [pack scoping](design/pack-scoping.md).
21
+
22
+ ```sh
23
+ wairon pack install ./appenser-1.2.0.wpack # into this wairon install's store
24
+ wairon pack which appenser # which version/digest/origin resolves?
25
+ cd my-project
26
+ wairon pack use appenser # THIS project applies it
27
+ ```
28
+
29
+ `pack use` records the selection by name in `.wai/project.yaml`, carrying the
30
+ origin the pack was installed from so a fresh clone or CI runner can obtain it:
31
+
32
+ ```yaml
33
+ extensions:
34
+ packs:
35
+ - name: appenser
36
+ version: 1.2.0 # only when pinned; omit to track latest installed
37
+ integrity: sha256-… # written by --pin
38
+ source: https://…/appenser-{version}.wpack
39
+ ```
40
+
41
+ - `--pin` freezes the resolved version **and** its content digest.
42
+ - `--source <url>` records an explicit fetch URL (overriding the install origin).
43
+ - `--bundle` marks the pack for committing under `.wai/packs/`, so the repo needs
44
+ no machine setup at all — the answer for private packs and air-gapped CI. A
45
+ committed bundle resolves *before* the store.
46
+ - `wairon pack unuse <name>` drops the selection; the pack stays installed.
47
+
48
+ **A declared pack that cannot be resolved is an error**, not a silent skip:
49
+ `validate`, `status`, `lock`, `generate`, and every `sdd_*` MCP call refuse and
50
+ name the pack plus how to install it. A gate that quietly enforces less than the
51
+ project declared is worse than one that fails.
52
+
53
+ The code names the remedy, so the message tells you which fix applies:
54
+
55
+ | Code | Meaning |
56
+ |------|---------|
57
+ | `PACK_NOT_INSTALLED` | absent from both the store and `.wai/packs/` — obtain it (`pack sync` / `pack install`) |
58
+ | `PACK_VERSION_UNSATISFIED` | the pack *is* installed, but not at the pinned version (which the message lists) — correct the pin |
59
+ | `PACK_INTEGRITY_MISMATCH` | resolved content does not match the pinned digest — reinstall, or update the pin if the change is intended |
60
+
61
+ All three are error severity and appear in `wairon rules list`, so a project can
62
+ retune them through `rules.sddRuleSeverity` if it must.
63
+
64
+ If you install from a local path, no fetchable source can be recorded and
65
+ `pack use` says so — bundle it, or pass `--source`.
66
+
67
+ ### Applying a pack to new projects by default
68
+
69
+ A pack in the store can ask to be selected by every project created from then on:
70
+
71
+ ```yaml
72
+ # pack.yaml
73
+ applyByDefault: true
74
+ ```
75
+
76
+ `wairon init` seeds it into the new project's `extensions.packs` as an explicit
77
+ `name@version` selection. That is what a machine-wide install *should* mean — a
78
+ default for new work, written where it is visible in review and removable — rather
79
+ than retroactive authority over projects that never mentioned it.
80
+
81
+ ### Reproducibility
82
+
83
+ With `rules.enforceReproducibility` (the default), a selection that neither pins a
84
+ version nor bundles is reported: `UNPINNED_PACK_SELECTION` resolves to whatever
85
+ this machine happens to have installed, so a clone or CI can enforce a different
86
+ rule set than you do. `PACK_SOURCE_UNFETCHABLE` means nothing can obtain the pack
87
+ elsewhere at all.
88
+
89
+ Both are **warnings while you work** and errors under `wairon validate --ci`, so
90
+ `wairon pack use appenser` stays a one-liner while CI refuses a pack set it cannot
91
+ reproduce. `--pin` or `pack bundle` clears them; `enforceReproducibility: false`
92
+ accepts the drift deliberately.
93
+
94
+ ### CI and a fresh machine
95
+
96
+ `wairon pack install` accepts an HTTP(S) URL, and **`wairon pack sync` takes no
97
+ arguments** — each selection carries its own source, so one command restores a
98
+ project's doctrine anywhere:
99
+
100
+ ```yaml
101
+ - uses: SYW-Apps/Waffle-AIron/.github/actions/setup-wairon@v<version> # the version you lock with (see docs/cli.md)
102
+ with:
103
+ packs: sync # sync (default) | none (bundled repos) | explicit sources
104
+ - run: npm ci # a TS/JS project with code: its own dependencies first (see docs/cli.md)
105
+ - run: wairon validate --ci
106
+ ```
107
+
108
+ Publishing a pack needs no registry — `wairon pack build` already emits
109
+ `<name>-<version>.wpack`, which is a release asset as-is:
110
+
111
+ ```sh
112
+ wairon pack build && gh release create v1.2.0 appenser-1.2.0.wpack
113
+ ```
114
+
115
+ `source` supports `{version}` for pinned selections, and GitHub's
116
+ `releases/latest/download/<asset>` resolves floating ones with no API call or
117
+ token. `${VAR}` expands from the environment, so a private URL can take a token
118
+ from a CI secret without committing it.
119
+
120
+ Fetching happens **only** in `pack install <url>` and `pack sync` — never during
121
+ `validate`, `status`, `generate`, or an MCP call, because the core workflow is
122
+ required to work offline.
123
+
124
+ ## Governance — what a pack changes, and what a parent requires
125
+
126
+ **Packs may loosen wairon's checks, and that is by design.** Packs exist to
127
+ adjust wairon's checks and behaviour: a pack for an automation or integration
128
+ platform — system design on a far less capable runtime — may have to turn rules
129
+ down or remove concepts that cannot exist there. A pack's loosening is never a
130
+ finding. What wairon insists on is that installing a pack is **intentional**,
131
+ that its **impact is understood** before it applies, and that it applies **per
132
+ project**.
133
+
134
+ ### Pack impact: shown before every pack write
135
+
136
+ ```sh
137
+ wairon pack impact appenser # or appenser@1.2.0 — writes nothing
138
+ ```
139
+
140
+ The report states, and never judges:
141
+
142
+ - the pack's **doctrine against wairon's defaults** — per profile, each rule it
143
+ loosens, raises, turns off or adds and a design depth it changes; each profile
144
+ it adds (or builtin profile it redefines), stereotype it removes or
145
+ discourages and edge it licenses; pack-wide, the patterns, guarantee tokens,
146
+ language tables and assertion codes it adds;
147
+ - which of its **profiles would govern** this project;
148
+ - the **findings that change** on this project if it is applied — a dry
149
+ validate of the current and the candidate configuration, neither written —
150
+ introduced, resolved and regraded, with both totals. What the report says the
151
+ findings will be is what validation reports after the write.
152
+
153
+ `pack use`, `pack unuse`, `pack add`, `pack remove`, and `pack install` when it
154
+ moves this project's floating selection all show this report and **ask before
155
+ writing**; anything but yes writes nothing. `-y, --yes` (or a run with no
156
+ terminal, as in CI) writes without the report and says so. On the MCP server,
157
+ `sdd_pack_impact` answers the same report read-only, so an agent can propose a
158
+ pack and its human applies it with `wairon pack use`.
159
+
160
+ A hosted instance does the same for its projects: the admin API, the web UI
161
+ and the hosted MCP tool `sdd_host_pack_impact` preview an install, an adoption
162
+ from the server-global catalog, or a removal with `project:read`, writing
163
+ nothing, and the web UI asks before it writes (an uploaded `.wpack` archive has
164
+ no preview yet, and the UI says so before asking). The hosted **policy** writes
165
+ — reconcile, setting a project type, and creating a project under the instance
166
+ pack policy — are unattended by nature, so they show nothing first; they return
167
+ the impact of every pack they applied instead, and a policy-governed creation
168
+ also records it in its audit event and, when it ran through an approval, on the
169
+ approval as its `executionSummary`.
170
+
171
+ ### Requiring packs of members — `composition.requirePolicies`
172
+
173
+ A project that contains others ([members](cli.md#members)) can require that
174
+ they run with given packs:
175
+
176
+ ```yaml
177
+ # the parent's .wai/project.yaml
178
+ composition:
179
+ requirePolicies:
180
+ - pack: acme/service-baseline
181
+ version: ^1.2 # a semver range: 1.2.0, ^1.2, ~1.2.3, 1.x, >=1.2.0 <2.0.0, a || b, *
182
+ profile: service # optional: the member must be governed by this profile of the pack
183
+ ```
184
+
185
+ A member **adopts** a requirement by selecting the pack in its own
186
+ `extensions.packs` — the same exact, digested selection `wairon pack use --pin`
187
+ records; there is no separate policy format. `*` also admits a pack that
188
+ declares no version. The hosted instance pack floor and hosted profile
189
+ selections keep working beside it; neither reads the other.
190
+
191
+ Requirements are judged **only in the family run** (`wairon validate` at a
192
+ project that declares members, or `--family`). A member's own gate never reads
193
+ its parent's requirements, so its verdict is the same with and without them.
194
+
195
+ | Code | Severity | Meaning |
196
+ |------|----------|---------|
197
+ | `POLICY_NOT_ADOPTED` | error | A member does not select a required pack, selects it unpinned (a floating selection is judged by nothing a family can reproduce), outside the range, or is not governed by the required profile. The message names the requirement and the member's selection. |
198
+ | `POLICY_DEVIATION` | notice | A member selects the pack but changes one of its settings in its own configuration — a rule severity, the design depth, a subsystem profile, a `lint.allow` over a rule the pack sets, or its naming, complexity or documentation settings. It names the setting, the pack's value and the member's. `--ci` never fails on it; a team that wants it to can raise it with `rules.sddRuleSeverity`. |
199
+ | `POLICY_REQUIREMENT_INVALID` | error | The requiring project's own `composition.requirePolicies` names a range that does not parse — reported at that project's own gate. |
200
+
201
+ The first two are tuned by the **requiring** project's `rules.sddRuleSeverity`.
202
+ A pack's own loosening of wairon's defaults is never reported by any of them.
203
+
204
+ `wairon member add`, and `wairon init` in a subdirectory, write the parent's
205
+ required packs into a new member's selection **once**, at creation — each pinned
206
+ to the highest installed version its range admits — and say what they applied
207
+ and which requirement nothing installed satisfies. Afterwards the selection is
208
+ the member's own.
209
+
210
+ ## Installing packs (legacy vendoring)
211
+
212
+ **Per project (recommended for repo doctrine):**
213
+
214
+ ```sh
215
+ wairon pack add path/to/pack.yaml # or a .cjs file, or a pack directory
216
+ ```
217
+
218
+ This **vendors** the pack into `.wai/packs/` and registers it in
219
+ `.wai/project.yaml → extensions.packs`. Commit `.wai/` — CI and every clone
220
+ then enforce the same doctrine; `wairon validate`, `--ci`, and the MCP
221
+ server's `sdd_validate_tree` all load packs identically, so an AI agent
222
+ authoring specs is held to the same rules as the pipeline.
223
+
224
+ **Machine-wide:**
225
+
226
+ ```sh
227
+ wairon pack add path/to/pack.yaml --global
228
+ ```
229
+
230
+ Installs into the global packs folder (`WAIRON_PACKS_DIR` or
231
+ `~/.wairon/packs`) — every pack there is auto-loaded for **every project on
232
+ this machine**, before the project's own packs (project packs win on
233
+ collision). Dropping pack files into that folder by hand works too; wairon
234
+ discovers `*.yaml`/`*.cjs`/`*.js` files and directories containing a pack
235
+ entry file (`pack.yaml` | `pack.cjs` | `index.cjs` | …). Because global
236
+ packs don't travel with the repo, use them for personal/org-machine
237
+ additions — a project can opt out with `extensions.useGlobalPacks: false`
238
+ for strict reproducibility.
239
+
240
+ **By hand** — packs are just config; you can skip the commands entirely:
241
+
242
+ ```yaml
243
+ # .wai/project.yaml
244
+ extensions:
245
+ packs:
246
+ - .wai/packs/my-doctrine.yaml
247
+ ```
248
+
249
+ Inspect and uninstall:
250
+
251
+ ```sh
252
+ wairon pack list # global + project packs, what each provides
253
+ wairon pack remove <name> # deregister + delete vendored files
254
+ wairon pack remove <name> --global
255
+ wairon init --pack <source> # doctrine applied at project birth
256
+ ```
257
+
258
+ ## Distributing a wrapper product (the ZIP recipe)
259
+
260
+ Wairon is distributed as standalone CLI binaries (GitHub releases) — a
261
+ wrapper product does **not** need npm either. Ship a release ZIP containing
262
+ your pack files plus a small install script; the user unzips and runs it:
263
+
264
+ ```sh
265
+ wairon pack add ./my-product-pack # per project, or:
266
+ wairon pack add ./my-product-pack --global # machine-wide
267
+ ```
268
+
269
+ The installer injects once and leaves — from then on plain `wairon
270
+ validate` enforces the doctrine, and `wairon pack remove` undoes it. See
271
+ [`examples/wrapper/install.js`](https://github.com/SYW-Apps/Waffle-AIron/blob/main/examples/wrapper/install.js) for the
272
+ template. A spec-only product (implementation lives on a cloud platform)
273
+ needs nothing more: the wrapped repository holds specs + docs, wairon holds
274
+ the gate.
275
+
276
+ ## Declarative packs (YAML)
277
+
278
+ Data only — profiles and language/platform tables:
279
+
280
+ ```yaml
281
+ name: my-doctrine
282
+
283
+ profiles:
284
+ my-platform: # becomes a valid `profile` / projectType value
285
+ family: neutral # neutral | backend-like | frontend-like
286
+ forbiddenStereotypes: # error: PROFILE_FORBIDDEN_STEREOTYPE
287
+ - types: [Actor, Supervisor]
288
+ reason: why this platform cannot express them
289
+ discouragedStereotypes: # warning: PROFILE_DISCOURAGED_STEREOTYPE
290
+ - types: [Observer]
291
+ reason: why to avoid them here
292
+
293
+ languages:
294
+ my-platform: # matched against targetLanguage (L0/L1), lowercased
295
+ unsupportedFlow: # warning: LANGUAGE_FOREIGN_FLOW
296
+ # keyspace: branch | switch | forEach | for | while | doWhile | try | throw | jump
297
+ doWhile: remodeling guidance shown to the spec author
298
+ ```
299
+
300
+ Semantics worth knowing:
301
+
302
+ - `family` opts into the built-in stereotype fencing (backend-like ⇒
303
+ frontend stereotypes are violations; frontend-like ⇒ backend runtime
304
+ stereotypes are warned; neutral ⇒ only your explicit lists apply).
305
+ - Language tables **merge over** the built-in ones (Rust/Go/Python/C gaps);
306
+ later packs win on collision, and project packs load after global packs —
307
+ load order is precedence.
308
+ - Flow gating is **warning severity by design**: "possible but not clean"
309
+ is guidance, not prohibition. `lint.allow` (with a reason) covers
310
+ deliberate exceptions; a project can harden a code to `error` via
311
+ `rules.sddRuleSeverity`.
312
+ - Unregistered `profile` / `projectType` names get `UNKNOWN_PROFILE`
313
+ (warning) naming the registered set.
314
+ - `foreignBuiltins` is **deprecated**: contracts speak one language-neutral
315
+ type grammar (see [the architecture standard](standards/architecture.md#6-entities-and-types)), so there is no
316
+ per-language builtin vocabulary left to declare. A pack that still declares
317
+ it loads, ignores the field and gets a `PACK_FIELD_DEPRECATED` notice; the
318
+ field is removed in the next release.
319
+
320
+ ## Pack-provided AI-agent skills
321
+
322
+ A **directory** pack can ship declarative AI-client skills (SKILL.md files)
323
+ alongside its manifest. `wairon skills install` / `wairon generate` install
324
+ them into the supported client targets, and the hosted MCP server publishes
325
+ them through the same `wairon-skill://` resource mirror as the built-ins — so
326
+ a profile can carry platform/domain implementation guidance while wairon stays
327
+ platform-agnostic. These are **AI-client skills, not MCP tools**.
328
+
329
+ ```yaml
330
+ name: appenser # a directory pack: pack.yaml + skills/
331
+ version: 1.2.0 # optional; shown as the skill's provenance version
332
+
333
+ skills:
334
+ - id: domain-implementer
335
+ source: skills/domain-implementer/SKILL.md # relative to the pack directory
336
+ targets: [claude, gemini] # client targets to install into
337
+ ```
338
+
339
+ ### Extending a builtin skill instead of standing beside it
340
+
341
+ A platform delta usually belongs *inside* the relevant builtin, not next to it.
342
+ Declare `extends` instead of `id`:
343
+
344
+ ```yaml
345
+ skills:
346
+ - extends: sdd-implement # sdd-architect | sdd-narrative | sdd-auditor
347
+ source: skills/make-implementer/SKILL.md
348
+ targets: [claude]
349
+ - id: make-control-plane # unchanged: a genuinely new skill
350
+ source: skills/make-control-plane/SKILL.md
351
+ targets: [claude]
352
+ ```
353
+
354
+ The section is appended to the builtin under `## Platform: <pack>`, in pack load
355
+ order, both on install and on `resources/read`. So an implementing agent reads
356
+ **one coherent instruction** with the platform part clearly attributed — instead
357
+ of noticing a parallel `appenser-make-implementer` and reconciling it, or (worse)
358
+ every wrapper forking the builtin wholesale.
359
+
360
+ - The **builtin stays wairon's**: an upgrade still updates the base text, and the
361
+ composed skill keeps the builtin's own frontmatter, so its identity does not
362
+ change. Only the appended sections come from packs.
363
+ - An extending skill publishes **no separate resource** — it *is* part of the
364
+ builtin now. Untouched builtins stay byte-identical to their templates.
365
+ - `extends` pointing at a skill that does not exist is an
366
+ `EXTENSION_LOAD_ERROR`, not a silently dropped section: an older wairon must
367
+ never quietly fail to apply a newer pack's doctrine.
368
+ - Exactly one of `id` / `extends` per entry.
369
+
370
+ - **Namespaced install.** Every *new* pack skill installs as
371
+ `<pack-id>-<skill-id>` (e.g. `appenser-domain-implementer`) — so skills from
372
+ different packs never collide, and provenance is legible in the name. The
373
+ built-in `sdd-*` skill names are reserved; a pack cannot shadow them, only
374
+ extend them.
375
+ - **Provenance + version** come from the pack (`name` + `version`), visible in
376
+ `wairon skills list` and each MCP resource descriptor.
377
+ - **Reproducibility** is automatic — pack skills are vendored under
378
+ `.wai/packs/` and pinned in `project.yaml`, so they travel with the repo.
379
+ - **No change when unused** — projects with no pack skills install exactly the
380
+ built-ins, as before.
381
+
382
+ ## Teaching the connecting agent (`instructions`)
383
+
384
+ An agent that connects to a wairon MCP server is **taught the SDD model on the
385
+ handshake**: wairon returns MCP `instructions` on `initialize` — the protocol's
386
+ field for "how to use this server", which clients inject into the agent's system
387
+ prompt. It states what a spec tree is, the L0→L5 shape, the authoring order, that
388
+ the `sdd_*` schemas are self-describing, the bound project's governing profile,
389
+ the loaded packs, and — pointing rather than repeating — that
390
+ `wairon-skill://sdd-architect` must be read **before** authoring.
391
+
392
+ That default is **wairon's**, deliberately: wairon owns the model, so wairon
393
+ teaches it, and it changes with wairon versions instead of drifting across every
394
+ wrapper that would otherwise reimplement it. A pack does not restate it — a pack
395
+ appends its **platform delta**:
396
+
397
+ ```yaml
398
+ # pack.yaml
399
+ instructions: >-
400
+ Specs in this project stay target-agnostic. Make.com mechanics (method
401
+ selector, JSON argument envelope, Router branches, blueprint patches) never
402
+ enter a spec — read wairon-skill://appenser-architect first.
403
+ ```
404
+
405
+ Blocks are appended after wairon's own text under an attributed heading
406
+ (`## From pack "appenser"`), in **pack load order** — so a reader can always tell
407
+ platform doctrine from wairon doctrine. For finer control, spell blocks out and
408
+ scope them to the governing profile the way assertions scope theirs:
409
+
410
+ ```yaml
411
+ instructions:
412
+ - text: Applies to every project this pack governs.
413
+ - text: Router branches never appear in a spec — model them as narrative branch steps.
414
+ profile: [make-automation] # only when this profile governs the project
415
+ ```
416
+
417
+ - A block with no `profile` always applies; a scoped block applies only when the
418
+ project's `projectType` matches one of its entries (a block scoped to a profile
419
+ that cannot be resolved is withheld, so platform doctrine never leaks into an
420
+ unrelated project).
421
+ - Because the text is composed **per server construction**, a hosted instance
422
+ reports each request's own bound project — its profile, its packs.
423
+ - Pack skills stay the place for depth. `instructions` is the map that makes an
424
+ agent go and read them; keeping it short is the point.
425
+
426
+ ## Reusable, versioned pattern references
427
+
428
+ Packs can declare named, versioned architecture patterns; component specs
429
+ reference them, and wairon resolves + surfaces the reference. This publishes a
430
+ reusable convention across projects without copy-pasting a spec shape, and
431
+ makes adoption/versioning explicit. Core only validates *identity* — the
432
+ pattern's actual constraints are enforced by the **pack's own rules**.
433
+
434
+ ```yaml
435
+ patterns:
436
+ - id: org/domain-pattern
437
+ version: 1.0.0
438
+ description: The organization's canonical domain-module shape.
439
+ ```
440
+
441
+ A component opts in via `patterns` (matching pack + version):
442
+
443
+ ```yaml
444
+ # a component spec
445
+ patterns:
446
+ - id: org/domain-pattern
447
+ version: 1.0.0 # optional; omit to accept any resolved version
448
+ ```
449
+
450
+ - A reference to a pattern no loaded pack declares is `UNKNOWN_PATTERN_REF`; a
451
+ pinned version with no match is `PATTERN_VERSION_MISMATCH` (both warnings,
452
+ tunable via `rules.sddRuleSeverity`).
453
+ - Loaded patterns are listed by `wairon patterns list` (id, version, source
454
+ pack) and exposed to programmatic pack rules via `ctx.ext.patterns`, giving
455
+ them a stable, typed target to enforce against.
456
+
457
+ ## Component variants (a dynamic layer on top of packs)
458
+
459
+ A **variant** is a named, base-anchored specialization of a core stereotype — a
460
+ "kind of `Orchestrator`/`Adapter`/`Portal`/…" (e.g. a `publisher`) — carrying
461
+ implementation guidance so the implementer treats every component of the same
462
+ variant alike, reusing one shared approach instead of reinventing it per instance.
463
+ The base stereotype stays authoritative for all of wairon's generic semantics and
464
+ dependency rules; the variant adds domain vocabulary + a stable rule target + the
465
+ guidance. It adds no dependency rule of its own: what an Orchestrator may depend on
466
+ is its `dependencyClass` field, not its variant.
467
+
468
+ **Built-in variants.** wairon ships five, described in the
469
+ [architecture standard](standards/architecture.md) §8:
470
+
471
+ | Variant | Base | Shape |
472
+ |---|---|---|
473
+ | `arbiter` | Orchestrator | subject + supplied facts → deterministic verdict + reasons |
474
+ | `projector` | Orchestrator | source model → self-contained derived view |
475
+ | `composer` | Orchestrator | templates + values → authored text or files |
476
+ | `codec` | Orchestrator | format ↔ format, both directions in one component |
477
+ | `gateway` | Portal | a Portal that authenticates, authorizes, validates or rate-limits before it dispatches |
478
+
479
+ **Three layers.** Variants load built-in first, then from the global directory
480
+ (`WAIRON_VARIANTS_DIR`, else `~/.wairon/variants`), then from the project's
481
+ `.wai/variants/`; a later layer overrides a variant with the same id, so a team can
482
+ reword a built-in's guidance without forking wairon.
483
+
484
+ Variants deliberately live **outside packs**: define one on demand — no pack edit
485
+ or release — and share it anywhere (a variant is a tiny, portable YAML). A
486
+ machine/org-wide directory makes a good variant reusable across projects, orgs,
487
+ and tenants.
488
+
489
+ ```yaml
490
+ # .wai/variants/publisher.yaml (or WAIRON_VARIANTS_DIR for machine/org-wide)
491
+ id: publisher
492
+ base: Orchestrator # required — the stereotype this variant specializes
493
+ guidance: >
494
+ In-process fan-out emitter. Reuse the shared publisher helper; do not
495
+ reimplement dispatch per instance.
496
+ # target: typescript # optional — only applies for this target language
497
+ # profile: event-driven # optional — only applies under this profile
498
+ ```
499
+ ```yaml
500
+ # on a component
501
+ componentType: Orchestrator
502
+ variant: publisher
503
+ ```
504
+
505
+ - A component's `variant` must resolve to a declared variant (`UNKNOWN_VARIANT`)
506
+ and its stereotype must equal the variant's `base` (`VARIANT_BASE_MISMATCH`,
507
+ error). A variant is always *a kind of a stereotype* — so cross-cutting
508
+ attributes (retriable, cached) can't be variants; those stay method `guarantees`.
509
+ - **One variant per component**: a genuine combination is a *new* combined variant,
510
+ not two stacked (combining is almost always a purity smell).
511
+ - Listed by `wairon variants list`; exposed to programmatic pack rules via
512
+ `ctx.variants` (a stable, typed target); and — the payoff — a component's
513
+ variant guidance and its same-variant siblings are **injected into the generated
514
+ owner/implementer agent context**, so the implementer reuses one shared approach
515
+ across every component of that variant.
516
+
517
+ ## Programmatic packs (JS)
518
+
519
+ A CommonJS module: the same declarative fields, plus `rules`. The pack does
520
+ NOT import wairon — rules are plain objects receiving the rule context:
521
+
522
+ ```js
523
+ module.exports = {
524
+ name: 'my-rules',
525
+ rules: [{
526
+ name: 'my-rule',
527
+ description: 'One paragraph: what it enforces and why.',
528
+ codes: [{ code: 'MY_CODE', defaultSeverity: 'warning', summary: '…' }],
529
+ check(ctx) {
530
+ for (const comp of ctx.components) {
531
+ // ctx: components/interfaces/implementations/types + componentMap,
532
+ // getComponentProfile(id), targetLanguageFor(subsystemId),
533
+ // isComponentDraft(id), ctx.ext.{profiles,languages}, …
534
+ ctx.addIssue('warning', 'MY_CODE', 'message', comp.id, ctx.isComponentDraft(comp.id));
535
+ }
536
+ },
537
+ }],
538
+ };
539
+ ```
540
+
541
+ What `ctx.addIssue` gives you for free:
542
+
543
+ - **Severity overrides** — projects re-tune your code via
544
+ `rules.sddRuleSeverity` (`error | warning | off`).
545
+ - **`lint.allow`** — a spec can suppress your *warning* on itself with a
546
+ reasoned allow; error findings always surface. Your codes are
547
+ automatically part of the known-code set, so allows against them don't
548
+ trip `UNKNOWN_LINT_ALLOW_CODE`, and the staleness audit runs after your
549
+ rules.
550
+ - **Scoping** — granular (`--subsystem`) validation filters your issues by
551
+ the `specId` you pass; always pass one when the finding is about a spec.
552
+ - The `isDraftContext` flag only downgrades codes in the completeness set —
553
+ for pack codes it is currently informational; pass it anyway.
554
+
555
+ Resolution: `./relative` and absolute paths resolve against the project
556
+ root; a directory loads its pack entry file; anything else resolves with
557
+ Node module semantics from the project root. A pack that fails to load or
558
+ is malformed becomes `EXTENSION_LOAD_ERROR` (error severity) — validation
559
+ never silently runs without declared doctrine. Pack rules appear in
560
+ `wairon rules list` tagged with their pack.
561
+
562
+ ## What packs can NOT do — the closed flow algebra
563
+
564
+ Packs can **gate** narrative flow constructs and enforce anything over the
565
+ spec tree, but cannot add new step kinds. Reachability/region analysis
566
+ depends on a closed successor semantics, and step renumbering relocates a
567
+ fixed set of jump fields — an unknown step-reference field would silently
568
+ corrupt narratives on insert/delete. (Same reason ESLint plugins add rules,
569
+ never AST node types.) A construct that genuinely cannot be expressed with
570
+ the existing algebra is essentially never platform-specific — propose it
571
+ for core, where it lands gated-off in profiles that lack it.
572
+
573
+ ## Embedding wairon as a library (advanced)
574
+
575
+ Only needed to build a branded gate binary with doctrine compiled in —
576
+ packs + installer cover everything else. Install the npm package
577
+ (`npm install @wairon/cli`) and require it; the library entry is CommonJS and
578
+ ships no TypeScript declarations:
579
+
580
+ ```js
581
+ const wairon = require('@wairon/cli');
582
+
583
+ wairon.setProjectRoot(projectDir);
584
+ const extensions = wairon.loadExtensions([...packPathsOrModuleIds], projectDir);
585
+ // extensions.errors: string[] — surface these; don't run "open"
586
+ const result = wairon.validateProject({ extensions });
587
+ ```
588
+
589
+ An explicitly passed `extensions` takes precedence over config/global
590
+ auto-loading. The relevant exported API: `validateProject(options)` (the
591
+ owner's gate: one project from its own files), `validateFamily(options)` (the
592
+ family run: every member's own gate verbatim, the composition of each
593
+ project's externals against their live producers, and the family checks),
594
+ `exportDesign` (the `wairon export` document — see [the format](design-export.md)),
595
+ `loadExtensions`, `loadExtensionPacks`, `loadProjectExtensions`,
596
+ `globalPacksDir`, `discoverPacks`, `setProjectRoot`, the `SddRule` /
597
+ `RuleContext` / `LoadedExtensions` / `DeclarativePack` types, `SDD_RULES`,
598
+ `composeRuleSequence`, and the spec loaders/savers. See
599
+ [`examples/wrapper/wrapper.js`](https://github.com/SYW-Apps/Waffle-AIron/blob/main/examples/wrapper/wrapper.js).
600
+
601
+ ## Spec-only wrappers
602
+
603
+ A wrapper whose implementation happens elsewhere (e.g. a cloud automation
604
+ platform) uses wairon purely for **specs + docs in the repository**. That
605
+ works out of the box; the natural configuration:
606
+
607
+ ```yaml
608
+ rules:
609
+ requireOwnedPaths: false # agents own no source paths
610
+ generateComponentImplementers: false
611
+ ```
612
+
613
+ Narratives still carry full value: the detail dial (`full | calls-only |
614
+ intent`) describes platform scenarios as well as code, `sourcePath` is
615
+ simply omitted, and L4 `technologies` still fences vendor specifics (e.g.
616
+ a datastore product) behind their owning Adapter/Store boundaries.