@eventmodelers/cli 1.0.81 → 1.0.85
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 +1 -1
- package/shared/skills/learn-eventmodelers-api/SKILL.md +14 -13
- package/stacks/modeling-kit/templates/.claude/skills/discover-storyboard/SKILL.md +19 -15
- package/stacks/modeling-kit/templates/.claude/skills/eventmodeling-designing-automation-chains/SKILL.md +9 -7
- package/stacks/modeling-kit/templates/.claude/skills/eventmodeling-identifying-inputs/SKILL.md +9 -7
- package/stacks/modeling-kit/templates/.claude/skills/eventmodeling-identifying-outputs/SKILL.md +10 -8
- package/stacks/modeling-kit/templates/.claude/skills/eventmodeling-orchestrating-event-modeling/SKILL.md +16 -14
- package/stacks/modeling-kit/templates/.claude/skills/eventmodeling-slicing-event-models/SKILL.md +2 -2
- package/stacks/modeling-kit/templates/.claude/skills/eventmodeling-slicing-event-models/references/examples.md +2 -2
- package/stacks/modeling-kit/templates/.claude/skills/eventmodeling-storyboarding-events/SKILL.md +11 -9
- package/stacks/modeling-kit/templates/.claude/skills/handle-comment/SKILL.md +2 -2
- package/stacks/modeling-kit/templates/.claude/skills/html-screen/SKILL.md +8 -6
- package/stacks/modeling-kit/templates/.claude/skills/place-element/SKILL.md +28 -21
- package/stacks/modeling-kit/templates/.claude/skills/place-element/references/api-fallback.md +1 -1
- package/stacks/modeling-kit/templates/.claude/skills/storyboard/SKILL.md +18 -14
- package/stacks/modeling-kit/templates/.claude/skills/timeline/SKILL.md +4 -4
- package/stacks/modeling-kit/templates/.claude/skills/wdyt/SKILL.md +19 -7
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@eventmodelers/cli",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.85",
|
|
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": {
|
|
@@ -17,6 +17,10 @@ You now have complete knowledge of the eventmodelers platform API. This is a ref
|
|
|
17
17
|
|
|
18
18
|
Server name: `eventmodelers`. Every tool takes `boardId` explicitly; none need `orgId` (resolved from the token) or `x-user-id` (the server attributes writes to the authenticated principal). REST section numbers below give the underlying implementation for tools that wrap a single endpoint 1:1.
|
|
19
19
|
|
|
20
|
+
Write tools take their items as an array (`nodeIds[]`, `elements[]`, `connections[]`, …) and there are no separate plural tools — put several items in one call rather than looping. The array form always answers `{results: [...]}`, one entry per item in request order, even for a single item. The older flat form (e.g. `delete_node { nodeId }`) still works and answers with just that one result, but use the array form in new instructions.
|
|
21
|
+
|
|
22
|
+
**Deprecated tools — don't call them:** `drop_node_to_cell` and `move_node_in_timeline` → `place_element` with `action: "move"` (`nodeId` + `cellName`); `clear_cell` → `place_element` with `action: "clear"` (`cellName`). They still work so older kits don't break, but they will be removed.
|
|
23
|
+
|
|
20
24
|
| Tool | Args | Purpose | REST equivalent |
|
|
21
25
|
|---|---|---|---|
|
|
22
26
|
| `list_boards` | — | List boards for the org | §1 `GET /api/boards` (org-scoped) |
|
|
@@ -26,23 +30,20 @@ Server name: `eventmodelers`. Every tool takes `boardId` explicitly; none need `
|
|
|
26
30
|
| `get_board_events` | `boardId` | All board events, in sequence | §1 `GET .../events` |
|
|
27
31
|
| `search_board_events` | `boardId`, `name` | Search events by node name | §1 `GET .../events/search` |
|
|
28
32
|
| `submit_node_events` | `boardId`, `events[]`, `autoConnect?`, `compact?` | Create/update nodes (raw `NodeChangeEvent`/edge events). Every event property is described on the tool's own `events[]` schema — read that rather than this skill when all you need is the event shape. An event needs its own `id` (required — a fresh uuid per event, never the node id) and its `eventType`, plus the keys of its kind (`nodeId`/`meta`/`node`, or `edgeId`/`source`/`target`). The `id` keys the returned `hashes` map and makes a resubmit idempotent — the same event sent twice under one id writes a single board event. Don't send `boardId` or `timestamp` per event — the server takes the board from the call and stamps the time itself, and ignores both if sent. `autoConnect: false` places freshly-created nodes without wiring them to their own/previous-column neighbors (avoids a stray nearest-left edge); `compact: true` returns `{persisted: <count>}` instead of the per-node hash map | §3 `POST .../nodes/events` |
|
|
29
|
-
| `delete_node` | `boardId`, `
|
|
30
|
-
| `create_drawing`
|
|
33
|
+
| `delete_node` | `boardId`, `nodeIds[]` | Delete nodes, applied in order — one failure doesn't stop the rest; answers `{results}`. Deleting a chapter (timeline) cascades — every node placed in one of its cells, plus any node parented to it (e.g. SLICE_BORDER), is deleted too, along with all their edges | (via `node:deleted` event, §3) |
|
|
34
|
+
| `create_drawing` | `boardId`, `drawings[]` (each `kind`, `x`, `y`, `width`, `height`, ...) | Freehand canvas annotation (path/rect/text/sticky) — never placed in a cell. An annotation is rarely one stroke: a loop plus its arrows and label is one call with several `drawings` entries, applied in order, not three calls | — (REST `POST .../drawing/draw` accepts a single drawing or an array) |
|
|
31
35
|
| `find_nodes_in_drawing` | `boardId`, `drawingId` | Nodes fully contained inside a drawing's bounding box | — (no REST equivalent; MCP-only) |
|
|
32
36
|
| `create_chapter` | `boardId`, `x?`, `y?`, `title?`, `columns?`, `lanes?: [{type, label?, height?}]` | Create a timeline. Omitting `x`/`y` auto-stacks it below the lowest existing chapter (by its *actual current* row-height total, not the height it was created with — safe even after `add_lane` growth), plus a fixed margin. Pass `title` to name it, `columns` for a known column count, and `lanes` to create named lanes (or several of one type, e.g. one swimlane per context) — none of the three needs a follow-up call, and the lanes are sorted into the required order automatically. The response carries every `columnId` and lane id, so the chapter needn't be read back before placing into it | §2 `POST .../chapters` |
|
|
33
37
|
| `get_chapter_bounds` | `boardId` | Absolute canvas bounding box `{id, title, x, y, width, height}` of every chapter on the board — width/height derived from each chapter's current row/column layout, not a guessed default. Use before picking explicit `x`/`y` for `create_chapter` (e.g. placing below the chapter with the largest `y + height`) to avoid overlapping one that grew since it was created | §2 `GET .../chapters/bounds` |
|
|
34
38
|
| `add_column` | `boardId`, `timelineId`, `index?`, `beforeNodeId?`, `afterNodeId?`, `count?` | Add one or more columns in one call. `count` inserts that many contiguously starting at the insertion point (default 1). Position with at most one of `index` (0-based), `beforeNodeId`, or `afterNodeId` (resolves the index from where that already-placed node currently sits) — omit all three to append | §2 `POST .../timelines/:id/columns` |
|
|
35
|
-
| `delete_column` | `boardId`, `timelineId`, `
|
|
36
|
-
| `add_lane`
|
|
39
|
+
| `delete_column` | `boardId`, `timelineId`, `columnIds[]` | Delete columns, applied in order — the "cannot delete the last column" rule is checked after each deletion; answers `{results}` | §2 `DELETE .../columns/:columnId` |
|
|
40
|
+
| `add_lane` | `boardId`, `timelineId`, `lanes[]` (each `type`, `label?`, `index?`, `height?`) | Add lanes/rows, applied in order — each `index` resolves against the grid the previous entry left; answers `{results}`. For a chapter that doesn't exist yet, pass `lanes` to `create_chapter` instead — that needs no lane call at all | §2 `POST .../timelines/:id/lanes` (accepts an array too) |
|
|
37
41
|
| `remove_lane` | `boardId`, `timelineId`, `rowId` | Remove a lane | — (extends §2; no direct REST route) |
|
|
38
|
-
| `move_node_in_timeline` | `boardId`, `timelineId`, `movedNodeId`, `toCellId` | Move a placed node to another cell — its previous cell is automatically cleared | — (MCP-only convenience) |
|
|
39
42
|
| `move_timeline_structure` | `boardId`, `timelineId`, `kind` (`'column'\|'lane'`), `id`, `toIndex` | Reorder a column or lane (row) — `kind` picks which `id` refers to | — (MCP-only convenience) |
|
|
40
43
|
| `move_timeline_position` | `boardId`, `timelineId`, `x`, `y` | Move a chapter node on canvas | — (MCP-only convenience) |
|
|
41
|
-
| `drop_node_to_cell` | `boardId`, `timelineId`, `cellId`, `nodeId`, `nodeType` | Place an existing node into a cell — if it was already placed elsewhere on this timeline, that cell is automatically cleared | §2 `POST .../cells/:cellId/drop` |
|
|
42
|
-
| `clear_cell` | `boardId`, `timelineId`, `cellId` | Unassign the node from a cell without deleting it — the cell becomes empty and the node survives (unplaced); no-op if already empty. Use `delete_node` to remove the node entirely | — (MCP-only convenience) |
|
|
43
44
|
| `create_slice` | `boardId`, `timelineId`, `type`, `index?`, `nodes?: {actor?, interaction?, swimlane?}` (each `{rowId?, title?, fields?}`), `status?` | Create a full slice (column + nodes + SLICE_BORDER). `rowId` targets a specific lane when the chapter has more than one lane of that type (e.g. several actor lanes); omit to use the first matching lane. `fields` writes that node's attributes in the same call, and `status` gives the SLICE_BORDER its `sliceStatus` on creation — neither needs a follow-up write | §5 `POST .../slices` |
|
|
44
|
-
| `create_slice_definition` | `boardId`, `timelineId`, `columnId`, `title`, `status?`, `data?`, `meta?` | Create
|
|
45
|
-
| `place_element`
|
|
45
|
+
| `create_slice_definition` | `boardId`, `timelineId`, `slices[]` (each `columnId`, `title`, `status?`, `data?`, `meta?`) | Create SLICE_BORDERs over existing columns, one per entry, applied in order; answers `{results}`. `status` sets its `sliceStatus` straight away instead of a follow-up `update_slice_status` | §5 `POST .../slice-definitions` |
|
|
46
|
+
| `place_element` | `boardId`, `timelineId`, `elements[]` (each `action?` = `create` (default) with `elementType`, `title`, `fields?`, `lane?`, `columnIndex?`, `autoConnect?` · `move` with `nodeId` + `cellName`/`cellId` · `clear` with `cellName`/`cellId`), `compact?` | Find/create an empty cell in the right lane and place a COMMAND/READMODEL/EVENT/AUTOMATION/MARKDOWN/TABLE. `action: "move"` puts an existing node of any type into that (empty) cell, moving it if it already sits elsewhere on the timeline; `action: "clear"` empties a cell without deleting its node (`delete_node` deletes). These replace `drop_node_to_cell`, `move_node_in_timeline` and `clear_cell`, which still exist but are deprecated — don't use them. `fields` writes the element's attributes in the same call — don't follow a placement with a `submit_node_events` just to set them. A whole slice's or column run's worth goes in one call, applied in order so each entry sees the columns the previous one added; answers `{results}` (`compact: true` → `{nodeId, cellName, columnIndex}` per entry). `autoConnect: false` places without wiring to timeline neighbors — wire the edges yourself | — (MCP-only convenience; composes §2+§3) |
|
|
46
47
|
| `list_slices` | `boardId` | List slices (id, title, status) | §8 `GET .../slicedata/slices` |
|
|
47
48
|
| `get_slice_rework` | `boardId`, `contextId` | How much each slice of one context has been reworked: changes, steps backwards, and reopens after Done, most reworked first, plus planning metrics over them. `contextId` is a MODEL_CONTEXT or a timeline (a timeline resolves to the context it belongs to). Always per context — there is no board-wide form | §8 `GET .../reporting/rework/contexts/:contextId` |
|
|
48
49
|
| `update_slice_status` | `boardId`, `newStatus`, plus exactly one of `sliceId` / `sliceTitle` / `columnId` | Change a SLICE_BORDER's `sliceStatus`. With a title or column id there is no need to call `list_slices` first; an ambiguous title comes back with its candidates. A slice being created takes its status from `create_slice`/`create_slice_definition` instead | — (via `node:changed` event, §3) |
|
|
@@ -53,12 +54,12 @@ Server name: `eventmodelers`. Every tool takes `boardId` explicitly; none need `
|
|
|
53
54
|
| `validate_model` | `boardId`, `chapterId`, `checks?[]` | Server-side Event Modeling structural checklist over one chapter — compact `findings` only. Checks: unplaced nodes, backward arrows (with the todo-list `EVENT→READMODEL` exception), zero/multi-issuer commands, sourceless read models, two-screens-in-a-column, missing scenarios. Replaces the manual per-type `get_nodes` + `get_node projection=edges` validation pass | — (MCP-only convenience) |
|
|
54
55
|
| `add_scenario` | `boardId`, `timelineId`, `columnId`, `scenarios[]`, `compact?` | Append GWT scenario(s) to a column's spec node — created automatically, and a scenario `id` is generated when omitted. A given/when/then step may be addressed by `{title, type}` instead of a node id, resolved against that timeline, so no `get_spec_info` call is needed first (an ambiguous title is reported with its candidates). `compact: true` returns `{specNodeId, added, scenarioCount, isNewNode}` instead of echoing every scenario back | §6 `POST .../scenarios` |
|
|
55
56
|
| `add_storyline` | `boardId`, `timelineId`, `columnId`, `storylines[]`, `compact?` | Append storyline(s) (ordered, branchable beats over existing elements) to a column's spec node. Use whenever `eventmodeling-elaborating-scenarios`'s GWT-vs-storyline decision rule calls for one (e.g. a todo list's open→close lifecycle) — not only when a user explicitly names "storyline"; that skill's own per-read-model judgment is the trigger, this catalog entry isn't a stricter gate on top of it. `compact: true` suppresses the full storyline echo | §6 `POST .../storylines` |
|
|
56
|
-
| `set_connection` | `boardId`, `source`, `target`, `action`
|
|
57
|
-
| `auto_connect_node` | `boardId`, `
|
|
57
|
+
| `set_connection` | `boardId`, `connections[]` (each `source`, `target`, `action` `'connect'\|'remove'`), `compact?` | Add or remove type-checked directed edges, applied in order (a later entry can rely on an earlier edge); answers `{results}`, or with `compact: true` a `{connected, existed, removed, notFound, failed, errors}` tally | — (via `edges` on §3 events) |
|
|
58
|
+
| `auto_connect_node` | `boardId`, `nodeIds[]` | Re-run auto-connect for nodes — incompatible neighbours come back in `skipped`, not as an error; answers `{results}` | §3 `POST .../nodes/:nodeId/auto-connect` |
|
|
58
59
|
| `link_element` | `boardId`, `nodeId`, plus either `targetNodeId` or `timelineId` (+ `columnIndex?`, `lane?`) | Turn a node into a linked copy of `nodeId` — it receives a full copy of that node's meta plus `meta.linkedTo`. Name an existing `targetNodeId`, or pass `timelineId` to have the copy placed and linked in this one call (inheriting the original's type and title), which is what a translation or automation chain wants | §3 `POST .../nodes/:nodeId/link` |
|
|
59
|
-
| `add_comment`
|
|
60
|
+
| `add_comment` | `boardId`, `comments[]` (each `nodeId`, `text`, `type?` `'COMMENT'\|'TASK'\|'QUESTION'`, `author?`) | Add comments on any nodes of one board — `QUESTION` is the type for a gap/edge case raised during review, and a review posts all its questions in one call; answers `{results}` in request order | §1 `POST .../boards/:boardId/comments` (batch) |
|
|
60
61
|
| `update_comment` | `boardId`, `nodeId`, `commentId`, `action` (`'resolve'\|'delete'`) | Resolve or delete a comment | — (via comment events) |
|
|
61
|
-
| `create_screen` | `boardId`, `
|
|
62
|
+
| `create_screen` | `boardId`, `screens[]` (each `contentType` `'image'\|'sketch'\|'html'`, `chapterId`, `cellId?`/`cellName?`, `nodeId?`, content fields `imageBase64`/`mimeType`, `elements[]`, or `pages[]`/`backgroundColor`, `title?`, `description?`, `fields?`), `autoConnect?` | Create + place new screen nodes (SCREEN or HTML_SCREEN) with their content, in one call; entries applied in order, one failure doesn't stop the rest; answers `{results}`. `title` names the node (`meta.title`) in the same call — no follow-up `node:changed` just to label the screen. `autoConnect: false` places without wiring to timeline neighbors | §4 `POST .../images/:id/sketch` + `image-nodes` |
|
|
62
63
|
| `render_screen` | `boardId`, `nodeId`, `elements[]?` (SCREEN) or `pages[]?`+`backgroundColor?` (HTML_SCREEN), `description?` | Update an existing screen's content — exactly one of `elements`/`pages` | §4 `POST .../images/:id/sketch` + `image-nodes` |
|
|
63
64
|
| `add_field_examples` | `boardId`, `nodeId?`, `name?`, `cellName?`, `timelineId?` | Fill empty field examples using linked-node context | — (MCP-only convenience) |
|
|
64
65
|
| `get_attribute_chain` | `boardId`, `timelineId`, `targetCellName`, `sourceCellName` | Resolve every node between two cells, ordered target→source. Both cell names must come from a node's reported `cellName` (`get_board_outline`, `get_nodes`, `get_node`) — a guessed address that happens to exist silently resolves the wrong chain | — (MCP-only convenience) |
|
|
@@ -303,13 +303,15 @@ Reconstruct the captured screen as a real HTML/CSS fragment from `screen.capture
|
|
|
303
303
|
```
|
|
304
304
|
mcp__eventmodelers__create_screen {
|
|
305
305
|
"boardId": "<BOARD_ID>",
|
|
306
|
-
"
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
306
|
+
"screens": [{
|
|
307
|
+
"contentType": "html",
|
|
308
|
+
"nodeId": "<SCREEN_NODE_ID>",
|
|
309
|
+
"chapterId": "<CHAPTER_ID>",
|
|
310
|
+
"cellId": "<CELL_ID>",
|
|
311
|
+
"title": "<screen.title>",
|
|
312
|
+
"pages": ["<reconstructed HTML fragment for this screen>"],
|
|
313
|
+
"description": "<screen.description — 'Shows X. Arrived via: Y. Actions: user can do A, user can do B.'>"
|
|
314
|
+
}]
|
|
313
315
|
}
|
|
314
316
|
```
|
|
315
317
|
|
|
@@ -320,14 +322,16 @@ mcp__eventmodelers__create_screen {
|
|
|
320
322
|
```
|
|
321
323
|
mcp__eventmodelers__create_screen {
|
|
322
324
|
"boardId": "<BOARD_ID>",
|
|
323
|
-
"
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
325
|
+
"screens": [{
|
|
326
|
+
"contentType": "image",
|
|
327
|
+
"nodeId": "<SCREEN_NODE_ID>",
|
|
328
|
+
"chapterId": "<CHAPTER_ID>",
|
|
329
|
+
"cellName": "<CELL_NAME>",
|
|
330
|
+
"title": "<screen.title>",
|
|
331
|
+
"imageBase64": "<base64-encoded contents of screen.filepath, no data: URI prefix>",
|
|
332
|
+
"mimeType": "image/png",
|
|
333
|
+
"description": "<screen.description — 'Shows X. Arrived via: Y. Actions: user can do A, user can do B.'>"
|
|
334
|
+
}]
|
|
331
335
|
}
|
|
332
336
|
```
|
|
333
337
|
|
|
@@ -84,9 +84,11 @@ The todo-list read model goes in the interaction lane, **one column before** its
|
|
|
84
84
|
mcp__eventmodelers__place_element {
|
|
85
85
|
"boardId": "<BOARD_ID>",
|
|
86
86
|
"timelineId": "<CHAPTER_ID>",
|
|
87
|
-
"
|
|
88
|
-
|
|
89
|
-
|
|
87
|
+
"elements": [{
|
|
88
|
+
"elementType": "READMODEL",
|
|
89
|
+
"title": "NotificationsToSend",
|
|
90
|
+
"columnIndex": <automationColumnIndex - 1>
|
|
91
|
+
}]
|
|
90
92
|
}
|
|
91
93
|
```
|
|
92
94
|
Then set `meta.fields` and `meta.listElement: true` on the returned node id:
|
|
@@ -102,7 +104,7 @@ mcp__eventmodelers__submit_node_events {
|
|
|
102
104
|
|
|
103
105
|
**Fallback (no MCP):** see `references/api-fallback.md` — "Placement — todo-list READMODEL, one column before its automation".
|
|
104
106
|
|
|
105
|
-
**Place every node in a translation chain with `autoConnect: false`** (`place_element` / `create_screen` / `submit_node_events` all take the flag). A chain's columns are inserted into the middle of an existing timeline, so the default nearest-left auto-connect will wire the new todo-list READMODEL or internal EVENT to whatever unrelated event sits in the column before it — the recurring "spurious auto-connect edge near an automation chain" cleanup. Suppress it and build the chain's edges yourself in the `
|
|
107
|
+
**Place every node in a translation chain with `autoConnect: false`** (`place_element` / `create_screen` / `submit_node_events` all take the flag). A chain's columns are inserted into the middle of an existing timeline, so the default nearest-left auto-connect will wire the new todo-list READMODEL or internal EVENT to whatever unrelated event sits in the column before it — the recurring "spurious auto-connect edge near an automation chain" cleanup. Suppress it and build the chain's edges yourself in the `set_connection` batch below, then confirm with `validate_model`.
|
|
106
108
|
|
|
107
109
|
> **Never call `drop` after using `cellId` in `node:created`.** The drop endpoint adds a second cell reference without removing the first.
|
|
108
110
|
|
|
@@ -112,11 +114,11 @@ For a **translation-chain automation**, place its three columns left to right in
|
|
|
112
114
|
|
|
113
115
|
1. **READMODEL → AUTOMATION first** — the automation reads its own todo list. The closing connection below is only accepted once this edge exists.
|
|
114
116
|
```
|
|
115
|
-
mcp__eventmodelers__set_connection { "boardId": "<BOARD_ID>", "source": "<readmodelNodeId>", "target": "<automationNodeId>", "action": "connect" }
|
|
117
|
+
mcp__eventmodelers__set_connection { "boardId": "<BOARD_ID>", "connections": [{ "source": "<readmodelNodeId>", "target": "<automationNodeId>", "action": "connect" }] }
|
|
116
118
|
```
|
|
117
119
|
2. **Every opening EVENT → READMODEL.**
|
|
118
120
|
```
|
|
119
|
-
mcp__eventmodelers__set_connection { "boardId": "<BOARD_ID>", "source": "<openingEventNodeId>", "target": "<readmodelNodeId>", "action": "connect" }
|
|
121
|
+
mcp__eventmodelers__set_connection { "boardId": "<BOARD_ID>", "connections": [{ "source": "<openingEventNodeId>", "target": "<readmodelNodeId>", "action": "connect" }] }
|
|
120
122
|
```
|
|
121
123
|
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.
|
|
122
124
|
|
|
@@ -124,7 +126,7 @@ For a **translation-chain automation**, place its three columns left to right in
|
|
|
124
126
|
|
|
125
127
|
Skip a connection silently if the target cell is empty (the element may not exist yet). Log each created arrow, e.g. `→ connected READMODEL→AUTOMATION "NotificationsToSend"→"Send Welcome Notification"`.
|
|
126
128
|
|
|
127
|
-
If this step is designing more than one automation's chain in the same pass,
|
|
129
|
+
If this step is designing more than one automation's chain in the same pass, put every connection from every automation into one `set_connection` call (with `compact: true`) instead of one call per edge — `set_connection` still applies its entries **in order**, so keep each automation's own three-edge sequence intact (READMODEL→AUTOMATION before its closing EVENT→READMODEL) within the combined array; different automations' triples can be interleaved or concatenated freely since they don't depend on each other.
|
|
128
130
|
|
|
129
131
|
## Verification (run before moving to Step 5)
|
|
130
132
|
|
package/stacks/modeling-kit/templates/.claude/skills/eventmodeling-identifying-inputs/SKILL.md
CHANGED
|
@@ -148,11 +148,13 @@ Commands go in the **interaction lane**, same column as the event they produce.
|
|
|
148
148
|
mcp__eventmodelers__place_element {
|
|
149
149
|
"boardId": "<BOARD_ID>",
|
|
150
150
|
"timelineId": "<CHAPTER_ID>",
|
|
151
|
-
"
|
|
152
|
-
|
|
151
|
+
"elements": [{
|
|
152
|
+
"elementType": "COMMAND",
|
|
153
|
+
"title": "ReserveBike"
|
|
154
|
+
}]
|
|
153
155
|
}
|
|
154
156
|
```
|
|
155
|
-
`place_element` finds/creates the empty cell in the interaction lane in the correct column and places the node
|
|
157
|
+
`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:
|
|
156
158
|
```
|
|
157
159
|
mcp__eventmodelers__submit_node_events {
|
|
158
160
|
"boardId": "<BOARD_ID>",
|
|
@@ -201,9 +203,9 @@ After `place-element` returns the COMMAND node ID, create the arrows that comple
|
|
|
201
203
|
```
|
|
202
204
|
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:
|
|
203
205
|
```
|
|
204
|
-
mcp__eventmodelers__set_connection { "boardId": "<BOARD_ID>", "source": "<screenNodeId>", "target": "<commandNodeId>", "action": "connect" }
|
|
206
|
+
mcp__eventmodelers__set_connection { "boardId": "<BOARD_ID>", "connections": [{ "source": "<screenNodeId>", "target": "<commandNodeId>", "action": "connect" }] }
|
|
205
207
|
```
|
|
206
|
-
If wiring more than one COMMAND in the same pass,
|
|
208
|
+
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.
|
|
207
209
|
|
|
208
210
|
**Fallback (no MCP):** see `references/api-fallback.md` — "Wire connections — Step 1: SCREEN → COMMAND".
|
|
209
211
|
|
|
@@ -217,7 +219,7 @@ After `place-element` returns the COMMAND node ID, create the arrows that comple
|
|
|
217
219
|
```
|
|
218
220
|
Read `cells["<swimlaneRowId>-<columnId>"]` for the occupying node id, then:
|
|
219
221
|
```
|
|
220
|
-
mcp__eventmodelers__set_connection { "boardId": "<BOARD_ID>", "source": "<commandNodeId>", "target": "<eventNodeId>", "action": "connect" }
|
|
222
|
+
mcp__eventmodelers__set_connection { "boardId": "<BOARD_ID>", "connections": [{ "source": "<commandNodeId>", "target": "<eventNodeId>", "action": "connect" }] }
|
|
221
223
|
```
|
|
222
224
|
|
|
223
225
|
**Fallback (no MCP):** see `references/api-fallback.md` — "Wire connections — Step 2: COMMAND → EVENT".
|
|
@@ -226,7 +228,7 @@ After `place-element` returns the COMMAND node ID, create the arrows that comple
|
|
|
226
228
|
|
|
227
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"`.
|
|
228
230
|
|
|
229
|
-
If wiring more than one COMMAND in this pass, send all the SCREEN→COMMAND and COMMAND→EVENT edges in one `
|
|
231
|
+
If wiring more than one COMMAND in this pass, send all the SCREEN→COMMAND and COMMAND→EVENT edges in one `set_connection` call with `compact: true` — you author every edge here deliberately, so the `{connected, existed, removed, notFound, failed, errors}` tally is enough.
|
|
230
232
|
|
|
231
233
|
**After wiring, run `validate_model` (`{boardId, chapterId}`).** Its `command-issuers` finding flags any COMMAND that ended up with two issuers — the classic symptom of the platform's auto-connect cross-wiring a previous-column SCREEN into a command that an AUTOMATION already drives (or vice versa). Fix each by removing the wrong edge. If you already know a placement sits next to an unrelated column whose SCREEN/AUTOMATION would be mis-wired, place that COMMAND with `autoConnect: false` and wire its single real issuer yourself.
|
|
232
234
|
|
package/stacks/modeling-kit/templates/.claude/skills/eventmodeling-identifying-outputs/SKILL.md
CHANGED
|
@@ -256,9 +256,11 @@ Read models go in the `interaction` lane. For a **SCREEN**, the primary read mod
|
|
|
256
256
|
mcp__eventmodelers__place_element {
|
|
257
257
|
"boardId": "<BOARD_ID>",
|
|
258
258
|
"timelineId": "<CHAPTER_ID>",
|
|
259
|
-
"
|
|
260
|
-
|
|
261
|
-
|
|
259
|
+
"elements": [{
|
|
260
|
+
"elementType": "READMODEL",
|
|
261
|
+
"title": "ActiveReservationView",
|
|
262
|
+
"columnIndex": <consumerScreenColumnIndex> // or <automationColumnIndex - 1> for an AUTOMATION consumer
|
|
263
|
+
}]
|
|
262
264
|
}
|
|
263
265
|
```
|
|
264
266
|
Then set `meta.fields` (with `mapping`/`generated`/`cardinality`) — and `meta.listElement: true` if this read model is list-shaped — on the returned node id:
|
|
@@ -279,7 +281,7 @@ mcp__eventmodelers__get_node { "boardId": "<BOARD_ID>", "nodeId": "<CHAPTER_ID>"
|
|
|
279
281
|
|
|
280
282
|
**Fallback (no MCP):** see `references/api-fallback.md` — "Step 5f — Placing a READMODEL node (full manual sequence)".
|
|
281
283
|
|
|
282
|
-
**Pass `autoConnect: false` on every `place_element` / `node:created` call in this step.** Output read-model columns are inserted right next to automation-chain and command columns whose events are *not* the read model's sources — the default auto-connect would wire the new READMODEL to the nearest event on its left, which is exactly the stray-edge cleanup this step has repeatedly generated. Suppress it here and wire every `EVENT → READMODEL` / `READMODEL → SCREEN` / `READMODEL → AUTOMATION` edge explicitly in Step 5h's `
|
|
284
|
+
**Pass `autoConnect: false` on every `place_element` / `node:created` call in this step.** Output read-model columns are inserted right next to automation-chain and command columns whose events are *not* the read model's sources — the default auto-connect would wire the new READMODEL to the nearest event on its left, which is exactly the stray-edge cleanup this step has repeatedly generated. Suppress it here and wire every `EVENT → READMODEL` / `READMODEL → SCREEN` / `READMODEL → AUTOMATION` edge explicitly in Step 5h's `set_connection` batch. After the batch, run `validate_model` (`{boardId, chapterId}`) — its `backward-arrows` and `readmodel-sources` findings confirm the wiring is what you intended and no stray edge slipped in.
|
|
283
285
|
|
|
284
286
|
**For an AUTOMATION** (actor lane) — its READMODEL always goes **one column to its left**, never the same column: the automation's own column already holds the COMMAND it issues (interaction row), so the read model can't also live there.
|
|
285
287
|
|
|
@@ -325,7 +327,7 @@ After `place-element` returns the READMODEL node ID, create the arrows that comp
|
|
|
325
327
|
```
|
|
326
328
|
mcp__eventmodelers__get_node { "boardId": "<BOARD_ID>", "nodeId": "<CHAPTER_ID>", "projection": "cells" }
|
|
327
329
|
# → read cells["<swimlaneRowId>-<columnId>"] for the occupying node id
|
|
328
|
-
mcp__eventmodelers__set_connection { "boardId": "<BOARD_ID>", "source": "<eventNodeId>", "target": "<readmodelNodeId>", "action": "connect" }
|
|
330
|
+
mcp__eventmodelers__set_connection { "boardId": "<BOARD_ID>", "connections": [{ "source": "<eventNodeId>", "target": "<readmodelNodeId>", "action": "connect" }] }
|
|
329
331
|
```
|
|
330
332
|
|
|
331
333
|
**Fallback (no MCP):** see `references/api-fallback.md` — "Step 5h.1 — Wire EVENT → READMODEL".
|
|
@@ -334,7 +336,7 @@ After `place-element` returns the READMODEL node ID, create the arrows that comp
|
|
|
334
336
|
|
|
335
337
|
**Prefer MCP:**
|
|
336
338
|
```
|
|
337
|
-
mcp__eventmodelers__set_connection { "boardId": "<BOARD_ID>", "source": "<readmodelNodeId>", "target": "<screenNodeId>", "action": "connect" }
|
|
339
|
+
mcp__eventmodelers__set_connection { "boardId": "<BOARD_ID>", "connections": [{ "source": "<readmodelNodeId>", "target": "<screenNodeId>", "action": "connect" }] }
|
|
338
340
|
```
|
|
339
341
|
|
|
340
342
|
**Fallback (no MCP):** see `references/api-fallback.md` — "Step 5h.2 — Wire READMODEL → SCREEN".
|
|
@@ -345,14 +347,14 @@ After `place-element` returns the READMODEL node ID, create the arrows that comp
|
|
|
345
347
|
|
|
346
348
|
**Prefer MCP:**
|
|
347
349
|
```
|
|
348
|
-
mcp__eventmodelers__set_connection { "boardId": "<BOARD_ID>", "source": "<readmodelNodeId>", "target": "<automationNodeId>", "action": "connect" }
|
|
350
|
+
mcp__eventmodelers__set_connection { "boardId": "<BOARD_ID>", "connections": [{ "source": "<readmodelNodeId>", "target": "<automationNodeId>", "action": "connect" }] }
|
|
349
351
|
```
|
|
350
352
|
|
|
351
353
|
**Fallback (no MCP):** see `references/api-fallback.md` — "Step 5h.3 — Wire READMODEL → AUTOMATION".
|
|
352
354
|
|
|
353
355
|
Skip a connection silently if the target cell is empty. Log each created arrow: `→ connected EVENT→READMODEL "OrderPlaced"→"OrderStatusView"`, `→ connected READMODEL→SCREEN "OrderStatusView"→"Order Status Screen"`, or `→ connected READMODEL→AUTOMATION "OrderStatusView"→"Fulfillment Processor"`.
|
|
354
356
|
|
|
355
|
-
If this step is processing more than one read model in the same pass, collect every connection resolved above (across all of them) into one `
|
|
357
|
+
If this step is processing more than one read model in the same pass, collect every connection resolved above (across all of them) into one `set_connection` call instead of one call per pair — this was the single largest source of individual tool calls in this step. Pass `compact: true` on that call: with `autoConnect: false` set at placement, every edge here is one you're deliberately creating, so a `{connected, existed, removed, notFound, failed, errors}` tally is all you need back — not a row per edge.
|
|
356
358
|
|
|
357
359
|
4. **Document the reasoning for each connected event** — for every EVENT → READMODEL edge wired in step 1 (including any added later, e.g. via Step 5g's backward-connection exemption or a Step 5c/copy pattern), record why that event feeds this read model: which field(s) it sets or updates, and why. Use one MARKDOWN note per read model, in that read model's own column (same feedback-lane + MARKDOWN mechanics as `eventmodeling-orchestrating-event-modeling`'s "Documenting decisions inline, at any step" / Step 11 — add the chapter's feedback lane first if it doesn't already exist, then place the note at `cellId = "<feedbackLaneId>-<readModelColumnId>"`). Extend the existing note (don't create a second one) when the read model later gains another connected event.
|
|
358
360
|
|
|
@@ -100,9 +100,9 @@ For each returned node, check whether it has a valid cell assignment. A node wit
|
|
|
100
100
|
**For each unplaced node:**
|
|
101
101
|
- **If it belongs in the current model** → compute the correct `cellId` and place it.
|
|
102
102
|
|
|
103
|
-
**Prefer MCP:** the node already exists
|
|
103
|
+
**Prefer MCP:** the node already exists, so give `place_element` an `action: "move"` entry with its `nodeId` and the target cell — the node keeps its type, and the same entry moves it if it already sits in another cell:
|
|
104
104
|
```
|
|
105
|
-
|
|
105
|
+
mcp__eventmodelers__place_element { "boardId": "$BOARD_ID", "timelineId": "<chapterId>", "elements": [{ "action": "move", "nodeId": "<nodeId>", "cellName": "<e.g. B2>" }] }
|
|
106
106
|
```
|
|
107
107
|
|
|
108
108
|
**Fallback (no MCP):** see `references/api-fallback.md` — "No unplaced elements (0,0 nodes) — Place a found unplaced node".
|
|
@@ -110,7 +110,7 @@ For each returned node, check whether it has a valid cell assignment. A node wit
|
|
|
110
110
|
|
|
111
111
|
**Prefer MCP:**
|
|
112
112
|
```
|
|
113
|
-
mcp__eventmodelers__delete_node { "boardId": "$BOARD_ID", "
|
|
113
|
+
mcp__eventmodelers__delete_node { "boardId": "$BOARD_ID", "nodeIds": ["<nodeId>"] }
|
|
114
114
|
```
|
|
115
115
|
|
|
116
116
|
**Fallback (no MCP):** see `references/api-fallback.md` — "No unplaced elements (0,0 nodes) — Delete an orphaned node".
|
|
@@ -135,18 +135,20 @@ Screens placed during Step 3 (Storyboarding) are provisional positions. Steps 4
|
|
|
135
135
|
### Column insertion
|
|
136
136
|
Use `add_column` with `{"index": N}` to insert a column at a specific position (shifts existing columns right) — or, when the insertion point is "immediately before/after a node already on the board" rather than a numeric position you'd otherwise have to compute, pass `beforeNodeId`/`afterNodeId` instead and let the tool resolve the index itself. Do not use no position at all (append) when placing read models or view screens — always target the correct position.
|
|
137
137
|
|
|
138
|
-
**Suppress auto-connect when inserting into an existing chain.** When you insert columns next to nodes that are *not* meant to connect to what you're about to place — e.g. slotting an output read model's column in beside an automation-chain column — the node placement's default auto-connect will wire the new node to whatever type-compatible node happens to sit in its own or the previous column (the "nearest event to the left"). That is the source of the recurring stray-edge cleanup. When the placement you're about to make should be wired only by your own explicit `
|
|
138
|
+
**Suppress auto-connect when inserting into an existing chain.** When you insert columns next to nodes that are *not* meant to connect to what you're about to place — e.g. slotting an output read model's column in beside an automation-chain column — the node placement's default auto-connect will wire the new node to whatever type-compatible node happens to sit in its own or the previous column (the "nearest event to the left"). That is the source of the recurring stray-edge cleanup. When the placement you're about to make should be wired only by your own explicit `set_connection` batch, pass `autoConnect: false` on the placing call (`submit_node_events`, `place_element`, `create_screen`) and then wire every edge yourself. Keep the default (auto-connect on) for Steps 1/3/4 where same-column neighbors are exactly the intended wiring.
|
|
139
139
|
|
|
140
|
-
###
|
|
140
|
+
### Every write tool takes an array — fill it
|
|
141
141
|
|
|
142
|
-
|
|
142
|
+
Each write tool takes its items as an array, so handling several in one pass is one call rather than a loop. Entries are applied in order — which matters where a later one depends on an earlier one, e.g. wiring a READMODEL→AUTOMATION edge before the backward EVENT→READMODEL edge that needs it — every entry reports its own outcome or error in `results`, and one failure does not stop the rest. Whenever a step's instructions below show a one-entry call and more than one item is being processed in the same pass, extend the array instead of repeating the call:
|
|
143
143
|
|
|
144
|
-
- `
|
|
145
|
-
- `
|
|
146
|
-
- `
|
|
147
|
-
- `
|
|
148
|
-
- `
|
|
149
|
-
- `
|
|
144
|
+
- `set_connection`'s `connections` — wiring multiple edges
|
|
145
|
+
- `auto_connect_node`'s `nodeIds` — auto-connecting multiple freshly-placed nodes
|
|
146
|
+
- `create_slice_definition`'s `slices` — defining multiple slices
|
|
147
|
+
- `create_screen`'s `screens` — creating multiple screens
|
|
148
|
+
- `place_element`'s `elements` — laying out a whole slice or column run, each entry seeing the columns the previous one added
|
|
149
|
+
- `place_element`'s `elements` with `{action: "move", nodeId, cellName}` entries — placing or moving multiple existing nodes within one timeline
|
|
150
|
+
- `delete_node`'s `nodeIds` / `delete_column`'s `columnIds` — removing multiple nodes or columns, e.g. a corrective cleanup after a modeling mistake
|
|
151
|
+
- `add_comment`'s `comments`, `create_drawing`'s `drawings`, `add_lane`'s `lanes` — same shape
|
|
150
152
|
- `add_column`'s `count` param (not `add_column` repeated) — appending or inserting several columns at once; `beforeNodeId`/`afterNodeId` resolve the insertion point from an already-placed node instead of a computed index
|
|
151
153
|
- `create_chapter`'s `columns` param — when the chapter's initial column count is already known, instead of creating the default 3 and appending more after
|
|
152
154
|
|
|
@@ -156,7 +158,7 @@ For a "what is on the board and how is it wired right now" check between steps
|
|
|
156
158
|
|
|
157
159
|
**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.
|
|
158
160
|
|
|
159
|
-
**Ask echo-heavy write tools for less.** `add_scenario`, `add_storyline`, `
|
|
161
|
+
**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.
|
|
160
162
|
|
|
161
163
|
### Documenting decisions inline, at any step
|
|
162
164
|
|
|
@@ -416,7 +418,7 @@ Not delegated to a separate skill — performed directly by this orchestrating s
|
|
|
416
418
|
|
|
417
419
|
**Prefer MCP:**
|
|
418
420
|
```
|
|
419
|
-
mcp__eventmodelers__add_lane { "boardId": "$BOARD_ID", "timelineId": "$CHAPTER_ID", "type": "feedback", "label": "Notes" }
|
|
421
|
+
mcp__eventmodelers__add_lane { "boardId": "$BOARD_ID", "timelineId": "$CHAPTER_ID", "lanes": [{ "type": "feedback", "label": "Notes" }] }
|
|
420
422
|
```
|
|
421
423
|
|
|
422
424
|
**Fallback (no MCP):** see `references/api-fallback.md` — "Step 11 — Document Reasoning — Add a feedback lane".
|
package/stacks/modeling-kit/templates/.claude/skills/eventmodeling-slicing-event-models/SKILL.md
CHANGED
|
@@ -99,7 +99,7 @@ For every column from Step 2 that doesn't already have a matching slice, mark ea
|
|
|
99
99
|
|
|
100
100
|
Prefer MCP:
|
|
101
101
|
```
|
|
102
|
-
|
|
102
|
+
mcp__eventmodelers__create_slice_definition { "boardId": "<BOARD_ID>", "timelineId": "<TL>", "slices": [
|
|
103
103
|
{ "columnId": "<colId1>", "title": "PlaceOrder" },
|
|
104
104
|
{ "columnId": "<colId2>", "title": "OrderStatusView" }
|
|
105
105
|
] }
|
|
@@ -112,7 +112,7 @@ mcp__eventmodelers__create_slice_definitions { "boardId": "<BOARD_ID>", "timelin
|
|
|
112
112
|
- READMODEL column → title = read model name (state-view slice)
|
|
113
113
|
- AUTOMATION column → title = automation name, or the command it issues (automation slice)
|
|
114
114
|
|
|
115
|
-
Use **`
|
|
115
|
+
Use **`create_slice_definition`/`slice-definitions`**, never `create_slice`/the plain **`slices`** endpoint here — `create_slice`/`slices` creates a brand-new column with its own swimlane/content nodes, which would duplicate the element already placed on the timeline. `create_slice_definition`/`slice-definitions` only adds a `SLICE_BORDER` node to each column you already resolved in Step 2. `title` always comes from the request body — it is never derived automatically from the command/read model/automation node.
|
|
116
116
|
|
|
117
117
|
**If Step 2 finds nothing to slice** (every COMMAND/READMODEL/AUTOMATION on the timeline already has a matching `SLICE_BORDER`), this skill's job is done — there is no existing element left to make explicit. Do not invent new model content here; that is out of scope for a skill whose whole design assumes the model is already complete. Invoke the `add-next-slice` skill instead — it owns deciding on and creating a genuinely new slice from scratch.
|
|
118
118
|
|
|
@@ -50,10 +50,10 @@ No slice depends on another slice directly — only on the events it produces.
|
|
|
50
50
|
|
|
51
51
|
## Creating These Slices via the API
|
|
52
52
|
|
|
53
|
-
These elements already exist on the timeline (from `spec-info`) — use `
|
|
53
|
+
These elements already exist on the timeline (from `spec-info`) — use `create_slice_definition`/`slice-definitions`, which only adds a `SLICE_BORDER` to each column's existing element. Never use `create_slice`/the plain `/slices` endpoint here: that endpoint creates a brand-new column with its own nodes, which would duplicate the element already on the board.
|
|
54
54
|
|
|
55
55
|
```
|
|
56
|
-
|
|
56
|
+
mcp__eventmodelers__create_slice_definition { "boardId": "<BOARD_ID>", "timelineId": "<TL>", "slices": [
|
|
57
57
|
{ "columnId": "<placeOrderColumnId>", "title": "PlaceOrder" },
|
|
58
58
|
{ "columnId": "<orderDetailViewColumnId>", "title": "OrderDetailView" },
|
|
59
59
|
{ "columnId": "<reserveInventoryOnPaymentColumnId>", "title": "ReserveInventoryOnPayment" }
|
package/stacks/modeling-kit/templates/.claude/skills/eventmodeling-storyboarding-events/SKILL.md
CHANGED
|
@@ -122,7 +122,7 @@ After completing the screen analysis, use the `handle-comment` skill to post a c
|
|
|
122
122
|
|
|
123
123
|
**Prefer MCP:**
|
|
124
124
|
```
|
|
125
|
-
mcp__eventmodelers__add_lane { "boardId": "$BOARD_ID", "timelineId": "$CHAPTER_ID", "type": "actor", "label": "<Role Name>" }
|
|
125
|
+
mcp__eventmodelers__add_lane { "boardId": "$BOARD_ID", "timelineId": "$CHAPTER_ID", "lanes": [{ "type": "actor", "label": "<Role Name>" }] }
|
|
126
126
|
```
|
|
127
127
|
|
|
128
128
|
**Fallback (no MCP):** see `references/api-fallback.md` — "Resolve One Actor Lane Per Human Role — Step 3: Create a new actor lane".
|
|
@@ -226,13 +226,15 @@ Every screen node requires rendered content. **HTML_SCREEN (via the `html-screen
|
|
|
226
226
|
```
|
|
227
227
|
mcp__eventmodelers__create_screen {
|
|
228
228
|
"boardId": "<BOARD_ID>",
|
|
229
|
-
"
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
229
|
+
"screens": [{
|
|
230
|
+
"contentType": "html",
|
|
231
|
+
"nodeId": "<node-uuid>",
|
|
232
|
+
"chapterId": "<CHAPTER_ID>",
|
|
233
|
+
"cellId": "<actorRowId>-<columnId>",
|
|
234
|
+
"pages": ["<div>...</div>"],
|
|
235
|
+
"description": "<concise description of what this screen shows>",
|
|
236
|
+
"fields": [ /* per "Mandatory Field Definitions" below — set in this same call */ ]
|
|
237
|
+
}]
|
|
236
238
|
}
|
|
237
239
|
```
|
|
238
240
|
|
|
@@ -240,7 +242,7 @@ mcp__eventmodelers__create_screen {
|
|
|
240
242
|
|
|
241
243
|
The MCP `create_screen` call above already sets `meta.fields` (per "Mandatory Field Definitions" below) in the same call — no separate `node:changed` follow-up needed when using MCP.
|
|
242
244
|
|
|
243
|
-
A storyboard screen is placed at a *provisional* position — Steps 4 and 5 wire it to its COMMAND / READMODEL once those exist, and may move it first. Pass `autoConnect: false` on `create_screen`
|
|
245
|
+
A storyboard screen is placed at a *provisional* position — Steps 4 and 5 wire it to its COMMAND / READMODEL once those exist, and may move it first. Pass `autoConnect: false` on `create_screen` here so the placement doesn't pre-wire the screen to whatever happens to sit in the adjacent column; the real `SCREEN → COMMAND` and `READMODEL → SCREEN` edges are created deliberately in Steps 4 and 5. When creating several screens whose HTML is already authored, put them all in one `create_screen` call's `screens` array with `autoConnect: false`.
|
|
244
246
|
|
|
245
247
|
Design the page(s) as real HTML/CSS, following the `html-screen` skill's guidance: write full-size markup (16px body text, generous padding — the canvas scales it down, don't shrink it yourself), one complete self-contained fragment per page (no `<html>`/`<head>`/`<body>` wrapper — the canvas adds those), no `<script>`/inline handlers (stripped server-side), and Bulma CSS classes (`title`, `button`, `is-primary`, `field`/`control`/`input`, etc. — remember heading size modifiers like `class="title is-1"`) since Bulma 0.9.4 is loaded by default. Every page MUST include real field labels matching the actual event/command fields this screen captures or displays, and at least one primary action (submit/confirm button) for command screens.
|
|
246
248
|
|
|
@@ -43,7 +43,7 @@ Route to the matching section below based on `action`.
|
|
|
43
43
|
**Prefer MCP** — one call, `type` (`COMMENT`/`TASK`) passed straight through:
|
|
44
44
|
|
|
45
45
|
```
|
|
46
|
-
mcp__eventmodelers__add_comment { "boardId": "$BOARD_ID", "nodeId": "$NODE_ID", "text": "<text>", "type": "<COMMENT|TASK>", "author": "<author>" }
|
|
46
|
+
mcp__eventmodelers__add_comment { "boardId": "$BOARD_ID", "comments": [{ "nodeId": "$NODE_ID", "text": "<text>", "type": "<COMMENT|TASK>", "author": "<author>" }] }
|
|
47
47
|
```
|
|
48
48
|
|
|
49
49
|
**Fallback (no MCP):** see `references/api-fallback.md` — "Action: place".
|
|
@@ -58,7 +58,7 @@ curl -s -X POST "$BASE_URL/api/org/$ORG_ID/boards/$BOARD_ID/comments" \
|
|
|
58
58
|
{"nodeId":"<id2>","text":"<text2>","type":"QUESTION","author":"wdyt"}]'
|
|
59
59
|
```
|
|
60
60
|
|
|
61
|
-
Response: `201 {"results":[{"nodeId":"<id>","id":"<commentId>"}, …]}` in request order — an entry whose node doesn't exist reports `error` there without dropping the rest. Over MCP this is the `
|
|
61
|
+
Response: `201 {"results":[{"nodeId":"<id>","id":"<commentId>"}, …]}` in request order — an entry whose node doesn't exist reports `error` there without dropping the rest. Over MCP this is the `add_comment` tool's `comments` array.
|
|
62
62
|
|
|
63
63
|
**Report:**
|
|
64
64
|
```
|
|
@@ -135,12 +135,14 @@ mcp__eventmodelers__render_screen {
|
|
|
135
135
|
```
|
|
136
136
|
mcp__eventmodelers__create_screen {
|
|
137
137
|
"boardId": "<BOARD_ID>",
|
|
138
|
-
"
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
138
|
+
"screens": [{
|
|
139
|
+
"contentType": "html",
|
|
140
|
+
"nodeId": "<generated-uuid>",
|
|
141
|
+
"chapterId": "<CHAPTER_ID>",
|
|
142
|
+
"cellName": "<CELL_NAME>",
|
|
143
|
+
"pages": ["<div>...</div>"],
|
|
144
|
+
"fields": [ /* optional — set meta.fields here too (see Step 5) instead of a separate call */ ]
|
|
145
|
+
}]
|
|
144
146
|
}
|
|
145
147
|
```
|
|
146
148
|
|
|
@@ -44,14 +44,17 @@ Use `BOARD_ID` and `BASE_URL` from the `connect` skill. If a `boardId` argument
|
|
|
44
44
|
mcp__eventmodelers__place_element {
|
|
45
45
|
"boardId": "<BOARD_ID>",
|
|
46
46
|
"timelineId": "<TIMELINE_ID>",
|
|
47
|
-
"
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
47
|
+
"elements": [{
|
|
48
|
+
"elementType": "<COMMAND|READMODEL|EVENT>",
|
|
49
|
+
"title": "<title>",
|
|
50
|
+
"columnIndex": <position,
|
|
51
|
+
if given>,
|
|
52
|
+
"fields": [{ "name": "orderId", "type": "String", "example": "ord-1" }]
|
|
53
|
+
}]
|
|
51
54
|
}
|
|
52
55
|
```
|
|
53
56
|
|
|
54
|
-
Pass `fields` whenever the element's attributes are already known — they are written by this same call, so don't follow a placement with a `submit_node_events` just to set them. Placing **more than one** element is
|
|
57
|
+
Pass `fields` whenever the element's attributes are already known — they are written by this same call, so don't follow a placement with a `submit_node_events` just to set them. Placing **more than one** element is more entries in the same `elements` array, applied in order so each entry sees the columns the previous one added — one call for a whole slice's or column run's worth instead of one per element.
|
|
55
58
|
|
|
56
59
|
This tool finds or creates an empty cell in the correct lane and places the node in one call — it collapses the "resolve timeline → fetch columns → determine lane → check occupancy → create node" sequence (Steps 2–3, 4, 6, 7b below) into a single round trip. A `columnIndex` past the timeline's current column count is handled automatically (columns are added to reach it) — no need to pre-check the column count or catch an out-of-range error yourself. If `timelineId` is unknown, resolve it first via Step 2's MCP call. Pass `compact: true` for a smaller `{nodeId, cellName, columnIndex}` response (plus `connectedCount` if auto-connect wired an edge) when you don't need the full `lane`/`elementType`/`title`/`autoConnected` detail back. Go straight to Step 8 once it returns.
|
|
57
60
|
|
|
@@ -274,13 +277,15 @@ mcp__eventmodelers__link_element { "boardId": "<BOARD_ID>", "nodeId": "<origin-n
|
|
|
274
277
|
```
|
|
275
278
|
mcp__eventmodelers__create_screen {
|
|
276
279
|
"boardId": "<BOARD_ID>",
|
|
277
|
-
"
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
280
|
+
"screens": [{
|
|
281
|
+
"contentType": "html",
|
|
282
|
+
"nodeId": "<node-uuid>",
|
|
283
|
+
"chapterId": "<TIMELINE_ID>",
|
|
284
|
+
"cellId": "<CELL_ID>",
|
|
285
|
+
"title": "<title>",
|
|
286
|
+
"pages": ["<div>...</div>"],
|
|
287
|
+
"description": "<title — what this screen shows>"
|
|
288
|
+
}]
|
|
284
289
|
}
|
|
285
290
|
```
|
|
286
291
|
|
|
@@ -293,13 +298,15 @@ mcp__eventmodelers__create_screen {
|
|
|
293
298
|
```
|
|
294
299
|
mcp__eventmodelers__create_screen {
|
|
295
300
|
"boardId": "<BOARD_ID>",
|
|
296
|
-
"
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
301
|
+
"screens": [{
|
|
302
|
+
"contentType": "sketch",
|
|
303
|
+
"nodeId": "<node-uuid>",
|
|
304
|
+
"chapterId": "<TIMELINE_ID>",
|
|
305
|
+
"cellId": "<CELL_ID>",
|
|
306
|
+
"title": "<title>",
|
|
307
|
+
"elements": [...],
|
|
308
|
+
"description": "<title — what this screen shows>"
|
|
309
|
+
}]
|
|
303
310
|
}
|
|
304
311
|
```
|
|
305
312
|
|
|
@@ -378,7 +385,7 @@ Count inbound edges where `target === COMMAND_NODE_ID` and the source node is ty
|
|
|
378
385
|
- **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:
|
|
379
386
|
|
|
380
387
|
```
|
|
381
|
-
mcp__eventmodelers__set_connection { "boardId": "<BOARD_ID>", "source": "<extra-issuer-node-id>", "target": "<COMMAND_NODE_ID>", "action": "remove" }
|
|
388
|
+
mcp__eventmodelers__set_connection { "boardId": "<BOARD_ID>", "connections": [{ "source": "<extra-issuer-node-id>", "target": "<COMMAND_NODE_ID>", "action": "remove" }] }
|
|
382
389
|
```
|
|
383
390
|
|
|
384
391
|
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.
|
|
@@ -409,4 +416,4 @@ Timeline: <timelineId>
|
|
|
409
416
|
|
|
410
417
|
## Example — place an EVENT via curl
|
|
411
418
|
|
|
412
|
-
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>", "elementType": "EVENT", "title": "Order Placed" }` (see "Prefer MCP" above).
|
|
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).
|
package/stacks/modeling-kit/templates/.claude/skills/place-element/references/api-fallback.md
CHANGED
|
@@ -164,7 +164,7 @@ Response: `{ "hashes": { "<event-id>": "<hash>" } }` — keyed by the `id` you s
|
|
|
164
164
|
|
|
165
165
|
## Full worked example — place an EVENT via curl, start to finish
|
|
166
166
|
|
|
167
|
-
With MCP connected, the same result is one call: `mcp__eventmodelers__place_element { "boardId": "<BOARD_ID>", "timelineId": "<TIMELINE_ID>", "elementType": "EVENT", "title": "Order Placed" }`.
|
|
167
|
+
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" }] }`.
|
|
168
168
|
|
|
169
169
|
Placing an EVENT called "Order Placed" at the end of a timeline, over curl:
|
|
170
170
|
|
|
@@ -179,13 +179,15 @@ Build the payload, then send a single call that creates the screen node, places
|
|
|
179
179
|
```
|
|
180
180
|
mcp__eventmodelers__create_screen {
|
|
181
181
|
"boardId": "<BOARD_ID>",
|
|
182
|
-
"
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
182
|
+
"screens": [{
|
|
183
|
+
"contentType": "html",
|
|
184
|
+
"nodeId": "<SCREEN_NODE_ID>",
|
|
185
|
+
"chapterId": "<CHAPTER_ID>",
|
|
186
|
+
"cellId": "<actorCellId>",
|
|
187
|
+
"title": "<screenTitle>",
|
|
188
|
+
"pages": ["<div>...</div>"],
|
|
189
|
+
"description": "<screenTitle — what this screen shows>"
|
|
190
|
+
}]
|
|
189
191
|
}
|
|
190
192
|
```
|
|
191
193
|
|
|
@@ -198,13 +200,15 @@ mcp__eventmodelers__create_screen {
|
|
|
198
200
|
```
|
|
199
201
|
mcp__eventmodelers__create_screen {
|
|
200
202
|
"boardId": "<BOARD_ID>",
|
|
201
|
-
"
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
203
|
+
"screens": [{
|
|
204
|
+
"contentType": "sketch",
|
|
205
|
+
"nodeId": "<SCREEN_NODE_ID>",
|
|
206
|
+
"chapterId": "<CHAPTER_ID>",
|
|
207
|
+
"cellId": "<actorCellId>",
|
|
208
|
+
"title": "<screenTitle>",
|
|
209
|
+
"elements": [...],
|
|
210
|
+
"description": "<screenTitle — what this screen shows>"
|
|
211
|
+
}]
|
|
208
212
|
}
|
|
209
213
|
```
|
|
210
214
|
|
|
@@ -191,7 +191,7 @@ emptyColumns = [columnId, ...] // in column order, ready to reuse
|
|
|
191
191
|
|
|
192
192
|
**Prefer MCP:**
|
|
193
193
|
```
|
|
194
|
-
mcp__eventmodelers__delete_column { "boardId": "<BOARD_ID>", "timelineId": "<CHAPTER_ID>", "
|
|
194
|
+
mcp__eventmodelers__delete_column { "boardId": "<BOARD_ID>", "timelineId": "<CHAPTER_ID>", "columnIds": ["<columnId>"] }
|
|
195
195
|
```
|
|
196
196
|
|
|
197
197
|
**Fallback (no MCP):** see `references/api-fallback.md` — "Delete a column".
|
|
@@ -260,7 +260,7 @@ mcp__eventmodelers__submit_node_events {
|
|
|
260
260
|
|
|
261
261
|
**Prefer MCP:** this whole "find/create a column, compute the cell, place the node" sequence collapses into one call:
|
|
262
262
|
```
|
|
263
|
-
mcp__eventmodelers__place_element { "boardId": "<BOARD_ID>", "timelineId": "<CHAPTER_ID>", "elementType": "EVENT", "title": "<EventName>", "columnIndex": <index> }
|
|
263
|
+
mcp__eventmodelers__place_element { "boardId": "<BOARD_ID>", "timelineId": "<CHAPTER_ID>", "elements": [{ "elementType": "EVENT", "title": "<EventName>", "columnIndex": <index> }] }
|
|
264
264
|
```
|
|
265
265
|
Extract `nodeId` and `columnId` directly from the tool result.
|
|
266
266
|
|
|
@@ -319,7 +319,7 @@ Two steps — delete the node, then delete the column:
|
|
|
319
319
|
|
|
320
320
|
**Prefer MCP:**
|
|
321
321
|
```
|
|
322
|
-
mcp__eventmodelers__delete_node { "boardId": "<BOARD_ID>", "
|
|
322
|
+
mcp__eventmodelers__delete_node { "boardId": "<BOARD_ID>", "nodeIds": ["<eventNodeId>"] }
|
|
323
323
|
```
|
|
324
324
|
|
|
325
325
|
**Fallback (no MCP):** see `references/api-fallback.md` — "Delete an event node".
|
|
@@ -328,7 +328,7 @@ mcp__eventmodelers__delete_node { "boardId": "<BOARD_ID>", "nodeId": "<eventNode
|
|
|
328
328
|
|
|
329
329
|
**Prefer MCP:**
|
|
330
330
|
```
|
|
331
|
-
mcp__eventmodelers__delete_column { "boardId": "<BOARD_ID>", "timelineId": "<CHAPTER_ID>", "
|
|
331
|
+
mcp__eventmodelers__delete_column { "boardId": "<BOARD_ID>", "timelineId": "<CHAPTER_ID>", "columnIds": ["<columnId>"] }
|
|
332
332
|
```
|
|
333
333
|
|
|
334
334
|
**Fallback (no MCP):** see `references/api-fallback.md` — "Delete a column".
|
|
@@ -138,7 +138,7 @@ Use the `handle-comment` skill with `action=place` to post each comment. Pass:
|
|
|
138
138
|
- `type` — `COMMENT` (there is no separate question type — the text itself carries the question)
|
|
139
139
|
- `author` — `wdyt`
|
|
140
140
|
|
|
141
|
-
Post them together, not one at a time: `handle-comment` sends every comment of a run in a single batch request (`
|
|
141
|
+
Post them together, not one at a time: `handle-comment` sends every comment of a run in a single batch request (one `add_comment` call over MCP, `POST .../boards/:boardId/comments` over REST).
|
|
142
142
|
|
|
143
143
|
Only post questions that are **genuinely unclear or missing** — don't post observations that are clearly intentional design decisions.
|
|
144
144
|
|
|
@@ -153,18 +153,30 @@ There are two kinds — no text-callout kind; a drawing never carries the questi
|
|
|
153
153
|
- **Arrow** (`kind: "path"`, `arrowEnd: true`) — the concern is about a missing or unclear relationship *between two elements* (e.g. "does this event actually reach this automation?"). Draw a straight line from one element's position to the other's. `path` is `M 0 0 L <dx> <dy>` in the box's own local coordinates; `x`/`y`/`width`/`height` describe that box in canvas space (so `width`/`height` = the delta between the two elements' positions).
|
|
154
154
|
```
|
|
155
155
|
mcp__eventmodelers__create_drawing {
|
|
156
|
-
"boardId": "$BOARD_ID",
|
|
157
|
-
"
|
|
158
|
-
|
|
156
|
+
"boardId": "$BOARD_ID",
|
|
157
|
+
"drawings": [{
|
|
158
|
+
"kind": "path",
|
|
159
|
+
"x": <sourceX>,
|
|
160
|
+
"y": <sourceY>,
|
|
161
|
+
"width": <dx>,
|
|
162
|
+
"height": <dy>,
|
|
163
|
+
"path": "M 0 0 L <dx> <dy>",
|
|
164
|
+
"arrowEnd": true
|
|
165
|
+
}]
|
|
159
166
|
}
|
|
160
167
|
```
|
|
161
168
|
Get element positions from the slice data already loaded in Step 2. If a position is missing, fetch the nodes you need in **one** call — `mcp__eventmodelers__get_nodes { "boardId": "$BOARD_ID", "nodeIds": [<the ids>] }` — not `get_node` per element.
|
|
162
169
|
- **Group loop** (`kind: "rect"`, drawn around a computed bounding box) — the concern spans a *cluster* of elements together (e.g. "this whole flow assumes nothing ever fails"). There's no dedicated group endpoint — union the elements' own `x`/`y`/`width`/`height` (plus some padding) yourself and draw one `rect` around that box:
|
|
163
170
|
```
|
|
164
171
|
mcp__eventmodelers__create_drawing {
|
|
165
|
-
"boardId": "$BOARD_ID",
|
|
166
|
-
"
|
|
167
|
-
|
|
172
|
+
"boardId": "$BOARD_ID",
|
|
173
|
+
"drawings": [{
|
|
174
|
+
"kind": "rect",
|
|
175
|
+
"x": <minX - pad>,
|
|
176
|
+
"y": <minY - pad>,
|
|
177
|
+
"width": <maxX - minX + 2*pad>,
|
|
178
|
+
"height": <maxY - minY + 2*pad>
|
|
179
|
+
}]
|
|
168
180
|
}
|
|
169
181
|
```
|
|
170
182
|
This is a visual grouping only — unrelated to the `MODEL_CONTEXT` node type; never touch a `modelContext` field to satisfy this.
|