@eventmodelers/cli 1.0.124 → 1.0.126

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@eventmodelers/cli",
3
- "version": "1.0.124",
3
+ "version": "1.0.126",
4
4
  "description": "Eventmodelers CLI — real-time Claude agent + skills for Claude Code, for any stack (Node, Supabase, Axon, OpenCQRS, UmaDB, Kurrent, or modeling-only)",
5
5
  "type": "module",
6
6
  "bin": {
@@ -26,7 +26,7 @@ Write tools take their items as an array (`nodeIds[]`, `elements[]`, `connection
26
26
  | Tool | Args | Purpose | REST equivalent |
27
27
  |---|---|---|---|
28
28
  | `list_boards` | — | List boards for the org | §1 `GET /api/boards` (org-scoped) |
29
- | `get_nodes` | `boardId`, `type?`, `name?`, `chapterId?`, `nodeIds?`, `projection?` (`"line"`) | List nodes, optionally by type and/or a partial case-insensitive title match. **Unscoped with full `meta` it is expensive** — about 2 KB per node, ~1 MB on a 500-node board — so always pass a filter or `projection: "line"`. `chapterId` scopes to one timeline — prefer this over an unscoped board-wide call whenever the step is working within one chapter (the common case); `nodeIds` fetches a known, scattered subset in one call (e.g. re-verifying exactly the nodes just touched by a batch write) instead of a full `type` refetch; `projection: "line"` maps each match to `{id, type, title, fields}` — `fields` being just the attribute names. No `cellName` — an existing node is addressed by its `id`; when a cell address is needed (placing into a cell), read it from `get_board_outline`. No `sliceStatus` — take it per column from `get_board_outline` | §3 `GET .../nodes` (REST records carry no `cellName`, `projection=line` included) |
29
+ | `get_nodes` | `boardId`, `type?`, `name?`, `chapterId?`, `nodeIds?`, `projection?` (`"line"` \| `"fields"`) | List nodes, optionally by type and/or a partial case-insensitive title match. **Unscoped with full `meta` it is expensive** — about 2 KB per node, ~1 MB on a 500-node board — so always pass a filter or a projection. `chapterId` scopes to one timeline — prefer this over an unscoped board-wide call whenever the step is working within one chapter (the common case); `nodeIds` fetches a known, scattered subset in one call (e.g. re-verifying exactly the nodes just touched by a batch write) instead of a full `type` refetch; `projection: "line"` maps each match to `{id, type, title, fields}` — `fields` being just the attribute names; `projection: "fields"` to `{id, type, title, parentId, fields}` with the full field definitions (type, example, idAttribute, …) but no html, description, sketch or grid data. No `cellName` — an existing node is addressed by its `id`; when a cell address is needed (placing into a cell), read it from `get_board_outline`. No `sliceStatus` — take it per column from `get_board_outline` | §3 `GET .../nodes` (REST records carry no `cellName`, `projection=line`/`fields` included) |
30
30
  | `get_node` | `boardId`, `nodeId`, `projection?` (`"cells"` \| `"edges"`) | Get one node (no `cellName` — address it by `nodeId`; cell addresses come only from `get_board_outline`). `projection: "cells"` (CHAPTER nodes only) returns just `{rows, columns, cells}` instead of the full `timelineData` — use whenever only the grid/occupancy is needed, not the whole chapter; `projection: "edges"` returns just that node's inbound/outbound connections instead of `findNodeById`'s full record. Both are opt-in — omitting `projection` is the unchanged full response. An unknown id is a `NODE_NOT_FOUND` error | §3 `GET .../nodes/:nodeId` (REST returns the node as stored, without `cellName`; `404` carries `code: 'NODE_NOT_FOUND'`) |
31
31
  | `get_node_comments` | `boardId`, `nodeId` | List comments on a node | §1 `GET .../nodes/:nodeId/comments` |
32
32
  | `get_chat_attachment` | `boardId`, `sessionId`, `attachmentId` | The file a chat message came with — the message's `attachments` list `{id, type, ending}`. Images come back as images, a PDF as an embedded resource, csv / xml / txt / json as text. Only the agent the message was addressed to can read it (`CHAT_ATTACHMENT_NOT_FOUND`); needs the connection's `x-agent-id` (`CHAT_AGENT_REQUIRED`). Max 5 MB per file. | §15 `GET /api/org/:orgId/chat/sessions/:sessionId/attachments/:attachmentId` |
@@ -83,13 +83,14 @@ Pick the cheapest read that answers the step's question, and name the reason in
83
83
  | a chapter's grid / cell occupancy | `get_node { nodeId: <chapterId>, projection: "cells" }` |
84
84
  | one node's connections | `get_node { nodeId, projection: "edges" }` |
85
85
  | what is where in a chapter (incl. cell names for placement), plus `sliceStatus` per column | `get_board_outline` |
86
- | field types, examples, descriptions, positions, HTML | full `get_nodes`, scoped by `chapterId` or `nodeIds` — never board-wide when avoidable |
86
+ | field types, examples, field descriptions | `get_nodes` with `projection: "fields"`, scoped by `chapterId` or `nodeIds` |
87
+ | a node's description, position, HTML | full `get_nodes`, scoped by `chapterId` or `nodeIds` — never board-wide when avoidable |
87
88
  | finding slices / their elements / element counts | `get_slice_data` with `projection: "outline"` (format `json`/`yaml`/`toon`) |
88
89
  | attribute, field-flow or completeness work | `get_slice_data` with `projection: "fields"` |
89
90
  | scenario / spec review, spec coverage | `get_slice_data` with `projection: "specs"` (format `json`/`yaml`/`toon`) |
90
91
  | elements **and** specs **and** comments/prompts together (e.g. a business review) | full `get_slice_data` — say why |
91
92
 
92
- Two cheap projected calls (e.g. `outline` + `specs`) beat one full call when the step doesn't need comments, prompts or field bodies. `projection: "line"` never carries `sliceStatus`, `meta`, positions, field types or examples — if a later step reads any of those, use the full read. `get_nodes { type: "CHAPTER" }` without a projection returns every chapter's full grid; to just list or pick chapters, add `projection: "line"`.
93
+ Two cheap projected calls (e.g. `outline` + `specs`) beat one full call when the step doesn't need comments, prompts or field bodies. `projection: "line"` never carries `sliceStatus`, `meta`, positions, field types or examples; `projection: "fields"` adds the full field definitions and `parentId`, still no `sliceStatus`, positions or node description — if a later step reads any of those, use the full read. `get_nodes { type: "CHAPTER" }` without a projection returns every chapter's full grid; to just list or pick chapters, add `projection: "line"`.
93
94
 
94
95
  ---
95
96
 
@@ -473,21 +474,21 @@ List all nodes on a board.
473
474
  - `type?: ElementType` — exact match.
474
475
  - `name?: string` — partial, case-insensitive title match.
475
476
  - `chapterId?: string` — only nodes placed in this chapter (timeline).
476
- - `projection?: "line"` — compact `{id, type, title, fields?}` per node (`fields` = attribute names only, absent on nodes without fields) instead of the full record. Neither form carries `cellName` — address existing nodes by `id`. Any other value is `400 PROJECTION_INVALID`.
477
+ - `projection?: "line" | "fields"` — `line`: compact `{id, type, title, fields?}` per node (`fields` = attribute names only, absent on nodes without fields); `fields`: `{id, type, title, parentId, fields?}` with the full field definitions as stored, but no html, description, sketch or grid data. Either instead of the full record. Neither form carries `cellName` — address existing nodes by `id`. Any other value is `400 PROJECTION_INVALID`.
477
478
  - `cellId?: string` — return only the node occupying this timeline cell (format `<rowId>-<colId>`). Requires `timelineId`. Empty array if the cell is unoccupied.
478
479
  - `colId?: string` — return every node occupying this column, across *all* rows of the timeline (e.g. to check for an existing COMMAND/READMODEL/SCREEN/AUTOMATION before placing one, since the server caps those at one per column even across separate lane rows). Requires `timelineId`. Combine with `type` to narrow to one element type.
479
480
  - `timelineId?: string` — the CHAPTER node `cellId`/`colId` are resolved against. Required together with either of those two; `400` if omitted.
480
481
 
481
482
  Cell/column occupancy lives only in the CHAPTER node's `meta.timelineData.cells`, never on the node rows themselves — this endpoint resolves `cellId`/`colId` against that timeline internally so callers don't have to fetch and parse the whole chapter node just to check occupancy.
482
483
 
483
- **Response**: `200` — node record array (or `line` projections). `400` — `cellId`/`colId` given without `timelineId`, `PROJECTION_INVALID`, or `QUERY_PARAM_INVALID`. `404` — `timelineId` doesn't reference an existing CHAPTER node with grid data.
484
+ **Response**: `200` — node record array (or `line`/`fields` projections). `400` — `cellId`/`colId` given without `timelineId`, `PROJECTION_INVALID`, or `QUERY_PARAM_INVALID`. `404` — `timelineId` doesn't reference an existing CHAPTER node with grid data.
484
485
 
485
486
  ---
486
487
 
487
488
  ### POST `/api/org/:orgId/boards/:boardId/nodes/query`
488
489
  Fetch a known set of nodes in one call.
489
490
 
490
- **Body**: `{ "ids": string[], "projection"?: "line" }` — `ids` non-empty. Missing ids are silently omitted. Without `projection`, full stored records; `line` gives the compact shape above. Neither carries `cellName`.
491
+ **Body**: `{ "ids": string[], "projection"?: "line" | "fields" }` — `ids` non-empty. Missing ids are silently omitted. Without `projection`, full stored records; `line`/`fields` give the shapes above. Neither carries `cellName`.
491
492
 
492
493
  **Response**: `200` — node array. `400` — `ids` empty/not an array, or `PROJECTION_INVALID`.
493
494
 
@@ -65,11 +65,10 @@ Index the result in memory: per slice → commands, events, read models, screens
65
65
 
66
66
  The code base is the directory resolved in Step 0 (`$EVENTMODELERS_CODE_DIR`, else the current directory) — every path below is relative to it, and it may hold any stack. Find out which, don't assume.
67
67
 
68
- **With a `branch` other than the one checked out, read the code from git, never from the working tree:** `git ls-tree -r --name-only $REF [<dir>]` lists the files, `git show $REF:<path>` reads one. That applies to everything below — `.build-kit/CLAUDE.md`, the `.build-kit/.slices` snapshot, the slice folders. Never `git checkout`/`git switch`: the working tree may hold someone's uncommitted work. Without `branch`, or when it is the current branch, read the working tree as usual.
68
+ **With a `branch` other than the one checked out, read the code from git, never from the working tree:** `git ls-tree -r --name-only $REF [<dir>]` lists the files, `git show $REF:<path>` reads one. That applies to everything below — `.build-kit/CLAUDE.md`, the slice folders. Never `git checkout`/`git switch`: the working tree may hold someone's uncommitted work. Without `branch`, or when it is the current branch, read the working tree as usual.
69
69
 
70
70
  1. Read `.build-kit/CLAUDE.md` (and `.build-kit/AGENTS.md` if present) — its *Structure* section says where slices live and how they're named (e.g. `src/slices/{slice}/` for Node, `src/main/java/.../slices/{context}/{slice}/` for Axon).
71
- 2. If `.build-kit/.slices/<context>/<slice>/slice.json` exists, it is the snapshot the code was **last built from** — useful for telling "the code changed" apart from "the model changed since the build" (see Step 5).
72
- 3. Without a build kit, infer the layout from the repo (folders named after slices, `*Command*`, `*Event*`, projection/read-model files, route/UI files). If you cannot find any slice structure, stop and tell the user what you looked at — do not fabricate a mapping.
71
+ 2. Without a build kit, infer the layout from the repo (folders named after slices, `*Command*`, `*Event*`, projection/read-model files, route/UI files). If you cannot find any slice structure, stop and tell the user what you looked at — do not fabricate a mapping.
73
72
 
74
73
  Build a **code inventory** per slice folder: the command type and its fields, the events it emits and their fields, read-model/projection types and fields, the screens/components/routes wired to it, tests and what they assert, and what each handler/projection/automation reads and writes.
75
74
 
@@ -109,9 +108,7 @@ For every finding, pick who should move:
109
108
 
110
109
  | Evidence | Fix goes to | How it's done |
111
110
  |----------|-------------|---------------|
112
- | Code differs from `.build-kit/.slices` snapshot (read from the same branch as the code), board still equals the snapshot | **Model** — the code moved on deliberately | Skill that changes the board: `/attributes` (fields), `/timeline` / `/place-element` (elements), `/eventmodeling-elaborating-scenarios` (specs), `/storyboard-screen` or `/html-screen` (screens — including updating every existing screen connected to a changed element, see below) |
113
- | Board differs from the snapshot, code still equals it | **Code** — the model moved on | Set the slice back to `Planned` with `/update-slice-status` so the build kit picks it up again and diffs code against `slice.json` |
114
- | Both moved, or no snapshot | **Judgement call** — present both options, recommend one | Recommend by which side is more specific/complete |
111
+ | A field, element, spec, screen or edge differs | **Judgement call** — present both options, recommend one | Recommend by which side is more specific/complete. Model fix: `/attributes` (fields), `/timeline` / `/place-element` (elements), `/eventmodeling-elaborating-scenarios` (specs), `/storyboard-screen` or `/html-screen` (screens — including updating every existing screen connected to a changed element, see below). Code fix: set the slice back to `Planned` with `/update-slice-status` so the build kit picks it up again |
115
112
  | Slice only in code | **Model** | Add the slice, via `/eventmodeling-slicing-event-models` after placing its elements |
116
113
  | Slice missing in code, board says `Done` | **Model status** (it isn't done) | `/update-slice-status` → `Planned` |
117
114
 
@@ -190,3 +187,4 @@ Omit empty sections. Say plainly when nothing drifted.
190
187
  - Fixing drift on the model side means updating the existing screens connected to the changed elements too (pages and `meta.fields`), in place — not just the command/event/read model.
191
188
  - `--branch` reads the code via `git ls-tree`/`git show` from that ref — never check it out, the working tree may hold someone's uncommitted work. No `--branch` = the working tree, as before.
192
189
  - Always read the MARKDOWN note in the feedback lane of each chapter's first column first — it is the chapter's context, and differences it explains are not drift.
190
+ - `.build-kit/.slices` is no evidence for the direction of a fix: every fetch, `/load-slice` and runner loop rewrites it, so it holds the last fetch, not what the code was built from — reading it as "the build" recommends undoing board changes. The direction is a judgement call.
@@ -235,7 +235,8 @@ mcp__eventmodelers__submit_node_events {
235
235
  "changedAttributes": ["meta.title"],
236
236
  "meta": { "type": "CHAPTER", "title": "<flow name>" },
237
237
  "node": { "id": "<CHAPTER_ID>", "data": {} }
238
- }]
238
+ }],
239
+ "compact": true
239
240
  }
240
241
  ```
241
242
 
@@ -116,7 +116,8 @@ mcp__eventmodelers__submit_node_events {
116
116
  "eventType": "node:changed",
117
117
  "nodeId": "<chapterId>",
118
118
  "meta": {"type": "CHAPTER", "title": "Reservation & Lending"}
119
- }]
119
+ }],
120
+ "compact": true
120
121
  }
