@eventmodelers/cli 1.0.90 → 1.0.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.
Files changed (59) hide show
  1. package/README.md +2 -0
  2. package/cli.js +26 -2
  3. package/lib/update-check.js +67 -0
  4. package/package.json +4 -1
  5. package/shared/build-kit/lib/adapters/pocketbase-realtime-adapter.js +11 -3
  6. package/shared/build-kit/lib/adapters/realtime-adapter.js +3 -1
  7. package/shared/build-kit/lib/adapters/supabase-realtime-adapter.js +14 -3
  8. package/shared/build-kit/lib/ralph.js +136 -59
  9. package/shared/build-kit/lib/redact.js +36 -0
  10. package/shared/build-kit/lib/turn.js +56 -20
  11. package/shared/build-kit/ralph-claude.js +7 -19
  12. package/shared/build-kit/ralph-exec.js +5 -9
  13. package/shared/build-kit/realtime-agent.js +4 -2
  14. package/shared/skills/connect/SKILL.md +14 -9
  15. package/shared/skills/learn-eventmodelers-api/SKILL.md +60 -21
  16. package/shared/skills/load-slice/SKILL.md +3 -1
  17. package/stacks/modeling-kit/templates/.claude/skills/add-next-slice/SKILL.md +4 -4
  18. package/stacks/modeling-kit/templates/.claude/skills/analyze-existing-model/SKILL.md +18 -9
  19. package/stacks/modeling-kit/templates/.claude/skills/analyze-existing-model/references/api-fallback.md +31 -11
  20. package/stacks/modeling-kit/templates/.claude/skills/attributes/SKILL.md +16 -13
  21. package/stacks/modeling-kit/templates/.claude/skills/attributes/references/api-fallback.md +3 -1
  22. package/stacks/modeling-kit/templates/.claude/skills/discover-storyboard/SKILL.md +6 -6
  23. package/stacks/modeling-kit/templates/.claude/skills/discover-storyboard/references/api-fallback.md +3 -1
  24. package/stacks/modeling-kit/templates/.claude/skills/eventmodeling-brainstorming-events/SKILL.md +4 -4
  25. package/stacks/modeling-kit/templates/.claude/skills/eventmodeling-brainstorming-events/references/api-fallback.md +4 -4
  26. package/stacks/modeling-kit/templates/.claude/skills/eventmodeling-checking-completeness/SKILL.md +8 -12
  27. package/stacks/modeling-kit/templates/.claude/skills/eventmodeling-checking-completeness/references/api-fallback.md +16 -6
  28. package/stacks/modeling-kit/templates/.claude/skills/eventmodeling-elaborating-scenarios/SKILL.md +3 -3
  29. package/stacks/modeling-kit/templates/.claude/skills/eventmodeling-elaborating-scenarios/references/api-fallback.md +1 -1
  30. package/stacks/modeling-kit/templates/.claude/skills/eventmodeling-identifying-inputs/SKILL.md +2 -2
  31. package/stacks/modeling-kit/templates/.claude/skills/eventmodeling-identifying-inputs/references/api-fallback.md +4 -4
  32. package/stacks/modeling-kit/templates/.claude/skills/eventmodeling-identifying-outputs/SKILL.md +4 -4
  33. package/stacks/modeling-kit/templates/.claude/skills/eventmodeling-identifying-outputs/references/api-fallback.md +4 -4
  34. package/stacks/modeling-kit/templates/.claude/skills/eventmodeling-orchestrating-event-modeling/SKILL.md +3 -3
  35. package/stacks/modeling-kit/templates/.claude/skills/eventmodeling-orchestrating-event-modeling/references/api-fallback.md +3 -1
  36. package/stacks/modeling-kit/templates/.claude/skills/eventmodeling-plotting-events/SKILL.md +2 -2
  37. package/stacks/modeling-kit/templates/.claude/skills/eventmodeling-plotting-events/references/api-fallback.md +1 -1
  38. package/stacks/modeling-kit/templates/.claude/skills/eventmodeling-slicing-event-models/SKILL.md +3 -3
  39. package/stacks/modeling-kit/templates/.claude/skills/eventmodeling-slicing-event-models/references/api-fallback.md +12 -4
  40. package/stacks/modeling-kit/templates/.claude/skills/eventmodeling-storyboarding-events/SKILL.md +7 -6
  41. package/stacks/modeling-kit/templates/.claude/skills/eventmodeling-storyboarding-events/references/api-fallback.md +4 -3
  42. package/stacks/modeling-kit/templates/.claude/skills/eventmodeling-validating-event-models/SKILL.md +22 -6
  43. package/stacks/modeling-kit/templates/.claude/skills/eventmodeling-validating-event-models/references/api-fallback.md +12 -3
  44. package/stacks/modeling-kit/templates/.claude/skills/eventmodeling-validating-event-models-checklist/SKILL.md +18 -6
  45. package/stacks/modeling-kit/templates/.claude/skills/eventmodeling-validating-event-models-checklist/references/api-fallback.md +12 -3
  46. package/stacks/modeling-kit/templates/.claude/skills/examples/SKILL.md +2 -2
  47. package/stacks/modeling-kit/templates/.claude/skills/examples/references/api-fallback.md +20 -14
  48. package/stacks/modeling-kit/templates/.claude/skills/place-element/SKILL.md +12 -12
  49. package/stacks/modeling-kit/templates/.claude/skills/place-element/references/api-fallback.md +6 -4
  50. package/stacks/modeling-kit/templates/.claude/skills/storyboard/SKILL.md +4 -4
  51. package/stacks/modeling-kit/templates/.claude/skills/storyboard/references/api-fallback.md +2 -2
  52. package/stacks/modeling-kit/templates/.claude/skills/timeline/SKILL.md +10 -10
  53. package/stacks/modeling-kit/templates/.claude/skills/timeline/references/api-fallback.md +4 -4
  54. package/stacks/modeling-kit/templates/.claude/skills/wdyt/SKILL.md +11 -24
  55. package/stacks/modeling-kit/templates/kit/AGENTS.md +3 -2
  56. package/stacks/modeling-kit/templates/kit/CLAUDE-STANDALONE.md +6 -4
  57. package/stacks/modeling-kit/templates/kit/CLAUDE.md +9 -4
  58. package/stacks/react/templates/build-kit/lib/backend-prompt.md +106 -102
  59. package/stacks/react/templates/build-kit/lib/prompt.md +102 -106
@@ -45,30 +45,37 @@ Save the full slice list. Count total slices and group by status:
45
45
 
46
46
  ## Step 3 — Discover contexts
47
47
 
48
- Fetch all `MODEL_CONTEXT` nodes to identify bounded contexts on the board:
48
+ Fetch the `MODEL_CONTEXT` nodes **and** the timelines — a timeline with no assigned or connected context is its own context (named after the timeline), so the MODEL_CONTEXT nodes alone miss it. Only ids and titles are needed, so use the `line` projection:
49
49
 
50
50
  **Prefer MCP:**
