@eventmodelers/cli 1.0.108 → 1.0.109

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@eventmodelers/cli",
3
- "version": "1.0.108",
3
+ "version": "1.0.109",
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": {
@@ -30,7 +30,7 @@ Write tools take their items as an array (`nodeIds[]`, `elements[]`, `connection
30
30
  | `get_node` | `boardId`, `nodeId`, `projection?` (`"cells"` \| `"edges"`) | Get one node (no `cellName` — address it by `nodeId`; cell addresses come only from `get_board_outline`). `projection: "cells"` (CHAPTER nodes only) returns just `{rows, columns, cells}` instead of the full `timelineData` — use whenever only the grid/occupancy is needed, not the whole chapter; `projection: "edges"` returns just that node's inbound/outbound connections instead of `findNodeById`'s full record. Both are opt-in — omitting `projection` is the unchanged full response. An unknown id is a `NODE_NOT_FOUND` error | §3 `GET .../nodes/:nodeId` (REST returns the node as stored, without `cellName`; `404` carries `code: 'NODE_NOT_FOUND'`) |
31
31
  | `get_node_comments` | `boardId`, `nodeId` | List comments on a node | §1 `GET .../nodes/:nodeId/comments` |
32
32
  | `get_chat_attachment` | `boardId`, `sessionId`, `attachmentId` | The file a chat message came with — the message's `attachments` list `{id, type, ending}`. Images come back as images, a PDF as an embedded resource, csv / xml / txt / json as text. Only the agent the message was addressed to can read it (`CHAT_ATTACHMENT_NOT_FOUND`); needs the connection's `x-agent-id` (`CHAT_AGENT_REQUIRED`). Max 5 MB per file. | §15 `GET /api/org/:orgId/chat/sessions/:sessionId/attachments/:attachmentId` |
33
- | `post_chat_message` | `boardId`, `text`, `replyTo?`, `sessionId?`, `preferUserId?`, `snippet?` | Write into a person's chat — a reply (`replyTo` marks their message answered) or a question you start (`preferUserId`). `text` ≤ 1000 characters. `snippet` attaches one interactive element under the text (confirm, poll, tasks, changes, link, image, code); the person's choice comes back as their next chat message. See §16 | §16 `POST /api/org/:orgId/boards/:boardId/chat/agent-messages` |
33
+ | `post_chat_message` | `boardId`, `text`, `replyTo?`, `sessionId?`, `preferUserId?`, `snippet?` | Write into a person's chat — a reply (`replyTo` marks their message answered) or a question you start (`preferUserId`). `text` ≤ 1000 characters. `snippet` attaches one interactive element under the text (confirm, poll, tasks, changes, link, image, code, report); the person's choice comes back as their next chat message. See §16 | §16 `POST /api/org/:orgId/boards/:boardId/chat/agent-messages` |
34
34
  | `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` |
35
35
  | `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` |
36
36
  | `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) |
@@ -1099,8 +1099,9 @@ Rule of thumb: if you are about to write "Shall I…?", "Want me to…?", "A or
1099
1099
  | to point at an element | `link` | nothing — it pans the canvas |
1100
1100
  | to show a picture | `image` | nothing |
1101
1101
  | to show a command or payload | `code` | nothing |
1102
+ | to show numbers as a diagram (counts per slice, a trend, shares of a whole) | `report` | nothing |
1102
1103
 
1103
- `confirm`, `poll`, `tasks` and `changes` are questions: a person answers once, then they show as answered. `link`, `image` and `code` are display only and never wait for anything.
1104
+ `confirm`, `poll`, `tasks` and `changes` are questions: a person answers once, then they show as answered. `link`, `image`, `code` and `report` are display only and never wait for anything.
1104
1105
 
1105
1106
  ### Shapes
1106
1107
  ```typescript
@@ -1118,6 +1119,9 @@ Rule of thumb: if you are about to write "Shall I…?", "Want me to…?", "A or
1118
1119
  { kind: 'link', nodeId: string, label?: string }
1119
1120
  { kind: 'image', url: string, alt?: string, caption?: string } // https or png/jpg/gif/webp data URL; no svg
1120
1121
  { kind: 'code', code: string, language?: string, title?: string } // monospaced, with a copy button
1122
+ { kind: 'report', chart: 'bar' | 'line' | 'pie', title?: string, unit?: string,
1123
+ labels: string[], // 1–50 points: x axis, or the pie slices
1124
+ series: Array<{ name: string, values: number[] }> } // one number per label; bar/line up to 6 series, pie uses only the first (no negatives)
1121
1125
  ```