121
122
  ```
122
123
 
@@ -219,7 +220,8 @@ mcp__eventmodelers__submit_node_events {
219
220
  {"name": "reservedAt", "type": "DateTime", "example": "2026-05-29T10:00:00Z"}
220
221
  ]
221
222
  }
222
- }]
223
+ }],
224
+ "compact": true
223
225
  }
224
226
  ```
225
227
 
@@ -88,7 +88,8 @@ mcp__eventmodelers__place_element {
88
88
  "elementType": "READMODEL",
89
89
  "title": "NotificationsToSend",
90
90
  "columnIndex": <automationColumnIndex - 1>
91
- }]
91
+ }],
92
+ "compact": true
92
93
  }
93
94
  ```
94
95
  Then set `meta.fields` and `meta.listElement: true` on the returned node id:
@@ -98,7 +99,8 @@ mcp__eventmodelers__submit_node_events {
98
99
  "events": [{
99
100
  "id": "<event-uuid>", "eventType": "node:changed", "nodeId": "<returned-node-id>",
100
101
  "meta": {"type": "READMODEL", "title": "NotificationsToSend", "fields": [...], "listElement": true}
101
- }]
102
+ }],
103
+ "compact": true
102
104
  }
103
105
  ```
104
106
 
@@ -114,11 +116,11 @@ For a **translation-chain automation**, place its three columns left to right in
114
116
 