51
51
  ```
52
- mcp__eventmodelers__get_nodes { "boardId": "$BOARD_ID", "type": "MODEL_CONTEXT" }
52
+ mcp__eventmodelers__get_nodes { "boardId": "$BOARD_ID", "type": "MODEL_CONTEXT", "projection": "line" }
53
+ mcp__eventmodelers__get_nodes { "boardId": "$BOARD_ID", "type": "CHAPTER", "projection": "line" }
53
54
  ```
54
55
 
55
56
  **Fallback (no MCP):** see `references/api-fallback.md` — "Step 3 — Discover contexts".
56
57
 
57
58
  - If a `contextName` argument was given, filter to that single context and skip others.
58
- - If no `MODEL_CONTEXT` nodes exist, continue with a single unnamed context scope.
59
- - Record each context's `id` and `title`.
59
+ - Record each MODEL_CONTEXT's `id` and `title`, and each timeline's `id` and `title`.
60
+ - Step 4 fetches every MODEL_CONTEXT by title first. Each slice in a response carries its `chapter`; every timeline never named there is its own context (or holds no slices) — fetch it too, passing the timeline's title as `contextName`. With no MODEL_CONTEXT nodes at all, that is every timeline.
60
61
 
61
62
  ---
62
63
 
63
64
  ## Step 4 — Fetch slice data per context
64
65
 
65
- For each resolved context, fetch the full element graph:
66
+ For each resolved context, fetch two trimmed projections instead of the full graph — this analysis never needs comments, codegen prompts, notes or screen images:
66
67
 
67
68
  **Prefer MCP:**
68
69
  ```
69
- mcp__eventmodelers__get_slice_data { "boardId": "$BOARD_ID", "contextName": "<CONTEXT_NAME>" }
70
+ mcp__eventmodelers__get_slice_data { "boardId": "$BOARD_ID", "contextName": "<CONTEXT_NAME>", "projection": "fields", "format": "toon" }
71
+ mcp__eventmodelers__get_slice_data { "boardId": "$BOARD_ID", "contextName": "<CONTEXT_NAME>", "projection": "specs", "format": "toon" }
70
72
  ```
71
73
 
74
+ - `fields` — slice header (id, title, status, sliceType, chapter, context) plus every element with its fields and **dependencies**. Covers 5a (inventory), 5b (status), 5d (gaps) and 5f (structural shapes — the bed needs the SCREEN → COMMAND dependencies).
75
+ - `specs` — slice header plus specifications (given/when/then) and storylines, no elements. Covers 5c (spec coverage).
76
+ - If the user only asked for counts/status/coverage (no gap or shape check), use `"projection": "outline"` instead of `fields` — elements as `{id, title, type}` only, the cheapest read.
77
+ - `outline` and `specs` only encode as `json`, `yaml` or `toon` (`textual`/`emlang`/`esdm` return `PROJECTION_FORMAT_UNSUPPORTED`); use `toon` (token-efficient) or `json`.
78
+
72
79
  **Fallback (no MCP):** see `references/api-fallback.md` — "Step 4 — Fetch slice data per context".
73
80
 
74
81
  Fetch all contexts in parallel if there are multiple. Merge results, keyed by context name.
@@ -97,7 +104,7 @@ Group slices by status. Report counts per status. Flag any status that suggests
97
104
 
98
105
  ### 5c — Spec coverage
99
106
 
100
- For each slice, check whether `specs` is non-empty. Calculate:
107
+ For each slice in the `specs` projection, check whether its specifications are non-empty. Calculate:
101
108
  - Slices **with** at least one GWT scenario
102
109
  - Slices **without** any scenarios (grouped by slice type if detectable)
103
110
 
@@ -201,8 +208,10 @@ If a context was specified but not found, tell the user clearly and list the con
201
208
  **Prefer MCP:**
202
209
  ```
203
210
  mcp__eventmodelers__list_slices { "boardId": "$BOARD_ID" }
204
- mcp__eventmodelers__get_nodes { "boardId": "$BOARD_ID", "type": "MODEL_CONTEXT" }
205
- mcp__eventmodelers__get_slice_data { "boardId": "$BOARD_ID", "contextName": "Ordering" }
211
+ mcp__eventmodelers__get_nodes { "boardId": "$BOARD_ID", "type": "MODEL_CONTEXT", "projection": "line" }
212
+ mcp__eventmodelers__get_nodes { "boardId": "$BOARD_ID", "type": "CHAPTER", "projection": "line" }
213
+ mcp__eventmodelers__get_slice_data { "boardId": "$BOARD_ID", "contextName": "Ordering", "projection": "fields", "format": "toon" }
214
+ mcp__eventmodelers__get_slice_data { "boardId": "$BOARD_ID", "contextName": "Ordering", "projection": "specs", "format": "toon" }
206
215
  ```
207
216
 
208
217
  **Fallback (no MCP):** see `references/api-fallback.md` — "Example — full board analysis".
@@ -19,23 +19,35 @@ Response: `{ "slices": [{ "id": "<uuid>", "title": "<name>", "status": "<status>
19
19
  curl -s \
20
20
  -H "x-token: $TOKEN" \
21
21
  -H "x-user-id: analyze-existing-model" \
22
- "$BASE_URL/api/org/$ORG_ID/boards/$BOARD_ID/nodes?type=MODEL_CONTEXT"
22
+ "$BASE_URL/api/org/$ORG_ID/boards/$BOARD_ID/nodes?type=MODEL_CONTEXT&projection=line"
23
+
24
+ # Timelines too — one without a context is its own context, named after the timeline
25
+ curl -s \
26
+ -H "x-token: $TOKEN" \
27
+ -H "x-user-id: analyze-existing-model" \
28
+ "$BASE_URL/api/org/$ORG_ID/boards/$BOARD_ID/nodes?type=CHAPTER&projection=line"
23
29
  ```
24
30
 
31
+ Returns `{id, type, title}` per node — all this step needs.
32
+
25
33
  ## Step 4 — Fetch slice data per context
26
34
 
27
35
  ```bash
28
36
  curl -s \
29
37
  -H "x-token: $TOKEN" \
30
38
  -H "x-user-id: analyze-existing-model" \
31
- "$BASE_URL/api/org/$ORG_ID/boards/$BOARD_ID/slicedata?contextName=<CONTEXT_NAME>"
39
+ "$BASE_URL/api/org/$ORG_ID/boards/$BOARD_ID/slicedata?contextName=<CONTEXT_NAME>&projection=fields&format=toon"
40
+
41
+ curl -s \
42
+ -H "x-token: $TOKEN" \
43
+ -H "x-user-id: analyze-existing-model" \
44
+ "$BASE_URL/api/org/$ORG_ID/boards/$BOARD_ID/slicedata?contextName=<CONTEXT_NAME>&projection=specs&format=toon"
32
45
  ```
33
46
 
