@eventmodelers/cli 1.0.80 → 1.0.84
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/cli.js +2 -2
- package/package.json +1 -1
- package/shared/skills/connect/SKILL.md +6 -4
- package/shared/skills/learn-eventmodelers-api/SKILL.md +24 -21
- package/shared/skills/request-feedback/SKILL.md +0 -2
- package/shared/skills/update-slice-status/SKILL.md +0 -2
- package/stacks/modeling-kit/templates/.claude/skills/attributes/SKILL.md +2 -2
- package/stacks/modeling-kit/templates/.claude/skills/attributes/references/api-fallback.md +1 -3
- package/stacks/modeling-kit/templates/.claude/skills/discover-storyboard/SKILL.md +19 -17
- package/stacks/modeling-kit/templates/.claude/skills/discover-storyboard/references/api-fallback.md +0 -2
- package/stacks/modeling-kit/templates/.claude/skills/eventmodeling-brainstorming-events/SKILL.md +0 -4
- package/stacks/modeling-kit/templates/.claude/skills/eventmodeling-brainstorming-events/references/api-fallback.md +0 -4
- package/stacks/modeling-kit/templates/.claude/skills/eventmodeling-designing-automation-chains/SKILL.md +9 -8
- package/stacks/modeling-kit/templates/.claude/skills/eventmodeling-designing-automation-chains/references/api-fallback.md +0 -1
- package/stacks/modeling-kit/templates/.claude/skills/eventmodeling-identifying-inputs/SKILL.md +9 -9
- package/stacks/modeling-kit/templates/.claude/skills/eventmodeling-identifying-inputs/references/api-fallback.md +0 -2
- package/stacks/modeling-kit/templates/.claude/skills/eventmodeling-identifying-outputs/SKILL.md +10 -9
- package/stacks/modeling-kit/templates/.claude/skills/eventmodeling-identifying-outputs/references/api-fallback.md +0 -2
- package/stacks/modeling-kit/templates/.claude/skills/eventmodeling-orchestrating-event-modeling/SKILL.md +14 -13
- package/stacks/modeling-kit/templates/.claude/skills/eventmodeling-orchestrating-event-modeling/references/api-fallback.md +4 -4
- 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 -11
- package/stacks/modeling-kit/templates/.claude/skills/eventmodeling-storyboarding-events/references/api-fallback.md +0 -2
- package/stacks/modeling-kit/templates/.claude/skills/examples/SKILL.md +1 -3
- package/stacks/modeling-kit/templates/.claude/skills/examples/references/api-fallback.md +1 -3
- 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 -8
- package/stacks/modeling-kit/templates/.claude/skills/html-screen/references/api-fallback.md +0 -1
- package/stacks/modeling-kit/templates/.claude/skills/place-element/SKILL.md +31 -26
- package/stacks/modeling-kit/templates/.claude/skills/place-element/references/api-fallback.md +5 -9
- package/stacks/modeling-kit/templates/.claude/skills/storyboard/SKILL.md +18 -15
- package/stacks/modeling-kit/templates/.claude/skills/storyboard/references/api-fallback.md +0 -1
- package/stacks/modeling-kit/templates/.claude/skills/storyboard-screen/SKILL.md +0 -1
- package/stacks/modeling-kit/templates/.claude/skills/storyboard-screen/references/api-fallback.md +0 -1
- package/stacks/modeling-kit/templates/.claude/skills/timeline/SKILL.md +4 -8
- package/stacks/modeling-kit/templates/.claude/skills/timeline/references/api-fallback.md +1 -7
- package/stacks/modeling-kit/templates/.claude/skills/wdyt/SKILL.md +19 -7
- package/stacks/modeling-kit/templates/kit/AGENTS.md +2 -1
- package/stacks/modeling-kit/templates/kit/CLAUDE.md +1 -1
package/cli.js
CHANGED
|
@@ -3595,11 +3595,11 @@ program
|
|
|
3595
3595
|
...agentHeaders(cfg),
|
|
3596
3596
|
},
|
|
3597
3597
|
body: JSON.stringify([{
|
|
3598
|
+
// The event id is the caller's and required; boardId/timestamp are the server's —
|
|
3599
|
+
// it derives both from the request and ignores anything sent here.
|
|
3598
3600
|
id: randomUUID(),
|
|
3599
3601
|
eventType: 'node:changed',
|
|
3600
3602
|
nodeId: slice.id,
|
|
3601
|
-
boardId: cfg.boardId,
|
|
3602
|
-
timestamp: Date.now(),
|
|
3603
3603
|
changedAttributes: ['sliceStatus'],
|
|
3604
3604
|
meta: { sliceStatus: newStatus },
|
|
3605
3605
|
}]),
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@eventmodelers/cli",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.84",
|
|
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": {
|
|
@@ -283,15 +283,17 @@ The same discipline applies to writes: `submit_node_events` takes `events[]`, so
|
|
|
283
283
|
|
|
284
284
|
Where per-node calls genuinely can't be avoided, issue them together in one message so they run concurrently rather than in sequence.
|
|
285
285
|
|
|
286
|
-
### Ids
|
|
286
|
+
### Ids
|
|
287
287
|
|
|
288
|
-
Elements you create carry client-side ids
|
|
288
|
+
Elements you create carry client-side ids — one `nodeId` per new node — and **every event carries its own `id`**, a fresh uuid per event, never the node's. Mint them **once per turn, in a single call**, and take from that pool as you assemble the event array:
|
|
289
289
|
|
|
290
290
|
```bash
|
|
291
|
-
for i in $(seq
|
|
291
|
+
for i in $(seq 8); do uuidgen; done
|
|
292
292
|
```
|
|
293
293
|
|
|
294
|
-
Nothing in that depends on anything you're about to read, so splitting it across
|
|
294
|
+
Nothing in that depends on anything you're about to read, so splitting it across several shells is round trips bought for nothing.
|
|
295
|
+
|
|
296
|
+
The event `id` is **required** — an event without one is rejected as an invalid shape. It is what keys the returned `hashes` map, and what makes a resubmit idempotent: the same event sent twice under the same id writes one board event, not two. An event's `boardId` and `timestamp`, by contrast, are **not** yours to send — the server takes the board from the call and stamps the time itself, and ignores both if you send them.
|
|
295
297
|
|
|
296
298
|
---
|
|
297
299
|
|
|
@@ -17,51 +17,53 @@ 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
|
+
|
|
20
22
|
| Tool | Args | Purpose | REST equivalent |
|
|
21
23
|
|---|---|---|---|
|
|
22
24
|
| `list_boards` | — | List boards for the org | §1 `GET /api/boards` (org-scoped) |
|
|
23
|
-
| `get_nodes` | `boardId`, `type?`, `name?`, `chapterId?`, `nodeIds?`, `projection?` (`"line"`) | List nodes, optionally by type and/or a partial case-insensitive title match. `chapterId` scopes to one timeline — prefer this over an unscoped board-wide call whenever the step is working within one chapter (the common case); `nodeIds` fetches a known, scattered subset in one call (e.g. re-verifying exactly the nodes just touched by a batch write) instead of a full `type` refetch; `projection: "line"` maps each match to `{id, type, title}` only | §3 `GET .../nodes` |
|
|
24
|
-
| `get_node` | `boardId`, `nodeId`, `projection?` (`"cells"` \| `"edges"`) | Get one node. `projection: "cells"` (CHAPTER nodes only) returns just `{rows, columns, cells}` instead of the full `timelineData` — use whenever only the grid/occupancy is needed, not the whole chapter; `projection: "edges"` returns just that node's inbound/outbound connections instead of `findNodeById`'s full record. Both are opt-in — omitting `projection` is the unchanged full response | §3 `GET .../nodes/:nodeId` |
|
|
25
|
+
| `get_nodes` | `boardId`, `type?`, `name?`, `chapterId?`, `nodeIds?`, `projection?` (`"line"`) | List nodes, optionally by type and/or a partial case-insensitive title match. `chapterId` scopes to one timeline — prefer this over an unscoped board-wide call whenever the step is working within one chapter (the common case); `nodeIds` fetches a known, scattered subset in one call (e.g. re-verifying exactly the nodes just touched by a batch write) instead of a full `type` refetch; `projection: "line"` maps each match to `{id, type, title}` only. Every node carries `cellName`, its cell address in the chapter it is placed in (absent when unplaced) — read the cell names the cell-addressed tools need from here, never derive or invent one | §3 `GET .../nodes` |
|
|
26
|
+
| `get_node` | `boardId`, `nodeId`, `projection?` (`"cells"` \| `"edges"`) | Get one node — the full response carries `cellName`, its cell address in the chapter it is placed in. `projection: "cells"` (CHAPTER nodes only) returns just `{rows, columns, cells}` instead of the full `timelineData` — use whenever only the grid/occupancy is needed, not the whole chapter; `projection: "edges"` returns just that node's inbound/outbound connections instead of `findNodeById`'s full record. Both are opt-in — omitting `projection` is the unchanged full response | §3 `GET .../nodes/:nodeId` |
|
|
25
27
|
| `get_node_comments` | `boardId`, `nodeId` | List comments on a node | §1 `GET .../nodes/:nodeId/comments` |
|
|
26
28
|
| `get_board_events` | `boardId` | All board events, in sequence | §1 `GET .../events` |
|
|
27
29
|
| `search_board_events` | `boardId`, `name` | Search events by node name | §1 `GET .../events/search` |
|
|
28
|
-
| `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. `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`
|
|
30
|
+
| `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` |
|
|
31
|
+
| `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) |
|
|
32
|
+
| `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
33
|
| `find_nodes_in_drawing` | `boardId`, `drawingId` | Nodes fully contained inside a drawing's bounding box | — (no REST equivalent; MCP-only) |
|
|
32
34
|
| `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
35
|
| `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
36
|
| `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`
|
|
37
|
+
| `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` |
|
|
38
|
+
| `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
39
|
| `remove_lane` | `boardId`, `timelineId`, `rowId` | Remove a lane | — (extends §2; no direct REST route) |
|
|
38
|
-
| `move_node_in_timeline` | `boardId`, `timelineId`, `movedNodeId`, `toCellId` | Move
|
|
40
|
+
| `move_node_in_timeline` | `boardId`, `timelineId`, `moves[]` (each `movedNodeId`, `toCellId`) | Move placed nodes to other cells — each previous cell is automatically cleared; moves are applied in order and independently; answers `{results}` | — (MCP-only convenience) |
|
|
39
41
|
| `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
42
|
| `move_timeline_position` | `boardId`, `timelineId`, `x`, `y` | Move a chapter node on canvas | — (MCP-only convenience) |
|
|
41
43
|
| `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
44
|
| `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
45
|
| `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`
|
|
46
|
+
| `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` |
|
|
47
|
+
| `place_element` | `boardId`, `timelineId`, `elements[]` (each `elementType`, `title`, `fields?`, `lane?`, `columnIndex?`, `autoConnect?`), `compact?` | Find/create an empty cell in the right lane and place a COMMAND/READMODEL/EVENT/AUTOMATION/MARKDOWN/TABLE. `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
48
|
| `list_slices` | `boardId` | List slices (id, title, status) | §8 `GET .../slicedata/slices` |
|
|
47
49
|
| `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
50
|
| `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) |
|
|
49
51
|
| `get_slice_data` | `boardId`, `contextName?`, `contextId?`, `sliceId?` | Full element graph for slices in a context | §8 `GET /slicedata` |
|
|
50
52
|
| `get_spec_info` | `boardId`, `timelineId`, `elementTypes?` | EVENT/COMMAND/READMODEL nodes valid in GWT steps. Pass `elementTypes` (subset of `EVENT`/`COMMAND`/`READMODEL`) to avoid pulling the full element list when only one or two types are needed — filtered server-side, not just after a full fetch | §6 `GET .../spec-info` |
|
|
51
|
-
| `get_board_outline` | `boardId`, `chapterId` | One chapter's structure, compact: per-column node lists (`{id, type, title, lane}`) + a flat edge list, no HTML pages / field bodies / meta. The cheap "what is where and how is it wired" read — prefer over `get_nodes` (no projection) for orientation checks | — (MCP-only convenience) |
|
|
53
|
+
| `get_board_outline` | `boardId`, `chapterId` | One chapter's structure, compact: per-column node lists (`{id, type, title, lane, cellName}`) + a flat edge list, no HTML pages / field bodies / meta. `cellName` is each node's real cell address (column letter + row number) — the one read that gives you every address in a chapter at once. The cheap "what is where and how is it wired" read — prefer over `get_nodes` (no projection) for orientation checks | — (MCP-only convenience) |
|
|
52
54
|
| `get_connected_nodes` | `boardId`, `nodeId`, `chapterId?`, `direction?` (`inbound`/`outbound`/`both`), `depth?`, `types?`, `includeFields?` | Neighbours of **one** node — what feeds it and what it feeds. Answers from a single anchor, unlike `get_attribute_chain` (which needs both ends of the chain as cell names up front). `depth` follows a whole chain; `types` filters the result only, never the traversal. Each neighbour carries `via`: `"edge"` for a real connection, `"layout"` when the node has none in that direction and the neighbour was inferred from the grid using auto-connect's own window (own column + adjacent one, forward-only pairs). Real edges always win. The `layout` fallback is what makes hand-built/imported chapters — which routinely carry **zero** edges — readable instead of falsely empty | — (MCP-only convenience) |
|
|
53
55
|
| `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
56
|
| `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
57
|
| `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`, `
|
|
58
|
+
| `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) |
|
|
59
|
+
| `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
60
|
| `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`
|
|
61
|
+
| `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
62
|
| `update_comment` | `boardId`, `nodeId`, `commentId`, `action` (`'resolve'\|'delete'`) | Resolve or delete a comment | — (via comment events) |
|
|
61
|
-
| `create_screen` | `boardId`, `
|
|
63
|
+
| `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
64
|
| `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
65
|
| `add_field_examples` | `boardId`, `nodeId?`, `name?`, `cellName?`, `timelineId?` | Fill empty field examples using linked-node context | — (MCP-only convenience) |
|
|
64
|
-
| `get_attribute_chain` | `boardId`, `timelineId`, `targetCellName`, `sourceCellName` | Resolve every node between two cells, ordered target→source | — (MCP-only convenience) |
|
|
66
|
+
| `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) |
|
|
65
67
|
| `verify_screen` | `boardId`, `nodeId` | Check a screen node exists and has rendered content — works for both SCREEN and HTML_SCREEN, dispatching on the node's actual type | — (MCP-only convenience) |
|
|
66
68
|
| `get_image_snapshot_description` | `boardId`, `nodeId` | Load the `{elements:[...]}` sketch description from storage | — (reads what §4 sketch endpoints write) |
|
|
67
69
|
| `validate_slice_data` | `sliceData` | Offline validation of a `SliceDataOutput` payload — no board access | — (MCP-only, pure function) |
|
|
@@ -337,8 +339,6 @@ Place a free-text markdown note in that lane the same way any other node is plac
|
|
|
337
339
|
"id": "<event-uuid>",
|
|
338
340
|
"eventType": "node:created",
|
|
339
341
|
"nodeId": "<node-uuid>",
|
|
340
|
-
"boardId": "<boardId>",
|
|
341
|
-
"timestamp": 1234567890,
|
|
342
342
|
"chapterId": "<chapterId>",
|
|
343
343
|
"cellId": "<feedbackRowId>-<columnId>",
|
|
344
344
|
"meta": {
|
|
@@ -370,13 +370,16 @@ A `node:deleted` event cascades: if the deleted node is a chapter (timeline), ev
|
|
|
370
370
|
|
|
371
371
|
**Request body**: `NodeChangeEvent[]`
|
|
372
372
|
|
|
373
|
+
Every event carries its own `id` — **required**, a fresh uuid per event and never the node id. An event without one is rejected (`400`, invalid event shape). It keys the response's `hashes` map, and it is what makes a resubmit safe: the board-event insert ignores a conflicting id, so the same batch sent twice writes one history row per event rather than two.
|
|
374
|
+
|
|
375
|
+
Don't send `boardId` or `timestamp` per event: the board is the path's (the one this request was authorized for) and the timestamp is the server's. Both are ignored if sent — older payloads that include them still work.
|
|
376
|
+
|
|
373
377
|
```typescript
|
|
374
378
|
interface NodeChangeEvent {
|
|
375
|
-
id: string // uuid
|
|
379
|
+
id: string // required — fresh uuid per event, NOT the node id; keys the response's hashes map
|
|
376
380
|
eventType: 'node:created' | 'node:changed' | 'node:deleted'
|
|
377
381
|
nodeId: string
|
|
378
|
-
boardId:
|
|
379
|
-
timestamp: number // unix ms
|
|
382
|
+
// boardId / timestamp: don't send them — server-derived, ignored if present
|
|
380
383
|
userId?: string
|
|
381
384
|
hash?: string // content hash
|
|
382
385
|
changedAttributes?: string[] // dot-paths e.g. 'meta.title'
|
|
@@ -128,8 +128,6 @@ curl -s -X POST "<BASE_URL>/api/org/<ORG_ID>/boards/<BOARD_ID>/nodes/events" \
|
|
|
128
128
|
"id": "<new-random-uuid>",
|
|
129
129
|
"eventType": "node:changed",
|
|
130
130
|
"nodeId": "<SLICE_NODE_ID>",
|
|
131
|
-
"boardId": "<BOARD_ID>",
|
|
132
|
-
"timestamp": <Date.now()>,
|
|
133
131
|
"changedAttributes": ["sliceStatus"],
|
|
134
132
|
"meta": {
|
|
135
133
|
"sliceStatus": "Blocked"
|
|
@@ -89,8 +89,6 @@ curl -s -X POST "<BASE_URL>/api/org/<ORG_ID>/boards/<BOARD_ID>/nodes/events" \
|
|
|
89
89
|
"id": "<new-random-uuid>",
|
|
90
90
|
"eventType": "node:changed",
|
|
91
91
|
"nodeId": "<SLICE_NODE_ID>",
|
|
92
|
-
"boardId": "<BOARD_ID>",
|
|
93
|
-
"timestamp": <Date.now()>,
|
|
94
92
|
"changedAttributes": ["sliceStatus"],
|
|
95
93
|
"meta": {
|
|
96
94
|
"sliceStatus": "<newStatus>"
|
|
@@ -42,6 +42,8 @@ mcp__eventmodelers__get_attribute_chain {
|
|
|
42
42
|
|
|
43
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
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.
|
|
46
|
+
|
|
45
47
|
### Fallback (no MCP) — resolve both cells to nodes
|
|
46
48
|
|
|
47
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`.
|
|
@@ -149,8 +151,6 @@ mcp__eventmodelers__submit_node_events {
|
|
|
149
151
|
"id": "<uuid>",
|
|
150
152
|
"eventType": "node:changed",
|
|
151
153
|
"nodeId": "<NODE_ID_1>",
|
|
152
|
-
"boardId": "$BOARD_ID",
|
|
153
|
-
"timestamp": <epoch-ms>,
|
|
154
154
|
"changedAttributes": ["meta.fields"],
|
|
155
155
|
"meta": { "fields": "<updated_fields_array_1>" }
|
|
156
156
|
},
|
|
@@ -15,13 +15,11 @@ Build the payload with Python to avoid JSON escaping issues, then POST it:
|
|
|
15
15
|
|
|
16
16
|
```bash
|
|
17
17
|
python3 - <<EOF > /tmp/attributes_payload.json
|
|
18
|
-
import json,
|
|
18
|
+
import json, uuid
|
|
19
19
|
payload = [{
|
|
20
20
|
"id": str(uuid.uuid4()),
|
|
21
21
|
"eventType": "node:changed",
|
|
22
22
|
"nodeId": "<NODE_ID>",
|
|
23
|
-
"boardId": "<BOARD_ID>",
|
|
24
|
-
"timestamp": int(time.time() * 1000),
|
|
25
23
|
"changedAttributes": ["meta.fields"],
|
|
26
24
|
"meta": {
|
|
27
25
|
"fields": <updated_fields_as_python_list>
|
|
@@ -232,8 +232,6 @@ mcp__eventmodelers__submit_node_events {
|
|
|
232
232
|
"id": "<uuid>",
|
|
233
233
|
"eventType": "node:changed",
|
|
234
234
|
"nodeId": "<CHAPTER_ID>",
|
|
235
|
-
"boardId": "<BOARD_ID>",
|
|
236
|
-
"timestamp": <NOW_MS>,
|
|
237
235
|
"changedAttributes": ["meta.title"],
|
|
238
236
|
"meta": { "type": "CHAPTER", "title": "<flow name>" },
|
|
239
237
|
"node": { "id": "<CHAPTER_ID>", "data": {} }
|
|
@@ -305,13 +303,15 @@ Reconstruct the captured screen as a real HTML/CSS fragment from `screen.capture
|
|
|
305
303
|
```
|
|
306
304
|
mcp__eventmodelers__create_screen {
|
|
307
305
|
"boardId": "<BOARD_ID>",
|
|
308
|
-
"
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
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
|
+
}]
|
|
315
315
|
}
|
|
316
316
|
```
|
|
317
317
|
|
|
@@ -322,14 +322,16 @@ mcp__eventmodelers__create_screen {
|
|
|
322
322
|
```
|
|
323
323
|
mcp__eventmodelers__create_screen {
|
|
324
324
|
"boardId": "<BOARD_ID>",
|
|
325
|
-
"
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
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
|
+
}]
|
|
333
335
|
}
|
|
334
336
|
```
|
|
335
337
|
|
package/stacks/modeling-kit/templates/.claude/skills/discover-storyboard/references/api-fallback.md
CHANGED
|
@@ -22,8 +22,6 @@ curl -s -X POST "$BASE_URL/api/org/$ORG_ID/boards/$BOARD_ID/nodes/events" \
|
|
|
22
22
|
"id": "<uuid>",
|
|
23
23
|
"eventType": "node:changed",
|
|
24
24
|
"nodeId": "<CHAPTER_ID>",
|
|
25
|
-
"boardId": "<BOARD_ID>",
|
|
26
|
-
"timestamp": <NOW_MS>,
|
|
27
25
|
"changedAttributes": ["meta.title"],
|
|
28
26
|
"meta": { "type": "CHAPTER", "title": "<flow name>" },
|
|
29
27
|
"node": { "id": "<CHAPTER_ID>", "data": {} }
|
package/stacks/modeling-kit/templates/.claude/skills/eventmodeling-brainstorming-events/SKILL.md
CHANGED
|
@@ -107,8 +107,6 @@ mcp__eventmodelers__submit_node_events {
|
|
|
107
107
|
"id": "<uuid>",
|
|
108
108
|
"eventType": "node:changed",
|
|
109
109
|
"nodeId": "<chapterId>",
|
|
110
|
-
"boardId": "<BOARD_ID>",
|
|
111
|
-
"timestamp": 1234567890,
|
|
112
110
|
"meta": {"type": "CHAPTER", "title": "Reservation & Lending"}
|
|
113
111
|
}]
|
|
114
112
|
}
|
|
@@ -200,8 +198,6 @@ mcp__eventmodelers__submit_node_events {
|
|
|
200
198
|
"id": "<event-uuid>",
|
|
201
199
|
"eventType": "node:created",
|
|
202
200
|
"nodeId": "<node-uuid>",
|
|
203
|
-
"boardId": "<BOARD_ID>",
|
|
204
|
-
"timestamp": 1234567890,
|
|
205
201
|
"chapterId": "<chapterId>",
|
|
206
202
|
"cellId": "<swimlaneRowId>-<columnId>",
|
|
207
203
|
"meta": {
|
|
@@ -35,8 +35,6 @@ curl -s -X POST "$BASE_URL/api/org/$ORG_ID/boards/$BOARD_ID/nodes/events" \
|
|
|
35
35
|
"id": "<uuid>",
|
|
36
36
|
"eventType": "node:changed",
|
|
37
37
|
"nodeId": "<chapterId>",
|
|
38
|
-
"boardId": "<boardId>",
|
|
39
|
-
"timestamp": 1234567890,
|
|
40
38
|
"meta": {"type": "CHAPTER", "title": "Reservation & Lending"}
|
|
41
39
|
}]'
|
|
42
40
|
```
|
|
@@ -78,8 +76,6 @@ Same body via `POST .../nodes/events`:
|
|
|
78
76
|
"id": "<event-uuid>",
|
|
79
77
|
"eventType": "node:created",
|
|
80
78
|
"nodeId": "<node-uuid>",
|
|
81
|
-
"boardId": "<boardId>",
|
|
82
|
-
"timestamp": 1234567890,
|
|
83
79
|
"chapterId": "<chapterId>",
|
|
84
80
|
"cellId": "<swimlaneRowId>-<columnId>",
|
|
85
81
|
"meta": {
|
|
@@ -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:
|
|
@@ -95,7 +97,6 @@ mcp__eventmodelers__submit_node_events {
|
|
|
95
97
|
"boardId": "<BOARD_ID>",
|
|
96
98
|
"events": [{
|
|
97
99
|
"id": "<event-uuid>", "eventType": "node:changed", "nodeId": "<returned-node-id>",
|
|
98
|
-
"boardId": "<BOARD_ID>", "timestamp": 1234567890,
|
|
99
100
|
"meta": {"type": "READMODEL", "title": "NotificationsToSend", "fields": [...], "listElement": true}
|
|
100
101
|
}]
|
|
101
102
|
}
|
|
@@ -103,7 +104,7 @@ mcp__eventmodelers__submit_node_events {
|
|
|
103
104
|
|
|
104
105
|
**Fallback (no MCP):** see `references/api-fallback.md` — "Placement — todo-list READMODEL, one column before its automation".
|
|
105
106
|
|
|
106
|
-
**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`.
|
|
107
108
|
|
|
108
109
|
> **Never call `drop` after using `cellId` in `node:created`.** The drop endpoint adds a second cell reference without removing the first.
|
|
109
110
|
|
|
@@ -113,11 +114,11 @@ For a **translation-chain automation**, place its three columns left to right in
|
|
|
113
114
|
|
|
114
115
|
1. **READMODEL → AUTOMATION first** — the automation reads its own todo list. The closing connection below is only accepted once this edge exists.
|
|
115
116
|
```
|
|
116
|
-
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" }] }
|
|
117
118
|
```
|
|
118
119
|
2. **Every opening EVENT → READMODEL.**
|
|
119
120
|
```
|
|
120
|
-
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" }] }
|
|
121
122
|
```
|
|
122
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.
|
|
123
124
|
|
|
@@ -125,7 +126,7 @@ For a **translation-chain automation**, place its three columns left to right in
|
|
|
125
126
|
|
|
126
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"`.
|
|
127
128
|
|
|
128
|
-
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.
|
|
129
130
|
|
|
130
131
|
## Verification (run before moving to Step 5)
|
|
131
132
|
|
|
@@ -14,7 +14,6 @@ Only needed when MCP is not connected. Every call below has an MCP equivalent in
|
|
|
14
14
|
-H "Content-Type: application/json" \
|
|
15
15
|
-d '[{
|
|
16
16
|
"id":"<event-uuid>","eventType":"node:created","nodeId":"<node-uuid>",
|
|
17
|
-
"boardId":"<BOARD_ID>","timestamp":1234567890,
|
|
18
17
|
"chapterId":"<CHAPTER_ID>","cellId":"<interactionRowId>-<columnId>",
|
|
19
18
|
"meta":{"type":"READMODEL","title":"NotificationsToSend","fields":[...]}
|
|
20
19
|
}]'
|
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>",
|
|
@@ -160,8 +162,6 @@ mcp__eventmodelers__submit_node_events {
|
|
|
160
162
|
"id": "<event-uuid>",
|
|
161
163
|
"eventType": "node:changed",
|
|
162
164
|
"nodeId": "<returned-node-id>",
|
|
163
|
-
"boardId": "<BOARD_ID>",
|
|
164
|
-
"timestamp": 1234567890,
|
|
165
165
|
"meta": {
|
|
166
166
|
"type": "COMMAND",
|
|
167
167
|
"title": "ReserveBike",
|
|
@@ -203,9 +203,9 @@ After `place-element` returns the COMMAND node ID, create the arrows that comple
|
|
|
203
203
|
```
|
|
204
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:
|
|
205
205
|
```
|
|
206
|
-
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" }] }
|
|
207
207
|
```
|
|
208
|
-
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.
|
|
209
209
|
|
|
210
210
|
**Fallback (no MCP):** see `references/api-fallback.md` — "Wire connections — Step 1: SCREEN → COMMAND".
|
|
211
211
|
|
|
@@ -219,7 +219,7 @@ After `place-element` returns the COMMAND node ID, create the arrows that comple
|
|
|
219
219
|
```
|
|
220
220
|
Read `cells["<swimlaneRowId>-<columnId>"]` for the occupying node id, then:
|
|
221
221
|
```
|
|
222
|
-
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" }] }
|
|
223
223
|
```
|
|
224
224
|
|
|
225
225
|
**Fallback (no MCP):** see `references/api-fallback.md` — "Wire connections — Step 2: COMMAND → EVENT".
|
|
@@ -228,7 +228,7 @@ After `place-element` returns the COMMAND node ID, create the arrows that comple
|
|
|
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
|
|
|
231
|
-
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.
|
|
232
232
|
|
|
233
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.
|
|
234
234
|
|
|
@@ -34,8 +34,6 @@ curl -X POST "$BASE_URL/api/org/$ORG_ID/boards/$BOARD_ID/nodes/events" \
|
|
|
34
34
|
"id": "<event-uuid>",
|
|
35
35
|
"eventType": "node:created",
|
|
36
36
|
"nodeId": "<node-uuid>",
|
|
37
|
-
"boardId": "<boardId>",
|
|
38
|
-
"timestamp": 1234567890,
|
|
39
37
|
"chapterId": "<chapterId>",
|
|
40
38
|
"cellId": "<interactionRowId>-<columnId>",
|
|
41
39
|
"meta": {
|
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:
|
|
@@ -267,7 +269,6 @@ mcp__eventmodelers__submit_node_events {
|
|
|
267
269
|
"boardId": "<BOARD_ID>",
|
|
268
270
|
"events": [{
|
|
269
271
|
"id": "<event-uuid>", "eventType": "node:changed", "nodeId": "<returned-node-id>",
|
|
270
|
-
"boardId": "<BOARD_ID>", "timestamp": 1234567890,
|
|
271
272
|
"meta": {"type": "READMODEL", "title": "ActiveReservationView", "fields": [...]}
|
|
272
273
|
}]
|
|
273
274
|
}
|
|
@@ -280,7 +281,7 @@ mcp__eventmodelers__get_node { "boardId": "<BOARD_ID>", "nodeId": "<CHAPTER_ID>"
|
|
|
280
281
|
|
|
281
282
|
**Fallback (no MCP):** see `references/api-fallback.md` — "Step 5f — Placing a READMODEL node (full manual sequence)".
|
|
282
283
|
|
|
283
|
-
**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.
|
|
284
285
|
|
|
285
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.
|
|
286
287
|
|
|
@@ -326,7 +327,7 @@ After `place-element` returns the READMODEL node ID, create the arrows that comp
|
|
|
326
327
|
```
|
|
327
328
|
mcp__eventmodelers__get_node { "boardId": "<BOARD_ID>", "nodeId": "<CHAPTER_ID>", "projection": "cells" }
|
|
328
329
|
# → read cells["<swimlaneRowId>-<columnId>"] for the occupying node id
|
|
329
|
-
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" }] }
|
|
330
331
|
```
|
|
331
332
|
|
|
332
333
|
**Fallback (no MCP):** see `references/api-fallback.md` — "Step 5h.1 — Wire EVENT → READMODEL".
|
|
@@ -335,7 +336,7 @@ After `place-element` returns the READMODEL node ID, create the arrows that comp
|
|
|
335
336
|
|
|
336
337
|
**Prefer MCP:**
|
|
337
338
|
```
|
|
338
|
-
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" }] }
|
|
339
340
|
```
|
|
340
341
|
|
|
341
342
|
**Fallback (no MCP):** see `references/api-fallback.md` — "Step 5h.2 — Wire READMODEL → SCREEN".
|
|
@@ -346,14 +347,14 @@ After `place-element` returns the READMODEL node ID, create the arrows that comp
|
|
|
346
347
|
|
|
347
348
|
**Prefer MCP:**
|
|
348
349
|
```
|
|
349
|
-
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" }] }
|
|
350
351
|
```
|
|
351
352
|
|
|
352
353
|
**Fallback (no MCP):** see `references/api-fallback.md` — "Step 5h.3 — Wire READMODEL → AUTOMATION".
|
|
353
354
|
|
|
354
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"`.
|
|
355
356
|
|
|
356
|
-
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.
|
|
357
358
|
|
|
358
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.
|
|
359
360
|
|
|
@@ -26,8 +26,6 @@ Only needed when MCP is not connected. Every call below has an MCP equivalent in
|
|
|
26
26
|
"id": "<event-uuid>",
|
|
27
27
|
"eventType": "node:created",
|
|
28
28
|
"nodeId": "<node-uuid>",
|
|
29
|
-
"boardId": "<BOARD_ID>",
|
|
30
|
-
"timestamp": 1234567890,
|
|
31
29
|
"chapterId": "<CHAPTER_ID>",
|
|
32
30
|
"cellId": "<interactionRowId>-<columnId>",
|
|
33
31
|
"meta": {"type": "READMODEL", "title": "ActiveReservationView", "fields": [...]}
|