115
117
  1. **READMODEL → AUTOMATION first** — the automation reads its own todo list. The closing connection below is only accepted once this edge exists.
116
118
  ```
117
- mcp__eventmodelers__set_connection { "boardId": "<BOARD_ID>", "connections": [{ "source": "<readmodelNodeId>", "target": "<automationNodeId>", "action": "connect" }] }
119
+ mcp__eventmodelers__set_connection { "boardId": "<BOARD_ID>", "connections": [{ "source": "<readmodelNodeId>", "target": "<automationNodeId>", "action": "connect" }], "compact": true }
118
120
  ```
119
121
  2. **Every opening EVENT → READMODEL.**
120
122
  ```
121
- mcp__eventmodelers__set_connection { "boardId": "<BOARD_ID>", "connections": [{ "source": "<openingEventNodeId>", "target": "<readmodelNodeId>", "action": "connect" }] }
123
+ mcp__eventmodelers__set_connection { "boardId": "<BOARD_ID>", "connections": [{ "source": "<openingEventNodeId>", "target": "<readmodelNodeId>", "action": "connect" }], "compact": true }
122
124
  ```
123
125
  3. **Every closing EVENT → READMODEL — worker-stage todo lists only.** Including the automation's own resulting event, even though that event is produced by the command this same automation issues. This is not a backward arrow: `EVENT → READMODEL` connections are exempt from column ordering when the read model already has a `READMODEL → AUTOMATION` edge (see `learn-eventmodelers-api` §3) — this todo-list read model qualifies because of the edge from step 1 above. A read model in this shape is a live projection, not a frozen snapshot — a later event closing an earlier-opened item is the normal case, not an exception to reach for only when convenient. **Do not add this edge for a translation automation's todo list** — it has no closing event at all (see the translation-chain rule above); wiring one back is a modeling error, not a convenience.
