@wairon/cli 5.1.1-dev.96 → 5.1.1-dev.98
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 +2136 -901
- package/dist/cli/index.js.map +1 -1
- package/dist/index.js +1921 -703
- package/dist/index.js.map +1 -1
- package/dist/templates/skills/sdd-architect.md +5 -4
- package/dist/templates/skills/sdd-implement.md +1 -1
- package/package.json +6 -3
- package/schemas/design-export-1.json +1168 -0
|
@@ -70,8 +70,8 @@ You must read, respect, and update the living quest log file: `.wai/phased_desig
|
|
|
70
70
|
The full standard is the source of truth; this is the summary you design against.
|
|
71
71
|
|
|
72
72
|
1. **Building blocks** (atomic roles): `Portal` (inbound transport entrypoint), `Orchestrator` (logic as a flowchart over injected collaborators, with one or more cohesive methods; `dependencyClass: pure` depends only on pure Orchestrators, `read` also on read Orchestrators, Repositories, Indexes and Adapters, and unset means a workflow), `Supervisor` (owns the set of live Actors and their lifecycle; may supervise Supervisors), `Actor` (owns one live thing — a session, connection, timer or entity instance — and its methods are full flowcharts), `Store` (authoritative state for one aggregate), `Index` (read-path projection over a Store — reference-sharing, coherent, never stale; as an exceptional case it may project another Index of the same Repository, never in a cycle), `Query` (a Repository member computing reads over its Store per call), `Registry` (write path / CUD for one aggregate), `Adapter` (the only block doing external I/O — DB/FS/HTTP/gRPC/message-bus client), `Observer` (subscribes to events, forwards to one workflow).
|
|
73
|
-
- **Strict Layer Isolation**: A `Portal` must **never** depend directly on a `Store`, `Registry`, `Adapter`, or `Query`. Reads MAY go straight through a `Repository` or `Index` facade (passthrough reads need no per-entity Orchestrator ceremony), but a Portal narrative call or dispatch-table binding that reaches a **write**-effect facade method is an error (`PORTAL_WRITE_SHORTCUT`) — every
|
|
74
|
-
- **Logic and process rules**: pure logic may be used by every block. A `Supervisor`
|
|
73
|
+
- **Strict Layer Isolation**: A `Portal` must **never** depend directly on a `Store`, `Registry`, `Adapter`, or `Query`. Reads MAY go straight through a `Repository` or `Index` facade (passthrough reads need no per-entity Orchestrator ceremony), but a Portal narrative call or dispatch-table binding that reaches a **write**- or **lifecycle**-effect facade method is an error (`PORTAL_WRITE_SHORTCUT`) — every mutation routes through an inbound `Orchestrator` to keep the presentation/transport layer out of domain mutations.
|
|
74
|
+
- **Logic and process rules**: pure logic may be used by every block. A `Supervisor` may `owns` its **supervision state** — a Store or Registry that is its own (restart counts, live sets, run brackets): one hop, private, full read/write (`SUPERVISOR_CONTAINMENT` for anything else it owns; a component depending on it is the intruder, `SUPERVISION_STATE_INTRUSION`). Shared data it does not own it reaches only through `read`- and `lifecycle`-effect methods; a write goes through an Orchestrator (`SUPERVISOR_WRITE_SHORTCUT`), and it stays out of presentation. A live `Actor` is reached through its supervision: callers look it up by id in a Registry its Supervisor maintains (owns, or keeps through lifecycle calls) — model that real lookup hop — or depend on that Supervisor (`ACTOR_REACHED_WITHOUT_SUPERVISOR`); a `Portal` may message a Supervisor by id. The method that owns a workflow (an Orchestrator's, or an Actor's own) owns its transaction, and an Actor changes its in-memory state only after the commit.
|
|
75
75
|
- **No wildcard block**: decompose instead. Held or derived state → Store/Index/Repository; behaviour over a value's own fields → a type method; external I/O → Adapter; logic → an Orchestrator with the narrowest `dependencyClass` that holds; one live thing → Actor. `Specialist` and `Gateway` are retired (`STEREOTYPE_RETIRED`): `wairon doctor --fix` retypes a Specialist as an Orchestrator, and a Gateway becomes the Portal it owned, with the `gateway` variant.
|
|
76
76
|
2. **Patterns** (named compositions; set `owns`): `Repository` — the data-access component for one aggregate: owns one Store + its Registry + Indexes + Queries + optional Adapter; consumers use the facade only, never the inner blocks. A `Query` lives only inside a Repository and depends only on its Store, a backend Adapter or pure logic. Reads across aggregates go through a read Orchestrator (or a read-model Repository whose Query runs the join), and an outbox is a sibling Repository. A pattern owns only building blocks, never another pattern — compose patterns at the subsystem (L1) level.
|
|
77
77
|
- **Variants** (`variant:` on a component): the built-in `arbiter`, `projector`, `composer` and `codec` (on Orchestrator) and `gateway` (on Portal: authenticates, authorizes, validates or rate-limits before it dispatches, with its inbound auth in `auth`), then any global and project variants, a later layer overriding by id. A variant carries guidance; what an Orchestrator may depend on is its own `dependencyClass`.
|
|
@@ -82,7 +82,7 @@ The full standard is the source of truth; this is the summary you design against
|
|
|
82
82
|
3. Point every consumer at the `<x>_repository` facade — never at the inner blocks.
|
|
83
83
|
- *Lightweight exception*: for genuinely simple held state, a deliberately **standalone Store** is sanctioned — consumers from the workflow layer only (a workflow Orchestrator or an Actor), acknowledged with a `lint.allow` reason on the `UNOWNED_STORE` warning. The state stays VISIBLE as a component either way.
|
|
84
84
|
- *Never*: hold state as fields inside an Orchestrator because a Store link was refused. A refused link means "apply this recipe", not "inline the state" — state hidden inside a logic component is invisible to the spec and unrecoverable.
|
|
85
|
-
3. **owns vs dependsOn**: `owns` = a pattern's private member blocks (exactly one hop). `dependsOn` = collaborators (other facades / standalone blocks). Never depend on a block privately owned by another pattern.
|
|
85
|
+
3. **owns vs dependsOn**: `owns` = a pattern's private member blocks (exactly one hop), or a Supervisor's supervision state (its own Stores and Registries). `dependsOn` = collaborators (other facades / standalone blocks). Never depend on a block privately owned by another pattern.
|
|
86
86
|
4. **Decoupling**: Registry (write) and Index (read) are independent — both work on the Store; the Registry never updates Indexes (Indexes share the Store's references and project structural changes). A Store is depended *upon*; it never depends on a Registry/Index. An Index depends on its Store, a backend Adapter or pure logic; only when one projection is itself worth re-presenting another way may a derived Index depend on another Index — owned by the same Repository, never in a cycle (`ARCHITECTURE_VIOLATION_INDEX_DEP` otherwise). Keep it the exception: it is not a way to chain lookups.
|
|
87
87
|
5. **Behaviour placement**: behaviour lives where it can be performed autonomously over its own state (`order.total()`, `dog.bark()`); behaviour needing an external actor lives on the *acting* component, taking the entity as an argument (a `Carrier` ships an order — not `order.ship()`). Prefer composition + interfaces over inheritance.
|
|
88
88
|
6. **Narrative coding (L5)**: each method reads top-to-bottom as named steps; one level of abstraction per function; a pattern facade's method is exactly one `call` step (pure 1:1 forwarding, no logic).
|
|
@@ -104,11 +104,12 @@ schemas:
|
|
|
104
104
|
- **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.
|
|
105
105
|
- **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.
|
|
106
106
|
- **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.
|
|
107
|
+
- **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.
|
|
107
108
|
- **targetLanguage**: set on L0 (`sdd_initialize_system`), override per L1 — enables language-aware validation (foreign builtins and flow constructs the language lacks are flagged). Extension packs (`.wai/project.yaml` → `extensions.packs`) may register additional languages/platforms and custom profiles.
|
|
108
109
|
- **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.
|
|
109
110
|
- **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.
|
|
110
111
|
- **Methods (L3)**: prefer structured `params: [{name, type}]` — authoritative for type checking; the prose `signature` is then display-only.
|
|
111
112
|
- **Types speak one neutral grammar** in every param, returns and field: the primitives `string`, `int`, `float`, `bool`, `bytes`, `date` (a calendar day), `datetime` (an instant), `duration`, `void`, `any`; the collections `list<T>`, `set<T>` and `map<K, V>` (K is `string`, `int` or an enum); `T?` for "T or no value" (an `optional: true` param or field means "may be left out", a different thing); `A | B` only between NAMED types; and `async T` (or `async void`) on a returns that completes later. A named type is an entity, a value-object, an `enum` (`sdd_add_type` kind `enum` with ordered `values`) or a signature type; a value that needs its own meaning but is one primitive is a named scalar, a value-object with `holds: string` (or another primitive) in place of fields. Write `async void`, never `Promise<void>`; `list<Invoice>`, never `Invoice[]`. TypeScript spellings are still accepted and stored canonical — the answer lists each respelling — but `number` is refused ("int or float?"), and inline object shapes, inline function types, literal unions (`'a' | 'b'`) and unions mixing in a primitive are refused with their named replacement (a value-object or named scalar, a signature type, an enum). `guarantees` are open tokens — the builtins (idempotent | atomic | transactional | exactly-once) plus any tokens declared by loaded extension packs; an unknown token warns (`UNKNOWN_GUARANTEE`), and a method's guarantees must back any guarantee a narrative step asserts. Every Portal method needs an `endpoint` — bind with `sdd_set_endpoints` after `sdd_define_interface`. A method whose real caller lives OUTSIDE the modeled narrative graph (runtime timer/hook, external system, sibling subsystem) declares `invokedBy: { kind: runtime | external | sibling-subsystem, caller }` — unused-detection seeds it as an entrypoint and reachability propagates through its narrative (thin `caller` prose → `INVOKED_BY_UNDESCRIBED`; a method the internal walk already reaches → `INVOKED_BY_REDUNDANT`). Prefer a `register` step when the wiring is internal.
|
|
112
113
|
- **Narratives (L5, inside L4)**: a FLAT numbered step list; order mimics the code lines. `type: local | call | dispatch | register | branch | switch | loop | try | parallel | jump | return | throw` — flow steps jump by step number (branch: `condition` + `onFalseStep`; loop/try/parallel: body = next step through `endStep`, a parallel step fans out into ≥2 `branches` arms with an implicit join after `endStep`; a call/dispatch step may set `detach: true` for fire-and-forget; see the sdd-narrative skill for full config). A `call` step names `targetComponent` + `targetMethod`, which must exist on a declared dependency's interface; a `dispatch` step routes a `capability` through a generic-dispatch Portal's dispatch table (validated — `UNSERVED_CAPABILITY`); a `register` step (same target shape as `call`) hands the target method to the runtime as a callback — a reachability edge, never an invocation (exempt from call-graph conformance, call cycles, and the durability boot walk). Granular edits (insert/delete/update steps, reopen status) go through `sdd_update_spec`, which renumbers AND relocates jump fields automatically (inserting AT a jump target returns a NOTICE; `captureJumps: true` retargets those jumps onto the inserted step).
|
|
113
|
-
- **Semantic wiring (dispatch / lifecycle / durability)**: a generic-dispatch Portal declares a machine-readable `dispatch` table (`capability → component.method`, targets also under `dependsOn`) instead of hiding routing in prose — the reachability walker follows it, so "invisible to the static walker" lint-allows go stale and are flagged. Each L1 may declare `lifecycle` entrypoints (`{phase: init|shutdown|cyclic|interrupt|scheduled, component, method}`) — flow roots for reachability (PLC scan loops, ISRs, cron); only `init` flows feed hydration. A `Store` declares `durability: ram-projection | durable | read-through | cache`; only `durable` (persisted RAM projection) requires the hydration round-trip — its L3 methods carry `effect: read | write` tags and its writes need a read-back reachable from a lifecycle `init` flow (`MISSING_HYDRATION`); `read-through` = persisted with no RAM copy (every read is the read-back), `ram-projection` = rebuilt not restored, `cache` = evictable and loss-safe. Cross-subsystem method params/returns must be typed — bare `any` (and what reads as it: `json`, `object`, `unknown`) on a published surface is flagged (`UNTYPED_SEAM`), and persistence claims that exist only in prose are flagged (`UNREALIZED_CLAIM`).
|
|
114
|
+
- **Semantic wiring (dispatch / lifecycle / durability)**: a generic-dispatch Portal declares a machine-readable `dispatch` table (`capability → component.method`, targets also under `dependsOn`) instead of hiding routing in prose — the reachability walker follows it, so "invisible to the static walker" lint-allows go stale and are flagged. Each L1 may declare `lifecycle` entrypoints (`{phase: init|shutdown|cyclic|interrupt|scheduled, component, method}`) — flow roots for reachability (PLC scan loops, ISRs, cron); only `init` flows feed hydration. A `Store` declares `durability: ram-projection | durable | read-through | cache`; only `durable` (persisted RAM projection) requires the hydration round-trip — its L3 methods carry `effect: read | write | lifecycle` tags and its writes and lifecycle changes need a read-back reachable from a lifecycle `init` flow (`MISSING_HYDRATION`); `lifecycle` creates, destroys or (un)registers what exists without modifying domain fields, and calls only read and lifecycle methods (`LIFECYCLE_CALLS_WRITE`); `read-through` = persisted with no RAM copy (every read is the read-back), `ram-projection` = rebuilt not restored, `cache` = evictable and loss-safe. Cross-subsystem method params/returns must be typed — bare `any` (and what reads as it: `json`, `object`, `unknown`) on a published surface is flagged (`UNTYPED_SEAM`), and persistence claims that exist only in prose are flagged (`UNREALIZED_CLAIM`).
|
|
114
115
|
- **Narrative detail dial**: per method (or L4 spec-level) `detail: full | calls-only | intent`; omitted = stereotype default (Portal/Observer/Adapter → calls-only, Store/Index/Query/Registry → intent, logic components → full). `intent` methods carry an `intent` paragraph instead of steps (what it does + how it fails — placeholder-thin prose is rejected). Levels are floors, not ceilings.
|
|
@@ -170,7 +170,7 @@ All implementation work must strictly adhere to these rules:
|
|
|
170
170
|
- `Portal` (inbound entrypoint composed of standard building blocks; dispatches to Orchestrators and never does domain work directly; with the `gateway` variant it authenticates, authorizes, validates or rate-limits before it dispatches).
|
|
171
171
|
- `Orchestrator` (logic as a flowchart over injected collaborators; with no `dependencyClass` it is a workflow that coordinates multi-step work and owns its transactions, never doing simple CUD directly).
|
|
172
172
|
- pure/read `Orchestrator` (`dependencyClass: pure` holds narrow deterministic rules over supplied values, e.g. Scanner, Router, Evaluator, Compiler, and depends only on pure Orchestrators; `dependencyClass: read` also reads through Repositories, Indexes and Adapters, and never writes).
|
|
173
|
-
- `Supervisor` (owns the set of live Actors and their lifecycle; reaches data only through
|
|
173
|
+
- `Supervisor` (owns the set of live Actors and their lifecycle, and may own its supervision state — a Store or Registry of its own; reaches shared data only through read- and lifecycle-effect methods, and writes it through a workflow).
|
|
174
174
|
- `Store` (authoritative in-memory/backend state boundary for one aggregate; returns references/pointers directly without copying).
|
|
175
175
|
- `Registry` (manages registration/CUD write paths).
|
|
176
176
|
- `Index` (handles read-path lookups, optimized query maps).
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wairon/cli",
|
|
3
|
-
"version": "5.1.1-dev.
|
|
3
|
+
"version": "5.1.1-dev.98",
|
|
4
4
|
"description": "SYW Waffle AIron — CLI for managing AI coding agent topology across projects",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"ai",
|
|
@@ -26,6 +26,7 @@
|
|
|
26
26
|
"main": "./dist/index.js",
|
|
27
27
|
"files": [
|
|
28
28
|
"dist",
|
|
29
|
+
"schemas",
|
|
29
30
|
"README.md"
|
|
30
31
|
],
|
|
31
32
|
"workspaces": [
|
|
@@ -38,7 +39,7 @@
|
|
|
38
39
|
"dev": "tsx src/cli/index.ts",
|
|
39
40
|
"build:sdk": "npm --prefix sdk run build",
|
|
40
41
|
"prebuild": "npm --prefix sdk run build",
|
|
41
|
-
"build": "tsup && node -e \"const fs=require('fs'); fs.cpSync('src/templates','dist/templates',{recursive:true});\" && node scripts/embed-web.mjs",
|
|
42
|
+
"build": "tsup && node -e \"const fs=require('fs'); fs.cpSync('src/templates','dist/templates',{recursive:true});\" && node scripts/embed-web.mjs && node scripts/design-schema.mjs",
|
|
42
43
|
"gen:canvas": "node scripts/gen-canvas-engine.mjs",
|
|
43
44
|
"build:web": "node scripts/gen-canvas-engine.mjs && npm --prefix web install --no-audit --no-fund && npm --prefix web run build",
|
|
44
45
|
"build:all": "npm run build:web && npm run build",
|
|
@@ -78,11 +79,13 @@
|
|
|
78
79
|
"@vitest/coverage-v8": "^4.1.9",
|
|
79
80
|
"@wairon/sdk": "*",
|
|
80
81
|
"@yao-pkg/pkg": "^5.12.0",
|
|
82
|
+
"ajv": "^8.20.0",
|
|
81
83
|
"cytoscape": "^3.34.0",
|
|
82
84
|
"rimraf": "^5.0.7",
|
|
83
85
|
"tsup": "^8.1.0",
|
|
84
86
|
"tsx": "^4.15.7",
|
|
85
87
|
"typescript": "^5.5.2",
|
|
86
|
-
"vitest": "^4.1.9"
|
|
88
|
+
"vitest": "^4.1.9",
|
|
89
|
+
"zod-to-json-schema": "^3.25.2"
|
|
87
90
|
}
|
|
88
91
|
}
|