1122
1126
 
1123
1127
  ### How to use them
@@ -1127,6 +1131,7 @@ Rule of thumb: if you are about to write "Shall I…?", "Want me to…?", "A or
1127
1131
  - **Name elements** with `nodeId` wherever you can (`tasks`, `changes`, `link`), so the person can click through to them — in a `tasks` list, put the element's name in `description` and its id in `nodeId`.
1128
1132
  - **Answers are text.** For a `tasks` answer, match the titles (or ids) back to your list; an element the person left unticked was declined, not forgotten. A `poll` option with a `message` of your own makes the answer unambiguous.
1129
1133
  - `get_chat_session` shows your snippet on the message as `snippet`, and the person's answer is the user message after it, carrying `context.snippetReply.messageId` (your message's id) — useful to tell an answer to a snippet from a new message.
1134
+ - **`report`** draws the numbers you send — it computes nothing, so aggregate first. A series whose length differs from `labels`, or a non-number, makes the whole snippet invisible. Keep `text` as a one-line summary of what the diagram shows.
1130
1135
  - A snippet is shown exactly as you send it — keep secrets and tokens out of `code` and `image`.
1131
1136
 
1132
1137
  ---
@@ -199,6 +199,31 @@ Total: <n> slices
199
199
  <2–4 sentences: overall model maturity, the most important gap or risk, one concrete suggestion>