124
126
 
@@ -151,7 +151,8 @@ mcp__eventmodelers__place_element {
151
151
  "elements": [{
152
152
  "elementType": "COMMAND",
153
153
  "title": "ReserveBike"
154
- }]
154
+ }],
155
+ "compact": true
155
156
  }
156
157
  ```
157
158
  `place_element` finds/creates the empty cell in the interaction lane in the correct column and places the node. Pass each entry's `fields` in the same call when the attributes are already known; otherwise follow up with `submit_node_events` (`node:changed`) to set `meta.fields` (with `mapping`/`generated`/`cardinality` per the rules above) on the node it returned:
@@ -171,7 +172,8 @@ mcp__eventmodelers__submit_node_events {
171
172
  {"name": "startTime", "type": "DateTime", "example": "2026-06-01T09:00:00Z","mapping": "user-input"}
172
173
  ]
173
174
  }
174
- }]
175
+ }],
176
+ "compact": true
175
177
  }
176
178
  ```
177
179
 
@@ -203,7 +205,7 @@ After `place-element` returns the COMMAND node ID, create the arrows that comple
203
205
  ```
204
206
  Read `cells["<actorRowId>-<columnId>"]` for the occupying node id (a cell id absent from that sparse array is empty — no SCREEN placed yet). Then connect with the type-checked edge tool, which auto-corrects direction and skips duplicates:
205
207
  ```
