@eventmodelers/cli 1.0.90 → 1.0.92
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/README.md +2 -0
- package/RELEASE_NOTES.md +3 -0
- package/cli.js +308 -23
- package/lib/update-check.js +67 -0
- package/package.json +4 -1
- package/shared/build-kit/lib/adapters/pocketbase-realtime-adapter.js +11 -3
- package/shared/build-kit/lib/adapters/realtime-adapter.js +3 -1
- package/shared/build-kit/lib/adapters/supabase-realtime-adapter.js +14 -3
- package/shared/build-kit/lib/ralph.js +136 -59
- package/shared/build-kit/lib/redact.js +36 -0
- package/shared/build-kit/lib/turn.js +56 -20
- package/shared/build-kit/ralph-claude.js +7 -19
- package/shared/build-kit/ralph-exec.js +5 -9
- package/shared/build-kit/realtime-agent.js +4 -2
- package/shared/skills/connect/SKILL.md +14 -9
- package/shared/skills/learn-eventmodelers-api/SKILL.md +61 -24
- package/shared/skills/load-slice/SKILL.md +3 -1
- package/stacks/modeling-kit/templates/.claude/skills/add-next-slice/SKILL.md +4 -4
- package/stacks/modeling-kit/templates/.claude/skills/analyze-existing-model/SKILL.md +18 -9
- package/stacks/modeling-kit/templates/.claude/skills/analyze-existing-model/references/api-fallback.md +31 -11
- package/stacks/modeling-kit/templates/.claude/skills/attributes/SKILL.md +16 -13
- package/stacks/modeling-kit/templates/.claude/skills/attributes/references/api-fallback.md +3 -1
- package/stacks/modeling-kit/templates/.claude/skills/discover-storyboard/SKILL.md +6 -6
- package/stacks/modeling-kit/templates/.claude/skills/discover-storyboard/references/api-fallback.md +3 -1
- package/stacks/modeling-kit/templates/.claude/skills/eventmodeling-brainstorming-events/SKILL.md +4 -4
- package/stacks/modeling-kit/templates/.claude/skills/eventmodeling-brainstorming-events/references/api-fallback.md +4 -4
- package/stacks/modeling-kit/templates/.claude/skills/eventmodeling-checking-completeness/SKILL.md +8 -12
- package/stacks/modeling-kit/templates/.claude/skills/eventmodeling-checking-completeness/references/api-fallback.md +16 -6
- package/stacks/modeling-kit/templates/.claude/skills/eventmodeling-elaborating-scenarios/SKILL.md +3 -3
- package/stacks/modeling-kit/templates/.claude/skills/eventmodeling-elaborating-scenarios/references/api-fallback.md +1 -1
- package/stacks/modeling-kit/templates/.claude/skills/eventmodeling-identifying-inputs/SKILL.md +2 -2
- package/stacks/modeling-kit/templates/.claude/skills/eventmodeling-identifying-inputs/references/api-fallback.md +4 -4
- package/stacks/modeling-kit/templates/.claude/skills/eventmodeling-identifying-outputs/SKILL.md +4 -4
- package/stacks/modeling-kit/templates/.claude/skills/eventmodeling-identifying-outputs/references/api-fallback.md +4 -4
- package/stacks/modeling-kit/templates/.claude/skills/eventmodeling-orchestrating-event-modeling/SKILL.md +3 -3
- package/stacks/modeling-kit/templates/.claude/skills/eventmodeling-orchestrating-event-modeling/references/api-fallback.md +3 -1
- package/stacks/modeling-kit/templates/.claude/skills/eventmodeling-plotting-events/SKILL.md +2 -2
- package/stacks/modeling-kit/templates/.claude/skills/eventmodeling-plotting-events/references/api-fallback.md +1 -1
- package/stacks/modeling-kit/templates/.claude/skills/eventmodeling-slicing-event-models/SKILL.md +3 -3
- package/stacks/modeling-kit/templates/.claude/skills/eventmodeling-slicing-event-models/references/api-fallback.md +12 -4
- package/stacks/modeling-kit/templates/.claude/skills/eventmodeling-storyboarding-events/SKILL.md +7 -6
- package/stacks/modeling-kit/templates/.claude/skills/eventmodeling-storyboarding-events/references/api-fallback.md +4 -3
- package/stacks/modeling-kit/templates/.claude/skills/eventmodeling-validating-event-models/SKILL.md +22 -6
- package/stacks/modeling-kit/templates/.claude/skills/eventmodeling-validating-event-models/references/api-fallback.md +12 -3
- package/stacks/modeling-kit/templates/.claude/skills/eventmodeling-validating-event-models-checklist/SKILL.md +18 -6
- package/stacks/modeling-kit/templates/.claude/skills/eventmodeling-validating-event-models-checklist/references/api-fallback.md +12 -3
- package/stacks/modeling-kit/templates/.claude/skills/examples/SKILL.md +2 -2
- package/stacks/modeling-kit/templates/.claude/skills/examples/references/api-fallback.md +20 -14
- package/stacks/modeling-kit/templates/.claude/skills/place-element/SKILL.md +12 -12
- package/stacks/modeling-kit/templates/.claude/skills/place-element/references/api-fallback.md +6 -4
- package/stacks/modeling-kit/templates/.claude/skills/storyboard/SKILL.md +4 -4
- package/stacks/modeling-kit/templates/.claude/skills/storyboard/references/api-fallback.md +2 -2
- package/stacks/modeling-kit/templates/.claude/skills/timeline/SKILL.md +10 -10
- package/stacks/modeling-kit/templates/.claude/skills/timeline/references/api-fallback.md +4 -4
- package/stacks/modeling-kit/templates/.claude/skills/wdyt/SKILL.md +11 -24
- package/stacks/modeling-kit/templates/kit/AGENTS.md +3 -3
- package/stacks/modeling-kit/templates/kit/CLAUDE-STANDALONE.md +56 -10
- package/stacks/modeling-kit/templates/kit/CLAUDE.md +110 -44
- package/stacks/react/templates/build-kit/lib/backend-prompt.md +106 -102
- package/stacks/react/templates/build-kit/lib/prompt.md +102 -106
|
@@ -17,9 +17,9 @@
|
|
|
17
17
|
// RALPH_EXEC_CMD="opencode run" node ralph-exec.js
|
|
18
18
|
// Or persist it as localAi.exec in .eventmodelers/config.json.
|
|
19
19
|
|
|
20
|
-
import { startRalph, loadLocalConfig, resolveAgentIdentity } from './lib/ralph.js';
|
|
20
|
+
import { startRalph, loadLocalConfig, resolveAgentIdentity, connectHeader } from './lib/ralph.js';
|
|
21
21
|
import { createSliceTracer, usageFromHarnessEvent, sumUsage } from './lib/tracing.js';
|
|
22
|
-
import { turnTimeoutMs, superviseTurn
|
|
22
|
+
import { turnTimeoutMs, superviseTurn } from './lib/turn.js';
|
|
23
23
|
import { spawn } from 'child_process';
|
|
24
24
|
import { writeFileSync, mkdtempSync } from 'fs';
|
|
25
25
|
import { tmpdir } from 'os';
|
|
@@ -42,12 +42,7 @@ if (!execCmd) {
|
|
|
42
42
|
process.exit(1);
|
|
43
43
|
}
|
|
44
44
|
|
|
45
|
-
|
|
46
|
-
// never reach the child even when config.json has them. Also as there: the token is
|
|
47
|
-
// passed by env var name, never by value — the prompt goes on the command line.
|
|
48
|
-
const inlineHeader = !localOnly && cfg.boardId
|
|
49
|
-
? `board=${cfg.boardId} token=$EVENTMODELERS_TOKEN org=${cfg.organizationId} baseUrl=${cfg.baseUrl}\n\n`
|
|
50
|
-
: '';
|
|
45
|
+
const inlineHeader = connectHeader(cfg, localOnly);
|
|
51
46
|
const timeoutMs = turnTimeoutMs(cfg);
|
|
52
47
|
|
|
53
48
|
const childEnv = {
|
|
@@ -128,7 +123,8 @@ function runExec(prompt, slice = null) {
|
|
|
128
123
|
const turn = superviseTurn(proc, { timeoutMs, label: 'exec turn', viaShell: true, log: (line) => console.error(`[ralph-exec] ${line}`) });
|
|
129
124
|
const usage = traced ? collectUsage(proc.stdout) : null;
|
|
130
125
|
proc.on('close', (code) => {
|
|
131
|
-
|
|
126
|
+
const timeout = turn.timeoutError();
|
|
127
|
+
if (timeout) return reject(timeout);
|
|
132
128
|
if (traced) {
|
|
133
129
|
const u = usage();
|
|
134
130
|
if (u) tracer.record({ ...slice, status: code === 0 ? 'ok' : 'error' }, { ...u, durationMs: u.durationMs ?? Date.now() - startedAt });
|
|
@@ -4,14 +4,16 @@
|
|
|
4
4
|
// only need this if you want to run the agent independently (e.g. separate terminal).
|
|
5
5
|
// Usage: node realtime-agent.js [kit_dir]
|
|
6
6
|
|
|
7
|
-
import { loadLocalConfig, fetchPlatformConfig,
|
|
7
|
+
import { loadLocalConfig, fetchPlatformConfig, retryTransient, startRealtimeAgent } from './lib/ralph.js';
|
|
8
|
+
import { redactConsole } from './lib/redact.js';
|
|
8
9
|
import { dirname, resolve } from 'path';
|
|
9
10
|
import { fileURLToPath } from 'url';
|
|
10
11
|
|
|
11
12
|
const kitDir = process.argv[2] ? resolve(process.argv[2]) : dirname(fileURLToPath(import.meta.url));
|
|
12
13
|
|
|
13
14
|
const local = loadLocalConfig(kitDir);
|
|
14
|
-
|
|
15
|
+
redactConsole([local.token]);
|
|
16
|
+
const cfg = await retryTransient('fetchPlatformConfig', () => fetchPlatformConfig(local));
|
|
15
17
|
|
|
16
18
|
console.log(`[agent] Starting — org=${cfg.organizationId}${cfg.boardId ? `, board=${cfg.boardId}` : ''}, base=${cfg.baseUrl}`);
|
|
17
19
|
|
|
@@ -61,7 +61,7 @@ If an inline `board=<uuid>` is found, use it as `BOARD_ID` — **it takes priori
|
|
|
61
61
|
|
|
62
62
|
(`agent=<uuid>` is not one of the four required values — it is optional, and its absence never makes this skill ask anything.)
|
|
63
63
|
|
|
64
|
-
**`token=$EVENTMODELERS_TOKEN` is an inline token too.** The build
|
|
64
|
+
**`token=$EVENTMODELERS_TOKEN` is an inline token too.** The build and modeling loops pass the env var's *name*, not its value, so the secret never sits in the prompt. Treat it exactly like a literal inline token: `TOKEN` is `$EVENTMODELERS_TOKEN` (it counts toward "all four inline" below). Keep using the reference, never its value — write curl headers as `-H "x-token: $EVENTMODELERS_TOKEN"` (double quotes, so the shell expands it), don't `echo` or print it, and hand it to a subagent as `token=$EVENTMODELERS_TOKEN` (it inherits the same environment). MCP calls need nothing extra — `.mcp.json` already reads the same variable. Never write the reference into a file either: if a later step would persist `TOKEN` (Step 3's `config.json`, Step 3.5's `settings.local.json`), leave the token out — the process that launched you already set the variable, and a file holding the literal `$EVENTMODELERS_TOKEN` text breaks auth for the next session.
|
|
65
65
|
|
|
66
66
|
**All four inline means this skill is already finished — stop here.** `token=`, `board=`, `org=` and `baseUrl=` arriving together is the shape a parent agent hands a subagent, and it resolves every required value in this one step. Do not walk the config file (Step 1), do not ask anything (Step 2), do not persist (Step 3), and do not make the verify call (Step 4): the parent resolved these values against this board and verified them there, so a subagent verifying them again learns nothing it wasn't just told and pays a round trip for it. Step 3.5 is a no-op too whenever `.mcp.json` already carries an `eventmodelers` entry — read the file, don't rewrite it, and don't re-register a server the session is already connected to. Print `Connected — board <BOARD_ID>` and return to the skill that invoked you.
|
|
67
67
|
|
|
@@ -228,9 +228,11 @@ MCP tools only become visible to the current agent session after the host (re)co
|
|
|
228
228
|
Prefer verifying through MCP if `mcp__eventmodelers__*` tools are already visible in this session (e.g. from a `.mcp.json` set up in an earlier turn or a previous session):
|
|
229
229
|
|
|
230
230
|
```
|
|
231
|
-
mcp__eventmodelers__get_nodes { "boardId": "<BOARD_ID>", "type": "CHAPTER" }
|
|
231
|
+
mcp__eventmodelers__get_nodes { "boardId": "<BOARD_ID>", "type": "CHAPTER", "projection": "line" }
|
|
232
232
|
```
|
|
233
233
|
|
|
234
|
+
(`projection: "line"` because this is only an access check — without it every chapter's full grid comes back.)
|
|
235
|
+
|
|
234
236
|
A successful result (even an empty array) confirms the token and board are valid. An error mentioning "not found or access denied" means the token/board pairing is wrong — treat it like the `403`/`404` curl cases below.
|
|
235
237
|
|
|
236
238
|
If the MCP server itself shows as needing authentication (e.g. the host lists it as "needs authentication" rather than connected), ask the user once:
|
|
@@ -245,7 +247,7 @@ Otherwise (no MCP tools visible yet this session), fall back to the equivalent c
|
|
|
245
247
|
curl -s -o /dev/null -w "%{http_code}" \
|
|
246
248
|
-H "x-token: <TOKEN>" \
|
|
247
249
|
-H "x-user-id: connect-skill" \
|
|
248
|
-
"<BASE_URL>/api/org/<ORG_ID>/boards/<BOARD_ID>/nodes?type=CHAPTER"
|
|
250
|
+
"<BASE_URL>/api/org/<ORG_ID>/boards/<BOARD_ID>/nodes?type=CHAPTER&projection=line"
|
|
249
251
|
```
|
|
250
252
|
|
|
251
253
|
| Response | Action |
|
|
@@ -263,21 +265,24 @@ curl -s -o /dev/null -w "%{http_code}" \
|
|
|
263
265
|
Connecting is also where the session's read discipline starts. Every skill that runs after this one shares the same board, so **fetch it once and index it in memory** instead of re-deriving it per step:
|
|
264
266
|
|
|
265
267
|
- **Orientation** (what is where, how is it wired) — `get_board_outline { boardId, chapterId }`. One compact call per chapter: per-column node lists plus a flat edge list, no HTML pages or field bodies.
|
|
266
|
-
- **Working set** (you need `meta.fields`, examples, descriptions) — `get_nodes { boardId, chapterId }`. One call returns every node in the chapter with full `meta`, plus `node.position` and `node.parentId`.
|
|
268
|
+
- **Working set** (you need `meta.fields`, examples, descriptions) — `get_nodes { boardId, chapterId }`. One call returns every node in the chapter with full `meta`, plus `node.position` and `node.parentId`. **An unscoped `get_nodes` is expensive** — full `meta` costs about 2 KB per node, roughly 1 MB (and as many tokens of context) for a 500-node board, most of it HTML screens and specs. Always scope it: one chapter is typically 1–30 KB. Never read the whole board to find something; `projection: "line"` or `get_board_outline` answers that for a fraction.
|
|
267
269
|
- **A known, scattered subset** — `get_nodes { boardId, nodeIds: [...] }`. One call, not one per id.
|
|
268
|
-
- **Just names/types** — add `projection: "line"` (
|
|
270
|
+
- **Just names/types** — add `projection: "line"` (`{id, type, title, fields}` — `fields` being just the attribute names; no `cellName` — cell names come only from `get_board_outline`). **Just a chapter's grid** — `get_node { nodeId: <chapterId>, projection: "cells" }`. **Just one node's wiring** — `get_node { nodeId, projection: "edges" }`. None of these carries `sliceStatus`, `meta`, positions, field types or examples — if the step needs those, it needs the working-set read.
|
|
271
|
+
- **Slice-level** — `get_slice_data` with `projection: "outline"` to find slices and their elements, `"fields"` for attribute/field-flow work, `"specs"` for scenario review (`outline`/`specs` only in format `json`/`yaml`/`toon`). The full graph only when a step needs elements, specs and comments together — and say why.
|
|
272
|
+
|
|
273
|
+
**Pick the cheapest read that answers the step, and name the reason next to the call** (e.g. "line — only need titles to dedupe"). The full decision table is in `learn-eventmodelers-api` → *Which read to use*.
|
|
269
274
|
|
|
270
275
|
**Orientation first, working set second — the two tiers are a sequence, not a choice.** "Where is the thing I was pointed at, and what sits around it" is an orientation question, and it is answered by `get_board_outline` or by `get_nodes` with `projection: "line"`. Answer it there, decide from it which nodes you are actually going to touch, and only then spend a full-`meta` read — `get_nodes` scoped by `chapterId`, or by `nodeIds` for a scattered handful — on those. Opening instead with an unscoped full-`meta` read drags every field body and every rendered HTML page on the board across the wire to work out which column someone poked at: the most expensive possible way to ask the cheapest question in the session.
|
|
271
276
|
|
|
272
277
|
**Each tier is once per session, not once per step.** A chapter's outline and grid don't change unless you or a human changes them, so keep the indexed result and answer later questions from memory; re-read only after a structural write (a node created, moved or deleted), and then only the part that moved. Three `get_board_outline` calls inside one turn means the first two were fetched and thrown away.
|
|
273
278
|
|
|
274
|
-
### The slice status comes with
|
|
279
|
+
### The slice status comes with the outline
|
|
275
280
|
|
|
276
|
-
|
|
281
|
+
The orientation read also tells you what you may write to. `get_board_outline` returns `sliceStatus` per column — a slice owns a column, so every element in that column shares its status. `get_nodes` does **not** carry it, so take it from the outline before any write:
|
|
277
282
|
|
|
278
|
-
**Only a slice in `Created` may be written to.** Any other status — `Planned`, `Assigned`, `InProgress`, `Review`, `Blocked`, `Done`, `Informational` — means someone is working on that slice: read its elements for context, but never change, move, rename or delete them, and never add scenarios, fields or examples to them. An element with **no** `sliceStatus` is in no slice at all, which is not the same as locked — that one is writable.
|
|
283
|
+
**Only a slice in `Created` may be written to.** Any other status — `Planned`, `Assigned`, `InProgress`, `Review`, `Blocked`, `Done`, `Informational` — means someone is working on that slice: read its elements for context, but never change, move, rename or delete them, and never add scenarios, fields or examples to them. An element in a column with **no** `sliceStatus` is in no slice at all, which is not the same as locked — that one is writable.
|
|
279
284
|
|
|
280
|
-
Never spend a `list_slices`/`get_slice_data` call to answer this; the
|
|
285
|
+
Never spend a `list_slices`/`get_slice_data` call to answer this; the outline already did.
|
|
281
286
|
|
|
282
287
|
`get_node` without a projection is for **one** node you did not already load — most often re-reading a node right after writing it. A step that issues it in a loop over nodes that were already in a list response is doing the same fetch N times; collapse it to the single chapter-scoped read above.
|
|
283
288
|
|
|
@@ -19,19 +19,18 @@ Server name: `eventmodelers`. Every tool takes `boardId` explicitly; none need `
|
|
|
19
19
|
|
|
20
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
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.
|
|
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"` (`nodeId`, or `cellName`). They still work so older kits don't break, but they will be removed.
|
|
23
23
|
|
|
24
24
|
| Tool | Args | Purpose | REST equivalent |
|
|
25
25
|
|---|---|---|---|
|
|
26
26
|
| `list_boards` | — | List boards for the org | §1 `GET /api/boards` (org-scoped) |
|
|
27
|
-
| `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}`
|
|
28
|
-
| `get_node` | `boardId`, `nodeId`, `projection?` (`"cells"` \| `"edges"`) | Get one node —
|
|
27
|
+
| `get_nodes` | `boardId`, `type?`, `name?`, `chapterId?`, `nodeIds?`, `projection?` (`"line"`) | List nodes, optionally by type and/or a partial case-insensitive title match. **Unscoped with full `meta` it is expensive** — about 2 KB per node, ~1 MB on a 500-node board — so always pass a filter or `projection: "line"`. `chapterId` scopes to one timeline — prefer this over an unscoped board-wide call whenever the step is working within one chapter (the common case); `nodeIds` fetches a known, scattered subset in one call (e.g. re-verifying exactly the nodes just touched by a batch write) instead of a full `type` refetch; `projection: "line"` maps each match to `{id, type, title, fields}` — `fields` being just the attribute names. No `cellName` — an existing node is addressed by its `id`; when a cell address is needed (placing into a cell), read it from `get_board_outline`. No `sliceStatus` — take it per column from `get_board_outline` | §3 `GET .../nodes` (REST records carry no `cellName`, `projection=line` included) |
|
|
28
|
+
| `get_node` | `boardId`, `nodeId`, `projection?` (`"cells"` \| `"edges"`) | Get one node (no `cellName` — address it by `nodeId`; cell addresses come only from `get_board_outline`). `projection: "cells"` (CHAPTER nodes only) returns just `{rows, columns, cells}` instead of the full `timelineData` — use whenever only the grid/occupancy is needed, not the whole chapter; `projection: "edges"` returns just that node's inbound/outbound connections instead of `findNodeById`'s full record. Both are opt-in — omitting `projection` is the unchanged full response. An unknown id is a `NODE_NOT_FOUND` error | §3 `GET .../nodes/:nodeId` (REST returns the node as stored, without `cellName`; `404` carries `code: 'NODE_NOT_FOUND'`) |
|
|
29
29
|
| `get_node_comments` | `boardId`, `nodeId` | List comments on a node | §1 `GET .../nodes/:nodeId/comments` |
|
|
30
30
|
| `get_board_events` | `boardId`, `since?`, `seq?` | A page of at most 300 board events, oldest first, as `{events, pageSize, hasMore}`. Not the whole log: while `hasMore` is true, page on by passing the last event's `seq` as `since`. `seq` returns only the event of exactly that seq | §1 `GET .../events` |
|
|
31
31
|
| `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` |
|
|
32
32
|
| `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) |
|
|
33
|
-
| `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:
|
|
34
|
-
| `find_nodes_in_drawing` | `boardId`, `drawingId` | Nodes fully contained inside a drawing's bounding box | — (no REST equivalent; MCP-only) |
|
|
33
|
+
| `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: an arrow plus its label is one call with several `drawings` entries, applied in order, not several calls | — (REST `POST .../drawing/draw` accepts a single drawing or an array) |
|
|
35
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` |
|
|
36
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` |
|
|
37
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` |
|
|
@@ -42,14 +41,14 @@ Write tools take their items as an array (`nodeIds[]`, `elements[]`, `connection
|
|
|
42
41
|
| `move_timeline_position` | `boardId`, `timelineId`, `x`, `y` | Move a chapter node on canvas | — (MCP-only convenience) |
|
|
43
42
|
| `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
43
|
| `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` |
|
|
45
|
-
| `place_element` | `boardId`, `timelineId`, `elements[]` (each `action?` = `create` (default) with `elementType`, `title`, `fields?`, `lane?`, `columnIndex?`, `autoConnect?` · `move` with `nodeId` + `cellName
|
|
46
|
-
| `list_slices` | `boardId` | List slices (id, title, status) | §8 `GET .../slicedata/slices` |
|
|
44
|
+
| `place_element` | `boardId`, `timelineId`, `elements[]` (each `action?` = `create` (default) with `elementType`, `title`, `fields?`, `lane?`, `columnIndex?`, `autoConnect?` · `move` with `nodeId` + target `cellName` · `clear` with `nodeId` (preferred for an existing node) or `cellName`), `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"` unplaces a node without deleting it (`delete_node` deletes) — pass its `nodeId` to empty the cell it sits in, or a `cellName`. 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) |
|
|
45
|
+
| `list_slices` | `boardId`, `contextName?` | List slices (id, title, status). `contextName` restricts to one MODEL_CONTEXT (or timeline) — an unknown name is a `CONTEXT_NOT_FOUND` error, not an empty list | §8 `GET .../slicedata/slices` |
|
|
47
46
|
| `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
|
-
| `update_slice_status` | `boardId`, `newStatus`, plus exactly one of `sliceId` / `sliceTitle`
|
|
49
|
-
| `get_slice_data` | `boardId`, `contextName?`, `contextId?`, `sliceId?` | Full element graph for slices in a context | §8 `GET /slicedata` |
|
|
47
|
+
| `update_slice_status` | `boardId`, `newStatus`, plus exactly one of `sliceId` / `sliceTitle` | Change a SLICE_BORDER's `sliceStatus`. With a title 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) |
|
|
48
|
+
| `get_slice_data` | `boardId`, `contextName?`, `contextId?`, `sliceId?`, `projection?` (`"outline"` \| `"fields"` \| `"specs"`), `format?` | Full element graph for slices in a context. `projection` trims it first: `outline` = slice header + each element as `{id, title, type}`; `fields` = elements with fields and dependencies, no specs/comments/prompts; `specs` = specifications and storylines, no elements — prefer one over the full graph when only part is needed. `outline`/`specs` encode only as `json`, `yaml` or `toon` (other formats are a `PROJECTION_FORMAT_UNSUPPORTED` error); an unknown `sliceId` is `SLICE_NOT_FOUND` | §8 `GET /slicedata` |
|
|
50
49
|
| `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, cellName}`) + a flat edge list, no HTML pages / field bodies / meta. `cellName` is each node's real cell address (column letter + row number) — the
|
|
52
|
-
| `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
|
|
50
|
+
| `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 only read that returns cell names at all; use it when a placement needs a cell address. For an empty target cell, take the column's `letter` plus the lane's 1-based position in the outline's `lanes` list (e.g. `letter: "C"`, second lane → `C2`) — never convert array indexes yourself. The cheap "what is where and how is it wired" read — prefer over `get_nodes` (no projection) for orientation checks | — (MCP-only convenience) |
|
|
51
|
+
| `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 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
52
|
| `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
53
|
| `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
54
|
| `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` |
|
|
@@ -58,9 +57,9 @@ Write tools take their items as an array (`nodeIds[]`, `elements[]`, `connection
|
|
|
58
57
|
| `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
58
|
| `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
59
|
| `update_comment` | `boardId`, `nodeId`, `commentId`, `action` (`'resolve'\|'delete'`) | Resolve or delete a comment | — (via comment events) |
|
|
61
|
-
| `create_screen` | `boardId`, `screens[]` (each `contentType` `'image'\|'sketch'\|'html'`, `chapterId`, `
|
|
60
|
+
| `create_screen` | `boardId`, `screens[]` (each `contentType` `'image'\|'sketch'\|'html'`, `chapterId`, `cellName` (required), `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; each HTML page at most 6000 characters (`HTML_SCREEN_PAGE_TOO_LONG` otherwise); 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
61
|
| `render_screen` | `boardId`, `nodeId`, `elements[]?` (SCREEN) or `pages[]?`+`backgroundColor?` (HTML_SCREEN), `description?` | Update an existing screen's content — exactly one of `elements`/`pages`; each HTML page at most 6000 characters (`HTML_SCREEN_PAGE_TOO_LONG` otherwise) | §4 `POST .../images/:id/sketch` + `image-nodes` |
|
|
63
|
-
| `get_attribute_chain` | `boardId`, `timelineId`, `
|
|
62
|
+
| `get_attribute_chain` | `boardId`, `timelineId`, `targetNodeId`, `sourceNodeId` | Resolve every node in the columns between two placed nodes (inclusive), ordered target→source (top-to-bottom within a column), each `{id, type, title, cellName, fields}`. A node not placed in that timeline is a `NODE_NOT_IN_TIMELINE` error | — (MCP-only convenience) |
|
|
64
63
|
| `get_image_snapshot_description` | `boardId`, `nodeId` | Load the `{elements:[...]}` sketch description from storage | — (reads what §4 sketch endpoints write) |
|
|
65
64
|
| `validate_slice_data` | `sliceData` | Offline validation of a `SliceDataOutput` payload — no board access | — (MCP-only, pure function) |
|
|
66
65
|
| `commit_board_to_git` | `boardId` | Force a git-extension commit/push, bypassing the autoCommit gate | — (MCP-only; git extension) |
|
|
@@ -68,7 +67,25 @@ Write tools take their items as an array (`nodeIds[]`, `elements[]`, `connection
|
|
|
68
67
|
|
|
69
68
|
**Not exposed via MCP at all** — always use REST/curl for these: §7 Config Import, §10 Snapshots, §11–12 Invitations, §13 Utility (`/api/user`, swagger), and the rest of §14 Prompts (submission, claiming, deletion, realtime-token) — only the status-update endpoint has an MCP tool (`update_prompt_status`, used by the `update-prompt-status` skill); everything else in Prompts is an intentionally separate lifecycle the board-content MCP server doesn't otherwise own.
|
|
70
69
|
|
|
71
|
-
**Capabilities with no direct MCP filter** — e.g. REST's `GET .../nodes?cellId=<id>&timelineId=<id>` and `?colId=<id>&timelineId=<id>` (§3) have no equivalent params on `get_nodes`. Either call the REST endpoint directly, or get the same answer by calling `get_node` on the CHAPTER and reading `
|
|
70
|
+
**Capabilities with no direct MCP filter** — e.g. REST's `GET .../nodes?cellId=<id>&timelineId=<id>` and `?colId=<id>&timelineId=<id>` (§3) have no equivalent params on `get_nodes`. Either call the REST endpoint directly, or get the same answer by calling `get_node` on the CHAPTER with `projection: "cells"` and reading `cells` (sparse array; a cell absent from it is empty) instead of asking the server to filter by cell/column.
|
|
71
|
+
|
|
72
|
+
### Which read to use
|
|
73
|
+
|
|
74
|
+
Pick the cheapest read that answers the step's question, and name the reason in one short clause next to the call:
|
|
75
|
+
|
|
76
|
+
| The step needs | Read |
|
|
77
|
+
|---|---|
|
|
78
|
+
| ids, titles, types, attribute names only (existence check, dedupe by title, pick a chapter/context, find a node by name, count elements) | `get_nodes` with `projection: "line"` |
|
|
79
|
+
| a chapter's grid / cell occupancy | `get_node { nodeId: <chapterId>, projection: "cells" }` |
|
|
80
|
+
| one node's connections | `get_node { nodeId, projection: "edges" }` |
|
|
81
|
+
| what is where in a chapter (incl. cell names for placement), plus `sliceStatus` per column | `get_board_outline` |
|
|
82
|
+
| field types, examples, descriptions, positions, HTML | full `get_nodes`, scoped by `chapterId` or `nodeIds` — never board-wide when avoidable |
|
|
83
|
+
| finding slices / their elements / element counts | `get_slice_data` with `projection: "outline"` (format `json`/`yaml`/`toon`) |
|
|
84
|
+
| attribute, field-flow or completeness work | `get_slice_data` with `projection: "fields"` |
|
|
85
|
+
| scenario / spec review, spec coverage | `get_slice_data` with `projection: "specs"` (format `json`/`yaml`/`toon`) |
|
|
86
|
+
| elements **and** specs **and** comments/prompts together (e.g. a business review) | full `get_slice_data` — say why |
|
|
87
|
+
|
|
88
|
+
Two cheap projected calls (e.g. `outline` + `specs`) beat one full call when the step doesn't need comments, prompts or field bodies. `projection: "line"` never carries `sliceStatus`, `meta`, positions, field types or examples — if a later step reads any of those, use the full read. `get_nodes { type: "CHAPTER" }` without a projection returns every chapter's full grid; to just list or pick chapters, add `projection: "line"`.
|
|
72
89
|
|
|
73
90
|
---
|
|
74
91
|
|
|
@@ -428,22 +445,37 @@ interface NodeChangeEvent {
|
|
|
428
445
|
### GET `/api/org/:orgId/boards/:boardId/nodes`
|
|
429
446
|
List all nodes on a board.
|
|
430
447
|
|
|
431
|
-
**Query params
|
|
448
|
+
**Query params** (each at most once — a repeated or nested param is `400 QUERY_PARAM_INVALID`):
|
|
432
449
|
- `type?: ElementType` — exact match.
|
|
450
|
+
- `name?: string` — partial, case-insensitive title match.
|
|
451
|
+
- `chapterId?: string` — only nodes placed in this chapter (timeline).
|
|
452
|
+
- `projection?: "line"` — compact `{id, type, title, fields?}` per node (`fields` = attribute names only, absent on nodes without fields) instead of the full record. Neither form carries `cellName` — address existing nodes by `id`. Any other value is `400 PROJECTION_INVALID`.
|
|
433
453
|
- `cellId?: string` — return only the node occupying this timeline cell (format `<rowId>-<colId>`). Requires `timelineId`. Empty array if the cell is unoccupied.
|
|
434
454
|
- `colId?: string` — return every node occupying this column, across *all* rows of the timeline (e.g. to check for an existing COMMAND/READMODEL/SCREEN/AUTOMATION before placing one, since the server caps those at one per column even across separate lane rows). Requires `timelineId`. Combine with `type` to narrow to one element type.
|
|
435
455
|
- `timelineId?: string` — the CHAPTER node `cellId`/`colId` are resolved against. Required together with either of those two; `400` if omitted.
|
|
436
456
|
|
|
437
457
|
Cell/column occupancy lives only in the CHAPTER node's `meta.timelineData.cells`, never on the node rows themselves — this endpoint resolves `cellId`/`colId` against that timeline internally so callers don't have to fetch and parse the whole chapter node just to check occupancy.
|
|
438
458
|
|
|
439
|
-
**Response**: `200` — node record array. `400` — `cellId`/`colId` given without `timelineId`. `404` — `timelineId` doesn't reference an existing CHAPTER node with grid data.
|
|
459
|
+
**Response**: `200` — node record array (or `line` projections). `400` — `cellId`/`colId` given without `timelineId`, `PROJECTION_INVALID`, or `QUERY_PARAM_INVALID`. `404` — `timelineId` doesn't reference an existing CHAPTER node with grid data.
|
|
460
|
+
|
|
461
|
+
---
|
|
462
|
+
|
|
463
|
+
### POST `/api/org/:orgId/boards/:boardId/nodes/query`
|
|
464
|
+
Fetch a known set of nodes in one call.
|
|
465
|
+
|
|
466
|
+
**Body**: `{ "ids": string[], "projection"?: "line" }` — `ids` non-empty. Missing ids are silently omitted. Without `projection`, full stored records; `line` gives the compact shape above. Neither carries `cellName`.
|
|
467
|
+
|
|
468
|
+
**Response**: `200` — node array. `400` — `ids` empty/not an array, or `PROJECTION_INVALID`.
|
|
440
469
|
|
|
441
470
|
---
|
|
442
471
|
|
|
443
472
|
### GET `/api/org/:orgId/boards/:boardId/nodes/:nodeId`
|
|
444
473
|
Get a single node.
|
|
445
474
|
|
|
446
|
-
**
|
|
475
|
+
**Query params**:
|
|
476
|
+
- `projection?: "cells" | "edges"` — `cells` (CHAPTER nodes only) returns just `{rows, columns, cells}` — read `cells` directly, not `.meta.timelineData.cells`; `edges` (any node) returns `{nodeId, edges}`, its inbound/outbound connections, without the node's content. Omit for the full stored node (no `cellName`).
|
|
477
|
+
|
|
478
|
+
**Response**: `200` — node record or the requested projection. `400` — `PROJECTION_INVALID`, `PROJECTION_NODE_TYPE_MISMATCH` (`cells` on a non-CHAPTER node), or `TIMELINE_NO_GRID` (a CHAPTER without grid data). `404` — node not found: `{ error, code: "NODE_NOT_FOUND", context }`.
|
|
447
479
|
|
|
448
480
|
---
|
|
449
481
|
|
|
@@ -725,18 +757,25 @@ Import an EventModelingJson config to populate a board.
|
|
|
725
757
|
|
|
726
758
|
**File**: `src/slices/slicedata/routes.ts`
|
|
727
759
|
|
|
728
|
-
### GET `
|
|
760
|
+
### GET `/api/org/:orgId/boards/:boardId/slicedata`
|
|
729
761
|
Build structured slice data from board state.
|
|
730
762
|
|
|
731
|
-
**Query params
|
|
732
|
-
|
|
763
|
+
**Query params**:
|
|
764
|
+
- `contextId` OR `contextName` (one required) — a MODEL_CONTEXT node, or a timeline name (a timeline with no assigned/connected context scopes to that timeline alone).
|
|
765
|
+
- `sliceId?` — one slice only.
|
|
766
|
+
- `projection?: "outline" | "fields" | "specs"` — `outline` = slice header + every element list as `{id, title, type}` (no fields, specs, comments); `fields` = header + elements with their fields and dependencies, plus actors and aggregates (no specs, storylines, comments, prompts, notes); `specs` = header + specifications and storylines, no elements. Omit for the full graph.
|
|
767
|
+
- `format?: "json" | "yaml" | "textual" | "toon" | "emlang" | "esdm"` (default `json`). `outline` and `specs` encode only as `json`, `yaml` or `toon`; `fields` works with every format.
|
|
768
|
+
|
|
769
|
+
**Response**: `200` — slice data matching event modeling schema, in the requested format. `400` — no context given, unsupported format, `PROJECTION_INVALID`, or `PROJECTION_FORMAT_UNSUPPORTED` (`outline`/`specs` with `textual`/`emlang`/`esdm`). `404` — no context or timeline matches, or `SLICE_NOT_FOUND` (unknown `sliceId`).
|
|
733
770
|
|
|
734
771
|
---
|
|
735
772
|
|
|
736
773
|
### GET `/api/org/:orgId/boards/:boardId/slicedata/slices`
|
|
737
774
|
List all slices on a board.
|
|
738
775
|
|
|
739
|
-
**
|
|
776
|
+
**Query params**: `contextName?` — restrict to slices of this MODEL_CONTEXT (or timeline) name.
|
|
777
|
+
|
|
778
|
+
**Response**: `200` — `{ slices: Array<{ id: string, title: string, status: string }> }`. `404` — `CONTEXT_NOT_FOUND` (an unknown `contextName` is an error, not an empty list).
|
|
740
779
|
|
|
741
780
|
---
|
|
742
781
|
|
|
@@ -930,9 +969,7 @@ carries its `type`, so a chapter is told from a command without a second lookup.
|
|
|
930
969
|
|
|
931
970
|
`hidden: true` marks an agent-only task: still claimed by `/prompts/next` like any other
|
|
932
971
|
prompt, but excluded from every client read (and from Supabase's `prompts_select` RLS policy),
|
|
933
|
-
so it never shows up in the user's prompt list.
|
|
934
|
-
this — a hidden prompt whose text is the bare word `Focus`, carrying `node_id` (when a single
|
|
935
|
-
element was selected) plus the `focusArea`, and nothing else.
|
|
972
|
+
so it never shows up in the user's prompt list.
|
|
936
973
|
|
|
937
974
|
**Response**: `201` — the created row, `status: "ADDED"`.
|
|
938
975
|
**Errors**: `400` missing required fields or malformed `context` · `403` no access to board · `404` board/timeline not found or no API token configured for the org
|
|
@@ -36,7 +36,9 @@ This returns `{ "slices": [ { "id": "...", "title": "...", "status": "..." } ] }
|
|
|
36
36
|
mcp__eventmodelers__get_slice_data { "boardId": "<BOARD_ID>", "contextName": "<name>" }
|
|
37
37
|
```
|
|
38
38
|
|
|
39
|
-
(`
|
|
39
|
+
This call stays full (no `projection`): Step 3 persists the complete slice object as `slice.json`, which the build skills read fields, specs and comments from. When only one slice is needed, pass `sliceId` as well.
|
|
40
|
+
|
|
41
|
+
(`get_slice_data` requires a `contextName` or `contextId`. If the context isn't already known, list the context names in one call — `mcp__eventmodelers__get_nodes { "boardId": "<BOARD_ID>", "type": "MODEL_CONTEXT", "projection": "line" }` (only titles needed; a timeline name works as `contextName` too, so fall back to `type: "CHAPTER"` with `projection: "line"` when there are no MODEL_CONTEXT nodes) — then map slices to contexts with `list_slices { "boardId": "<BOARD_ID>", "contextName": "<name>" }` per context. Don't `get_node` each `SLICE_BORDER`.)
|
|
40
42
|
|
|
41
43
|
**Fallback (no MCP):**
|
|
42
44
|
|
|
@@ -5,7 +5,7 @@ description: Decide on and create a genuinely new slice from scratch when there'
|
|
|
5
5
|
|
|
6
6
|
# Add Next Slice
|
|
7
7
|
|
|
8
|
-
> **Before doing anything else**, invoke the `connect` skill — if not already connected — to resolve `TOKEN`, `BOARD_ID`, `ORG_ID`, and `BASE_URL`. Do not proceed until it has completed. Every API call this skill needs (`create_slice`, `get_nodes`/`get_node`) is already given in full below — do not additionally load `learn-eventmodelers-api`; only reach for it if you hit an error this file doesn't cover.
|
|
8
|
+
> **Before doing anything else**, invoke the `connect` skill — if not already connected — to resolve `TOKEN`, `BOARD_ID`, `ORG_ID`, and `BASE_URL`. Do not proceed until it has completed. Every API call this skill needs (`create_slice`, `get_board_outline`, `get_nodes`/`get_node`) is already given in full below — do not additionally load `learn-eventmodelers-api`; only reach for it if you hit an error this file doesn't cover.
|
|
9
9
|
|
|
10
10
|
**Purpose**: Given a timeline where every existing COMMAND/READMODEL/AUTOMATION already has a slice (or there's no model content at all yet), decide on a plausible next capability and create it as a brand-new slice — screen, command/read model, and event all at once.
|
|
11
11
|
|
|
@@ -31,7 +31,7 @@ Same rules as `eventmodeling-slicing-event-models`'s own "Core Concept" section
|
|
|
31
31
|
|
|
32
32
|
**If the prompt gives no specific instruction about what the next slice should be** (e.g. a bare "add the next slice"), don't ask for one — look at the previous slices already on the timeline and derive the next one yourself:
|
|
33
33
|
|
|
34
|
-
- Read the existing COMMAND/READMODEL/AUTOMATION titles and their fields in narrative order (left to right on the timeline) — they tell a story (e.g. Reserve Table → Cancel Reservation → Check-In → ...).
|
|
34
|
+
- Read the existing COMMAND/READMODEL/AUTOMATION titles and their fields in narrative order (left to right on the timeline) — they tell a story (e.g. Reserve Table → Cancel Reservation → Check-In → ...). Titles and attribute names are enough here: `mcp__eventmodelers__get_board_outline { "boardId": "<BOARD_ID>", "chapterId": "<TL>" }` gives the per-column order, and `mcp__eventmodelers__get_nodes { "boardId": "<BOARD_ID>", "chapterId": "<TL>", "projection": "line" }` adds each node's field names — no full node read needed.
|
|
35
35
|
- Identify what the story is missing next: the natural following lifecycle stage, an unaddressed affordance implied by an existing screen (a button/link with nothing behind it yet), or a CRUD/notification gap the existing entity clearly has (created but never updated/cancelled/queried in detail, an action with no confirmation view, etc.).
|
|
36
36
|
- Prefer the option that most directly continues the existing narrative over one that starts an unrelated new thread — the goal is the slice that makes the most sense as *next*, not just *any* plausible slice.
|
|
37
37
|
|
|
@@ -47,7 +47,7 @@ mcp__eventmodelers__create_slice { "boardId": "<BOARD_ID>", "timelineId": "<TL>"
|
|
|
47
47
|
|
|
48
48
|
Pick `type` based on what you decided in Step 1 — `state-change` for a new command, `state-view` for a new read model, `automation` for a new automation. Always pass `nodes.interaction.title` as the command/read model/automation name you decided on in Step 1 — per the Core Concept above, the slice is *named after that element*, and the backend only derives the slice title from this field; omitting it produces a useless generic "State Change"/"State View"/"Automation" label instead. This also creates the slice's `SLICE_BORDER` automatically — no separate `create_slice_definition` call needed.
|
|
49
49
|
|
|
50
|
-
If the chapter has more than one lane of the same type (e.g. several actor lanes for different user roles), `create_slice` places each node in the **first** matching lane by default — pass `nodes.<actor|interaction|swimlane>.rowId` (the target row's id, from the chapter's `
|
|
50
|
+
If the chapter has more than one lane of the same type (e.g. several actor lanes for different user roles), `create_slice` places each node in the **first** matching lane by default — pass `nodes.<actor|interaction|swimlane>.rowId` (the target row's id, from the chapter's `rows` — `get_node { "boardId": "<BOARD_ID>", "nodeId": "<TL>", "projection": "cells" }`) to target a specific lane instead of the first one.
|
|
51
51
|
|
|
52
52
|
**Fallback (no MCP):** see `references/api-fallback.md` — "Step 2: Create the Slice".
|
|
53
53
|
|
|
@@ -55,7 +55,7 @@ If the chapter has more than one lane of the same type (e.g. several actor lanes
|
|
|
55
55
|
|
|
56
56
|
For `state-change`/`state-view`, the actor node this creates is an `HTML_SCREEN` — but only as a **stub**: a single visibly-placeholder page ("Untitled screen — design pending"), never real content. Never leave it at that:
|
|
57
57
|
|
|
58
|
-
1. Look up the board's other screens on this timeline (`mcp__eventmodelers__get_nodes { "boardId": "<BOARD_ID>", "type": "HTML_SCREEN" }
|
|
58
|
+
1. Look up the board's other screens on this timeline (`mcp__eventmodelers__get_nodes { "boardId": "<BOARD_ID>", "type": "HTML_SCREEN", "chapterId": "<TL>" }` — a full read on purpose, since the style lives in the HTML bodies; scoped to this chapter, not board-wide) and note their established visual style — layout conventions, color/tone, recurring components (nav bar, card style, button treatment).
|
|
59
59
|
2. Invoke the `html-screen` skill on the new stub node, passing a `description` that covers **both** what this screen should contain (derived from the new command/read model's fields) **and** the style to match (derived from what you just observed in step 1). `html-screen` has no visibility into other screens on the board — gathering that context and folding it into the description is this skill's job, not something to expect `html-screen` to do on its own.
|
|
60
60
|
3. Default to `html-screen` here, never `storyboard-screen` (wireframe sketch) — a sketch is only for an explicit user request for one.
|
|
61
61
|
|
|
@@ -45,30 +45,37 @@ Save the full slice list. Count total slices and group by status:
|
|
|
45
45
|
|
|
46
46
|
## Step 3 — Discover contexts
|
|
47
47
|
|
|
48
|
-
Fetch
|
|
48
|
+
Fetch the `MODEL_CONTEXT` nodes **and** the timelines — a timeline with no assigned or connected context is its own context (named after the timeline), so the MODEL_CONTEXT nodes alone miss it. Only ids and titles are needed, so use the `line` projection:
|
|
49
49
|
|
|
50
50
|
**Prefer MCP:**
|
|
51
51
|
```
|
|
52
|
-
mcp__eventmodelers__get_nodes { "boardId": "$BOARD_ID", "type": "MODEL_CONTEXT" }
|
|
52
|
+
mcp__eventmodelers__get_nodes { "boardId": "$BOARD_ID", "type": "MODEL_CONTEXT", "projection": "line" }
|
|
53
|
+
mcp__eventmodelers__get_nodes { "boardId": "$BOARD_ID", "type": "CHAPTER", "projection": "line" }
|
|
53
54
|
```
|
|
54
55
|
|
|
55
56
|
**Fallback (no MCP):** see `references/api-fallback.md` — "Step 3 — Discover contexts".
|
|
56
57
|
|
|
57
58
|
- If a `contextName` argument was given, filter to that single context and skip others.
|
|
58
|
-
-
|
|
59
|
-
-
|
|
59
|
+
- Record each MODEL_CONTEXT's `id` and `title`, and each timeline's `id` and `title`.
|
|
60
|
+
- Step 4 fetches every MODEL_CONTEXT by title first. Each slice in a response carries its `chapter`; every timeline never named there is its own context (or holds no slices) — fetch it too, passing the timeline's title as `contextName`. With no MODEL_CONTEXT nodes at all, that is every timeline.
|
|
60
61
|
|
|
61
62
|
---
|
|
62
63
|
|
|
63
64
|
## Step 4 — Fetch slice data per context
|
|
64
65
|
|
|
65
|
-
For each resolved context, fetch the full
|
|
66
|
+
For each resolved context, fetch two trimmed projections instead of the full graph — this analysis never needs comments, codegen prompts, notes or screen images:
|
|
66
67
|
|
|
67
68
|
**Prefer MCP:**
|
|
68
69
|
```
|
|
69
|
-
mcp__eventmodelers__get_slice_data { "boardId": "$BOARD_ID", "contextName": "<CONTEXT_NAME>" }
|
|
70
|
+
mcp__eventmodelers__get_slice_data { "boardId": "$BOARD_ID", "contextName": "<CONTEXT_NAME>", "projection": "fields", "format": "toon" }
|
|
71
|
+
mcp__eventmodelers__get_slice_data { "boardId": "$BOARD_ID", "contextName": "<CONTEXT_NAME>", "projection": "specs", "format": "toon" }
|
|
70
72
|
```
|
|
71
73
|
|
|
74
|
+
- `fields` — slice header (id, title, status, sliceType, chapter, context) plus every element with its fields and **dependencies**. Covers 5a (inventory), 5b (status), 5d (gaps) and 5f (structural shapes — the bed needs the SCREEN → COMMAND dependencies).
|
|
75
|
+
- `specs` — slice header plus specifications (given/when/then) and storylines, no elements. Covers 5c (spec coverage).
|
|
76
|
+
- If the user only asked for counts/status/coverage (no gap or shape check), use `"projection": "outline"` instead of `fields` — elements as `{id, title, type}` only, the cheapest read.
|
|
77
|
+
- `outline` and `specs` only encode as `json`, `yaml` or `toon` (`textual`/`emlang`/`esdm` return `PROJECTION_FORMAT_UNSUPPORTED`); use `toon` (token-efficient) or `json`.
|
|
78
|
+
|
|
72
79
|
**Fallback (no MCP):** see `references/api-fallback.md` — "Step 4 — Fetch slice data per context".
|
|
73
80
|
|
|
74
81
|
Fetch all contexts in parallel if there are multiple. Merge results, keyed by context name.
|
|
@@ -97,7 +104,7 @@ Group slices by status. Report counts per status. Flag any status that suggests
|
|
|
97
104
|
|
|
98
105
|
### 5c — Spec coverage
|
|
99
106
|
|
|
100
|
-
For each slice
|
|
107
|
+
For each slice in the `specs` projection, check whether its specifications are non-empty. Calculate:
|
|
101
108
|
- Slices **with** at least one GWT scenario
|
|
102
109
|
- Slices **without** any scenarios (grouped by slice type if detectable)
|
|
103
110
|
|
|
@@ -201,8 +208,10 @@ If a context was specified but not found, tell the user clearly and list the con
|
|
|
201
208
|
**Prefer MCP:**
|
|
202
209
|
```
|
|
203
210
|
mcp__eventmodelers__list_slices { "boardId": "$BOARD_ID" }
|
|
204
|
-
mcp__eventmodelers__get_nodes { "boardId": "$BOARD_ID", "type": "MODEL_CONTEXT" }
|
|
205
|
-
|
|
211
|
+
mcp__eventmodelers__get_nodes { "boardId": "$BOARD_ID", "type": "MODEL_CONTEXT", "projection": "line" }
|
|
212
|
+
mcp__eventmodelers__get_nodes { "boardId": "$BOARD_ID", "type": "CHAPTER", "projection": "line" }
|
|
213
|
+
mcp__eventmodelers__get_slice_data { "boardId": "$BOARD_ID", "contextName": "Ordering", "projection": "fields", "format": "toon" }
|
|
214
|
+
mcp__eventmodelers__get_slice_data { "boardId": "$BOARD_ID", "contextName": "Ordering", "projection": "specs", "format": "toon" }
|
|
206
215
|
```
|
|
207
216
|
|
|
208
217
|
**Fallback (no MCP):** see `references/api-fallback.md` — "Example — full board analysis".
|
|
@@ -19,23 +19,35 @@ Response: `{ "slices": [{ "id": "<uuid>", "title": "<name>", "status": "<status>
|
|
|
19
19
|
curl -s \
|
|
20
20
|
-H "x-token: $TOKEN" \
|
|
21
21
|
-H "x-user-id: analyze-existing-model" \
|
|
22
|
-
"$BASE_URL/api/org/$ORG_ID/boards/$BOARD_ID/nodes?type=MODEL_CONTEXT"
|
|
22
|
+
"$BASE_URL/api/org/$ORG_ID/boards/$BOARD_ID/nodes?type=MODEL_CONTEXT&projection=line"
|
|
23
|
+
|
|
24
|
+
# Timelines too — one without a context is its own context, named after the timeline
|
|
25
|
+
curl -s \
|
|
26
|
+
-H "x-token: $TOKEN" \
|
|
27
|
+
-H "x-user-id: analyze-existing-model" \
|
|
28
|
+
"$BASE_URL/api/org/$ORG_ID/boards/$BOARD_ID/nodes?type=CHAPTER&projection=line"
|
|
23
29
|
```
|
|
24
30
|
|
|
31
|
+
Returns `{id, type, title}` per node — all this step needs.
|
|
32
|
+
|
|
25
33
|
## Step 4 — Fetch slice data per context
|
|
26
34
|
|
|
27
35
|
```bash
|
|
28
36
|
curl -s \
|
|
29
37
|
-H "x-token: $TOKEN" \
|
|
30
38
|
-H "x-user-id: analyze-existing-model" \
|
|
31
|
-
"$BASE_URL/api/org/$ORG_ID/boards/$BOARD_ID/slicedata?contextName=<CONTEXT_NAME
|
|
39
|
+
"$BASE_URL/api/org/$ORG_ID/boards/$BOARD_ID/slicedata?contextName=<CONTEXT_NAME>&projection=fields&format=toon"
|
|
40
|
+
|
|
41
|
+
curl -s \
|
|
42
|
+
-H "x-token: $TOKEN" \
|
|
43
|
+
-H "x-user-id: analyze-existing-model" \
|
|
44
|
+
"$BASE_URL/api/org/$ORG_ID/boards/$BOARD_ID/slicedata?contextName=<CONTEXT_NAME>&projection=specs&format=toon"
|
|
32
45
|
```
|
|
33
46
|
|
|
34
|
-
|
|
35
|
-
- `
|
|
36
|
-
- `elements
|
|
37
|
-
- `
|
|
38
|
-
- `edges`: relationships between elements
|
|
47
|
+
- `projection=fields` — slice header (id, title, status, sliceType, chapter, context) + elements (commands, events, readmodels, screens, processors, tables) with their fields and dependencies. No specs/comments.
|
|
48
|
+
- `projection=specs` — slice header + specifications (given/when/then) and storylines. No elements.
|
|
49
|
+
- `projection=outline` — slice header + elements as `{id, title, type}` only; use it instead of `fields` when no gap/shape check is needed.
|
|
50
|
+
- `outline` and `specs` require `format=json|yaml|toon` (others → 400 `PROJECTION_FORMAT_UNSUPPORTED`).
|
|
39
51
|
|
|
40
52
|
## Example — full board analysis
|
|
41
53
|
|
|
@@ -46,17 +58,25 @@ curl -s \
|
|
|
46
58
|
-H "x-user-id: analyze-existing-model" \
|
|
47
59
|
"$BASE_URL/api/org/$ORG_ID/boards/$BOARD_ID/slicedata/slices"
|
|
48
60
|
|
|
49
|
-
# 2. Fetch MODEL_CONTEXT nodes
|
|
61
|
+
# 2. Fetch MODEL_CONTEXT nodes and timelines (a timeline without a context is its own context)
|
|
62
|
+
curl -s \
|
|
63
|
+
-H "x-token: $TOKEN" \
|
|
64
|
+
-H "x-user-id: analyze-existing-model" \
|
|
65
|
+
"$BASE_URL/api/org/$ORG_ID/boards/$BOARD_ID/nodes?type=MODEL_CONTEXT&projection=line"
|
|
50
66
|
curl -s \
|
|
51
67
|
-H "x-token: $TOKEN" \
|
|
52
68
|
-H "x-user-id: analyze-existing-model" \
|
|
53
|
-
"$BASE_URL/api/org/$ORG_ID/boards/$BOARD_ID/nodes?type=
|
|
69
|
+
"$BASE_URL/api/org/$ORG_ID/boards/$BOARD_ID/nodes?type=CHAPTER&projection=line"
|
|
54
70
|
|
|
55
|
-
# 3. Fetch
|
|
71
|
+
# 3. Fetch fields + specs projections for a context
|
|
72
|
+
curl -s \
|
|
73
|
+
-H "x-token: $TOKEN" \
|
|
74
|
+
-H "x-user-id: analyze-existing-model" \
|
|
75
|
+
"$BASE_URL/api/org/$ORG_ID/boards/$BOARD_ID/slicedata?contextName=Ordering&projection=fields&format=toon"
|
|
56
76
|
curl -s \
|
|
57
77
|
-H "x-token: $TOKEN" \
|
|
58
78
|
-H "x-user-id: analyze-existing-model" \
|
|
59
|
-
"$BASE_URL/api/org/$ORG_ID/boards/$BOARD_ID/slicedata?contextName=Ordering"
|
|
79
|
+
"$BASE_URL/api/org/$ORG_ID/boards/$BOARD_ID/slicedata?contextName=Ordering&projection=specs&format=toon"
|
|
60
80
|
```
|
|
61
81
|
|
|
62
82
|
Replace `$TOKEN`, `$ORG_ID`, `$BOARD_ID`, and the context name with real values resolved from the `connect` skill.
|