34
- Each response contains a `slices` array. Each slice entry includes:
35
- - `id`, `title`, `status`
36
- - `elements`: array of `{ type, id, title, fields[] }` — element types: `EVENT`, `COMMAND`, `READMODEL`, `SCREEN`, `AUTOMATION`
37
- - `specs`: array of GWT scenarios (may be empty)
38
- - `edges`: relationships between elements
47
+ - `projection=fields` — slice header (id, title, status, sliceType, chapter, context) + elements (commands, events, readmodels, screens, processors, tables) with their fields and dependencies. No specs/comments.
48
+ - `projection=specs` — slice header + specifications (given/when/then) and storylines. No elements.
49
+ - `projection=outline` — slice header + elements as `{id, title, type}` only; use it instead of `fields` when no gap/shape check is needed.
50
+ - `outline` and `specs` require `format=json|yaml|toon` (others → 400 `PROJECTION_FORMAT_UNSUPPORTED`).
39
51
 
40
52
  ## Example — full board analysis
41
53
 
@@ -46,17 +58,25 @@ curl -s \
46
58
  -H "x-user-id: analyze-existing-model" \
47
59
  "$BASE_URL/api/org/$ORG_ID/boards/$BOARD_ID/slicedata/slices"
48
60
 
49
- # 2. Fetch MODEL_CONTEXT nodes
61
+ # 2. Fetch MODEL_CONTEXT nodes and timelines (a timeline without a context is its own context)
62
+ curl -s \
63
+ -H "x-token: $TOKEN" \
64
+ -H "x-user-id: analyze-existing-model" \
65
+ "$BASE_URL/api/org/$ORG_ID/boards/$BOARD_ID/nodes?type=MODEL_CONTEXT&projection=line"
50
66
  curl -s \
51
67
  -H "x-token: $TOKEN" \
52
68
  -H "x-user-id: analyze-existing-model" \
53
- "$BASE_URL/api/org/$ORG_ID/boards/$BOARD_ID/nodes?type=MODEL_CONTEXT"
69
+ "$BASE_URL/api/org/$ORG_ID/boards/$BOARD_ID/nodes?type=CHAPTER&projection=line"
54
70
 
55
- # 3. Fetch full slice data for a context
71
+ # 3. Fetch fields + specs projections for a context
72
+ curl -s \
73
+ -H "x-token: $TOKEN" \
74
+ -H "x-user-id: analyze-existing-model" \
75
+ "$BASE_URL/api/org/$ORG_ID/boards/$BOARD_ID/slicedata?contextName=Ordering&projection=fields&format=toon"
56
76
  curl -s \
57
77
  -H "x-token: $TOKEN" \
58
78
  -H "x-user-id: analyze-existing-model" \
59
- "$BASE_URL/api/org/$ORG_ID/boards/$BOARD_ID/slicedata?contextName=Ordering"
79
+ "$BASE_URL/api/org/$ORG_ID/boards/$BOARD_ID/slicedata?contextName=Ordering&projection=specs&format=toon"
60
80
  ```
61
81
 
62
82
  Replace `$TOKEN`, `$ORG_ID`, `$BOARD_ID`, and the context name with real values resolved from the `connect` skill.
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: attributes
3
- description: Add a new attribute or rename an existing attribute across a chain of elements from a source cell to a target cell, following inbound dependencies
3
+ description: Add a new attribute or rename an existing attribute across a chain of elements from a source element to a target element, following inbound dependencies
4
4
  ---
5
5
 
6
6
  # Attributes
@@ -9,7 +9,7 @@ description: Add a new attribute or rename an existing attribute across a chain
9
9
 
10
10
  Prefer `mcp__eventmodelers__*` tools when available (registered by the `connect` skill) — the curl blocks below are the fallback for sessions without MCP connected.
11
11
 
12
- You are propagating an attribute change (add or rename) across a chain of elements on an eventmodelers board. You start at the target cell, apply the change, then walk backwards through inbound dependencies until you reach the source cell, applying the change to every element along the way.
12
+ You are propagating an attribute change (add or rename) across a chain of elements on an eventmodelers board. You start at the target element, apply the change, then walk backwards through inbound dependencies until you reach the source element, applying the change to every element along the way.
13
13
 
14
14
  ---
15
15
 
@@ -17,8 +17,8 @@ You are propagating an attribute change (add or rename) across a chain of elemen
17
17
 
18
18
  Ask the user for all required information **in a single message** (do not ask one at a time):
19
19
 
20
- 1. **Target cell** — the end of the chain (e.g. `B2`)
21
- 2. **Source cell** — the start of the chain (e.g. `A2`)
20
+ 1. **Target element** — the end of the chain: a node id, a title, or a cell name (e.g. `B2`)
21
+ 2. **Source element** — the start of the chain: a node id, a title, or a cell name (e.g. `A2`)
22
22
  3. **Operation** — `add` a new attribute, or `rename` an existing one
23
23
  4. If **rename**: which attribute name to rename FROM, and what to rename it TO
24
24
  5. If **add**: the name of the new attribute to add
@@ -29,24 +29,27 @@ If any of these were already provided in `$ARGUMENTS`, skip asking for them.
29
29
 
30
30
  ## Step 2 — Resolve the chain
31
31
 
32
- **Prefer MCP:** `get_attribute_chain` resolves every node between the source and target cells (inclusive), ordered target→source, each with its full `fields[]` — this collapses the manual cell-resolution and inbound-edge walk below into one call. You still need `TIMELINE_ID` (the chapter to search): if multiple chapters exist on the board, resolve which one first (see 2a fallback below, or `mcp__eventmodelers__get_nodes { "boardId": "$BOARD_ID", "type": "CHAPTER" }`) and ask the user if ambiguous.
32
+ **Prefer MCP:** `get_attribute_chain` resolves every node in the columns between the source and target nodes (inclusive), ordered target→source (top-to-bottom within a column), each as `{id, type, title, cellName, fields}` — this collapses the manual inbound-edge walk below into one call. You still need `TIMELINE_ID` (the chapter to search): if multiple chapters exist on the board, resolve which one first (see 2a fallback below, or `mcp__eventmodelers__get_nodes { "boardId": "$BOARD_ID", "type": "CHAPTER", "projection": "line" }` — ids/titles are enough to pick one) and ask the user if ambiguous.
33
+
34
+ First resolve target and source to node ids (`TARGET_NODE_ID`, `SOURCE_NODE_ID`):
35
+ - **Node id** (incl. a node the task or a comment points at) — use it as-is.
36
+ - **Title** — `mcp__eventmodelers__get_nodes { "boardId": "$BOARD_ID", "chapterId": "$CHAPTER_ID", "name": "<title>", "projection": "line" }`; ask the user if several match.
37
+ - **Cell name** (e.g. `B2`) — `mcp__eventmodelers__get_board_outline { "boardId": "$BOARD_ID", "chapterId": "$CHAPTER_ID" }` and take the node whose `cellName` equals it. `get_board_outline` is the only read that reports `cellName`; never compute a node from a cell address yourself.
33
38
 
34
39
  ```
35
40
  mcp__eventmodelers__get_attribute_chain {
36
41
  "boardId": "$BOARD_ID",
37
42
  "timelineId": "$CHAPTER_ID",
38
- "targetCellName": "<target cell, e.g. B2>",
39
- "sourceCellName": "<source cell, e.g. A2>"
43
+ "targetNodeId": "<TARGET_NODE_ID>",
44
+ "sourceNodeId": "<SOURCE_NODE_ID>"
40
45
  }