206
- mcp__eventmodelers__set_connection { "boardId": "<BOARD_ID>", "connections": [{ "source": "<screenNodeId>", "target": "<commandNodeId>", "action": "connect" }] }
208
+ mcp__eventmodelers__set_connection { "boardId": "<BOARD_ID>", "connections": [{ "source": "<screenNodeId>", "target": "<commandNodeId>", "action": "connect" }], "compact": true }
207
209
  ```
208
210
  If wiring more than one COMMAND in the same pass, put every SCREEN→COMMAND and COMMAND→EVENT pair across all of them into one `set_connection` call's `connections` array (see step 2 below) instead of one call per pair.
209
211
 
@@ -219,7 +221,7 @@ After `place-element` returns the COMMAND node ID, create the arrows that comple
219
221
  ```
220
222
  Read `cells["<swimlaneRowId>-<columnId>"]` for the occupying node id, then:
221
223
  ```
222
- mcp__eventmodelers__set_connection { "boardId": "<BOARD_ID>", "connections": [{ "source": "<commandNodeId>", "target": "<eventNodeId>", "action": "connect" }] }
224
+ mcp__eventmodelers__set_connection { "boardId": "<BOARD_ID>", "connections": [{ "source": "<commandNodeId>", "target": "<eventNodeId>", "action": "connect" }], "compact": true }
223
225
  ```
224
226
 
225
227
  **Fallback (no MCP):** see `references/api-fallback.md` — "Wire connections — Step 2: COMMAND → EVENT".
@@ -260,7 +260,8 @@ mcp__eventmodelers__place_element {
260
260
  "elementType": "READMODEL",
261
261
  "title": "ActiveReservationView",
262
262
  "columnIndex": <consumerScreenColumnIndex> // or <automationColumnIndex - 1> for an AUTOMATION consumer
263
- }]
263
+ }],
264
+ "compact": true
264
265
  }
265
266
  ```
266
267
  Then set `meta.fields` (with `mapping`/`generated`/`cardinality`) — and `meta.listElement: true` if this read model is list-shaped — on the returned node id:
@@ -270,7 +271,8 @@ mcp__eventmodelers__submit_node_events {
270
271
  "events": [{
271
272
  "id": "<event-uuid>", "eventType": "node:changed", "nodeId": "<returned-node-id>",
272
273
  "meta": {"type": "READMODEL", "title": "ActiveReservationView", "fields": [...]}
273
- }]
274
+ }],
275
+ "compact": true
274
276
  }
275
277
  ```
276
278
  To determine the consumer's column index beforehand, or to check whether a specific interaction cell is already occupied (`get_nodes` filters by `type`, `name`, `chapterId` and `nodeIds`, but has no `cellId` filter — see the note under "Wire connections" below), fetch the chapter and read its cell map — `projection: "cells"` returns just `{rows, columns, cells}`, not the whole chapter node:
@@ -327,7 +329,7 @@ After `place-element` returns the READMODEL node ID, create the arrows that comp
327
329
  ```
328
330
  mcp__eventmodelers__get_node { "boardId": "<BOARD_ID>", "nodeId": "<CHAPTER_ID>", "projection": "cells" }
329
331
  # → read cells["<swimlaneRowId>-<columnId>"] for the occupying node id
330
- mcp__eventmodelers__set_connection { "boardId": "<BOARD_ID>", "connections": [{ "source": "<eventNodeId>", "target": "<readmodelNodeId>", "action": "connect" }] }
332
+ mcp__eventmodelers__set_connection { "boardId": "<BOARD_ID>", "connections": [{ "source": "<eventNodeId>", "target": "<readmodelNodeId>", "action": "connect" }], "compact": true }
331
333
  ```
