@wairon/cli 5.1.1-dev.112 → 5.1.1-dev.113
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/dist/cli/index.js +2062 -374
- package/dist/cli/index.js.map +1 -1
- package/dist/index.d.ts +252 -11
- package/dist/index.js +1915 -345
- package/dist/index.js.map +1 -1
- package/dist/templates/skills/sdd-architect.md +1 -1
- package/dist/templates/skills/sdd-delegate.md +7 -0
- package/dist/templates/skills/sdd-implement.md +8 -0
- package/docs/cli.md +142 -23
- package/package.json +1 -1
|
@@ -114,7 +114,7 @@ schemas:
|
|
|
114
114
|
- **Externals — the pin gates, live drift is visible**: declare a project this one consumes with `sdd_add_external` (alias; source `../sibling`, `hosted:<id>`, `<git url>` or `<git url>#<commit>`; optional `project`, `use`, `description`) instead of editing `.wai/project.yaml`. It refuses in one sentence what cannot be declared, takes the declaration back out when the producer contradicts it (another id, an unexported `use` name), and pins it by default. The owner's gate judges each external against its pin; `sdd_validate_tree` and `sdd_get_status` add ADVISORY live-drift findings (`EXTERNAL_LIVE_INCOMPATIBLE`, `EXTERNAL_DRIFTED`, `EXTERNAL_LIVE_UNCOMPARED`) that never make the tree invalid — adapt the uses, then the human re-pins. `wairon externals status` is the human's opt-in live gate.
|
|
115
115
|
- **Growing a system — subsystems → parts → projects**: boundaries are earned. Keep subsystems in one folder until a piece needs its own folder or repository; then make it a **part** — `members: { scheduler: services/scheduler }` (or `../admin`, or `git-url#<commit>`), created by `sdd_add_member` (a part by default) or `sdd_externalize_subsystem`. A part's subsystems stay this project's own: local ids, the ordinary subsystem rules, this project's lock. Make a **project** — its own id, exports and lock, referenced as `alias::name` — only when it needs its own team, release, approval or public surface: `sdd_promote_member`, and `sdd_demote_member` to undo it. What a member is follows from its content (an id, an L0 or a lock); `as:` only asserts it.
|
|
116
116
|
- **Members & the family's shape**: relocate a member with `sdd_move_member`. Every other change of a family's shape is a **family migration**: `sdd_attach_member` (an existing project becomes a member), `sdd_detach_member` / `sdd_adopt_member` (out of the family and back), `sdd_rename_project`, `sdd_rename_member_alias`, `sdd_internalize_member` (a member folded back in, its metadata sent to explicit homes) and `sdd_externalize_subsystem`. Call it with `dryRun: true` first and show the human the plan; applied, it writes every project it touches or none, and it never locks — it names the projects to re-lock.
|
|
117
|
-
- **Renames keep a trace**: rename with `sdd_rename_component`, `sdd_rename_method` and `sdd_rename_type
|
|
117
|
+
- **Renames keep a trace**: rename with `sdd_rename_component`, `sdd_rename_method` and `sdd_rename_type` (a contract or an implementation on its own with `sdd_rename_spec`), move methods with `sdd_move_methods` and a component or a type to another subsystem with `sdd_move_spec` — never by deleting and re-adding a spec or editing an id or a subsystem through `sdd_update_spec`. The tools rewrite every reference, record the old key in the element's rename trace (`previousIds`, a method's `previousNames`; the design export shows it as `formerly`, which is how a generator keeps its user's code across the rename), and keep a published name through `as`. A retired id or method name cannot be taken again (`id-retired`, `name-retired`), and a hand-edited trace that collides is `RENAME_TRACE_CONFLICT`. Never write a trace yourself; unsetting one releases its names at the cost that consumers read a delete plus an add.
|
|
118
118
|
- **targetLanguage**: set on L0 (`sdd_initialize_system`), override per L1 — enables language-aware validation (flow constructs the language lacks are flagged) and the brief's type mapping for implementers. Extension packs (`.wai/project.yaml` → `extensions.packs`) may register additional languages/platforms and custom profiles.
|
|
119
119
|
- **technologies (L4)**: an external technology (database, vendor SDK, service) is abstracted as a component — wrapped by an Adapter behind an intent-language interface (inside a Repository for persistence; a standalone Adapter for an external API). Declare the binding on that component's L4: `technologies: [mysql]`. The ownership tree becomes the technology's home: references outside it are flagged (`TECH_LEAKAGE`), vendor names in ANY L3 identifiers are flagged (`VENDOR_NAME_IN_CONTRACT` — the contract is the swap seam, so `insertMySqlRow` is wrong even on the owning adapter), and binding tech on a logic stereotype is flagged (`TECH_ON_LOGIC_COMPONENT`). Swapping the technology then touches one L4.
|
|
120
120
|
- **lint.allow (per-spec suppression)**: any L1–L4/type spec may carry `lint: { allow: [{ code, reason }] }` — silences that WARNING (or NOTICE) code on that spec only (wairon's `#[allow]`). Errors are never locally suppressible; unknown codes and allows that no longer match anything are flagged. Prefer fixing — an allow is for a documented false positive or a deliberate, reviewable exception.
|
|
@@ -65,6 +65,13 @@ Point the subagent at that skill and spend the brief on what only you know:
|
|
|
65
65
|
it: a paraphrase drifts, and the subagent cannot tell which copy is current.
|
|
66
66
|
* **The premise you are asking them to act on**, stated *as* a premise, so it can
|
|
67
67
|
be contradicted.
|
|
68
|
+
* **For a Portal, its handler shape** — the contract's own parameters per verb,
|
|
69
|
+
the handles its framework hands each function (to be named in the
|
|
70
|
+
implementation's `injectedParams`), and its `router` linkage (an entry of its
|
|
71
|
+
own file, `<module>#<name>`, or a central module) — quoted from the
|
|
72
|
+
implementation spec, with a pointer to `sdd-implement`'s **Handler shape** and
|
|
73
|
+
**Routers** rules. A subagent left to guess writes `(req, res)` handlers, and
|
|
74
|
+
the gate reads those as substitutions.
|
|
68
75
|
|
|
69
76
|
One design decision never goes into a brief as an instruction: **an entry**. A
|
|
70
77
|
subagent that meets an unreached Portal verb (`UNUSED_COMPONENT` /
|
|
@@ -184,6 +184,14 @@ All implementation work must strictly adhere to these rules:
|
|
|
184
184
|
- A Portal's verbs are reached by the callers the design models or by the outside callers its `invokedBy` entry names. Serve exactly those: the Portal's L4 `router` is the entry its file exports for whatever process hosts it. Never add a verb, route or command the contract does not declare, and never ask for an entry to make an `UNUSED_*` finding go away — report the unreached verb instead.
|
|
185
185
|
- Another project's library (an `InProcess` Portal) is imported and called directly from the component the narrative names — no wrapper Adapter unless the design has one. Keep pure and read logic to the library verbs whose `effect` the design allows.
|
|
186
186
|
- Function names follow the contract's method names in the language's casing; a wire name that differs (an HTTP path, a CLI command, a JSON-RPC method) belongs to the endpoint binding — route it there, do not rename the function after it.
|
|
187
|
+
- **Handler shape — the green path for a framework handler** (no design change, no re-lock):
|
|
188
|
+
- A verb's function takes the contract's OWN parameters, in order, under the contract's names. `cancelOrder(req, res)` realizing `cancelOrder(orderId, customerId)` is reported as two substitutions (`UNREALIZED_PARAM` + `UNDECLARED_PARAM`), annotated or not; so is a request object, or any object sharing none of the record's fields, where the contract declares a record.
|
|
189
|
+
- What a framework hands the function besides those — a context, a request, a response, `next` — is wiring: name it in the implementation's `injectedParams` with `sdd_update_spec` (code linkage, outside the approval). It is matched with or without its leading underscore, at the START or the END of the list: `handler(ctx, req, orderId, customerId, res)` with `injectedParams: [ctx, req, res]` is green.
|
|
190
|
+
- Two honest shapes: (a) the router unpacks the path, query and body and calls `cancelOrder(orderId, customerId)` (handles injected around them if the framework insists); (b) the framework hands the handler the request alone — inject it and read each contract parameter off it **by its own name** (`req.params.orderId`, `req.query['limit']`, `const { orderId } = req.params`), which realizes the parameter through the handle. A contract parameter never read is still `UNREALIZED_PARAM`.
|
|
191
|
+
- An injection no handler takes is `UNUSED_INJECTED_PARAM` — drop it. A leading `_` marks a parameter the body provably never reads; a used `_secret` is judged like any other.
|
|
192
|
+
- `conformance: off` (on the implementation or one method) is for code wairon is not meant to compare with its contract — generated or vendored code. It switches the realization checks off (method, parameters, async, forward call claims), never the doctrine: an unnarrated write, a write shortcut and a call the analysis cannot follow are still reported.
|
|
193
|
+
- **Routers**: set the Portal implementation's `router` (code linkage) to the entry its file exports (`handleOrderRequest`), or to a central module's table or entry (`src/routes.ts#ROUTES`), or to the module alone (`src/routes.ts`). Write routes in a readable idiom: guards on `<request>.method` and `parts[i]`, or a route table — an array of `{ method, path }` objects or an object keyed `VERB /path` (template-literal keys over constants, and a table returned in place, are read). Table paths are written under the prefix the router strips, and that prefix must be the Portal's `basePath`.
|
|
194
|
+
- **Another project's code** is reached through what it exports — its member alias or a pinned external — or through a binding module the implementation declares in `bindings`. Importing a name straight out of its source tree that it does not export is `CROSS_PROJECT_SOURCE_IMPORT`.
|
|
187
195
|
- **Strict Layer Isolation & No Persistence Shortcuts**:
|
|
188
196
|
- A `Portal` must **never** depend directly on a `Store`, `Registry`, `Adapter` or `Query`. Passthrough READS may go through a `Repository`/`Index` facade; every WRITE must route through an `Orchestrator` (a Portal narrative call or dispatch-table binding that reaches a write-effect facade method is a `PORTAL_WRITE_SHORTCUT` error).
|
|
189
197
|
- Held domain state always lives in a dedicated data component, never as fields inside an `Orchestrator`. Two sanctioned shapes: the RECOMMENDED `Repository` pattern (owns `Store` + `Registry` + `Index`; consumers depend on the facade), or — for genuinely simple state — a deliberately standalone `Store` (workflow-layer consumers only, acknowledged via `lint.allow` on `UNOWNED_STORE`). Do **not** combine Store/Registry/Index functionality into a single helper component, and never fold state into a consuming component because a link was refused.
|
package/docs/cli.md
CHANGED
|
@@ -151,14 +151,19 @@ warnings as errors (notices are printed and counted, never fatal).
|
|
|
151
151
|
declare the producer's names in the binding itself), or its component reaches
|
|
152
152
|
neither a pinned external nor a member project that exports something. Both
|
|
153
153
|
ES module exports and CommonJS exports are read (`exports.X = { … }`,
|
|
154
|
-
`module.exports.X = function (…) {…}`, each property of
|
|
155
|
-
{ … }`
|
|
154
|
+
`module.exports.X = function (…) {…}` or `= X`, each property of
|
|
155
|
+
`module.exports = { … }` — a shorthand `{ Client }` included —
|
|
156
|
+
`module.exports = class X { … }`, a TypeScript `export = X`, and JSDoc
|
|
157
|
+
`@typedef`/`@property`), a bare identifier through the module's own
|
|
158
|
+
declaration of it; a `_private` class or object member is not compared, and
|
|
159
|
+
an object tagged as a whole has each member judged against its tag. Return types are not compared. A binding into a contained **member**
|
|
156
160
|
project (declared under `members`, never pinned — named by its alias or its
|
|
157
161
|
project id) is compared the same way with what the member's L0 exports now
|
|
158
162
|
(its exported contracts and types, and the types they name), so a
|
|
159
163
|
`rename-param` or `rename-field` in the member reaches its parent's binding
|
|
160
164
|
at `validate` time as `BINDING_DRIFT` with the rename to follow; a member is
|
|
161
|
-
never told to pin — one that exports nothing is told to export there. A
|
|
165
|
+
never told to pin — one that exports nothing is told to export there. A
|
|
166
|
+
member in a `../` sibling checkout is a contained one here. A git
|
|
162
167
|
member is pinned like an external (`wairon externals pin <alias>`) and
|
|
163
168
|
compared with that pin. A file of another project imported directly from its
|
|
164
169
|
source tree is not a binding: it is the producer's own code.
|
|
@@ -198,7 +203,8 @@ warnings as errors (notices are printed and counted, never fatal).
|
|
|
198
203
|
`MISSING_SOURCE_FILE` (error) and a contract method naming no file is
|
|
199
204
|
`METHOD_SOURCE_PATH_MISSING` (warning). `rules.conformance.requireCode: true`
|
|
200
205
|
in `project.yaml` reports the two notices as errors, for a CI that must say
|
|
201
|
-
every designed implementation has code.
|
|
206
|
+
every designed implementation has code. It is code-conformance configuration,
|
|
207
|
+
outside the approval identity: flipping it never stales a lock (see `lock-check`).
|
|
202
208
|
- **Types are linked to code too.** A type spec without a `sourcePath` is
|
|
203
209
|
`MISSING_TYPE_SOURCE_PATH` — a notice before its subsystem has code, a
|
|
204
210
|
warning after. Plan a type's `sourcePath` when you plan the implementations'.
|
|
@@ -263,6 +269,57 @@ warnings as errors (notices are printed and counted, never fatal).
|
|
|
263
269
|
that Portal is realized by the link, never reported `UNREALIZED_DEPENDENCY`:
|
|
264
270
|
never add an import across a network boundary to satisfy a check.
|
|
265
271
|
|
|
272
|
+
#### Framework handlers, injected handles and routers
|
|
273
|
+
|
|
274
|
+
The parameter check pairs a verb's contract parameters with the function that
|
|
275
|
+
realizes it, in order. A framework handler is green without any design change:
|
|
276
|
+
|
|
277
|
+
- **Take the contract's own parameters, under its names.** `cancelOrder(req, res)`
|
|
278
|
+
realizing `cancelOrder(orderId, customerId)` is two substitutions
|
|
279
|
+
(`UNREALIZED_PARAM` + `UNDECLARED_PARAM`), annotated or not: a parameter named
|
|
280
|
+
as a transport handle (req, res, ctx, request, response, next, reply) where the
|
|
281
|
+
contract declares an argument of its own is never a silent pairing. Neither is a
|
|
282
|
+
platform transport class (`IncomingMessage`, a fetch `Request`) or an object
|
|
283
|
+
sharing none of a record's fields where the contract declares that record; an
|
|
284
|
+
object holding every field under another name is a rename (`PARAM_NAME_MISMATCH`).
|
|
285
|
+
- **Declare what the framework hands you as wiring.** Name the handles in the
|
|
286
|
+
implementation's `injectedParams` (code linkage: `sdd_update_spec`, no
|
|
287
|
+
re-lock). A name matches with or without its leading `_`, at the start or the
|
|
288
|
+
end of the list — `handler(ctx, req, orderId, customerId, res)` with
|
|
289
|
+
`injectedParams: [ctx, req, res]`. An injection no handler takes is
|
|
290
|
+
`UNUSED_INJECTED_PARAM`.
|
|
291
|
+
- **Or read the parameters off the request.** Where the framework hands the
|
|
292
|
+
handler the request alone, inject it and read each contract parameter off it by
|
|
293
|
+
its own name (`req.params.orderId`, `req.query['limit']`,
|
|
294
|
+
`const { orderId } = req.params`): that realizes the parameter through the
|
|
295
|
+
handle. A parameter never read off it stays `UNREALIZED_PARAM`.
|
|
296
|
+
- **`conformance: off`** on an implementation or a method is for code wairon is
|
|
297
|
+
not meant to compare with its contract (generated or vendored code). It switches
|
|
298
|
+
the realization checks off — method, parameters, async, whether narrated calls
|
|
299
|
+
are found — and never the doctrine: an unnarrated write
|
|
300
|
+
(`UNDECLARED_WRITE_CALL`), a call the analysis cannot follow, a Portal write
|
|
301
|
+
shortcut and route coverage are judged whatever it says.
|
|
302
|
+
|
|
303
|
+
A Portal's `router` (code linkage) names the entry its own file exports
|
|
304
|
+
(`handleOrderRequest`), a central module's entry or table
|
|
305
|
+
(`src/routes.ts#ROUTES`), or a module alone (`src/routes.ts`, every route-bearing
|
|
306
|
+
export read). Several Portals may name one router; a route is declared when any
|
|
307
|
+
of them declares it. Guards on `<request>.method` and `parts[i]` are read, and so
|
|
308
|
+
are route tables — an array of `{ method, path }` objects or an object keyed
|
|
309
|
+
`VERB /path` (template-literal keys over constants included), bound to a const or
|
|
310
|
+
returned in place. A table is read under the ONE prefix its router strips off the
|
|
311
|
+
path (`path.slice(BASE.length)`), so that prefix must be the Portal's `basePath`:
|
|
312
|
+
another one is `UNDECLARED_ROUTE` + `UNROUTED_ENDPOINT`, naming both.
|
|
313
|
+
|
|
314
|
+
Another project's code is reached through what it publishes — its member alias, a
|
|
315
|
+
pinned external — or through a binding module the implementation declares in
|
|
316
|
+
`bindings`. An import that resolves into its source under a name it does not
|
|
317
|
+
export is `CROSS_PROJECT_SOURCE_IMPORT` (warning); one into a module realizing its
|
|
318
|
+
Store is the persistence shortcut past its surface. A claimed call into a
|
|
319
|
+
component whose code is in another language (a Rust member behind a binding) is
|
|
320
|
+
not judged by the TypeScript call graph: it is said once per project and language
|
|
321
|
+
as `CALL_ACROSS_LANGUAGE` (notice), never as an unrealized call.
|
|
322
|
+
|
|
266
323
|
#### Technologies and their packages
|
|
267
324
|
|
|
268
325
|
`TECH_LEAKAGE_IN_CODE` compares each import's package with the packages of the
|
|
@@ -332,8 +389,16 @@ When any spec is still `draft` or `design`, the lock says how many of how many,
|
|
|
332
389
|
per kind (subsystems, components, contracts, implementations, types), and that
|
|
333
390
|
it approves them as they stand. Re-running it when nothing it records moved
|
|
334
391
|
keeps the record on file — its `lockedAt` included — so `.wai/lock.json` stays
|
|
335
|
-
unmodified; a change of approver, verdict
|
|
336
|
-
|
|
392
|
+
unmodified; a change of approver, verdict or member pin writes a new record. A
|
|
393
|
+
new wairon release writes nothing by itself: when its built-in design rules are
|
|
394
|
+
the same as the ones the record was taken under (the record's `gateParts.release`
|
|
395
|
+
is unchanged), the approval simply still matches and the record — its release
|
|
396
|
+
stamp included — is left as it is; when they moved, the approval carries over
|
|
397
|
+
by re-validation and one `wairon lock` refreshes the stamp (see *Upgrading
|
|
398
|
+
wairon* under `lock-check`). Evidence recorded *beside* the approval (the code
|
|
399
|
+
results with the analyzer's version, the design counts, gate parts, a release
|
|
400
|
+
stamp that is missing, no version or newer than the running wairon) is refreshed
|
|
401
|
+
and named; the summary says when this run left the file untouched.
|
|
337
402
|
|
|
338
403
|
**What it certifies is the design.** Only design findings can refuse a lock.
|
|
339
404
|
Code-conformance findings (the code↔spec checks) are recorded **beside** the
|
|
@@ -419,11 +484,28 @@ whole design's, so it is refused as a first approval (run `wairon lock`), and
|
|
|
419
484
|
refused whenever a spec outside the subsystem — or an input the identity covers
|
|
420
485
|
besides the design (rule tuning, packs, network, a pin, a member's approval,
|
|
421
486
|
the release) — moved since the approval; when it passes, the rest stays approved
|
|
422
|
-
exactly as it was, and the record is the one a full lock would write.
|
|
423
|
-
|
|
424
|
-
|
|
487
|
+
exactly as it was, and the record is the one a full lock would write. A
|
|
488
|
+
member project's subsystem (`--subsystem payments_svc::payments` at a parent) is
|
|
489
|
+
refused: a member's subsystem is approved at the member's own root
|
|
490
|
+
(`wairon lock --subsystem payments` in its folder), then the parent locks to pin
|
|
491
|
+
its new approval. When
|
|
492
|
+
nothing changed, `lock` leaves the approval on record as it is — its `lockedAt`
|
|
493
|
+
and restamp trace — and `.wai/lock.json` untouched by that run; only evidence
|
|
425
494
|
recorded *beside* the approval (the code results, the design counts, gate parts
|
|
426
|
-
that are missing or do not match the identity
|
|
495
|
+
that are missing or do not match the identity, a release stamp that is missing,
|
|
496
|
+
no version or newer than this wairon — replaced with this release) is refreshed,
|
|
497
|
+
and it says which. When a lock records the project's own inputs for the first
|
|
498
|
+
time (a first approval, or a record from before gate parts) or the rule tuning
|
|
499
|
+
or packs moved, it prints the inputs it records — projectType, each rule
|
|
500
|
+
severity override, the selected packs, a declared network, `composition` — so
|
|
501
|
+
the tuning you approve is shown, not only digested.
|
|
502
|
+
|
|
503
|
+
**In a family, lock bottom-up.** A parent's record pins each member's approval,
|
|
504
|
+
so lock every member that is not approved at its own root first, then the
|
|
505
|
+
parent: three projects cost three locks. A parent locked first records the
|
|
506
|
+
member as drifted, its `lock-check` fails until the member re-locks, and it
|
|
507
|
+
must lock again to pin the new approval — `lock` warns before it writes when a
|
|
508
|
+
direct member is not covered by its own approval.
|
|
427
509
|
Another approver locking an unchanged design records their own approval. A shell
|
|
428
510
|
with no terminal and no `--yes` writes nothing.
|
|
429
511
|
|
|
@@ -439,8 +521,10 @@ record counts too, so commit it before you rely on a local pass.
|
|
|
439
521
|
| **`locked`** — the approval still covers this design | pass (0) | pass (0) |
|
|
440
522
|
| **`stale`** — the design moved past its approval | **fail (1)** | **fail (1)** |
|
|
441
523
|
| **`unlocked`** — nothing was ever approved | pass, with a notice (0) | **fail (1)** |
|
|
442
|
-
| no `.wai/specs` in this directory at all | pass, saying so (0) | **fail (1)** |
|
|
443
|
-
|
|
|
524
|
+
| no `.wai/specs` in this directory at all, and no approval on record | pass, saying so (0) | **fail (1)** |
|
|
525
|
+
| no `.wai/specs` (or none left), while `.wai/lock.json` approves spec files | **fail (1)**: the tree was deleted | **fail (1)** |
|
|
526
|
+
| `.wai/project.yaml` missing while an approval is on record | **fail (1)**, naming the file | **fail (1)** |
|
|
527
|
+
| a member project's own approval does not cover it (spec changes, inputs that moved, a record that predates recorded inputs) | **fail (1)**, in the member's own terms | **fail (1)** |
|
|
444
528
|
| a member project was never approved by anyone | pass, naming it as *not gated* (0) | **fail (1)** |
|
|
445
529
|
| 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)** |
|
|
446
530
|
|
|
@@ -488,8 +572,30 @@ them; later upgrades then carry over*, and `wairon lock` takes a real approval
|
|
|
488
572
|
(never a restamp). The same holds for gate parts that do not break down the
|
|
489
573
|
`stateId` beside them (they are ignored, with a notice; when the identity still
|
|
490
574
|
matches, `wairon lock` rewrites them keeping the approval), and for a release
|
|
491
|
-
stamp that is no wairon version or is *newer* than the running wairon
|
|
492
|
-
*written by a newer wairon* is never
|
|
575
|
+
stamp that is no wairon version or is *newer* than the running wairon when the
|
|
576
|
+
identity no longer matches — a record *written by a newer wairon* is never
|
|
577
|
+
re-judged by an older one. When the identity still matches, the identity
|
|
578
|
+
decides: the approval holds, and every surface (`lock-check`, `status`,
|
|
579
|
+
`sdd_get_status`, `validate`) reports the stamp as a notice (`its release stamp
|
|
580
|
+
"banana" is no wairon version`, `… says wairon 9.9.9 took it, a newer release than
|
|
581
|
+
this one`); `wairon lock` replaces it with the running release. A matching
|
|
582
|
+
record that carries no gate parts at all gets the same notice `{}` gets, and
|
|
583
|
+
`lock` rewrites them. A `lock.json` that names one key twice (what a careless
|
|
584
|
+
conflict resolution leaves) cannot be read, like one with conflict markers.
|
|
585
|
+
|
|
586
|
+
**What the identity covers, and what it leaves out.** The project's own *design*
|
|
587
|
+
rule tuning — `projectType`, `sddRuleSeverity` overrides of design rules, the
|
|
588
|
+
other design settings under `rules` — its pack selection, network declaration
|
|
589
|
+
and `composition` are inputs: changing one stales the approval. A selected pack
|
|
590
|
+
that cannot load (not installed, not vendored) leaves the approval *owed* —
|
|
591
|
+
`lock-check` fails, naming the pack — because the doctrine it was judged under
|
|
592
|
+
cannot be read. **Code-conformance configuration is deliberately outside the
|
|
593
|
+
identity:** `rules.conformance` (`requireCode` included) and severity overrides
|
|
594
|
+
of rules that judge code govern how the *code* is held to the design, not what
|
|
595
|
+
the design is, so flipping `requireCode` changes what `validate --ci` fails on
|
|
596
|
+
and never stales an approval — exactly as adding a `sourcePath` never does. The
|
|
597
|
+
analyzer's doctrine is recorded beside the claim (`code.analyzer.doctrineDigest`)
|
|
598
|
+
instead.
|
|
493
599
|
|
|
494
600
|
**It is optional by construction.** By default only a moved approval (or a
|
|
495
601
|
member's unapproved changes) refuses, and neither can happen in a project that
|
|
@@ -1015,10 +1121,14 @@ content — `as:` only asserts it (a contradiction is `MEMBER_KIND_MISMATCH`):
|
|
|
1015
1121
|
`partOf: { project: <parent id>, path: <parent root> }`.
|
|
1016
1122
|
- A **project** is an independent boundary: its tree declares an id, holds an L0
|
|
1017
1123
|
or carries a lock. It is reached only as `alias::name` through its L0 exports,
|
|
1018
|
-
judged by its own gate and locked at its own root. A project
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1124
|
+
judged by its own gate and locked at its own root. A project contained in this
|
|
1125
|
+
project, or in a `../x` sibling checkout beside it, is followed by the family
|
|
1126
|
+
scan and composed **live**: its exports are read where it is stored, its own
|
|
1127
|
+
gate runs in the family run, and nothing is pinned (`wairon externals pin`
|
|
1128
|
+
says so for a member alias). A project fetched from git or a `hosted:` record
|
|
1129
|
+
is a **referenced** project: this project's gate judges it against its pin
|
|
1130
|
+
(`wairon externals pin`), and the family run opens it where it is stored and
|
|
1131
|
+
composes it per use. A hosted instance never reads a `../` source.
|
|
1022
1132
|
|
|
1023
1133
|
Boundaries are earned: grow a system from subsystems, to parts when a piece
|
|
1024
1134
|
needs its own folder or repository, to projects only when it needs its own team,
|
|
@@ -1097,7 +1207,7 @@ widen it (`EXPORT_WIDENS_AUDIENCE`).
|
|
|
1097
1207
|
| `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) |
|
|
1098
1208
|
| `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). `<path>` reads as `member add` reads a location: inside this project, or a sibling with a leading `../`. Refused when its id collides with a family project's, and for any other path out of this project (`not-contained`) |
|
|
1099
1209
|
| `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 |
|
|
1100
|
-
| `wairon member adopt <alias>` | Make this project's external found by
|
|
1210
|
+
| `wairon member adopt <alias>` | Make this project's external found by path a member again — detach's inverse. Its source reads as `member attach` reads a location: inside this project, or a sibling with a leading `../` (a sibling member is composed live like a contained one, never pinned) |
|
|
1101
1211
|
| `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 |
|
|
1102
1212
|
| `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 |
|
|
1103
1213
|
| `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 |
|
|
@@ -1217,14 +1327,14 @@ project, so it holds nothing machine-specific: it runs `wairon mcp serve` from t
|
|
|
1217
1327
|
and the server attaches to the project it is started in. A `--global` registration is
|
|
1218
1328
|
machine-wide and names the running CLI by its absolute path.
|
|
1219
1329
|
|
|
1220
|
-
The local server offers
|
|
1330
|
+
The local server offers 49 tools:
|
|
1221
1331
|
|
|
1222
1332
|
| Group | Tools |
|
|
1223
1333
|
|-------|-------|
|
|
1224
1334
|
| Topology (read) | `listAgents`, `getAgent`, `listDomains`, `validateTopology`, `getProjectConfig` |
|
|
1225
|
-
| Authoring | `sdd_initialize_system`, `sdd_add_subsystem`, `sdd_set_public_interfaces`, `sdd_add_component`, `sdd_define_interface`, `sdd_set_endpoints
|
|
1335
|
+
| Authoring | `sdd_initialize_system`, `sdd_add_subsystem`, `sdd_set_public_interfaces`, `sdd_add_component`, `sdd_define_interface`, `sdd_set_endpoints` (dry run), `sdd_write_narrative`, `sdd_add_type` (dry run), `sdd_update_spec` (dry run, search), `sdd_delete_spec` (dry run, force, search) |
|
|
1226
1336
|
| Reading and checking | `sdd_get_spec`, `sdd_get_status`, `sdd_validate_tree` |
|
|
1227
|
-
| Renames and moves (in this tree) — each with `dryRun` | `sdd_rename_component`, `sdd_rename_method`, `sdd_rename_param`, `sdd_rename_type`, `sdd_rename_field`, `sdd_move_methods` |
|
|
1337
|
+
| Renames and moves (in this tree) — each with `dryRun` | `sdd_rename_component`, `sdd_rename_method`, `sdd_rename_param`, `sdd_rename_type`, `sdd_rename_field`, `sdd_rename_spec` (a contract or an implementation on its own), `sdd_move_methods`, `sdd_move_spec` (a component or a type to another subsystem) |
|
|
1228
1338
|
| Members | `sdd_add_member`, `sdd_move_member` |
|
|
1229
1339
|
| Family migrations (each takes `dryRun`) | `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` |
|
|
1230
1340
|
| Externals | `sdd_add_external`, `sdd_update_external`, `sdd_remove_external`, `sdd_pin_externals`, `sdd_get_externals_status` |
|
|
@@ -1233,6 +1343,15 @@ The local server offers 47 tools:
|
|
|
1233
1343
|
| Packs | `sdd_pack_impact` |
|
|
1234
1344
|
| Delegation | `sdd_get_agent_brief` (the live brief for one agent; also served as the `wairon-agent://` resource) |
|
|
1235
1345
|
|
|
1346
|
+
Every tool description ends with its argument names (`Arguments: id, newId, dryRun?.`). Every write
|
|
1347
|
+
tool refuses a spec of another project — a member's spec read through this tree is written from the
|
|
1348
|
+
member's own root, and the refusal names its folder and the call to make there. `sdd_delete_spec` and
|
|
1349
|
+
a contract method removed through `sdd_update_spec` report `published` (the public names a consumer
|
|
1350
|
+
reaches what goes through) and `breaks` (the consumers in reach, and in the `search` folders, that use
|
|
1351
|
+
one), as the renames do; a removed contract method takes its implementation entries with it
|
|
1352
|
+
(`cascaded`) and is refused while another spec still calls it. A delta never changes a spec's id or
|
|
1353
|
+
subsystem: `sdd_rename_spec` and `sdd_move_spec` do, with every reference and published entry.
|
|
1354
|
+
|
|
1236
1355
|
A hosted server adds its own `sdd_host_*` and `sdd_landscape_*` tools; see the
|
|
1237
1356
|
[hosted server guide](https://github.com/SYW-Apps/Waffle-AIron/blob/main/docs/design/hosted-mcp-server.md).
|
|
1238
1357
|
|
|
@@ -1347,7 +1466,7 @@ return the impact of every pack they applied in their results.
|
|
|
1347
1466
|
| `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) |
|
|
1348
1467
|
| `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 |
|
|
1349
1468
|
| `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 |
|
|
1350
|
-
| `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 one object-typed param left IS the body, as a client sends it (named under `x-wairon-body-param`, so `surface import` puts it back), and several are the properties of a body object. 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. Each operation answers
|
|
1469
|
+
| `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 one object-typed param left IS the body, as a client sends it (named under `x-wairon-body-param`, so `surface import` puts it back), and several are the properties of a body object. 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. Each operation answers the success `status` its endpoint states (`sdd_set_endpoints` `status`: `202` Accepted, a `3xx` redirect with a `Location` header and no body, a `201` for a workflow verb that files something), else its conventional code — `204` with no content for a method returning nothing; `201 Created` for a POST that creates: a method whose `effect` is `lifecycle`, or one whose effect is undeclared, `write` or `io` and whose name says it creates (`createHabit`, `placeOrder`, `signUp`); else `200` (a cancel, an archive, a log-in) — and a method returning `result<T, E>` also answers a `default` error response carrying E's schema (`surface import` reads both back). A `custom` auth becomes an `apiKey` scheme only when the Portal's `auth.name` (with `auth.in`, default header) names where the credential travels; naming none, it is an `http` scheme `custom` with the design's description — no header name is invented. Two operations of one Portal on the same verb and path (placeholders compared by position) are refused naming both, never dropped (`validate` reports them as `ENDPOINT_ROUTE_DUPLICATE`). A type another project publishes resolves from its pin, or — for a member project — from what the member exports now; a type nothing resolves is named in a warning (`The document has no schema for N type(s) it names: …`), never passed under a ✔. The components are exactly the types the operations reach, each ONCE — a member's type reached under its alias, its project id or another producer's closure is one component, keyed `<alias>.<id>` (a qualified id's `::` written `.`, so every key matches `^[a-zA-Z0-9._-]+$`); a field or parameter typed `T?` is not `required` (it stays nullable), and a field's description is carried on its property. Every status line goes to stderr, so `surface export --format openapi > api.json` writes the JSON alone; an export that publishes nothing, or of a design `validate` refuses (its error count and codes), is a warning, never a ✔. An unknown `--portal` is refused naming the portals the surface renders |
|
|
1351
1470
|
| `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. A field renamed per its trace keeps its own row beside its type's rename (traced, or a changed public name of the same definition — never "removed + added"), and an exported type reshaped only by a rename it embeds says which (`embeds renamed field "money.currency" → "currencyCode"`). Against a revision it also covers the project's own HTTP Portals (its service API, exported or not), with every type they name from a pinned external or a member project expanded from the pin or member of each side: a re-pin that reshapes the project's own wire format (`GET /routes/:id/tiles` answering a renamed field) is a change of its own surface, named on the verb. 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`). An L0 export entry added since that publishes nothing (a wildcard over a subsystem that publishes nothing) is listed as such, never read as no change. `--against` takes a native snapshot or a git revision: an OpenAPI document, or a file the native schema does not read, is refused in one line. `sdd_surface_diff` is the same answer for an assistant |
|
|
1352
1471
|
| `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) |
|
|
1353
1472
|
|