200
200
  ```
201
201
 
202
+ ### In a chat — answer with a pie chart
203
+
204
+ When this run has a chat (`CHAT_SESSION_ID` is set — a prompt's `origin_session_id`, or a `CHAT` turn), the report goes
205
+ to the chat as **one** `post_chat_message` (`sessionId` = `CHAT_SESSION_ID`; `replyTo` = this turn's `message_id` in a
206
+ `CHAT` turn) carrying a **`report` snippet**: a pie of the slice status breakdown (5b). The text is the short summary
207
+ (contexts, spec coverage, the most important gap) — that is also the fallback if the snippet cannot be shown. Skip the
208
+ long format above; mention the structural gaps and shapes in a sentence or two, and offer the full list on request.
209
+
210
+ ```
211
+ mcp__eventmodelers__post_chat_message {
212
+ "boardId": "$BOARD_ID",
213
+ "sessionId": "<CHAT_SESSION_ID>",
214
+ "text": "3 contexts, 24 slices. 18 of 24 have scenarios; Checkout has 4 slices without any.",
215
+ "snippet": {
216
+ "kind": "report", "chart": "pie", "title": "Slices by status", "unit": "slices",
217
+ "labels": ["Done", "InProgress", "Planned", "Blocked"],
218
+ "series": [{ "name": "Slices", "values": [12, 6, 4, 2] }]
219
+ }
220
+ }
221
+ ```
222
+ - Only statuses with at least one slice, one number per label — the chart draws exactly what you send, so count first.
223
+ - One snippet per message: if the person asks for more diagrams (spec coverage, element inventory per type as a `bar`),
224
+ answer each in its own reply.
225
+ - Still read-only: nothing is posted to the board.
226
+
202
227
  If a context was specified but not found, tell the user clearly and list the contexts that do exist.
203
228
 
204
229
  ---
@@ -0,0 +1,165 @@
1
+ ---
2
+ name: detect-architecture-drift
3
+ description: Compare the event model on the board with the code in this repository and report where they have drifted apart — fields on commands/events/read models, specifications vs tests, screens, edges, slices missing in code, and slices in code that the model doesn't have. Read-only; every finding comes with a suggested fix the user confirms first.
4
+ ---
5
+
6
+ # Detect Architecture Drift
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 the connect skill has completed.
9
+
10
+ Prefer `mcp__eventmodelers__*` tools when available. See `learn-eventmodelers-api` for the curl fallback.
11
+
12
+ The board is the intended design, the code is what was built. Over time the two part ways: someone edits a handler without touching the model, someone renames a field on the board after the slice was built. This skill finds those places and proposes how to close each gap. **It never changes the board or the code** — it reports, and only acts on what the person confirms.
13
+
14
+ ---
15
+
16
+ ## Step 1 — Parse arguments and resolve scope
17
+
18
+ From `$ARGUMENTS` and the prompt context, extract:
19
+
20
+ | Field | How to find it | Default |
21
+ |-------|---------------|---------|
22
+ | `timelineId` | `context.timelineId` of the prompt (overrules the prompt's own `timeline_id`), or a timeline/chapter named in the text | whole board |
23
+ | `contextName` | a bounded context named in the text | all contexts |
24
+ | `sliceTitle` | a single slice named in the text | all slices in scope |
25
+
26
+ **If a timeline is in focus, only that timeline's slices are compared** — both on the board side and, through the slice → code mapping of Step 3, on the code side. Code that belongs to no slice in scope is not reported as "additional" in a scoped run; say so in the report footer instead of guessing.
27
+
28
+ ---
29
+
30
+ ## Step 2 — Read the model (once)
31
+
32
+ Cheapest read first, once per run:
33
+
34
+ ```
35
+ mcp__eventmodelers__list_slices { "boardId": "$BOARD_ID" }
36
+ mcp__eventmodelers__get_slice_data { "boardId": "$BOARD_ID", "contextName": "<CONTEXT>", "format": "toon" }
37
+ ```
38
+
39
+ Use the **full** slice data (no `projection`): you need fields with types and `idAttribute`, dependencies (the edges), specifications, storylines and screens together. If the scope is a timeline, pass its title as `contextName` and keep only slices whose `chapter` is that timeline.
40
+
41
+ Call `validate_model { boardId, chapterId }` for a scoped timeline — it already lists structural gaps on the board side (zero/multi-issuer commands, sourceless read models, missing scenarios). Those are model problems, not drift: mention them in one line, don't duplicate them as findings.
42
+
43
+ Index the result in memory: per slice → commands, events, read models, screens, automations, specifications, dependencies.
44
+
45
+ ---
46
+
47
+ ## Step 3 — Understand the code
48
+
49
+ You are in a repository that may hold any stack. Find out which, don't assume.
50
+
51
+ 1. Read `.build-kit/CLAUDE.md` (and `.build-kit/AGENTS.md` if present) — its *Structure* section says where slices live and how they're named (e.g. `src/slices/{slice}/` for Node, `src/main/java/.../slices/{context}/{slice}/` for Axon).
52
+ 2. If `.build-kit/.slices/<context>/<slice>/slice.json` exists, it is the snapshot the code was **last built from** — useful for telling "the code changed" apart from "the model changed since the build" (see Step 5).
53
+ 3. Without a build kit, infer the layout from the repo (folders named after slices, `*Command*`, `*Event*`, projection/read-model files, route/UI files). If you cannot find any slice structure, stop and tell the user what you looked at — do not fabricate a mapping.
54
+
55
+ Build a **code inventory** per slice folder: the command type and its fields, the events it emits and their fields, read-model/projection types and fields, the screens/components/routes wired to it, tests and what they assert, and what each handler/projection/automation reads and writes.
56
+
57
+ Map slice → code folder by name (ignore case, spaces and the `slice:` prefix, same slug rule as `load-slice`). A slice with no matching folder, or a folder with no matching slice, is itself a finding (5a/5b).
58
+
59
+ Read only the slice folders in scope. Shared event/type folders are read only for the events the in-scope slices reference.
60
+
61
+ ---
62
+
63
+ ## Step 4 — Compare
64
+
65
+ Work slice by slice. Every finding records: **kind**, **slice**, **element** (board `nodeId` where there is one), **what the model says**, **what the code says**.
66
+
67
+ | Kind | Look for |
68
+ |------|----------|
69
+ | **Slice missing in code** | Slice on the board in status `Planned`/`InProgress`/`Done`… with no code folder. Status `Created`/`Planned` is expected not to have code yet — report only slices the board calls `Done`/`Review`. |
70
+ | **Slice only in code** | Code folder shaped like a slice (command handler, projection, automation) with no slice on the board. |
71
+ | **Fields** | On every COMMAND, EVENT and READMODEL: a field only in the model, a field only in the code, a name that differs (case or typo), a type that differs, `idAttribute` vs `@TargetEntityId`/event tag/aggregate key mismatch, optional vs required. |
72
+ | **Specifications** | A model GWT scenario with no test asserting it; a test (given/when/then) with no matching model scenario; a scenario whose events/fields no longer match the code. Storylines are narrative — compare their beats only for elements that no longer exist. |
73
+ | **Screens** | A SCREEN on the board with no UI counterpart; a UI screen/route/component wired to a slice's command or read model that the board doesn't show; a screen whose displayed read-model fields or submitted command fields differ from the model's dependencies. |
74
+ | **Edges** | Model dependency with no code counterpart, and the reverse: command → event (handler emits it), event → read model (projection consumes it), read model → screen, event → automation → command. A handler that emits an event the model doesn't connect, or a projection subscribing to events the model doesn't wire in. |
75
+ | **Elements** | An event/command/read model present on one side only, or a rename (same fields, different name). |
76
+
77
+ Rules for judging, in order:
78
+
79
+ - **Obvious typos and clearly wrong flags are one finding with a direct fix** (`custoemrId` in the code vs `customerId` on the board; `idAttribute: false` on the entity's own id) — name the correct side, don't ask which it is.
80
+ - **Direction matters.** A mismatch with no earlier evidence has no "right" side: propose the fix in the direction the evidence points (see Step 5), and say when it is a judgement call.
81
+ - Don't report framework noise: generated ids, metadata/envelope fields, serialization annotations, test fixtures' helper types, routes when the slice has no screen.
82
+ - Don't flag what is clearly intentional (an internal slice with no screen; a read model used only by another read model).
83
+ - Report each underlying difference **once**, at the element where it starts; a renamed field shows up on the command and the event and the read model — that is one finding listing the chain, not three.
84
+
85
+ ---
86
+
87
+ ## Step 5 — Decide the direction, propose the fix
88
+
89
+ For every finding, pick who should move:
90
+
91
+ | Evidence | Fix goes to | How it's done |
92
+ |----------|-------------|---------------|
93
+ | Code differs from `.build-kit/.slices` snapshot, board still equals the snapshot | **Model** — the code moved on deliberately | Skill that changes the board: `/attributes` (fields), `/timeline` / `/place-element` (elements), `/eventmodeling-elaborating-scenarios` (specs), `/storyboard-screen` or `/html-screen` (screens) |
94
+ | Board differs from the snapshot, code still equals it | **Code** — the model moved on | Set the slice back to `Planned` with `/update-slice-status` so the build kit picks it up again and diffs code against `slice.json` |
95
+ | Both moved, or no snapshot | **Judgement call** — present both options, recommend one | Recommend by which side is more specific/complete |
96
+ | Slice only in code | **Model** | Add the slice, via `/eventmodeling-slicing-event-models` after placing its elements |
97
+ | Slice missing in code, board says `Done` | **Model status** (it isn't done) | `/update-slice-status` → `Planned` |
98
+
99
+ Never propose "fix the code" by editing it from this skill — code changes belong to the build kit (`/update-slice-status` → `Planned`).
100
+
101
+ ---
102
+
103
+ ## Step 6 — Show the findings; act only on confirmation
104
+
105
+ Nothing is modified until the person answers.
106
+
107
+ ### In a chat (`CHAT_SESSION_ID` is set)
108
+
109
+ Post **one** `post_chat_message` with a `tasks` snippet — the list of findings *and* the question which fixes to apply (same mechanics as `/wdyt` Step 4.0):
110
+
111
+ ```
112
+ mcp__eventmodelers__post_chat_message {
113
+ "boardId": "$BOARD_ID",
114
+ "sessionId": "<CHAT_SESSION_ID>",
115
+ "text": "Found 6 places where the model and the code differ in Registration. Tick the fixes to apply.",
116
+ "snippet": {
117
+ "kind": "tasks",
118
+ "headline": "Apply these fixes?",
119
+ "submitLabel": "Apply",
120
+ "tasks": [
121
+ { "id": "d:model:<nodeId>:field:customerId", "title": "Rename field “custoemrId” to “customerId” on Register Customer", "description": "Code has customerId — model has a typo", "nodeId": "<nodeId>" },
122
+ { "id": "d:code:<sliceId>", "title": "Rebuild “Cancel Order” — the board added the field “reason” after it was built", "description": "Sets the slice back to Planned", "nodeId": "<sliceId>" }
123
+ ]
124
+ }
125
+ }
126
+ ```
127
+
128
+ - `title` is the proposed fix, one sentence, plain language. `description` says what differs (model vs code). `nodeId` is the element or slice border to jump to.
129
+ - `id` is `d:<model|code|decide>:<nodeId>:<kind>[:<detail>]` — the answering turn remembers nothing, so it must carry the direction and target. Put **every** finding in the one snippet (it pages itself at 5).
130
+ - Zero findings: a one-line text reply, no snippet. Then **end the turn.**
131
+
132
+ The answer comes as a `CHAT` turn (`Please do these:` + ticked titles or ids). Read the snippet back with `get_chat_session`, match what was ticked, and create **one prompt** per direction: model fixes → *"Apply these model-drift fixes: `<id> | <title>` per line"*; code fixes → set each slice to `Planned`. Unticked fixes are declined — drop them.
133
+
134
+ ### Without a chat
135
+
136
+ Print the report below, with every finding as a `- [ ]` checkbox carrying its proposed fix, and ask which to apply. Wait for the answer.
137
+
138
+ ```
139
+ ## Architecture Drift — <scope: board | timeline "<title>" | context "<name>">
140
+ Compared: <n> slices on the board, <n> slice folders in code Analysed: <ISO timestamp>
141
+
142
+ ### Slices
143
+ - [ ] <slice> — in code, not on the board → add it to the model
144
+ - [ ] <slice> — board says Done, no code → set back to Planned
145
+
146
+ ### Fields
147
+ - [ ] <slice> / <element>: model `customerId: String`, code `custoemrId` → fix model typo
148
+
149
+ ### Specifications / Screens / Edges
150
+ - [ ] ...
151
+
152
+ ### Summary
153
+ <2–3 sentences: how far apart they are, which direction the drift mostly runs, the one fix worth doing first.>
154
+ Not checked: <what was out of scope or unreadable>
155
+ ```
156
+
157
+ Omit empty sections. Say plainly when nothing drifted.
158
+
159
+ ---
160
+
161
+ ## Learnings
162
+
163
+ - Read-only until confirmed — the report is the product; the fixes run as separate, ticked work.
164
+ - A scoped run (timeline in focus) never reports "slice only in code" for code outside the scope.
165
+ - One difference, one finding: follow a rename along the chain instead of listing it per element.
@@ -203,7 +203,7 @@ for it. Instead:
203
203
  and posts nothing to the board until they answer (`/wdyt` Step 4.0). Your reply here is one line —
204
204
  *"On it — looking at Registration."*
205
205
  - **a skill by name** — the message starts with `/<name>` of one of your skills (the chat panel's menu
206
- writes these: `/analyze-existing-model`, `/add-next-slice`, `/examples`, `/timeline …`,
206
+ writes these: `/analyze-existing-model`, `/detect-architecture-drift`, `/add-next-slice`, `/examples`, `/timeline …`,
207
207
  `/html-screen …`, `/storyboard …`): the person chose that skill, so it **is** the work — create one prompt,
208
208
  *"Run /<name> <the rest of the message>"*, with the target resolved from `context=` like any other
209
209
  (selected elements, chapter), and say so in one line. Don't re-interpret it as a question. A skill that
@@ -356,6 +356,7 @@ nothing to sanitize either, since a board change is not user text.
356
356
  | Design or update a single wireframe/sketch screen (explicit request only) | `/storyboard-screen` |
357
357
  | Business analysis, gap spotting, posting questions | `/wdyt` |
358
358
  | Analyse the existing model structure, slice coverage, element counts | `/analyze-existing-model` |
359
+ | Find where the model and the code have drifted apart (fields, specs, screens, edges, missing/extra slices) and propose fixes | `/detect-architecture-drift` |
359
360
  | Turn a document or image (e.g. a tender/RFP) into a chaptered event model with screens, scenarios and a written blueprint summary | `/summarize` |
360
361
  | Look up any API endpoint or element type not already covered by the skill you're executing | `/learn-eventmodelers-api` |
361
362
  | Add or rename an attribute across a chain of elements | `/attributes` |