332
334
 
333
335
  **Fallback (no MCP):** see `references/api-fallback.md` — "Step 5h.1 — Wire EVENT → READMODEL".
@@ -336,7 +338,7 @@ After `place-element` returns the READMODEL node ID, create the arrows that comp
336
338
 
337
339
  **Prefer MCP:**
338
340
  ```
339
- mcp__eventmodelers__set_connection { "boardId": "<BOARD_ID>", "connections": [{ "source": "<readmodelNodeId>", "target": "<screenNodeId>", "action": "connect" }] }
341
+ mcp__eventmodelers__set_connection { "boardId": "<BOARD_ID>", "connections": [{ "source": "<readmodelNodeId>", "target": "<screenNodeId>", "action": "connect" }], "compact": true }
340
342
  ```
341
343
 
342
344
  **Fallback (no MCP):** see `references/api-fallback.md` — "Step 5h.2 — Wire READMODEL → SCREEN".
@@ -347,7 +349,7 @@ After `place-element` returns the READMODEL node ID, create the arrows that comp
347
349
 
348
350
  **Prefer MCP:**
349
351
  ```
350
- mcp__eventmodelers__set_connection { "boardId": "<BOARD_ID>", "connections": [{ "source": "<readmodelNodeId>", "target": "<automationNodeId>", "action": "connect" }] }
352
+ mcp__eventmodelers__set_connection { "boardId": "<BOARD_ID>", "connections": [{ "source": "<readmodelNodeId>", "target": "<automationNodeId>", "action": "connect" }], "compact": true }
351
353
  ```
352
354
 
353
355
  **Fallback (no MCP):** see `references/api-fallback.md` — "Step 5h.3 — Wire READMODEL → AUTOMATION".
@@ -163,7 +163,7 @@ For a "what is on the board and how is it wired right now" check between steps
163
163
 
164
164
  **Structural validation is one call, not a scan.** `validate_model` (`{boardId, chapterId}`) runs the whole structural checklist server-side — unplaced nodes, backward arrows (todo-list exception applied), zero/multi-issuer commands, sourceless read models, two-screens-in-a-column, missing scenarios — and returns only `findings`. Use it for the mandatory post-step unplaced check and as the first move in Step 9, instead of per-type `get_nodes` loops and `get_node` `projection: "edges"` spot-checks.
165
165
 
166
- **Ask echo-heavy write tools for less.** `add_scenario`, `add_storyline`, `set_connection` and `submit_node_events` each accept `compact: true`, which drops the full-object echo from the response (returning `{specNodeId, added, count, isNewNode}`, a `{connected, existed, removed, notFound, failed, errors}` tally, or `{persisted: <count>}` respectively). Pass it whenever you're not going to read individual fields back off the response — which is almost always for a large `set_connection` batch or a bulk scenario post.
166
+ **Ask echo-heavy write tools for less.** `place_element`, `add_scenario`, `add_storyline`, `set_connection` and `submit_node_events` each accept `compact: true`, which drops the full-object echo from the response (returning `{nodeId, cellName, columnIndex}` per placed node, `{specNodeId, added, count, isNewNode}`, a `{connected, existed, removed, notFound, failed, errors}` tally, or `{persisted: <count>}` respectively). Pass it by default — every example in these skills does — and drop it only when a later step reads a field the compact form leaves out (e.g. `place_element`'s `autoConnected` edge list). Failures are still reported in full either way.
167
167
 
168
168
  ### Documenting decisions inline, at any step
169
169
 
@@ -440,7 +440,8 @@ Not delegated to a separate skill — performed directly by this orchestrating s
440
440
  "id": "<event-uuid>", "eventType": "node:created", "nodeId": "<node-uuid>",
441
441
  "chapterId": "$CHAPTER_ID", "cellId": "<feedbackLaneId>-<firstColumnId>",
442
442
  "meta": { "type": "MARKDOWN", "title": "Modeling Reasoning — <Chapter Name>", "description": "<full markdown body>" }
443
- }]
443
+ }],
444
+ "compact": true
444
445
  }
445
446
  ```
446
447
 
@@ -263,7 +263,8 @@ mcp__eventmodelers__submit_node_events {
263
263
  "chapterId": "<CHAPTER_ID>",
264
264
  "cellId": "<actorRowId>-<columnId>",
265
265
  "meta": {"type": "SCREEN", "title": "<Screen Title>", "fields": [...]}
266
- }]
266
+ }],
267
+ "compact": true
267
268
  }
268
269
  ```
