@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
|
@@ -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.
|