41
46
  ```
42
47
 
43
- The result gives you the ordered chain directly — save it as the chain used in Step 4, and skip the manual walk in 2a–3c below. Continue with the fallback only if MCP isn't connected.
44
-
45
- **Both cell names must be real.** When the task named a node (an id, a title, a comment on it) instead of a cell, read that node's `cellName` off `get_board_outline` (every node in the chapter, with its address), `get_nodes`, or `get_node` — all three report it. Never compute an address from where a node appeared in a list, and never hand `get_attribute_chain` an address you have not read: a cell that exists but holds a different node resolves a wrong chain without any error.
48
+ The result gives you the ordered chain directly — save it as the chain used in Step 4, and skip the manual walk in 2a–3c below. A `NODE_NOT_IN_TIMELINE` error means one of the two nodes isn't placed in `$CHAPTER_ID` — pick the right chapter (or tell the user) rather than retrying. Continue with the fallback only if MCP isn't connected.
46
49
 
47
- ### Fallback (no MCP) — resolve both cells to nodes
50
+ ### Fallback (no MCP) — resolve target and source to nodes
48
51
 
49
- For each cell (target and source), resolve it to a node using the exact same cell-resolution steps as the `examples` skill's "2c — Cell name" section (fetch chapters, fetch the chapter fresh to decode the grid, decode the cell name into a `CELL_ID`, then always fetch the cell live — `get_nodes` has no `cellId` filter) — see there for the full mechanics, substituting `x-user-id: attributes-skill`.
52
+ A node id needs no resolving (`GET .../nodes/:nodeId`); a title resolves via `GET .../nodes?chapterId=&name=`. For a cell name, resolve it to a node using the exact same cell-resolution steps as the `examples` skill's "2c — Cell name" section (fetch chapters, fetch the chapter fresh to decode the grid, decode the cell name into a `CELL_ID`, then always fetch the cell live via REST `?cellId=&timelineId=` — MCP `get_nodes` has no `cellId` filter) — see there for the full mechanics, substituting `x-user-id: attributes-skill`.
50
53
 
51
54
  Take the first non-CHAPTER result as the node for each cell. Save as `TARGET_NODE` and `SOURCE_NODE`.
52
55
 
@@ -165,7 +168,7 @@ Nodes that are skipped (field already exists / field not found) simply contribut
165
168
 
166
169
  Verify the response is HTTP 200. If the batch fails, report the error and stop; nothing was partially applied from your side, so re-run after fixing the cause rather than retrying node by node.
167
170
 
168
- If you also need to verify the result, re-read the whole chain in one call — `get_nodes { "boardId": "$BOARD_ID", "nodeIds": [<every node id you just wrote>] }` — never one `get_node` per node.
171
+ If you also need to verify the result, re-read the whole chain in one call — `get_nodes { "boardId": "$BOARD_ID", "nodeIds": [<every node id you just wrote>], "projection": "line" }` when you only check that the attribute name is present/renamed (`line` lists each node's field names), or without `projection` when you must also check the field's type/example — never one `get_node` per node.
169
172
 
170
173
  ---
171
174
 
@@ -5,10 +5,12 @@ Only needed when MCP is not connected. Every call below has an MCP equivalent in
5
5
  ## 3a — Use Node Edges
6
6
 
7
7
  ```bash
8
- curl -s "$BASE_URL/api/org/$ORG_ID/boards/$BOARD_ID/nodes/$EDGE_SOURCE_ID" \
8
+ curl -s "$BASE_URL/api/org/$ORG_ID/boards/$BOARD_ID/nodes/$EDGE_SOURCE_ID?projection=edges" \
9
9
  -H "x-token: $TOKEN" -H "x-user-id: attributes-skill"
10
10
  ```
11
11
 
12
+ `projection=edges` returns `{nodeId, edges}` — the node's inbound/outbound connections only, no content (the full node record carries no edges). Fields for Step 4 come from the chapter-scoped `GET .../nodes?chapterId=` read.
13
+
12
14
  ## Step 4 — Apply the Change to Each Node in the Chain
13
15
 
14
16
  Build the payload with Python to avoid JSON escaping issues, then POST it:
@@ -256,11 +256,11 @@ mcp__eventmodelers__get_node { "boardId": "<BOARD_ID>", "nodeId": "<CHAPTER_ID>"
256
256
 
257
257
  **Fallback (no MCP):** see `references/api-fallback.md` — "Step 6 — Fetch chapter grid".
258
258
 
259
- From the result (`rows`/`columns`/`cells` directly via MCP, or `meta.timelineData` via the REST fallback):
259
+ From the result (`rows`/`columns`/`cells` directly — the REST fallback's `?projection=cells` returns the same shape):
260
260
  - `rows` — find the row with `type === "actor"` → save its `id` as `actorRowId` and its 0-based position in `rows` as `actorRowIndex`
261
261
  - `columns` — ordered list; build an empty-column queue, remembering each entry's 0-based position in `columns` as its `columnIndex`
262
262
 
263
- **Cell ID convention**: `<rowId>-<columnId>` — always computed directly, never looked up. **Cell name convention** (spreadsheet-style, needed for the MCP tool in Step 7b): `<columnLetter><rowNumber>`, where `columnLetter` is `columnIndex` converted to spreadsheet letters (0→A, 1→B, …25→Z, 26→AA, …) and `rowNumber` is `actorRowIndex + 1`.
263
+ **Cell ID convention**: `<rowId>-<columnId>` — always computed directly, never looked up. **Cell name** (spreadsheet-style, needed for the MCP tool in Step 7b): read, never computed from `columnIndex`/`actorRowIndex`. Call `mcp__eventmodelers__get_board_outline { "boardId": "<BOARD_ID>", "chapterId": "<CHAPTER_ID>" }` once and take the target column's `letter` plus the actor lane's 1-based position in its `lanes` list (lanes are listed top to bottom) — e.g. `letter: "D"`, actor lane first in `lanes` → `D1`.
264
264
 
265
265
  ---
266
266
 
@@ -272,7 +272,7 @@ For each screen:
272
272
 
273
273
  ### 7a — Acquire a column slot
274
274
 
275
- **If the empty-column queue is non-empty** — pop the first entry → `columnId` and its remembered `columnIndex`. Compute `CELL_ID = actorRowId + "-" + columnId` and `CELL_NAME` per the convention above.
275
+ **If the empty-column queue is non-empty** — pop the first entry → `columnId` and its remembered `columnIndex`. Compute `CELL_ID = actorRowId + "-" + columnId` and take `CELL_NAME` from the outline as described above.
276
276
 
277
277
  **If the empty-column queue is empty** — create a new column.
278
278
 
@@ -280,11 +280,11 @@ For each screen:
280
280
  ```
281
281
  mcp__eventmodelers__add_column { "boardId": "<BOARD_ID>", "timelineId": "<CHAPTER_ID>" }
