@wairon/cli 5.1.1-dev.103 → 5.1.1-dev.104
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/README.md +5 -1
- package/dist/cli/index.js +2219 -1355
- package/dist/cli/index.js.map +1 -1
- package/dist/index.d.ts +382 -43
- package/dist/index.js +1678 -950
- package/dist/index.js.map +1 -1
- package/dist/templates/skills/sdd-architect.md +2 -0
- package/dist/templates/skills/sdd-delegate.md +1 -1
- package/dist/templates/skills/sdd-implement.md +3 -1
- package/docs/cli.md +50 -7
- package/package.json +1 -1
|
@@ -107,6 +107,8 @@ schemas:
|
|
|
107
107
|
- **Subsystem consumers**: an entry may list `consumers: [<subsystem>, …]` — the subsystems the surface is published to. Absent, any subsystem may depend on it; present, only those may, and a dependency from any other subsystem is `CROSS_SUBSYSTEM_UNLISTED_CONSUMER` (an error). Use it for an internal Portal meant for one or two peers, so the boundary says who may call it.
|
|
108
108
|
- **Portal mounts (listeners)**: a Portal's routes are its methods' `endpoint` bindings, but something must SERVE the portal: the HTTP listener the host starts (the server process — an Express/Fastify app, a framework's server entry). For an HTTP service, model that listener as a Portal and give it `mounts: [{ portal, prefixes: ["/api"], via? }]`, one entry per portal it routes to; the portals it serves declare no `mounts` of their own. Declare `mounts` ONLY on a listener: the field's presence — even `mounts: []` — says "the host starts this portal directly and nothing else needs to mount it", which exempts it from `UNMOUNTED_PORTAL`. `mounts: []` is therefore right only for a Portal that really IS the whole HTTP server and serves only its own routes; never set it to quiet the warning, and never give it to two HTTP portals of one service — that claims two listeners where there is one server, and hides that nothing mounts them. Every Portal with HTTP endpoints must be mounted by some listener or be one (`UNMOUNTED_PORTAL`), and each mounted endpoint must lie under one of its prefixes (`ENDPOINT_OUTSIDE_MOUNT`; a prefix matches whole path segments, so `/` covers only the root). Mounts judge HTTP only: an MCP, gRPC or CLI portal leaves the field off.
|
|
109
109
|
- **trustedLinks (L1)**: `{ subsystem, reason }` — declared on the SOURCE subsystem, it licenses a direct in-process edge into the named sibling **without the client-Adapter shim** (the deliberate fast lane). The target must still be in the sibling's `publicInterfaces` (its published Portal); a target-side declaration grants nothing. It also acknowledges a mutual-dependency pair.
|
|
110
|
+
- **Declare planned code at design time**: give each implementation the `sourcePath` its code will live at (and a method its own `sourcePath` when its body lives elsewhere). Code linkage — `sourcePath`, `symbol`, `exportedVia`, `simPath`, `injectedParams`, conformance tiers — is not part of the approval, so declaring or changing it never asks for a re-lock. A file not written yet is `SOURCE_FILE_PLANNED` (a notice, never a `--ci` failure) until the component's realization begins; never strip a planned path to quiet anything. A team that wants CI to demand code for every designed implementation sets `rules.conformance.requireCode: true`.
|
|
111
|
+
- **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.
|
|
110
112
|
- **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.
|
|
111
113
|
- **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.
|
|
112
114
|
- **Renames keep a trace**: rename with `sdd_rename_component`, `sdd_rename_method` and `sdd_rename_type`, and move methods with `sdd_move_methods` — never by deleting and re-adding a spec or editing an id 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.
|
|
@@ -28,7 +28,7 @@ You are the **Delegation Orchestrator**. Your job is to hand scoped work to a fo
|
|
|
28
28
|
- **Never reuse a brief across delegations or after a re-lock** — fetch fresh per delegation; the call is cheap and the brief is always current.
|
|
29
29
|
3. **Spawn a GENERIC subagent from the brief**:
|
|
30
30
|
- Prompt: `brief.instructions`, plus the concrete task description.
|
|
31
|
-
- Write fence: the subagent may only modify files matching `brief.ownedPaths` (within `brief.domainRoot` when set).
|
|
31
|
+
- Write fence: the subagent may only modify files matching `brief.ownedPaths` (within `brief.domainRoot` when set). The fence holds every code location the specs name, existing or planned; a planned file is marked `(planned — create it)` and the subagent creates it there. When the brief names no code location, declare the planned `sourcePath` first (`sdd_update_spec` on the implementation) — code linkage is not part of the approval, so this costs no re-lock — and fetch the brief again.
|
|
32
32
|
- Required first reading: `brief.readPaths` — the subagent reads these before any edit.
|
|
33
33
|
- Pass `brief.variantGuidance` along when present, and `brief.typeMapping` — the subagent writes `list<T>`, `T?`, `async T`, `result<T, E>` and an enum by that mapping, never by guess.
|
|
34
34
|
4. **Apply `brief.budget` when it is present** — constituting the subagent correctly is part of spawning it, not a separate concern. When the brief carries no budget the project has not opted in; spawn as you otherwise would.
|
|
@@ -51,7 +51,9 @@ You are the **Spec-to-Code Compiler**. Your job is to generate concrete source c
|
|
|
51
51
|
- Confirm the design is approved: `sdd_get_status` reports "Approved: …" and does not list any of this component's specs as changed since the approval. If there is no approval on record, or this component's specs changed after it, stop immediately and ask the human to review the design and run `wairon lock` (never run it yourself).
|
|
52
52
|
2. **Setup Workspace & Align with Standards**:
|
|
53
53
|
- Align with the inlined **Core Architecture & Coding Standards** (see below) to ensure naming, narrative coding, and stereotype conventions are respected. Do NOT read these standards from disk; they are already fully specified in your system context.
|
|
54
|
-
- Locate the target implementation source file (mapped by `sourcePath` in L4).
|
|
54
|
+
- Locate the target implementation source file (mapped by `sourcePath` in L4). A file the brief marks `(planned — create it)` does not exist yet: create it at exactly that path — the fence already holds it. Until the first file a component names exists, a planned file is only `SOURCE_FILE_PLANNED` (a notice); once one exists, a missing file is `MISSING_SOURCE_FILE` (an error) and a contract method naming no file is `METHOD_SOURCE_PATH_MISSING`.
|
|
55
|
+
- **Code linkage is not approval.** `sourcePath`, `symbol`, `exportedVia`, `simPath`, `injectedParams` and conformance tiers say where the design is realized, not what it is: they are outside the approved digests. When the implementation names no `sourcePath` yet, or names the wrong file, set it with `sdd_update_spec` — it costs no re-lock and never turns the approval stale.
|
|
56
|
+
- **Externals.** A contract you consume from another project is judged against its pin. If `sdd_validate_tree` reports an ADVISORY `EXTERNAL_LIVE_INCOMPATIBLE` (a used member changed, was renamed — the new name is given — or is gone in the live producer), adapt the uses, then ask the human to re-pin (`wairon externals pin <alias>`). Advisory findings never fail the gate; they are not a reason to stop, nor to code against the live producer's new shape without adapting the spec first.
|
|
55
57
|
3. **Write Tests First (TDD)**:
|
|
56
58
|
- Create or update the companion test file (e.g. `<component>.test.ts` or similar).
|
|
57
59
|
- Mock all direct L2 dependencies using the signatures defined in their respective L3 `.interface.yaml` files.
|
package/docs/cli.md
CHANGED
|
@@ -50,7 +50,10 @@ Each member project prints as `[Project] alias (id)` holding its own subsystems,
|
|
|
50
50
|
with its **approval state** computed at the member's own root (`approved`,
|
|
51
51
|
`drifted`, `never`) and how this project's lock pinned it (`matches`, `moved`,
|
|
52
52
|
`unpinned`). The report closes with this project's own state. Asked at the parent
|
|
53
|
-
or at the member, the answer is the same.
|
|
53
|
+
or at the member, the answer is the same. When an external moved in its live
|
|
54
|
+
producer, drifted or could not be compared, an **Externals** section follows
|
|
55
|
+
the verdict (the same advisory comparison `validate` prints); it never changes
|
|
56
|
+
the exit code.
|
|
54
57
|
|
|
55
58
|
### `wairon validate [--ci] [--all] [--subsystem <id>] [--family] [--no-recursive]`
|
|
56
59
|
Run the architecture-conformance gate over the spec tree: reference integrity,
|
|
@@ -65,6 +68,26 @@ warnings as errors (notices are printed and counted, never fatal).
|
|
|
65
68
|
member's own gate, and this project's externals composed against their live
|
|
66
69
|
producers. `--no-recursive` runs this project's gate alone; `--family` runs
|
|
67
70
|
the family run from a member.
|
|
71
|
+
- **Externals, compared live (advisory: the pin gates).** The gate judges each
|
|
72
|
+
external against its pin. Then every external the run did not compose is
|
|
73
|
+
compared with its **live** producer, offline, and printed in its own section:
|
|
74
|
+
`EXTERNAL_LIVE_INCOMPATIBLE` (warning: a used member changed at signature
|
|
75
|
+
level, was renamed — the new name is given — or is gone; it names the specs
|
|
76
|
+
that use it and the fix: adapt the uses, then `wairon externals pin <alias>`),
|
|
77
|
+
`EXTERNAL_DRIFTED` (notice: the producer moved, nothing used did) and
|
|
78
|
+
`EXTERNAL_LIVE_UNCOMPARED` (notice: the producer could not be read — a git
|
|
79
|
+
external is never fetched here; `wairon externals status` compares it). These
|
|
80
|
+
findings are **advisory**: printed and counted, but neither the exit code nor
|
|
81
|
+
`--ci` ever decides on them. A family run composes the externals in its reach
|
|
82
|
+
as its gate (`EXTERNAL_INCOMPATIBLE`, error) and gives them no second word.
|
|
83
|
+
- **Planned code is a notice.** An implementation whose `sourcePath` names a
|
|
84
|
+
file not written yet is `SOURCE_FILE_PLANNED`, and one naming no file is
|
|
85
|
+
`MISSING_SOURCE_PATH` — both notices — until realization begins (any file the
|
|
86
|
+
implementation names exists). From then on a missing file is
|
|
87
|
+
`MISSING_SOURCE_FILE` (error) and a contract method naming no file is
|
|
88
|
+
`METHOD_SOURCE_PATH_MISSING` (warning). `rules.conformance.requireCode: true`
|
|
89
|
+
in `project.yaml` reports the two notices as errors, for a CI that must say
|
|
90
|
+
every designed implementation has code.
|
|
68
91
|
|
|
69
92
|
### `wairon generate [--target <name>] [--domain <id>] [--domains <ids>] [--root] [--family] [--no-prune] [--global] [--dry-run]`
|
|
70
93
|
Reconcile the generated guides, skills and context, and — only when the project
|
|
@@ -114,7 +137,7 @@ before its code exists.
|
|
|
114
137
|
**It approves this project only.** The gate identity it records covers this
|
|
115
138
|
project's own specs, the design doctrine, its declared inputs, its `composition`
|
|
116
139
|
block, and each direct member's **composition subject** — the `stateId` in the
|
|
117
|
-
member's own lock record. The record (format
|
|
140
|
+
member's own lock record. The record (format 3) lists each direct member under
|
|
118
141
|
`members` with its subject and state (`approved`, `drifted`, `never`). Nothing is
|
|
119
142
|
written below the project: each member locks at its own root. With
|
|
120
143
|
`composition.requireApprovedMembers: true` in `project.yaml`, the lock refuses
|
|
@@ -125,8 +148,21 @@ if a spec, the doctrine, an input or a member's approval moves while the lock
|
|
|
125
148
|
runs, it refuses and writes nothing. (`--no-recursive` is accepted for one
|
|
126
149
|
release and changes nothing: a lock never reaches below its project.)
|
|
127
150
|
|
|
151
|
+
**Code linkage is not part of the approval.** The gate identity and the per-spec
|
|
152
|
+
digests read each spec's **design**: where it is realized — `sourcePath`,
|
|
153
|
+
`symbol`, `exportedVia`, `simPath`, `injectedParams`, conformance tiers, a mount's
|
|
154
|
+
`via`, `externalLinks` — and the `createdAt`/`updatedAt` timestamps are left
|
|
155
|
+
out (`specsReading: design`). Linking code to an approved design, or a change and
|
|
156
|
+
its revert, never stales the lock; declare planned `sourcePath`s at design time.
|
|
157
|
+
|
|
158
|
+
A **format-2** record (written before this reading) still passes `lock-check`
|
|
159
|
+
while nothing it covered moved. `wairon doctor --fix` re-expresses it as format 3
|
|
160
|
+
in place — keeping `lockedAt`, `lockedBy`, the results and the member pins, and
|
|
161
|
+
recording `reexpressed: { at, fromAlgorithm, fromReading, by }`. A format-2 record
|
|
162
|
+
that no longer matches reads stale once; one `wairon lock` clears it for good.
|
|
163
|
+
|
|
128
164
|
It writes **nothing into your spec tree**. The approval is one sha256 per spec
|
|
129
|
-
|
|
165
|
+
(over its design view) on `.wai/lock.json`, the record that was always committed — so your
|
|
130
166
|
teammates, a fresh clone and CI all see the same approval you gave, and
|
|
131
167
|
`wairon status` elsewhere can name what has drifted from it. Keys are sorted, so
|
|
132
168
|
re-approving a one-spec change shows up as a two-line diff. A failed or
|
|
@@ -280,6 +316,10 @@ literal union, a union mixing in a primitive, a `number` with no proposal) is
|
|
|
280
316
|
listed with its replacement for an author. Plain `wairon doctor` prints the same
|
|
281
317
|
plan without writing it.
|
|
282
318
|
|
|
319
|
+
`--fix` also re-expresses a format-2 lock record that still provably covers the
|
|
320
|
+
tree as format 3 (see `wairon lock`), keeping who approved it and when; when it
|
|
321
|
+
no longer covers the tree, doctor says that one `wairon lock` is needed.
|
|
322
|
+
|
|
283
323
|
### `wairon list` (alias `ls`) / `wairon show <id>`
|
|
284
324
|
List, or show full details of, the agents resolved from the spec tree
|
|
285
325
|
(`system-architect`, `<subsystem>-owner`, owners for free-standing domains, and
|
|
@@ -637,7 +677,7 @@ project, so it holds nothing machine-specific: it runs `wairon mcp serve` from t
|
|
|
637
677
|
and the server attaches to the project it is started in. A `--global` registration is
|
|
638
678
|
machine-wide and names the running CLI by its absolute path.
|
|
639
679
|
|
|
640
|
-
The local server offers
|
|
680
|
+
The local server offers 38 tools:
|
|
641
681
|
|
|
642
682
|
| Group | Tools |
|
|
643
683
|
|-------|-------|
|
|
@@ -646,7 +686,7 @@ The local server offers 37 tools:
|
|
|
646
686
|
| Reading and checking | `sdd_get_spec`, `sdd_get_status`, `sdd_validate_tree` |
|
|
647
687
|
| Renames and moves (in this tree) | `sdd_rename_component`, `sdd_rename_method`, `sdd_rename_type`, `sdd_move_methods` |
|
|
648
688
|
| Members and family migrations (each takes `dryRun`) | `sdd_add_member`, `sdd_move_member`, `sdd_externalize_subsystem`, `sdd_promote_member`, `sdd_demote_member`, `sdd_internalize_member`, `sdd_attach_member`, `sdd_detach_member`, `sdd_adopt_member`, `sdd_rename_project`, `sdd_rename_member_alias` |
|
|
649
|
-
| Externals | `sdd_pin_externals`, `sdd_get_externals_status` |
|
|
689
|
+
| Externals | `sdd_add_external`, `sdd_pin_externals`, `sdd_get_externals_status` |
|
|
650
690
|
| Packs | `sdd_pack_impact` |
|
|
651
691
|
| Delegation | `sdd_get_agent_brief` (the live brief for one agent; also served as the `wairon-agent://` resource) |
|
|
652
692
|
|
|
@@ -725,7 +765,7 @@ principal.
|
|
|
725
765
|
| `wairon host doctor [--fix]` | Inspect the data dir and, with `--fix`, migrate it: roll back a transaction a crash left unfinished there, apply the permission-model migration, then register every hosted family's members as records of their own (no grant written — access is inherited through the parent chain — and every member-qualified key entry rewritten to a record id), all or nothing, audited |
|
|
726
766
|
| `wairon host key list [--project <id>]` | List API keys |
|
|
727
767
|
| `wairon host key revoke --id <id>` | Revoke a key |
|
|
728
|
-
| `wairon host lock --project <id>` | The same lock flow as `wairon lock` (design gate, `members`, `code` beside the claim, format
|
|
768
|
+
| `wairon host lock --project <id>` | The same lock flow as `wairon lock` (design gate, `members`, `code` beside the claim, format 3) against the hosted project |
|
|
729
769
|
| `wairon host git enable \| disable \| sync \| commit \| status \| sync-config` | Bind a hosted project to its real repository (wairon commits only `.wai/`) |
|
|
730
770
|
| `wairon host producer configure \| produce \| remove \| list` | Project a hosted project to Notion or Miro |
|
|
731
771
|
| `wairon host secret set \| list` | Set integration secrets at runtime (`git-token`, `notion-token`, `miro-token`, `signing-secret`) — no restart |
|
|
@@ -757,7 +797,10 @@ return the impact of every pack they applied in their results.
|
|
|
757
797
|
|
|
758
798
|
| Command | Description |
|
|
759
799
|
|---------|-------------|
|
|
760
|
-
| `wairon externals
|
|
800
|
+
| `wairon externals add <alias> [<source>] [--project <id>] [--ref <ref>] [--dir <dir>] [--use a,b\|'*'] [--description <text>] [--no-pin] [--dry-run] [--json]` | Declare one external in `.wai/project.yaml`. The source is the location grammar members use: `../sibling`, `hosted:<id>`, `<git url>` or `<git url>#<commit>` (the commit is the ref the pin follows, fixed there); omit it when the family provides the producer. It is checked against the producer it reaches: one answering to another id, or not exporting a `use` name to this project, takes the declaration back out and names the id or the closest exported names. Pins by default; a producer that cannot be read leaves it declared and unpinned, saying why. A refusal is one sentence naming the accepted form, and exits 1 |
|
|
801
|
+
| `wairon externals pin [alias…] [--json]` | Pin declared externals into `.wai/externals/<alias>.yaml` and `.wai/externals.lock.yaml`; exits 1 when an alias could not be pinned (unresolved or unreachable — its previous pin stays) |
|
|
802
|
+
| `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`). 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 |
|
|
803
|
+
| `wairon externals list [--json]` | The declared externals, how each resolves and what is pinned; a malformed declaration is listed with its problem, never hidden |
|
|
761
804
|
| `wairon surface export \| import \| list [--audience <level>] [--format native\|openapi] [--portal <id>] [--out <path>] [--source <path>]` | Exchange a public surface document: export this project's (native snapshot or one OpenAPI document per portal), import one, or list them |
|
|
762
805
|
| `wairon produce <notion\|miro> [--page <id>] [--token <token>]` | Project the local spec tree to Notion or Miro (the token comes from `--token`, the environment, else a prompt; nothing is stored) |
|
|
763
806
|
|