269
270
 
@@ -37,7 +37,7 @@ with a `confirm` snippet — `CLAUDE.md` § *Locked slices in a chat* — and po
37
37
 
38
38
  `target` is singular, but the common real request is "fill in the examples across this chapter". When you have more than one target, **do not run this skill once per element and do not fetch context a node at a time.**
39
39
 
40
- 1. Read the chapter **once**: `mcp__eventmodelers__get_nodes { "boardId": "$BOARD_ID", "chapterId": "$CHAPTER_ID" }`. That single response carries every element's `meta.fields` *including the `example` values already filled in* — which is exactly the canonical-value pool Step 3c asks for, for the whole chapter, in one call.
40
+ 1. Read the chapter **once**: `mcp__eventmodelers__get_nodes { "boardId": "$BOARD_ID", "chapterId": "$CHAPTER_ID", "projection": "fields" }`. That single response carries every element's `fields` *including the `example` values already filled in*, without the html, descriptions and grid data a full read drags along — which is exactly the canonical-value pool Step 3c asks for, for the whole chapter, in one call.
41
41
  2. Pick the canonical value per field name from that pool (e.g. `customerId: "cust-123"`, `email: "jane@example.com"`) before writing anything, so every element ends up consistent.
42
42
  3. Then generate every target's values and write them all in a single `submit_node_events { events: [...] }` call — one `node:changed` event per element, never one call per element.
43
43
 
@@ -105,7 +105,8 @@ mcp__eventmodelers__submit_node_events {
105
105
  "nodeId": "<TARGET_NODE.id>",
106
106
  "changedAttributes": ["meta.fields"],
107
107
  "meta": { "fields": "<updated-fields-array>" }
108
- }]
108
+ }],
109
+ "compact": true
109
110
  }
110
111
  ```
111
112
 
@@ -105,7 +105,8 @@ mcp__eventmodelers__submit_node_events {
105
105
  "marks": [{"id": "em-abc123", "color": "#e74c3c", "pageIndex": 0, "blurOutside": true, "whiteOutside": false}],
106
106
  "fields": [...]
107
107
  }
108
- }]
108
+ }],
109
+ "compact": true
109
110
  }
110
111
  ```
111
112
 
@@ -181,7 +182,8 @@ mcp__eventmodelers__submit_node_events {
181
182
  "meta": { "type": "HTML_SCREEN", "fields": [
182
183
  {"name": "status", "type": "String", "example": "confirmed", "mapping": "ActiveReservationView.status", "cardinality": "Single"}
183
184
  ] }
184
- }]
185
+ }],
186
+ "compact": true
185
187
  }
186
188
  ```
187
189
 
@@ -50,7 +50,8 @@ mcp__eventmodelers__place_element {
50
50
  "columnIndex": <position,
51
51
  if given>,
52
52
  "fields": [{ "name": "orderId", "type": "String", "example": "ord-1" }]
53
- }]
53
+ }],
54
+ "compact": true
54
55
  }
55
56
  ```
56
57
 
@@ -132,7 +133,8 @@ mcp__eventmodelers__add_scenario {
132
133
  "boardId": "<BOARD_ID>",
133
134
  "timelineId": "<TL>",
134
135
  "columnId": "<COL>",
135
- "scenarios": [ /* same scenario objects as the curl body below */ ]
136
+ "scenarios": [ /* same scenario objects as the curl body below */ ],
137
+ "compact": true
136
138
  }
137
139
  ```
138
140
 
@@ -335,7 +337,8 @@ mcp__eventmodelers__submit_node_events {
335
337
  "cellId": "<CELL_ID>",
336
338
  "meta": { "type": "<ELEMENT_TYPE>", "title": "<title>" },
337
339
  "node": { "data": { "title": "<title>" } }
338
- }]
340
+ }],
341
+ "compact": true
339
342
  }
340
343
  ```
341
344
 
@@ -356,7 +359,8 @@ mcp__eventmodelers__submit_node_events {
356
359
  "cellName": "<CELL_NAME>",
357
360
  "meta": { "type": "<ELEMENT_TYPE>", "title": "<title>" },
358
361
  "node": { "data": { "title": "<title>" } }
359
- }]
362
+ }],
363
+ "compact": true
360
364
  }
361
365
  ```
362
366
 
@@ -385,7 +389,7 @@ Take the inbound edges (`target === COMMAND_NODE_ID`) and resolve their source t
385
389
  - **2 or more** → keep the edge whose source sits in the COMMAND's own column (the deliberate, same-slice issuer) and remove every other one:
386
390
 
387
391
  ```
388
- mcp__eventmodelers__set_connection { "boardId": "<BOARD_ID>", "connections": [{ "source": "<extra-issuer-node-id>", "target": "<COMMAND_NODE_ID>", "action": "remove" }] }
392
+ mcp__eventmodelers__set_connection { "boardId": "<BOARD_ID>", "connections": [{ "source": "<extra-issuer-node-id>", "target": "<COMMAND_NODE_ID>", "action": "remove" }], "compact": true }
389
393
  ```
390
394
 
391
395
  If it's not clear which edge is the deliberate one (e.g. neither source sits in the COMMAND's own column), do not guess — leave both edges and post a comment on the COMMAND node via `handle-comment` instead, describing the ambiguity.
@@ -416,4 +420,4 @@ Timeline: <timelineId>
416
420
 
417
421
  ## Example — place an EVENT via curl
418
422
 
419
- A full worked example (add column → fetch chapter → create node) placing an EVENT called "Order Placed" at the end of a timeline lives in `references/api-fallback.md`'s "Full worked example" section. With MCP connected, the same result is one call: `mcp__eventmodelers__place_element { "boardId": "<BOARD_ID>", "timelineId": "<TIMELINE_ID>", "elements": [{ "elementType": "EVENT", "title": "Order Placed" }] }` (see "Prefer MCP" above).
423
+ A full worked example (add column → fetch chapter → create node) placing an EVENT called "Order Placed" at the end of a timeline lives in `references/api-fallback.md`'s "Full worked example" section. With MCP connected, the same result is one call: `mcp__eventmodelers__place_element { "boardId": "<BOARD_ID>", "timelineId": "<TIMELINE_ID>", "elements": [{ "elementType": "EVENT", "title": "Order Placed" }], "compact": true }` (see "Prefer MCP" above).
@@ -230,7 +230,8 @@ mcp__eventmodelers__submit_node_events {
230
230
  "id": "<event-uuid>", "eventType": "node:changed", "nodeId": "<SCREEN_NODE_ID>",
231
231
  "changedAttributes": ["meta.fields"],
232
232
  "meta": { "type": "HTML_SCREEN", "fields": [ /* planned fields */ ] }
233
- }]
233
+ }],
234
+ "compact": true
234
235
  }
235
236
  ```
236
237
 
@@ -126,7 +126,8 @@ mcp__eventmodelers__submit_node_events {
126
126
  "meta": { "type": "SCREEN", "fields": [
127
127
  {"name": "status", "type": "String", "example": "confirmed", "mapping": "ActiveReservationView.status", "cardinality": "Single"}
128
128
  ] }
129
- }]
129
+ }],
130
+ "compact": true
130
131
  }
131
132
  ```
132
133
 
@@ -290,7 +290,8 @@ mcp__eventmodelers__submit_node_events {
290
290
  "cellId": "<CELL_ID>",
291
291
  "meta": { "type": "EVENT", "title": "<EventName>" },
292
292
  "node": { "id": "<node-uuid>", "data": { "title": "<EventName>" } }
293
- }]
293
+ }],
294
+ "compact": true
294
295
  }
295
296
  ```
296
297
 
@@ -300,7 +301,7 @@ mcp__eventmodelers__submit_node_events {
300
301
 
301
302
  **Prefer MCP:** this whole "find/create a column, compute the cell, place the node" sequence collapses into one call:
302
303
  ```
303
- mcp__eventmodelers__place_element { "boardId": "<BOARD_ID>", "timelineId": "<CHAPTER_ID>", "elements": [{ "elementType": "EVENT", "title": "<EventName>", "columnIndex": <index> }] }
304
+ mcp__eventmodelers__place_element { "boardId": "<BOARD_ID>", "timelineId": "<CHAPTER_ID>", "elements": [{ "elementType": "EVENT", "title": "<EventName>", "columnIndex": <index> }], "compact": true }
304
305
  ```
305
306
  Extract `nodeId` and `columnId` directly from the tool result.
306
307
 
@@ -343,7 +344,8 @@ mcp__eventmodelers__submit_node_events {
343
344
  "changedAttributes": ["meta.title"],
344
345
  "meta": { "type": "EVENT", "title": "<NewTitle>" },
345
346
  "node": { "id": "<eventNodeId>", "data": {} }
346
- }]
347
+ }],
348
+ "compact": true
347
349
  }
348
350
  ```
349
351
 
@@ -111,7 +111,7 @@ Per step:
111
111
  mcp__eventmodelers__post_chat_message {
112
112
  "boardId": "$BOARD_ID",
113
113
  "replyTo": "<message_id>",
114
- "text": "Registration in 5 steps — pick a step to jump to it, or press Play.",
114
+ "text": "Registration in 5 steps — step through it here, or press Play.",
115
115
  "snippet": {
116
116
  "kind": "walkthrough",
117
117
  "title": "How registration works",