282
282
  ```
283
- The result's `index` field is the new column's `columnIndex` (use it directly for `CELL_NAME` — no need to recompute from the full column list).
283
+ Re-read `get_board_outline` after adding the column and take the new (last) column's `letter` for `CELL_NAME` — don't convert the result's `index` into a letter yourself.
284
284
 
285
285
  **Fallback (no MCP):** see `references/api-fallback.md` — "Step 7a — Create column".
286
286
 
287
- Extract `columnId`. Compute `CELL_ID = actorRowId + "-" + columnId` and `CELL_NAME` per the convention above.
287
+ Extract `columnId`. Compute `CELL_ID = actorRowId + "-" + columnId` and take `CELL_NAME` from the outline as described above.
288
288
 
289
289
  ### 7b — Create the SCREEN node, atomically
290
290
 
@@ -307,7 +307,7 @@ mcp__eventmodelers__create_screen {
307
307
  "contentType": "html",
308
308
  "nodeId": "<SCREEN_NODE_ID>",
309
309
  "chapterId": "<CHAPTER_ID>",
310
- "cellId": "<CELL_ID>",
310
+ "cellName": "<CELL_NAME>",
311
311
  "title": "<screen.title>",
312
312
  "pages": ["<reconstructed HTML fragment for this screen>"],
313
313
  "description": "<screen.description — 'Shows X. Arrived via: Y. Actions: user can do A, user can do B.'>"
@@ -32,9 +32,11 @@ curl -s -X POST "$BASE_URL/api/org/$ORG_ID/boards/$BOARD_ID/nodes/events" \
32
32
 
33
33
  ```bash
34
34
  curl -s -H "x-token: $TOKEN" \
35
- "$BASE_URL/api/org/$ORG_ID/boards/$BOARD_ID/nodes/$CHAPTER_ID"
35
+ "$BASE_URL/api/org/$ORG_ID/boards/$BOARD_ID/nodes/$CHAPTER_ID?projection=cells"
36
36
  ```
37
37
 
38
+ Returns `{rows, columns, cells}` directly (not wrapped in `meta.timelineData`).
39
+
38
40
  ## Step 7a — Create column
39
41
 
40
42
  ```bash
@@ -47,20 +47,20 @@ Follow **`eventmodeling-interview-protocol`** to run this interview and record i
47
47
 
48
48
  ## Board Context
49
49
 
50
- Before brainstorming, check for EVENT nodes already on the board to avoid duplicating events from a previous session:
50
+ Before brainstorming, check for EVENT nodes already on the board to avoid duplicating events from a previous session — titles (plus field names) are all a dedupe needs, so use `projection: "line"`:
51
51
 
52
52
  **Prefer MCP:**
53
53
  ```
54
- mcp__eventmodelers__get_nodes { "boardId": "<BOARD_ID>", "type": "EVENT" }
54
+ mcp__eventmodelers__get_nodes { "boardId": "<BOARD_ID>", "type": "EVENT", "projection": "line" }
55
55
  ```
56
56
 
57
57
  **Fallback (no MCP):** see `references/api-fallback.md` — "Board Context — Check existing EVENT nodes".
58
58
 
