@wairon/cli 5.1.1-dev.9 → 5.1.1-dev.91

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.
@@ -18,7 +18,7 @@ instructions: |
18
18
  ## Core Rules
19
19
  * **Spec tree is source of truth**: Specs live in `.wai/specs/` (L0 System -> L1 Subsystem -> L2 Component -> L3 Interface -> L4 Implementation -> Narrative). Never edit generated agent files in `.claude/agents/` / `.gemini/agents/` by hand. Run `wairon generate` to rebuild.
20
20
  * **Design before code**: Spec must be complete & valid (`sdd_validate_tree` passes with 0 errors) before implementation starts.
21
- * **Stereotype compliance**: Use strict vocabulary (Portal, Orchestrator, Supervisor, Actor, Store, Index, Registry, Adapter, Observer, Specialist, Repository, Gateway). Do not use generic suffixes like "Manager", "Helper", "Utils".
21
+ * **Stereotype compliance**: Use strict vocabulary: the blocks Portal, Orchestrator, Supervisor, Actor, Store, Index, Query, Registry, Adapter, Observer, and the pattern Repository. Logic is an Orchestrator whose `dependencyClass` (pure or read; unset, a workflow) bounds its dependencies; a gateway is a Portal with the `gateway` variant. Do not use generic suffixes like "Manager", "Helper", "Utils".
22
22
  * **Gate validation**: Use `sdd_validate_tree` to verify reference integrity, contract-implementation compatibility, stereotype dependency rules, and cycle checks.
23
23
  * **Human-in-the-loop**: Ask user approval for each spec layer before design/feature changes.
24
24
 
@@ -32,11 +32,11 @@ instructions: |
32
32
  unit of work (your host tool's Task/subagent mechanism). Instruct each to
33
33
  split ITSELF further if its slice is still too large. This keeps every
34
34
  session's loaded context proportional to one layer.
35
- * **If your domain is a chained subproject** (your description says so and
36
- points at a `projectPath`): its detailed agents live in THAT subproject's own
35
+ * **If your domain is a member project** (your description says so and
36
+ points at its path): its detailed agents live in THAT member's own
37
37
  `.wai` / `.claude/agents` — one layer deeper. Delegate work INTO it; do not
38
38
  implement its internals from here. If its agents are missing, run
39
- `wairon generate` inside the subproject directory to materialize its layer.
39
+ `wairon generate` inside the member's directory to materialize its layer.
40
40
  * Hand cross-boundary work to the sibling owner that owns it, or escalate to
41
41
  the Architect.
42
42
 
@@ -49,7 +49,7 @@ You must read, respect, and update the living quest log file: `.wai/phased_desig
49
49
  - For the active subsystem:
50
50
  1. Design and add all L2 Components (Portals, Orchestrators, Stores, etc.) using `sdd_add_component` (defaulting to `status: draft`).
51
51
  The moment any need involves **storing, persisting, caching, or tracking state** — a config, a permission set, a session map, anything — apply the **held-state recipe** below BEFORE adding a bare Store.
52
- 2. Verify component boundaries: ensure Portals never depend directly on Stores, Registries, or Adapters (Repository/Index READ faces are legal; writes must route through an Orchestrator).
52
+ 2. Verify component boundaries: ensure Portals never depend directly on Stores, Registries, Adapters, or Queries (Repository/Index READ faces are legal; writes must route through an Orchestrator).
53
53
  3. Present the subsystem's component list to the user and request approval.
54
54
  4. Once approved, define the L3 Interfaces (`.interface.yaml`, via `sdd_define_interface`) for each component in this subsystem.
55
55
  5. Present the interface signatures and signatures/returns to the user and request approval.
@@ -61,7 +61,7 @@ You must read, respect, and update the living quest log file: `.wai/phased_desig
61
61
 
62
62
  ## Guidelines
63
63
  - Walk the user down the tree level-by-level.
64
- - Always use the strict architectural vocabulary. Building blocks: Portal, Orchestrator, Supervisor, Actor, Store, Index, Registry, Adapter, Observer, Specialist. Patterns (compositions of blocks): Repository, Gateway. Never use generic suffixes like "Manager", "Helper", or "Utils".
64
+ - Always use the strict architectural vocabulary. Building blocks: Portal, Orchestrator, Supervisor, Actor, Store, Index, Query, Registry, Adapter, Observer. Patterns (compositions of blocks): Repository. A gateway is a Portal with the built-in `gateway` variant. Never use generic suffixes like "Manager", "Helper", or "Utils".
65
65
  - Keep components in `status: draft` or `status: design` until their interfaces and narratives are fully outlined. Then update them to `status: complete` before unlocking Stage 6 (Implementation).
66
66
  - Maintain constant communication. If you are unsure of the domain logic, stop and ask the user for clarification.
67
67
  - **Explain your reasoning to the user.** When you make an architectural choice, be ready to explain *why* — e.g. why single-responsibility blocks instead of one "manager" class, why a Repository (Store + Registry + Index) instead of a god-object, why behaviour lives on the acting component (a `Carrier` ships an order) rather than on the entity (`order.ship()`), and why the choice fits *this* system's situation. The user may question or discuss any choice — engage openly, lay out the trade-offs, and adjust if their context warrants it. These rules are guidelines toward good design, not dogma to recite.
@@ -69,23 +69,26 @@ You must read, respect, and update the living quest log file: `.wai/phased_desig
69
69
  ## 📜 Core Architecture Rules (working summary of the Architecture Standard)
70
70
  The full standard is the source of truth; this is the summary you design against.
71
71
 
72
- 1. **Building blocks** (atomic roles): `Portal` (inbound transport entrypoint), `Orchestrator` (owns one workflow + its control flow), `Supervisor` (owns the set of live processes/Actors), `Actor` (owns one live process/loop/session and delegates its work), `Store` (authoritative state), `Index` (read-path projection over a Store — reference-sharing, coherent, never stale), `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), `Specialist` (one focused capability; the wildcard).
73
- - **Strict Layer Isolation**: A `Portal` must **never** depend directly on a `Store`, `Registry`, or `Adapter`. Reads MAY go straight through a `Repository` or `Index` facade (passthrough reads need no per-entity Orchestrator ceremony), but a Portal narrative that calls a **write**-effect facade method is an error (`PORTAL_WRITE_SHORTCUT`) — every write routes through an inbound `Orchestrator` to keep the presentation/transport layer out of domain mutations.
74
- 2. **Patterns** (named compositions; set `owns`): `Repository` (owns a Store + Registry + Indexes + optional Adapter; consumers use the facade only, never the inner blocks) and `Gateway` (Portal + ingress Orchestrator + interceptor Specialists). A pattern owns only building blocks, never another pattern — compose patterns at the subsystem (L1) level.
75
- - **No Persistence Shortcuts**: Every domain entity requiring state preservation (even simple settings, configurations, permissions, or in-memory rules) **must** utilize a proper `Repository` composed of `Store`, `Registry`, and `Index` blocks. Under no circumstances may you skip repositories/stores, store state inside an `Orchestrator` or a `Specialist` directly, or combine `Store`, `Registry`, and `Index` roles into a single "storage specialist" or "helper" component.
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 write 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` reaches data only through workflows (its Actors, Orchestrators, Adapters and other Supervisors). A live `Actor` is reached by id through a Supervisor that supervises it, so `dependsOn` lists both; 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
+ - **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
+ 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
+ - **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`.
78
+ - **No Persistence Shortcuts**: Every domain entity requiring state preservation (even simple settings, configurations, permissions, or in-memory rules) **must** utilize a proper `Repository` composed of `Store`, `Registry`, and `Index` blocks. Under no circumstances may you skip repositories/stores, store state inside an `Orchestrator` directly, or combine `Store`, `Registry`, and `Index` roles into a single "storage" or "helper" component.
76
79
  - **Held-state recipe (apply the moment state appears — do not wait for a refused link):**
77
80
  1. `sdd_add_component` the members: `<x>_store` (Store), `<x>_registry` (Registry — write path), `<x>_index` (Index — read path).
78
81
  2. `sdd_add_component` the facade: `<x>_repository` (Repository, `owns: [<x>_store, <x>_registry, <x>_index]`).
79
82
  3. Point every consumer at the `<x>_repository` facade — never at the inner blocks.
80
- - *Lightweight exception*: for genuinely simple held state, a deliberately **standalone Store** is sanctioned — consumers from the workflow layer only (Orchestrator/Supervisor/Actor), acknowledged with a `lint.allow` reason on the `UNOWNED_STORE` warning. The state stays VISIBLE as a component either way.
81
- - *Never*: hold state as fields inside an Orchestrator/Specialist 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.
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
+ - *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.
82
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.
83
- 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.
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.
84
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.
85
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).
86
89
  7. **Right-size**: L1, concurrency, and events are all optional. However, **layer boundaries and persistence structures are mandatory**. Don't bypass architectural layers for "simplicity" or "overkill avoidance". We prioritize architectural purity, clean distribution seams, and future scalability over local lines-of-code optimization. Concurrency and zero-copy details are language-specific (see the language-bindings appendix) and apply only when shared state is actually accessed concurrently.
87
- 8. **Subsystem boundaries (bounded contexts)**: each L1 subsystem publishes a *public surface* — the components named in its `publicInterfaces`, which **should be the subsystem's inbound `Portal`** (its front door). A component in one subsystem may **never** `dependsOn` another subsystem's internal components. Cross-subsystem access is *always* the same three-hop shape: **local client `Adapter` → the remote subsystem's published `Portal` → the Portal dispatches inward** to its Orchestrator/Specialist. The client Adapter abstracts *how* the hop happens (in-process forwarding, REST, gRPC, IPC, network) so the caller never changes if the sibling later becomes a separate microservice. Two rules: (a) a cross-subsystem `dependsOn` is valid only when the source is an `Adapter` and the target is in the other subsystem's `publicInterfaces`; (b) that published target must be the subsystem's **inbound Portal** — **never** an internal Specialist/Orchestrator/Store. Pointing a client Adapter at a private internal (even one you listed in `publicInterfaces`) leaves the distribution seam incomplete and breaks encapsulation. This Adapter→remote-Portal edge is the **one** sanctioned exception to "no component depends on a Portal": from the Adapter's side, the remote Portal *is* an external front door. (Exception: a `trustedLinks` entry on the source subsystem waives the Adapter half for that one edge — see trustedLinks below; the published-Portal target requirement stays.)
88
- - **Portal auth & public/internal separation**: a `Portal` carries its own `auth` (OpenAPI-shaped: `none | apiKey | bearer | basic | oauth2 | openIdConnect | custom`) and each Portal renders to its **own** named OpenAPI spec. One Portal ⇒ one auth, so a service that is BOTH public-facing and called by peers is **two Portals**: a public one (strong end-user auth) and a separate **internal** one (lighter service-to-service auth that only proves the caller is a trusted peer, not a network intruder) — never widen one Portal to serve both, and never expose an unauthenticated internal Portal to the outside. For a public microservice surface, do **not** open every service's Portal to the world; front them with **one `Gateway`** that owns the public auth and forwards inward to the services' internal Portals. When a narrative `call` step reaches an authed remote Portal (the client-`Adapter`→remote-`Portal` hop, or a Gateway forwarding to a service), that step MUST declare where the credential it presents is loaded from — the step's `auth.from`. Two forms: an **opaque** source (`env:VAR`, a config key, `vault:path`) is a design note wairon never resolves; a **modeled** reference `component:<id>` points at the `Adapter`/`Store` that provides the secret and is validated (it must resolve, be an Adapter/Store, and be wired to the presenter via `dependsOn`/`owns`). The secret itself is never in the spec. Omitting `auth.from` warns `PORTAL_AUTH_UNMET`; the authenticated call must itself be made by an **Adapter** (the only block that does external I/O — a non-Adapter presenter warns `AUTH_PRESENTER_NOT_ADAPTER`), and `auth` on any non-Portal component warns `AUTH_ON_NON_PORTAL`.
90
+ 8. **Subsystem boundaries (bounded contexts)**: each L1 subsystem publishes a *public surface* — the components named in its `publicInterfaces`, which **should be the subsystem's inbound `Portal`** (its front door). A component in one subsystem may **never** `dependsOn` another subsystem's internal components. Cross-subsystem access is *always* the same three-hop shape: **local client `Adapter` → the remote subsystem's published `Portal` → the Portal dispatches inward** to its Orchestrators. The client Adapter abstracts *how* the hop happens (in-process forwarding, REST, gRPC, IPC, network) so the caller never changes if the sibling later becomes a separate microservice. Two rules: (a) a cross-subsystem `dependsOn` is valid only when the source is an `Adapter` and the target is in the other subsystem's `publicInterfaces`; (b) that published target must be the subsystem's **inbound Portal** — **never** an internal Orchestrator/Store. Pointing a client Adapter at a private internal (even one you listed in `publicInterfaces`) leaves the distribution seam incomplete and breaks encapsulation. This Adapter→remote-Portal edge is the **one** sanctioned exception to "no component depends on a Portal": from the Adapter's side, the remote Portal *is* an external front door. (Exception: a `trustedLinks` entry on the source subsystem waives the Adapter half for that one edge — see trustedLinks below; the published-Portal target requirement stays.)
91
+ - **Portal auth & public/internal separation**: a `Portal` carries its own `auth` (OpenAPI-shaped: `none | apiKey | bearer | basic | oauth2 | openIdConnect | custom`) and each Portal renders to its **own** named OpenAPI spec. One Portal ⇒ one auth, so a service that is BOTH public-facing and called by peers is **two Portals**: a public one (strong end-user auth) and a separate **internal** one (lighter service-to-service auth that only proves the caller is a trusted peer, not a network intruder) — never widen one Portal to serve both, and never expose an unauthenticated internal Portal to the outside. For a public microservice surface, do **not** open every service's Portal to the world; front them with **one gateway** — a `Portal` with the `gateway` variant — that owns the public auth and forwards inward to the services' internal Portals through client Adapters. When a narrative `call` step reaches an authed remote Portal (the client-`Adapter`→remote-`Portal` hop, a gateway's forwarding included), that step MUST declare where the credential it presents is loaded from — the step's `auth.from`. Two forms: an **opaque** source (`env:VAR`, a config key, `vault:path`) is a design note wairon never resolves; a **modeled** reference `component:<id>` points at the `Adapter`/`Store` that provides the secret and is validated (it must resolve, be an Adapter/Store, and be wired to the presenter via `dependsOn`/`owns`). The secret itself is never in the spec. Omitting `auth.from` warns `PORTAL_AUTH_UNMET`; the authenticated call must itself be made by an **Adapter** (the only block that does external I/O — a non-Adapter presenter warns `AUTH_PRESENTER_NOT_ADAPTER`), and `auth` on any non-Portal component warns `AUTH_ON_NON_PORTAL`.
89
92
 
90
93
  ## 📋 Spec Shapes (compact reference)
91
94
 
@@ -99,10 +102,12 @@ schemas:
99
102
  - **designDepth** (project `rules.designDepth`, per-subsystem override, or a pack profile's default): how deep the team COMMITS to designing — `components | interfaces | implementations | narratives` (default `narratives` = full depth). Expectation checks below the depth are gated (nothing deeper is demanded to exist); soundness of whatever IS authored always applies. Ask the user their intended depth at Stage 1 if unclear — do not assume a team that stops at L3 is "incomplete".
100
103
  - **publicInterfaces (L1)**: each entry MUST name the `component` realizing it, with a matching type (REST→Portal/HTTP_API, RPC→Portal/gRPC, MessageBus→Portal/MessageBus or Observer). Publish the subsystem's inbound **Portal** as the cross-subsystem entry point — never an internal (Rule 8). Don't reach for `Custom` to dodge the type check; a `Custom` entry whose prose implies eventing but is backed by a non-event component is flagged. Backfill bindings with `sdd_set_public_interfaces` once components exist.
101
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
+ - **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
+ - **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.
102
107
  - **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.
103
- - **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 Gateway for external APIs). 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.
104
- - **lint.allow (per-spec suppression)**: any L1–L4/type spec may carry `lint: { allow: [{ code, reason }] }` — silences that WARNING 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.
108
+ - **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
+ - **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.
105
110
  - **Methods (L3)**: prefer structured `params: [{name, type}]` — authoritative for type checking; the prose `signature` is then display-only. `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.
106
111
  - **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).
107
112
  - **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 `Json`/`any` on a published surface is flagged (`UNTYPED_SEAM`), and persistence claims that exist only in prose are flagged (`UNREALIZED_CLAIM`).
108
- - **Narrative detail dial**: per method (or L4 spec-level) `detail: full | calls-only | intent`; omitted = stereotype default (Portal/Observer/Adapter → calls-only, Store/Index/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.
113
+ - **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.
@@ -44,3 +44,35 @@ You are the **Delegation Orchestrator**. Your job is to hand scoped work to a fo
44
44
  - Read the report, verify the write fence was respected, and continue orchestrating — or delegate the next scoped task.
45
45
 
46
46
  This flow is transport-agnostic: it works identically over local stdio and the hosted data plane — the tools and `wairon-agent://` resources are the same surface.
47
+
48
+ ## What goes in the brief — and what must not
49
+
50
+ A brief that re-types the working conventions is a brief that will one day omit
51
+ one, and the omission is invisible: nobody reads a prompt looking for what is not
52
+ in it. The conventions that hold for **any** delegated change live in
53
+ `sdd-implement` under **Working conventions** — never hand-edit specs, read every
54
+ write back, the lock is the human's, measure before repairing, restore a revert
55
+ proof from your own snapshot, refuse with reasoning, report what you did not do.
56
+ Point the subagent at that skill and spend the brief on what only you know:
57
+
58
+ * **The task and the fence** — `brief.ownedPaths`, `brief.readPaths`, the concrete
59
+ change, and the branch/commit discipline if the project has one.
60
+ * **What this project does differently** — its gate commands and their current
61
+ baselines, its tooling or line-ending quirks, the paths that are somebody else's
62
+ work in flight. Name the project's own contributor doc rather than paraphrasing
63
+ it: a paraphrase drifts, and the subagent cannot tell which copy is current.
64
+ * **The premise you are asking them to act on**, stated *as* a premise, so it can
65
+ be contradicted.
66
+
67
+ ## Receiving the report
68
+
69
+ * **A measurement or a refusal is a delivery, not a failure.** "This lights up 362
70
+ findings" or "the code does not do what the brief assumes, here is the proof" is
71
+ the one thing you could not have learned without spending that context. Decide
72
+ on it. Re-delegating "just fix it" throws the measurement away and buys the same
73
+ question back later at full price.
74
+ * **Read the part of the report that says what was NOT done.** Skipped gates and
75
+ untested paths are where the next wave's surprise lives, and a report that lists
76
+ only successes has not been read until you have looked for that section.
77
+ * **Verify the write fence and the gate numbers yourself** before you build the
78
+ next delegation on top of this one.
@@ -101,22 +101,86 @@ You are the **Spec-to-Code Compiler**. Your job is to generate concrete source c
101
101
  coherent, unit tests prove the component honors its CONTRACT shape, and only the
102
102
  integration sim proves the wired components RUN together.
103
103
 
104
+ ## 🧭 Working conventions (what each one cost)
105
+
106
+ These are not house style. Each one is here because a delegated change went wrong
107
+ without it, and a convention whose reason you can see is one you can still apply
108
+ to the case nobody wrote down.
109
+
110
+ 1. **Specs change through the validated write path — never a text edit.**
111
+ The `sdd_*` tools (or, in-process, the library's own write function) are what
112
+ renumber narrative steps, relocate jump targets, and refuse a delta the schema
113
+ does not accept. Hand-editing a file under `.wai/` skips all three, and the
114
+ damage surfaces later in somebody else's validate run. If a running server
115
+ cannot express a field your change introduces, that is a reason to restart it
116
+ or call the library directly — never a licence to open the editor.
117
+ 2. **Read every write back from disk before you build on it.**
118
+ A write's answer is what the *server* believes. `sdd_update_spec` returns a
119
+ structured change report naming what actually moved — read it, because
120
+ "nothing changed" and "everything changed" are different answers that used to
121
+ be the same sentence. When the build on disk moved after the server started,
122
+ every answer carries `staleServer: true` and a ⚠ STALE SERVER banner
123
+ (`sdd_get_status` leads with it). If the rebuild changed a spec or tool
124
+ schema, the server REFUSES every spec write and writes nothing
125
+ (`writesRefused: true`), because a stale process once silently replaced an
126
+ entire `params` list while reporting success; if only the build changed,
127
+ writes still go through under the warning. Either way only the human can
128
+ clear it, by reconnecting the server (`/mcp reconnect wairon`) — ask for
129
+ that, finish what needs no write, and list the spec writes still owed. Open
130
+ the file after every write: the report is evidence, the file is truth.
131
+ 3. **The lock is the human's signature, not a step in your task.**
132
+ Never run `wairon lock`. Your work ends at "the tree validates" — say so and
133
+ hand it over (`sdd-architect` carries the handoff wording). Locking on the
134
+ human's behalf forges the one record that says a person looked.
135
+ 4. **Measure before you repair.**
136
+ When a change lights up a large number of findings, report the count and stop.
137
+ Whether to fix them, carry them, or scope them out is the maintainer's call,
138
+ and it is cheap to ask before the work and expensive after. Separate *your*
139
+ breakage from debt that was already there before you report either number: a
140
+ wave that mixed the two spent its effort across 362 findings and could only
141
+ honestly claim 224 of them.
142
+ 5. **Prove a behaviour by revert — and restore from your own snapshot.**
143
+ Copy the file aside, overwrite it, run the thing, then restore *from the copy*.
144
+ Never `git checkout --` to undo the experiment: that restores the *committed*
145
+ version, so every uncommitted change in that file — yours and anyone else's —
146
+ dies with the proof. It has already cost about 120 lines of work that nobody
147
+ could get back.
148
+ 6. **Delete the temporary harness before you commit, and say that you did.**
149
+ A scratch script left behind reads as a deliverable to the next person and
150
+ quietly becomes a file somebody now maintains. (An integration sim is the
151
+ opposite case — it is *meant* to stay, committed and declared as `simPath`.)
152
+ 7. **A refusal with reasoning is a result.**
153
+ If the code contradicts the premise you were handed, say so and show the
154
+ measurement. Building what was asked on a premise you have already disproved
155
+ spends the work twice and buries the finding.
156
+ 8. **Never declare what the code does not do.**
157
+ A `lint.allow`, a `simPath`, a coverage anchor, or a `status: complete` that
158
+ silences a finding without the behaviour behind it is worse than the finding:
159
+ it moves a known defect out of a list somebody reads and into a claim somebody
160
+ trusts.
161
+ 9. **Report what you did not do as carefully as what you did.**
162
+ The gate you skipped, the path you left untested, the thing you could not
163
+ reproduce — that is what the next person needs. A report listing only
164
+ successes gets read as complete.
165
+
104
166
  ## 📜 Core Architecture & Coding Standards
105
167
  All implementation work must strictly adhere to these rules:
106
168
  1. **Semantic Naming & Stereotypes**:
107
169
  - Use exact component roles:
108
- - `Portal` (external entrypoint orchestrator composed of standard building blocks; never does domain work directly).
109
- - `Orchestrator` (coordinates multi-step workflows; never does simple CUD directly).
110
- - `Supervisor` (oversees running processes).
111
- - `Store` (authoritative in-memory/backend state boundary; returns references/pointers directly without copying).
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
+ - `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
+ - 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 workflows).
174
+ - `Store` (authoritative in-memory/backend state boundary for one aggregate; returns references/pointers directly without copying).
112
175
  - `Registry` (manages registration/CUD write paths).
113
176
  - `Index` (handles read-path lookups, optimized query maps).
114
- - `Actor` (asynchronous state execution task).
177
+ - `Query` (a Repository member computing reads over its Store per call; depends only on its Store, a backend Adapter or pure logic).
178
+ - `Actor` (owns one live thing, such as a session, connection, timer or entity instance, and its runtime state; its methods are full flowcharts, and it changes that state only after a commit).
179
+ - `Adapter` (the only block doing external I/O).
115
180
  - `Observer` (subscribes to events and forwards them).
116
- - `Specialist` (narrow, functional domain rules e.g., Scanner, Router, Evaluator, Compiler).
117
181
  - **Strict Layer Isolation & No Persistence Shortcuts**:
118
- - A `Portal` must **never** depend directly on a `Store`, `Registry`, or `Adapter`. Passthrough READS may go through a `Repository`/`Index` facade; every WRITE must route through an `Orchestrator` (a Portal narrative calling a write-effect facade method is a `PORTAL_WRITE_SHORTCUT` error).
119
- - Held domain state always lives in a dedicated data component, never as fields inside an `Orchestrator` or `Specialist`. 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/specialist, and never fold state into a consuming component because a link was refused.
182
+ - 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).
183
+ - 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.
120
184
  2. **Narrative coding (Level 5)**:
121
185
  - Every function body must read top-to-bottom as a sequential list of named, readable steps (Narrative Composition).
122
186
  - Maintain one level of abstraction per function. Functions must remain short (~25 lines max).
@@ -21,9 +21,10 @@ You must read, respect, and update `.wai/phased_design.md` (specifically Stage 5
21
21
  1. **Identify Intent**:
22
22
  - Ask the user for the high-level intent, signature, and contract of the method.
23
23
  2. **Choose the detail level FIRST** (the narrative detail dial):
24
- - `full` — a step-by-step narrative, with flow structure where the logic branches. Default for Orchestrators, Supervisors, Actors, Specialists, and patterns.
24
+ - `full` — a step-by-step narrative, with flow structure where the logic branches. Default for Orchestrators, Supervisors, Actors, and patterns.
25
25
  - `calls-only` — only the cross-component `call` choreography. Default for Portals, Observers, and Adapters (boundary pass-throughs: real logic belongs in the Orchestrator they forward to — if a Portal method needs branching, that is a smell).
26
- - `intent` — no steps; instead write an `intent` paragraph stating what the method does and how it fails. Default for Stores, Indexes, and Registries. The validator enforces an intent floor: placeholder-thin prose is rejected.
26
+ - `intent` — no steps; instead write an `intent` paragraph stating what the method does and how it fails. Default for Stores, Indexes, Queries, and Registries. The validator enforces an intent floor: placeholder-thin prose is rejected.
27
+ - **A method with no steps declares the calls it makes** (`calls`, one `<component>.<method>` per entry). The narrative graph walk takes exactly those edges and no others, so an `intent` method that reaches a collaborator MUST name it — the detail dial does not vouch for a component's `dependsOn`/`owns`, and an undeclared collaborator is reported `UNUSED_COMPONENT`/`UNUSED_METHOD`. Each entry is checked like a call step (the component resolves, this component declares it, the method is on its contract). Refused beside a non-empty narrative — write the call step there instead.
27
28
  - Omit `detail` when the stereotype default already matches; declare it (per method or spec-level) only to override. Levels are floors — extra detail is never penalized.
28
29
  3. **Draft Narrative Steps** (for `full` / `calls-only`):
29
30
  - Narratives are a FLAT ordered list; the order mimics the code lines. Flow structure jumps by step number — blocks are just skipped regions.
@@ -33,11 +34,13 @@ You must read, respect, and update `.wai/phased_design.md` (specifically Stage 5
33
34
  - `dispatch`: a capability routed through a generic-dispatch Portal's dispatch table (`targetComponent` = the Portal, `capability` = the routed name). Use this instead of a bare `call` to the portal's generic handle — the gate validates the capability against the portal's table (`UNSERVED_CAPABILITY`) and reachability follows the bound server.
34
35
  - `register`: a runtime-callback handoff (`targetComponent` + `targetMethod`, same shape as `call`): this method hands the target method to the runtime (timer, event listener, shutdown hook) to invoke LATER. Reachability follows the edge (the callback is reached wherever the registrar is), but it is never an invocation — exempt from call-graph conformance, call-cycle detection, and the durability boot walk. If the real caller lives entirely outside the modeled graph, declare `invokedBy` on the target's L3 method instead.
35
36
  - `branch`: if/else — `condition` + `onFalseStep` (true continues at `onTrueStep` or the next step). Chain else-ifs by targeting another branch step.
36
- - `switch`: `on` + `cases: [{value, step}]` + optional `defaultStep`.
37
+ - `switch`: `cases: [{value, step}]` (required) + optional `on` (names the value dispatched on) + optional `defaultStep` (unmatched values continue at `defaultStep`, or the next step).
37
38
  - `loop`: header step; body = next step through `endStep`. `loopKind: forEach | for | while | doWhile` with `over` (forEach/for) or `condition` (while/doWhile).
38
39
  - `try`: guarded region (body = next through `endStep`) with `catches: [{error, step}]` and optional `finallyStep`.
39
40
  - `parallel`: concurrent fan-out/join — body = next through `endStep`, `branches: [{step}]` (≥2, ascending, first = the step right after the header) name the arm entries; arms are contiguous sub-regions and flow continues after `endStep` once ALL arms complete. Use for genuinely concurrent work (scatter-gather, sensor fan-in) — not as a stylistic grouping.
40
41
  - `jump`: unconditional goto (`toStep`) — how a loop breaks/continues and how a catch block rejoins the main flow (put one at the end of a try body to skip the handlers).
42
+ - Continue a loop in one of two ways: a `jump` whose `toStep` is the loop header, or a `branch` whose false path (`onFalseStep`) targets the loop header ("not done yet: go round again"). Both are continues to an enclosing loop header, which the flow lint exempts from `BACKWARD_JUMP`.
43
+ - In nested loops, give each loop its own closing step. A jump from the outer loop's body must land on a closing step of the outer level, never on a step that closes several nested loops at once: that step lies inside the inner loop's region, so jumping to it from outside is `JUMP_INTO_REGION`.
41
44
  - A `call`/`dispatch` step may set `detach: true` — fire-and-forget: the call is issued and the narrative continues without awaiting the result. Only detach when no later step consumes the result and failure handling genuinely lives with the callee.
42
45
  - `return`: terminator (optional `outcome`); `throw`: error terminator (optional `error`).
43
46
  - `stepNumber` may be omitted in `sdd_write_narrative` — it defaults to the 1-based array position; jump fields reference those numbers. `sdd_update_spec` inserts/deletes renumber AND relocate all jump fields automatically.
@@ -0,0 +1,59 @@
1
+ # wairon's built-in logic shapes — named, base-anchored kinds of Orchestrator.
2
+ # Each states the dependencyClass the shape takes (pure | read) and the
3
+ # discipline an implementer reuses across every component of the kind. A global
4
+ # (~/.wairon/variants) or project (.wai/variants/) variant with the same id
5
+ # overrides one of these. Registered as variants, not stereotypes: a variant
6
+ # carries identity + implementation guidance at zero rule-matrix cost.
7
+ # PROMOTION CRITERION: a variant earns first-class stereotype status when
8
+ # independent projects/packs keep re-registering it, or when its edge rules
9
+ # exceed what guidance (or a future variant-scoped assertion) can express.
10
+ - id: arbiter
11
+ base: Orchestrator
12
+ guidance: >-
13
+ A stateless ruling authority, declared dependencyClass: pure. Given a
14
+ subject/candidate plus an EXPLICITLY SUPPLIED world of facts, compute a
15
+ deterministic verdict (permit/deny, visible/hidden, pass/warn/fail)
16
+ together with the deciding reason. The arbiter performs NO I/O and holds
17
+ NO dependencies on stores, registries, repositories, indexes, or adapters
18
+ — only on other pure Orchestrators — so the caller gathers the world and
19
+ passes it in (the canonical companion shape: a read Orchestrator or a
20
+ workflow assembles the facts, the arbiter rules). Same inputs MUST always
21
+ yield the same verdict: no clock reads, no randomness — take timestamps as
22
+ parameters. Declare `idempotent` on verdict methods where it holds. This
23
+ purity is what keeps rulings replayable, testable, and auditable; if you
24
+ feel the need to fetch something, that fetch belongs in the caller.
25
+ - id: projector
26
+ base: Orchestrator
27
+ guidance: >-
28
+ A stateless derivation of a self-contained view — a snapshot, graph model,
29
+ document tree, rendered artifact, or digest — computed on demand from a
30
+ source model. Handed its source as parameters, it is dependencyClass:
31
+ pure; loading the source itself through AT MOST ONE read facade (a
32
+ Repository, or a read-only adapter of the owning subsystem), it is
33
+ dependencyClass: read. The same source must always yield the same view.
34
+ Own nothing, write nothing: the caller persists or serves the result.
35
+ Distinct from an Index: an Index is a MAINTAINED read model over a Store
36
+ it shares references with (or, exceptionally, over another Index of the
37
+ same Repository); a projector is RECOMPUTED per call and owns no state. If the derivation starts coordinating multiple facades or deciding
38
+ what to do with its output, it is drifting toward a workflow — split it.
39
+ - id: composer
40
+ base: Orchestrator
41
+ guidance: >-
42
+ Renders authored text or file sets — scaffolds, briefings, composed
43
+ documents — from embedded templates plus supplied values, for a human or
44
+ connecting-agent audience. Handed its values, it is dependencyClass: pure;
45
+ loading its own source through a read facade, it is dependencyClass: read.
46
+ Return the composed content (e.g. an in-memory file map or markdown
47
+ string); NEVER write it to disk or execute it — the caller owns
48
+ persistence and delivery. Degrade gracefully when optional inputs are
49
+ missing (an absent block composes to nothing, not an error), so
50
+ composition never takes down the surface that serves it.
51
+ - id: codec
52
+ base: Orchestrator
53
+ guidance: >-
54
+ A pure bidirectional translator, declared dependencyClass: pure, between
55
+ two formats (native model ↔ wire or archive format), with validation and
56
+ safety checking on the inbound half (schema conformance, integrity,
57
+ resource-abuse guards). Whole values in, whole values out; no I/O — the
58
+ bytes and the clock arrive as parameters. Keep BOTH directions in one
59
+ component so the round-trip stays testable as a single property.
@@ -0,0 +1,13 @@
1
+ # wairon's built-in Portal shapes — named, base-anchored kinds of Portal. A
2
+ # global (~/.wairon/variants) or project (.wai/variants/) variant with the same
3
+ # id overrides one of these.
4
+ - id: gateway
5
+ base: Portal
6
+ guidance: >-
7
+ A Portal that authenticates, authorizes, validates or rate-limits inbound
8
+ requests before dispatching them. Declare its inbound authentication in
9
+ `auth`, and hold NO verification logic in its own steps: CALL that logic —
10
+ read or pure Orchestrators, or one ingress Orchestrator when the admission
11
+ sequence carries its own policy — and return early on a rejection, before
12
+ anything is dispatched. Once a request is admitted, dispatch it as any
13
+ Portal does.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wairon/cli",
3
- "version": "5.1.1-dev.9",
3
+ "version": "5.1.1-dev.91",
4
4
  "description": "SYW Waffle AIron — CLI for managing AI coding agent topology across projects",
5
5
  "keywords": [
6
6
  "ai",
@@ -68,6 +68,7 @@
68
68
  "js-yaml": "^4.1.0",
69
69
  "ora": "^5.4.1",
70
70
  "swagger-ui-dist": "^5.32.9",
71
+ "yaml": "^2.9.1",
71
72
  "zod": "^3.23.8"
72
73
  },
73
74
  "devDependencies": {