59
- If events already exist, treat them as the starting list and focus on discovering what might be missing. Also check for existing chapters (timelines) so you can reuse them:
59
+ If events already exist, treat them as the starting list and focus on discovering what might be missing. Also check for existing chapters (timelines) so you can reuse them — ids and titles only, so `projection: "line"` (without it every chapter's full grid comes back):
60
60
 
61
61
  **Prefer MCP:**
62
62
  ```
63
- mcp__eventmodelers__get_nodes { "boardId": "<BOARD_ID>", "type": "CHAPTER" }
63
+ mcp__eventmodelers__get_nodes { "boardId": "<BOARD_ID>", "type": "CHAPTER", "projection": "line" }
64
64
  ```
65
65
 
66
66
  **Fallback (no MCP):** see `references/api-fallback.md` — "Board Context — Check existing CHAPTER nodes".
@@ -6,14 +6,14 @@ Only needed when MCP is not connected. Every call below has an MCP equivalent in
6
6
 
7
7
  ```bash
8
8
  curl -s -H "x-token: $TOKEN" \
9
- "$BASE_URL/api/org/$ORG_ID/boards/$BOARD_ID/nodes?type=EVENT"
9
+ "$BASE_URL/api/org/$ORG_ID/boards/$BOARD_ID/nodes?type=EVENT&projection=line"
10
10
  ```
11
11
 
12
12
  ## Board Context — Check existing CHAPTER nodes
13
13
 
14
14
  ```bash
15
15
  curl -s -H "x-token: $TOKEN" \
16
- "$BASE_URL/api/org/$ORG_ID/boards/$BOARD_ID/nodes?type=CHAPTER"
16
+ "$BASE_URL/api/org/$ORG_ID/boards/$BOARD_ID/nodes?type=CHAPTER&projection=line"
17
17
  ```
18
18
 
19
19
  ## Timeline Discovery — Step 2: Create one chapter per group — create the chapter
@@ -63,8 +63,8 @@ curl -s -X POST "$BASE_URL/api/org/$ORG_ID/boards/$BOARD_ID/timelines/$CHAPTER_I
63
63
 
64
64
  ```bash
65
65
  curl -s -H "x-token: $TOKEN" \
66
- "$BASE_URL/api/org/$ORG_ID/boards/$BOARD_ID/nodes/$CHAPTER_ID"
67
- # → node.meta.timelineData.rows — find the row where type === "swimlane"
66
+ "$BASE_URL/api/org/$ORG_ID/boards/$BOARD_ID/nodes/$CHAPTER_ID?projection=cells"
67
+ # → rows — find the row where type === "swimlane" ({rows, columns, cells} directly, not under meta.timelineData)
68
68
  ```
69
69
 
70
70
  ## Mode A — Step D: Create the event with cellId
@@ -18,18 +18,17 @@ Prefer `mcp__eventmodelers__*` tools when available (registered by the `connect`
18
18
 
19
19
  Before starting, read the current board state to drive the analysis from what is actually on the board rather than relying solely on conversation context:
20
20
 
21
- **Prefer MCP:**
21
+ **Prefer MCP** — the field origin/destination checks need every element's fields plus its dependencies, which the `fields` projection of the slice data returns per slice in one call (no specs, comments or prompts):
22
22
  ```
23
- mcp__eventmodelers__get_nodes { "boardId": "<BOARD_ID>", "type": "EVENT" }
24
- mcp__eventmodelers__get_nodes { "boardId": "<BOARD_ID>", "type": "COMMAND" }
25
- mcp__eventmodelers__get_nodes { "boardId": "<BOARD_ID>", "type": "READMODEL" }
23
+ mcp__eventmodelers__get_slice_data { "boardId": "<BOARD_ID>", "contextName": "<CONTEXT_NAME>", "projection": "fields", "format": "toon" }
26
24
  ```
25
+ Drive it by timeline, not by MODEL_CONTEXT — a timeline with no assigned or connected context is its own context, so listing only the MODEL_CONTEXT nodes misses it. List the timelines (ids/titles only): `mcp__eventmodelers__get_nodes { "boardId": "<BOARD_ID>", "type": "CHAPTER", "projection": "line" }`, then pass each timeline's title as `contextName`. The response covers that timeline's whole effective context — every timeline in the same context — and each slice carries its `chapter`, so skip any timeline already named in a response you have (call the rest in parallel).Drop `projection` only for a node whose field types/examples you actually need to inspect — and then scope it with `nodeIds`.
27
26
 
28
27
  **Fallback (no MCP):** see `references/api-fallback.md` — "Board Context".
29
28
 
30
29
  Use these results as the source of truth for the completeness check.
31
30
 
32
- **Before treating any nodes as duplicates**: check each node's `data.linkedTo` field (see `learn-eventmodelers-api`). A node with `linkedTo` set is an intentional **linked copy** of another node — placed elsewhere on the timeline for readability, not a modeling defect. When two or more nodes share a title/type:
31
+ **Before treating any nodes as duplicates**: check each node's `data.linkedTo` field (see `learn-eventmodelers-api`). Neither projection above carries it — fetch the full records of just the same-titled candidates with `mcp__eventmodelers__get_nodes { "boardId": "<BOARD_ID>", "nodeIds": [<their ids>] }`. A node with `linkedTo` set is an intentional **linked copy** of another node — placed elsewhere on the timeline for readability, not a modeling defect. When two or more nodes share a title/type:
33
32
  - If any of them carries `linkedTo`, do not report a duplicate. This is expected, not a gap.
34
33
  - Never propose deleting, suppressing, or "cleaning up" either node in a linked pair. Specifically, never target the node that has *no* `linkedTo` (the original) for removal — copies reference it via `moveToWidget=<originNodeId>`, so deleting it breaks every copy.
35
34
  - Only flag same-titled nodes as an actual duplicate gap when **none** of them has `linkedTo` — i.e., they are genuinely two independent, unlinked nodes describing the same concept.
@@ -53,18 +52,15 @@ Verify every field a read model displays is sourced from a connected event. A fu
53
52
 
54
53
  Every column that holds a COMMAND or READMODEL node must have a slice defined (a SLICE_BORDER node on that column) — otherwise it can never be built as a feature. Skip columns whose COMMAND/READMODEL node has `data.linkedTo` set: it's a linked copy (see Board Context above), and only the original's column needs a slice.
55
54
 
56
- **Prefer MCP** — `list_slices` is lighter than filtering all nodes, and also returns each slice's status:
55
+ **Prefer MCP** — `get_board_outline` answers this directly per chapter: every column with its node list (id, type, title) and a `sliceStatus` when a slice spans it — no need to pull full SLICE_BORDER records and join on `columnId`:
57
56
  ```
58
- mcp__eventmodelers__list_slices { "boardId": "<BOARD_ID>" }
59
- ```
60
- Or, to get the full SLICE_BORDER nodes (with `columnId`) the same way as the curl fallback:
61
- ```
62
- mcp__eventmodelers__get_nodes { "boardId": "<BOARD_ID>", "type": "SLICE_BORDER" }
57
+ mcp__eventmodelers__get_board_outline { "boardId": "<BOARD_ID>", "chapterId": "<CHAPTER_ID>" }
63
58
  ```
59
+ Get the chapter ids from `mcp__eventmodelers__get_nodes { "boardId": "<BOARD_ID>", "type": "CHAPTER", "projection": "line" }` (ids/titles only). For just the slice list with statuses, `mcp__eventmodelers__list_slices { "boardId": "<BOARD_ID>" }` is the lightest read.
64
60
 
65
61
  **Fallback (no MCP):** see `references/api-fallback.md` — "4. Check Slice Coverage".
66
62
 
67
- Cross-reference each COMMAND/READMODEL node's column against the `columnId` of the SLICE_BORDER nodes, flagging any column with no matching slice as a gap (skipping linked copies, which are exempt). A full worked example is in `references/examples.md`.
63
+ Flag any column that holds a COMMAND or READMODEL node but carries no `sliceStatus` as a gap (skipping linked copies, which are exempt — confirm `linkedTo` on the flagged nodes with a `nodeIds`-scoped full `get_nodes`). A full worked example is in `references/examples.md`.
68
64
 
69
65
  ### 5. Check Event Stream Completeness
70
66
  Verify no "missing" events:
@@ -5,18 +5,28 @@ Only needed when MCP is not connected. Every call below has an MCP equivalent in
5
5
  ## Board Context
6
6
 
7
7
  ```bash
8
- # All nodes (events, commands, read models, screens)
8
+ # Timelines (ids/titles only) — drive the slice data by timeline: a timeline without a context is its own context
9
9
  curl -s -H "x-token: $TOKEN" \
10
- "$BASE_URL/api/org/$ORG_ID/boards/$BOARD_ID/nodes?type=EVENT"
11
- curl -s -H "x-token: $TOKEN" \
12
- "$BASE_URL/api/org/$ORG_ID/boards/$BOARD_ID/nodes?type=COMMAND"
13
- curl -s -H "x-token: $TOKEN" \
14
- "$BASE_URL/api/org/$ORG_ID/boards/$BOARD_ID/nodes?type=READMODEL"
10
+ "$BASE_URL/api/org/$ORG_ID/boards/$BOARD_ID/nodes?type=CHAPTER&projection=line"
11
+
12
+ # Elements with fields + dependencies for a timeline's effective context (no specs/comments).
13
+ # Skip timelines already named in a previous response's slice `chapter` values.
14
+ curl -s -G -H "x-token: $TOKEN" \
15
+ "$BASE_URL/api/org/$ORG_ID/boards/$BOARD_ID/slicedata" \
16
+ --data-urlencode "contextName=<TIMELINE_TITLE>" -d projection=fields -d format=toon
17
+
18
+ # Full records (incl. data.linkedTo) for just the duplicate candidates
19
+ curl -s -X POST -H "x-token: $TOKEN" -H "Content-Type: application/json" \
20
+ "$BASE_URL/api/org/$ORG_ID/boards/$BOARD_ID/nodes/query" -d '{"ids": ["<id1>", "<id2>"]}'
15
21
  ```
16
22
 
17
23
  ## 4. Check Slice Coverage
18
24
 
25
+ There is no REST equivalent of `get_board_outline`, so this fallback keeps the full SLICE_BORDER rows — the cross-reference needs their `columnId`, which `projection=line` does not carry:
26
+
19
27
  ```bash
20
28
  curl -s -H "x-token: $TOKEN" \
21
29
  "$BASE_URL/api/org/$ORG_ID/boards/$BOARD_ID/nodes?type=SLICE_BORDER"
22
30
  ```
31
+
32
+ Cross-reference each COMMAND/READMODEL node's column against the `columnId` of the SLICE_BORDER nodes; a column with no matching slice is a gap (linked copies exempt).
@@ -327,18 +327,18 @@ After designing all scenarios, post them to the board using the timeline/column
327
327
 
328
328
  ### Step 1 — Identify the target timeline and column
329
329
 
330
- Fetch all CHAPTER nodes to find the timeline.
330
+ Fetch all CHAPTER nodes to find the timeline — ids and titles only, so `projection: "line"` (without it every chapter's full grid comes back).
331
331
 
332
332
  **Prefer MCP:**
333
333
  ```
334
- mcp__eventmodelers__get_nodes { "boardId": "<BOARD_ID>", "type": "CHAPTER" }
334
+ mcp__eventmodelers__get_nodes { "boardId": "<BOARD_ID>", "type": "CHAPTER", "projection": "line" }
335
335
  ```
336
336
 
337
337
  **Fallback (no MCP):** see `references/api-fallback.md` — "Post Scenarios to Board — Step 1: Identify the Target Timeline and Column".
338
338
 
339
339
  If there is more than one chapter, ask the user which timeline to target.
340
340
 
341
- For each command or view being specified, find its column: fetch the chapter node and read `meta.timelineData.columns`. Match the column to the COMMAND or READMODEL node that occupies the interaction row in that column. If the user named the slice, find the SLICE_BORDER node with that title to get its `colId`.
341
+ For each command or view being specified, find its column: `mcp__eventmodelers__get_board_outline { "boardId": "<BOARD_ID>", "chapterId": "<TL>" }` lists every column (`columnId`, index) with the nodes in it — take the column whose node list holds that COMMAND or READMODEL. If the user named the slice, find the SLICE_BORDER node with that title (`get_nodes { "type": "SLICE_BORDER", "name": "<slice title>", "projection": "line" }` gives its id) or its column in the outline to get the `colId`. (REST fallback: `GET .../nodes/$TL?projection=cells` → `{rows, columns, cells}`; find the cell whose `nodeId` is the COMMAND/READMODEL and take its `colId`.)
342
342
 
343
343
  ### Step 2 — Load valid step elements
344
344
 
@@ -5,7 +5,7 @@ Only needed when MCP is not connected. Every call below has an MCP equivalent in
5
5
  ## Post Scenarios to Board — Step 1: Identify the Target Timeline and Column
6
6
 
7
7
  ```bash
8
- curl -s "$BASE_URL/api/org/$ORG_ID/boards/$BOARD_ID/nodes?type=CHAPTER" \
8
+ curl -s "$BASE_URL/api/org/$ORG_ID/boards/$BOARD_ID/nodes?type=CHAPTER&projection=line" \
9
9
  -H "x-token: $TOKEN"
10
10
  ```
11
11
 
@@ -197,7 +197,7 @@ After `place-element` returns the COMMAND node ID, create the arrows that comple
197
197
 
198
198
  1. **SCREEN → COMMAND** — find the SCREEN node in the actor row of the same column.
199
199
 
200
- **Prefer MCP** — there is no `cellId` filter on `get_nodes` (see note below), so read the chapter's cell map instead — `projection: "cells"` returns just `{rows, columns, cells}`, not the whole chapter node:
200
+ **Prefer MCP** — `get_nodes` has no `cellId` filter (see note below), so read the chapter's cell map instead — `projection: "cells"` returns just `{rows, columns, cells}`, not the whole chapter node:
201
201
  ```
202
202
  mcp__eventmodelers__get_node { "boardId": "<BOARD_ID>", "nodeId": "<CHAPTER_ID>", "projection": "cells" }
203
203
  ```
@@ -224,7 +224,7 @@ After `place-element` returns the COMMAND node ID, create the arrows that comple
224
224
 
225
225
  **Fallback (no MCP):** see `references/api-fallback.md` — "Wire connections — Step 2: COMMAND → EVENT".
226
226
 
227
- *Note:* `get_nodes` has no `cellId` filter (only `type`) — the `get_node`-on-CHAPTER (`projection: "cells"`) + `cells` lookup above is the only way to check single-cell occupancy via MCP.
227
+ *Note:* `get_nodes` filters by `type`, `name` (partial title), `chapterId` and `nodeIds`, but has no `cellId` filter — the `get_node`-on-CHAPTER (`projection: "cells"`) + `cells` lookup above is the only way to check single-cell occupancy via MCP. (The REST `GET .../nodes` does take `cellId`/`colId`, each requiring `timelineId` — see the fallback file.)
228
228
 
229
229
  Skip a connection silently if the target cell is empty (the element may be placed in a later step). Log each created arrow: `→ connected SCREEN→COMMAND "PlaceOrder"` or `→ connected COMMAND→EVENT "PlaceOrder"→"OrderPlaced"`.
230
230
 
@@ -14,8 +14,8 @@ curl -s -H "x-token: $TOKEN" \
14
14
  **Step B — Fetch the chapter to find the interaction row ID:**
15
15
  ```bash
16
16
  curl -s -H "x-token: $TOKEN" \
17
- "$BASE_URL/api/org/$ORG_ID/boards/$BOARD_ID/nodes/$CHAPTER_ID"
18
- # → timelineData.rows — find the row where type === "interaction"
17
+ "$BASE_URL/api/org/$ORG_ID/boards/$BOARD_ID/nodes/$CHAPTER_ID?projection=cells"
18
+ # → rows — find the row where type === "interaction" ({rows, columns, cells} directly, not under meta.timelineData)
19
19
  ```
20
20
  Save `interactionRow.id`.
21
21
 
@@ -51,7 +51,7 @@ curl -X POST "$BASE_URL/api/org/$ORG_ID/boards/$BOARD_ID/nodes/events" \
51
51
  ## Wire connections — Step 1: SCREEN → COMMAND
52
52
 
53
53
  ```bash
54
- curl -s "$BASE_URL/api/org/$ORG_ID/boards/$BOARD_ID/nodes?cellId=<actorRowId>-<columnId>" \
54
+ curl -s "$BASE_URL/api/org/$ORG_ID/boards/$BOARD_ID/nodes?cellId=<actorRowId>-<columnId>&timelineId=$CHAPTER_ID" \
55
55
  -H "x-token: $TOKEN" -H "x-user-id: eventmodeling-identifying-inputs"
56
56
  ```
57
57
  If a SCREEN node exists, connect it:
@@ -65,7 +65,7 @@ curl -s -X POST "$BASE_URL/api/org/$ORG_ID/boards/$BOARD_ID/connections" \
65
65
  ## Wire connections — Step 2: COMMAND → EVENT
66
66
 
67
67
  ```bash
68
- curl -s "$BASE_URL/api/org/$ORG_ID/boards/$BOARD_ID/nodes?cellId=<swimlaneRowId>-<columnId>" \
68
+ curl -s "$BASE_URL/api/org/$ORG_ID/boards/$BOARD_ID/nodes?cellId=<swimlaneRowId>-<columnId>&timelineId=$CHAPTER_ID" \
69
69
  -H "x-token: $TOKEN" -H "x-user-id: eventmodeling-identifying-inputs"
70
70
  ```
71
71
  Connect command to its resulting event:
@@ -195,7 +195,7 @@ Before finalizing any read model, ask: "does this screen contain more than one c
195
195
 
196
196
  **Do not re-derive read model needs from a screen's title or description alone, and do not rely on the orchestrator's phase-summary handoff for this** — if you arrived here via `eventmodeling-orchestrating-event-modeling`, the handoff after Step 3 is a short hand-written prose summary (`.eventmodelers/interviews/.../EVENTMODELING.md`), not the actual field data. It will not reliably carry the per-field mappings forward. Go back to the board itself:
197
197
 
198
- For every SCREEN node, fetch it directly (`get_node`/`get_nodes`, never from memory) and read its `meta.fields`. Step 3 already required every field to carry a `mapping`, and for view fields that mapping is already in the exact form `"<ReadModelTitle>.<fieldName>"` — recorded specifically so this step doesn't have to re-guess it.
198
+ For every SCREEN node, fetch it directly (`get_node`/`get_nodes`, never from memory) and read its `meta.fields` — a full read on purpose (`projection: "line"` carries field names only, not the `mapping` this step groups by); scope it to the chapter, e.g. `get_nodes { "boardId": "<BOARD_ID>", "type": "SCREEN", "chapterId": "<CHAPTER_ID>" }`, rather than board-wide. Step 3 already required every field to carry a `mapping`, and for view fields that mapping is already in the exact form `"<ReadModelTitle>.<fieldName>"` — recorded specifically so this step doesn't have to re-guess it.
199
199
 
200
200
  - **Group the screen's fields by the `<ReadModelTitle>` already named in their `mapping`.** That grouping — not a fresh read of the screen's visuals — is the read model's title and field list. Build the READMODEL node from it directly.
201
201
  - If a field's `mapping` names a read model that isn't `"<CommandTitle>.<fieldName>"` or `"session:..."` or `"derived:..."`, it is a read-model reference — treat it as a requirement, not a suggestion.
@@ -273,7 +273,7 @@ mcp__eventmodelers__submit_node_events {
273
273
  }]
274
274
  }
275
275
  ```
276
- To determine the consumer's column index beforehand, or to check whether a specific interaction cell is already occupied (there is no `cellId` filter on `get_nodes` — 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:
276
+ 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:
277
277
  ```
278
278
  mcp__eventmodelers__get_node { "boardId": "<BOARD_ID>", "nodeId": "<CHAPTER_ID>", "projection": "cells" }
279
279
  # → rows (find "interaction"/"actor" rows) and cells (sparse; absent id = empty)
@@ -323,7 +323,7 @@ After `place-element` returns the READMODEL node ID, create the arrows that comp
323
323
 
324
324
  1. **EVENT → READMODEL** — find the primary source EVENT node in the swimlane row of the same column.
325
325
 
326
- **Prefer MCP** — `get_nodes` has no `cellId` filter (only `type`); look up occupancy via the chapter's cell map instead (`projection: "cells"`), then connect with the type-checked edge tool (auto-corrects direction, skips duplicates):
326
+ **Prefer MCP** — `get_nodes` has no `cellId` filter (it filters by `type`, `name`, `chapterId`, `nodeIds` only); look up occupancy via the chapter's cell map instead (`projection: "cells"`), then connect with the type-checked edge tool (auto-corrects direction, skips duplicates):
327
327
  ```
328
328
  mcp__eventmodelers__get_node { "boardId": "<BOARD_ID>", "nodeId": "<CHAPTER_ID>", "projection": "cells" }
329
329
  # → read cells["<swimlaneRowId>-<columnId>"] for the occupying node id
@@ -369,7 +369,7 @@ If this step is processing more than one read model in the same pass, collect ev
369
369
 
370
370
  ### Step 5i — Mandatory per-node verification (run before declaring this step done)
371
371
 
372
- Do not declare Step 5 complete on the strength of the read models you happened to design. Instead, **re-fetch every SCREEN and AUTOMATION node on the board** (`get_nodes` per type — don't rely on the list built earlier in this step, the board may have moved on) and check each one individually:
372
+ Do not declare Step 5 complete on the strength of the read models you happened to design. Instead, **re-fetch every SCREEN and AUTOMATION node on the board** (don't rely on the list built earlier in this step, the board may have moved on) and check each one individually. Questions 1–3 need only node types and edges: `get_board_outline { "boardId": "<BOARD_ID>", "chapterId": "<CHAPTER_ID>" }` per chapter returns every node per column plus the flat edge list — the full node records from `get_nodes` carry no edges. Read a node in full (`get_node`) only when you fix a gap and need its `meta.fields`/`mapping`:
373
373
 
374
374
  1. Does it now have an incoming `READMODEL → SCREEN` or `READMODEL → AUTOMATION` connection?
375
375
  2. If it's a SCREEN and not connected — is it a provably blank creation form with no prior state? State the reason in one line (e.g. `"Register Account" screen: blank form, no prior state — exempt`). This exemption applies to screens only.
@@ -7,12 +7,12 @@ Only needed when MCP is not connected. Every call below has an MCP equivalent in
7
7
  1. Find the column where the consumer SCREEN or AUTOMATION lives. For an AUTOMATION, the read model's target column is always the one immediately **before** it (skip straight to inserting that column — its interaction row is guaranteed occupied by the automation's own COMMAND). For a SCREEN, target the screen's own column. Fetch the timeline to get the interaction row ID:
8
8
  ```bash
9
9
  curl -s -H "x-token: $TOKEN" \
10
- "$BASE_URL/api/org/$ORG_ID/boards/$BOARD_ID/nodes/$CHAPTER_ID"
11
- # → timelineData.rows — find the row where type === "interaction"
10
+ "$BASE_URL/api/org/$ORG_ID/boards/$BOARD_ID/nodes/$CHAPTER_ID?projection=cells"
11
+ # → rows — find the row where type === "interaction" ({rows, columns, cells} directly, not under meta.timelineData)
12
12
  ```
13
13
  2. Check if the target interaction cell is already occupied (existing COMMAND):
14
14
  ```bash
15
- curl -s "$BASE_URL/api/org/$ORG_ID/boards/$BOARD_ID/nodes?cellId=<interactionRowId>-<columnId>" \
15
+ curl -s "$BASE_URL/api/org/$ORG_ID/boards/$BOARD_ID/nodes?cellId=<interactionRowId>-<columnId>&timelineId=$CHAPTER_ID" \
16
16
  -H "x-token: $TOKEN"
17
17
  ```
18
18
  If a COMMAND occupies that cell, insert a new column immediately **before** it (`{"index": currentIndex}` — this shifts the consumer's column, and everything after it, one to the right) and use that new column's ID instead. The read model must end up upstream of (to the left of) its consumer, never downstream of it.
@@ -35,7 +35,7 @@ Only needed when MCP is not connected. Every call below has an MCP equivalent in
35
35
  ## Step 5h.1 — Wire EVENT → READMODEL
36
36
 
37
37
  ```bash
38
- curl -s "$BASE_URL/api/org/$ORG_ID/boards/$BOARD_ID/nodes?cellId=<swimlaneRowId>-<columnId>" \
38
+ curl -s "$BASE_URL/api/org/$ORG_ID/boards/$BOARD_ID/nodes?cellId=<swimlaneRowId>-<columnId>&timelineId=$CHAPTER_ID" \
39
39
  -H "x-token: $TOKEN" -H "x-user-id: eventmodeling-identifying-outputs"
40
40
  ```
41
41
  Connect it: