@eventmodelers/cli 1.0.21 → 1.0.23
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/shared/skills/learn-eventmodelers-api/SKILL.md +46 -0
- package/stacks/modeling-kit/templates/.claude/skills/eventmodeling-elaborating-scenarios/SKILL.md +10 -1
- package/stacks/modeling-kit/templates/.claude/skills/eventmodeling-identifying-outputs/SKILL.md +13 -0
- package/stacks/modeling-kit/templates/.claude/skills/eventmodeling-orchestrating-event-modeling/SKILL.md +95 -8
- package/stacks/modeling-kit/templates/.claude/skills/eventmodeling-storyboarding-events/SKILL.md +11 -0
- package/stacks/modeling-kit/templates/.claude/skills/eventmodeling-validating-event-models/SKILL.md +3 -0
- package/stacks/modeling-kit/templates/.claude/skills/eventmodeling-validating-event-models-checklist/SKILL.md +28 -9
- package/stacks/modeling-kit/templates/.claude/skills/place-element/SKILL.md +28 -0
package/package.json
CHANGED
|
@@ -110,6 +110,7 @@ READMODEL // Query result / materialized view
|
|
|
110
110
|
SCENARIO // GWT scenario
|
|
111
111
|
LANE // Timeline row
|
|
112
112
|
SLICE_BORDER // Slice boundary marker
|
|
113
|
+
MARKDOWN // Free-text markdown note — the content type a `feedback` lane accepts (see §2)
|
|
113
114
|
```
|
|
114
115
|
|
|
115
116
|
---
|
|
@@ -283,6 +284,33 @@ Drop a node into a timeline cell. Validates placement rules. If the node was alr
|
|
|
283
284
|
|
|
284
285
|
---
|
|
285
286
|
|
|
287
|
+
### Feedback lanes and MARKDOWN nodes (free-text notes)
|
|
288
|
+
|
|
289
|
+
A chapter has no `feedback` lane by default — add one first via the lanes endpoint above (`{"type": "feedback", "label": "Notes"}`), which returns a `laneId`. This is a normal row in `meta.timelineData.rows` (`type: "feedback"`) alongside `actor`/`interaction`/`swimlane`/`spec`.
|
|
290
|
+
|
|
291
|
+
Place a free-text markdown note in that lane the same way any other node is placed — a plain `node:created` event through `POST .../nodes/events` (§3), **not** the cell-drop endpoint above. `cellId` is `"<feedbackRowId>-<columnId>"`, same convention as every other lane:
|
|
292
|
+
|
|
293
|
+
```json
|
|
294
|
+
{
|
|
295
|
+
"id": "<event-uuid>",
|
|
296
|
+
"eventType": "node:created",
|
|
297
|
+
"nodeId": "<node-uuid>",
|
|
298
|
+
"boardId": "<boardId>",
|
|
299
|
+
"timestamp": 1234567890,
|
|
300
|
+
"chapterId": "<chapterId>",
|
|
301
|
+
"cellId": "<feedbackRowId>-<columnId>",
|
|
302
|
+
"meta": {
|
|
303
|
+
"type": "MARKDOWN",
|
|
304
|
+
"title": "Modeling Reasoning — <Chapter Name>",
|
|
305
|
+
"description": "# Heading\n\nFull markdown body here — headings, lists, bold, etc. all render."
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
```
|
|
309
|
+
|
|
310
|
+
The node's content lives in `meta.description` (a plain string of markdown source) — **not** `meta.content`; that field is silently accepted and stored but never rendered, producing a visibly empty note. There is no `fields[]` array on this element type, and no separate render/sketch call is needed (unlike SCREEN/HTML_SCREEN). `node.type` comes back as `"markdown"` (lowercase) on read.
|
|
311
|
+
|
|
312
|
+
---
|
|
313
|
+
|
|
286
314
|
## 3. Nodes
|
|
287
315
|
|
|
288
316
|
**File**: `src/slices/change/api-nodes/routes.ts`
|
|
@@ -314,6 +342,7 @@ interface NodeChangeEvent {
|
|
|
314
342
|
backgroundColor?: string
|
|
315
343
|
title?: string
|
|
316
344
|
url?: string
|
|
345
|
+
linkedTo?: string // origin node id — marks this node as a linked copy (see below)
|
|
317
346
|
// ...other node data fields
|
|
318
347
|
// Do NOT set a "type" here — the server derives the node's render type from
|
|
319
348
|
// meta.type automatically. Setting one yourself risks it being read as the
|
|
@@ -363,12 +392,29 @@ Auto-connect a node to its timeline neighbors — mirrors the frontend's auto-co
|
|
|
363
392
|
|
|
364
393
|
Incompatible or already-connected neighbors are reported in `skipped`, not an error. Returns an empty result for nodes not placed on any timeline, or not a connectable element type (e.g. SCENARIO/spec nodes are never auto-connected).
|
|
365
394
|
|
|
395
|
+
**Known gap**: the "own column already has a SCREEN" guard checks only for a SCREEN specifically — not an AUTOMATION. Placing an AUTOMATION in a COMMAND's own column, with a SCREEN sitting in the previous column, wires *both* into the COMMAND, leaving it with two issuers. A command is never issued by more than one thing — see `place-element` Step 7c for the check-and-fix.
|
|
396
|
+
|
|
397
|
+
**Connections (both auto-connect and `set_connection`) only ever pair nodes on the same timeline** — a node in Chapter A can never be wired directly to a node in Chapter B, even when the type pair is otherwise valid (e.g. EVENT→READMODEL). No direct cross-timeline connection is possible.
|
|
398
|
+
|
|
399
|
+
**The supported workaround is a linked copy**: place a copy of the source EVENT into its own swimlane on the *consuming* timeline, with `node.data.linkedTo` set to the origin node's id (see `linkedTo` in the `NodeChangeEvent` shape above; `eventmodeling-checking-completeness` documents how to recognize one when reading the board — it's an intentional copy, never a duplicate to clean up). Once the copy exists on the consuming timeline, it's a same-timeline node like any other and can be wired normally (e.g. linked-EVENT→READMODEL→SCREEN) to satisfy that context's local data need. Only fall back to documenting an integration gap when a linked copy genuinely isn't the right shape for the need (e.g. the consuming context needs live/aggregate data no single event copy can represent).
|
|
400
|
+
|
|
366
401
|
**Response**:
|
|
367
402
|
- `200` — `{ connected: [{edgeId, source, target, created}], skipped: [{nodeId, reason}] }`
|
|
368
403
|
- `404` — node not found
|
|
369
404
|
|
|
370
405
|
---
|
|
371
406
|
|
|
407
|
+
### POST `/api/org/:orgId/boards/:boardId/connections`
|
|
408
|
+
Create a single type-checked directed edge between two existing nodes — the REST fallback for `set_connection`.
|
|
409
|
+
|
|
410
|
+
**Request body**: `{ source: string, target: string }` (node ids)
|
|
411
|
+
|
|
412
|
+
**Response**: `200`/`201` — `{ edgeId, source, target }` on success · `400` — the pair is not one of the allowed type combinations · `404` — a node id doesn't exist
|
|
413
|
+
|
|
414
|
+
**`EVENT → READMODEL` is exempt from column ordering** — an event in a later column can connect to a read model in an earlier column, and vice versa. A read model is a continuously-listening projection, not a point-in-time action, so it can be fed by an event anywhere on its timeline. Every other pair (`SCREEN → COMMAND`, `COMMAND → EVENT`, `READMODEL → SCREEN`, `READMODEL → AUTOMATION`, `AUTOMATION → COMMAND`) is still forward-only. If a connection you expect to work gets rejected, retry once before concluding it's blocked — a transient rejection has been observed on an otherwise-valid pair.
|
|
415
|
+
|
|
416
|
+
---
|
|
417
|
+
|
|
372
418
|
## 4. Images
|
|
373
419
|
|
|
374
420
|
**File**: `src/slices/change/api-images/routes.ts`
|
package/stacks/modeling-kit/templates/.claude/skills/eventmodeling-elaborating-scenarios/SKILL.md
CHANGED
|
@@ -19,6 +19,8 @@ Prefer `mcp__eventmodelers__*` tools when available (registered by the `connect`
|
|
|
19
19
|
artifact — only build one when the user's request explicitly asks for a "storyline", "walkthrough",
|
|
20
20
|
or "narrative". Never generate a storyline as a side effect of ordinary scenario elaboration.
|
|
21
21
|
|
|
22
|
+
**Exception — the mandatory read-model scenario pass** (see the "Per read model" section of the Quality Checklist below): when the user's request already frames read-model coverage as open to either form — e.g. "add scenarios for the read models, GWT or storylines", or the orchestrating skill's Step 7 gate, which requires exactly that — the explicit-ask condition is satisfied at that request level, not per read model. Within that pass, decide GWT vs. storyline **per read model** based on domain fit (does this read model's row genuinely walk through multiple states worth narrating?), not by asking again for every individual read model and not by defaulting to GWT for all of them just because that's the baseline elsewhere in this skill. Outside that pass — an ad-hoc "write scenarios for X" with no mention of storylines — the strict on-demand rule above still applies unchanged.
|
|
23
|
+
|
|
22
24
|
A GWT scenario asserts one isolated transition: a single precondition, a single action, a single
|
|
23
25
|
resulting outcome. A storyline instead narrates one specific use case as an ordered sequence of
|
|
24
26
|
**beats**, where the *same* element (usually a read model) is walked through multiple states across
|
|
@@ -804,11 +806,18 @@ After posting, tell the user:
|
|
|
804
806
|
|
|
805
807
|
**No command has only 2 scenarios unless all other types were reviewed and found inapplicable.**
|
|
806
808
|
|
|
809
|
+
**Per read model — this is a separate, equally mandatory pass, not an afterthought of the command pass above:**
|
|
810
|
+
- [ ] **Every READMODEL on the board has at least one view scenario** — GWT (`given`: source EVENTs, `when`: empty, `then`: the READMODEL) or a storyline. A model with dozens of command scenarios and 0 read-model scenarios is not a complete Step 7 — it's easy to walk away thinking coverage is thorough because the command side looks exhaustive, so check the read-model side explicitly before reporting this step done.
|
|
811
|
+
- [ ] **Population scenario** — the view shows correct data after its source event(s)
|
|
812
|
+
- [ ] **Removal/update scenario, where applicable** — a row disappears or changes (`expectEmptyList: true` for list-type views) after an event that supersedes it (expiry, return, archival, withdrawal, status change, etc.). `EVENT → READMODEL` is exempt from column ordering (see `learn-eventmodelers-api` §3), so a later event connecting back to an earlier-placed read model is normal — add the connection if it's missing rather than assuming the scenario is impossible. Only skip this scenario, with a documented gap (TASK comment), when the superseding event genuinely lives in a different chapter.
|
|
813
|
+
- [ ] **GWT vs. storyline decided per read model, not applied uniformly** — reach for a storyline wherever the *same* read model row genuinely walks through multiple states worth narrating; the rest of the read models in the same model may be correctly GWT-only. Don't default to one format for every read model just because it worked for the first one, and don't judge "multiple states" by counting *distinct connected event types* — that undercounts real candidates. **A single event type recurring with different data is just as valid a storyline driver as several different event types**: `AccountFunded($40)` then `AccountFunded($70)` walking a balance read model from $40 to $110 is exactly as strong a storyline as a multi-event lifecycle. In practice this means almost every list/aggregate read model qualifies — a titles list growing from one row to two as the same `TitleAdded`-shaped event recurs, a dashboard's counters incrementing as the same `CopyAdded` event recurs, are both genuine storylines, not "just" GWT territory. Ask "does replaying this read model's *actually connected* event(s) more than once produce an interesting accumulated/changed state?" — not "how many different event types feed this."
|
|
814
|
+
- [ ] **No redundancy or contradiction between a read model's GWT scenarios and its storyline** — if both exist for the same read model, read the storyline's beats before finalizing the GWTs. A GWT that asserts the same state a beat already shows is redundant (delete it); a GWT written without tracing the same causal sequence the storyline encodes can end up asserting something the storyline's beats actually contradict (e.g. claiming two entities coexist in a view when the storyline correctly shows one superseding the other) — delete or fix it, never leave a contradiction on the board.
|
|
815
|
+
- [ ] **Cross-context read models handled honestly** — if a read model's true source events live in a different chapter, `given` can't reference them (same-timeline-only, like connections); write the scenario with an empty `given` and say so explicitly in the scenario title, rather than silently omitting the scenario or fabricating a same-timeline event that isn't the real source
|
|
816
|
+
|
|
807
817
|
**Format and posting:**
|
|
808
818
|
- [ ] State preconditions are explicit in Given (not just "Given an order")
|
|
809
819
|
- [ ] Actions are clear in When
|
|
810
820
|
- [ ] Outcomes are verifiable in Then (event produced or rejection with reason)
|
|
811
|
-
- [ ] Every view has at least one update scenario
|
|
812
821
|
- [ ] All scenarios posted to board spec cells via the `/scenarios` API
|
|
813
822
|
- [ ] **Workshop facilitation approach documented**
|
|
814
823
|
- [ ] **All stakeholder roles (PO, Dev, QA, Domain Expert) perspectives captured**
|
package/stacks/modeling-kit/templates/.claude/skills/eventmodeling-identifying-outputs/SKILL.md
CHANGED
|
@@ -372,6 +372,18 @@ READ MODEL → AUTOMATION → COMMAND → EVENT
|
|
|
372
372
|
|
|
373
373
|
Treat any screen or automation without an incoming read model as a gap unless it provably needs no prior state at all (e.g., a blank registration form).
|
|
374
374
|
|
|
375
|
+
### One read model per screen region, not one read model per screen
|
|
376
|
+
|
|
377
|
+
**Do not default to a single monolithic read model that supplies an entire screen.** If a screen has more than one visually distinct data region (e.g. a stats row plus a list below it, or a summary card plus a detail table), each region gets its **own** read model, sourced only from the events that region actually needs — not the union of every event any part of the screen touches.
|
|
378
|
+
|
|
379
|
+
Why this matters, beyond tidiness:
|
|
380
|
+
- **It prevents backward arrows.** A single screen-wide read model is forced to aggregate from whatever events each of its regions needs, which often means reaching back across many columns to events scattered throughout the timeline — and the read model can only sit in one column, so some of those connections end up spanning a wide gap or, worse, pushing the read model's column later than some of its screen's other consumers require. Splitting by region lets each narrower read model sit close to its own natural source event(s), keeping every `EVENT → READMODEL` arrow short and forward.
|
|
381
|
+
- **Regions evolve independently.** A stats tile and a "recently added" list are driven by different events, change at different rates, and are typically owned by different slices in implementation. Bundling them into one read model couples their release/change cadence for no reason.
|
|
382
|
+
|
|
383
|
+
**How to realize this on the board**: each region's read model gets its own column (inserted immediately after that region's primary source event, per the placement rule above) and its own **screen copy** — a duplicate of the same screen layout where the region that read model drives is shown normally and every other region is visually de-emphasized (e.g. `filter: blur(3px); opacity: 0.45; pointer-events: none;` on the non-relevant regions, keeping the target region crisp, optionally with a highlight border). Title each copy after the region it foregrounds, e.g. `"Librarian Dashboard — Statistics"` and `"Librarian Dashboard — Recently Added"` as two separate HTML_SCREEN nodes, each in its own column, each wired `READMODEL → SCREEN` to only its own read model. This is the `html-screen` skill's job when asked to produce the copy — pass it the full original screen markup plus which region to foreground.
|
|
384
|
+
|
|
385
|
+
Before finalizing any read model, ask: "does this screen have more than one visually distinct data region?" If yes, split — don't ask whether splitting is worth the extra columns, it always is at this scale, since the alternative is a hidden coupling and a higher chance of a backward-arrow layout error.
|
|
386
|
+
|
|
375
387
|
### Read models serve existing screens and automations
|
|
376
388
|
|
|
377
389
|
**Before designing any read model, enumerate every SCREEN and AUTOMATION already placed on the board** (from Step 3 — Storyboarding). Read models exist to serve those elements:
|
|
@@ -717,6 +729,7 @@ Identify UI needs without event sources:
|
|
|
717
729
|
- [ ] **Every SCREEN from storyboarding is connected to at least one read model** (via `READMODEL → SCREEN`); only blank creation forms may be exempt — verified via the mandatory per-node pass above, not assumed
|
|
718
730
|
- [ ] **Every AUTOMATION from storyboarding is connected to at least one read model** (via `READMODEL → AUTOMATION`) — same per-node verification
|
|
719
731
|
- [ ] **No read model is placed without a connected SCREEN or AUTOMATION consumer**
|
|
732
|
+
- [ ] **No read model spans more than one visually distinct screen region** — a screen with N distinct data regions gets N read models and N highlighted screen copies, not one screen-wide read model
|
|
720
733
|
- [ ] Every read model has clear purpose
|
|
721
734
|
- [ ] Every data field has event source
|
|
722
735
|
- [ ] Update logic for each event is explicit
|
|
@@ -13,7 +13,7 @@ allowed-tools:
|
|
|
13
13
|
|
|
14
14
|
Prefer `mcp__eventmodelers__*` tools when available (registered by the `connect` skill) — the curl blocks below are the fallback for sessions without MCP connected.
|
|
15
15
|
|
|
16
|
-
Coordinates the
|
|
16
|
+
Coordinates the 11-step Event Modeling workflow. Each step delegates to a
|
|
17
17
|
specialized skill — this skill holds the sequence, transition conditions, and
|
|
18
18
|
what to carry forward between steps.
|
|
19
19
|
|
|
@@ -35,6 +35,9 @@ These rules govern how every element is placed on the board. Enforce them throug
|
|
|
35
35
|
### Never stack read models at the end
|
|
36
36
|
Placing all read models in new columns at the very end of the timeline severs the visual connection to the events they're derived from. The board must show a coherent left-to-right narrative where each slice is self-contained.
|
|
37
37
|
|
|
38
|
+
### One read model per screen region — the main defense against backward arrows
|
|
39
|
+
A screen-wide read model that has to aggregate from events scattered across many columns is the most common source of backward-arrow layout errors: it can only occupy one column, but its regions each pull from different, differently-positioned source events. The fix is architectural, not just positional — **split the read model by UI region before it ever gets placed.** A screen with a stats row and a list below it is two read models and two screen copies (each copy showing the full screen with the non-relevant region blurred/dimmed via CSS, the relevant region left normal), not one. Each narrower read model then sits naturally close to its own source event, and the `EVENT → READMODEL → SCREEN` chain for each region stays short and forward. See `eventmodeling-identifying-outputs` ("One read model per screen region") and `eventmodeling-storyboarding-events` ("View screens with multiple data regions get one copy per region") for the mechanics — apply this during Steps 3 and 5, before the placement problem exists, rather than reordering columns to patch it afterward.
|
|
40
|
+
|
|
38
41
|
### No unplaced elements (0,0 nodes)
|
|
39
42
|
|
|
40
43
|
After each step that creates elements (Steps 1–5), scan for any nodes that have no cell reference and are stranded at the default canvas position (0,0). These arise when `node:created` is called without `cellId`.
|
|
@@ -101,6 +104,10 @@ Screens placed during Step 3 (Storyboarding) are provisional positions. Steps 4
|
|
|
101
104
|
### Column insertion
|
|
102
105
|
Use `POST /timelines/:tl/columns` with `{"index": N}` to insert a column at a specific position (shifts existing columns right). Do not use `{}` (append) when placing read models or view screens — always target the correct position.
|
|
103
106
|
|
|
107
|
+
### Documenting decisions inline, at any step
|
|
108
|
+
|
|
109
|
+
Separate from the Step 11 chapter-level reasoning note: at **any** step (1–10), if that step makes a decision or assumption important enough that a later reader could otherwise misread the model, add a small MARKDOWN note in the **column where that decision applies** (same feedback-lane + MARKDOWN mechanics as Step 11 — see there for the exact calls). Use sparingly — this is for a genuine "why is it like this" moment (an assumption that fills a gap the brief left open, a rejected alternative, a non-obvious constraint), not routine narration of what a step did.
|
|
110
|
+
|
|
104
111
|
---
|
|
105
112
|
|
|
106
113
|
## Interview Phase
|
|
@@ -166,7 +173,7 @@ Also update the Interview Trail table row for this step (Status → Done, Key Ou
|
|
|
166
173
|
|
|
167
174
|
After writing the summary, run `/compact` to clear the accumulated context before loading the next skill. The summary written above is the handoff — the next skill reads it from the file, not from the conversation history.
|
|
168
175
|
|
|
169
|
-
This keeps each step's context lean and prevents token bloat from accumulating across all
|
|
176
|
+
This keeps each step's context lean and prevents token bloat from accumulating across all 11 steps.
|
|
170
177
|
|
|
171
178
|
---
|
|
172
179
|
|
|
@@ -266,9 +273,14 @@ if Conway's Law boundaries are not relevant to the project.
|
|
|
266
273
|
Invoke `eventmodeling-elaborating-scenarios`.
|
|
267
274
|
|
|
268
275
|
**Input**: Commands and read models.
|
|
269
|
-
**Output to carry forward**: Given-When-Then specifications
|
|
270
|
-
|
|
271
|
-
|
|
276
|
+
**Output to carry forward**: Given-When-Then specifications (or storylines, for
|
|
277
|
+
walkthrough-style coverage) for every command **and every read model**, posted
|
|
278
|
+
to the board spec cells.
|
|
279
|
+
**Gate**: Every command has scenarios covering **all applicable types** from
|
|
280
|
+
the elaborating-scenarios workflow — not just happy path + one error case —
|
|
281
|
+
**and every READMODEL on the board has at least one view scenario**. See the
|
|
282
|
+
gate checklist below. A command-only pass is an incomplete Step 7, even if
|
|
283
|
+
every command's coverage looks exhaustive.
|
|
272
284
|
|
|
273
285
|
> **Do not reduce scenarios to a simple good-case / bad-case pair.** The `eventmodeling-elaborating-scenarios` skill defines a structured scenario workshop covering seven scenario types per command. All applicable types must be written before this step is complete.
|
|
274
286
|
|
|
@@ -283,7 +295,9 @@ and view, posted to the board spec cells.
|
|
|
283
295
|
|
|
284
296
|
For each type, ask the relevant question against the business case and write a scenario if the situation can occur. Do not decide based on brevity — decide based on the domain.
|
|
285
297
|
|
|
286
|
-
>
|
|
298
|
+
> **Read models need scenarios too — easy to forget since the seven types above are command-shaped.** Every READMODEL needs at least one view scenario (GWT or storyline); a read model with zero scenarios is as incomplete as a command with zero. `eventmodeling-elaborating-scenarios`'s own checklist covers the details — connectivity rules, GWT-vs-storyline judgment per read model, and avoiding redundancy between a storyline and its GWTs — don't re-derive those here, just enforce the gate.
|
|
299
|
+
|
|
300
|
+
> The `eventmodeling-elaborating-scenarios` skill designs scenarios **and** posts them to the board. It uses `GET /timelines/$TL/spec-info` to resolve node IDs, then `POST /timelines/$TL/columns/$COL/scenarios` with all scenarios for that column in one call (array body) — this applies identically whether the column holds a COMMAND or a READMODEL. The SCENARIO spec node is created automatically. Ensure the timeline and column IDs are resolved and passed to the skill before invoking it.
|
|
287
301
|
|
|
288
302
|
---
|
|
289
303
|
|
|
@@ -331,6 +345,76 @@ already had one.
|
|
|
331
345
|
|
|
332
346
|
---
|
|
333
347
|
|
|
348
|
+
### Step 11: Document Reasoning
|
|
349
|
+
|
|
350
|
+
Not delegated to a separate skill — performed directly by this orchestrating skill, since the reasoning being documented is the *orchestrator's own* accumulated context across all prior steps, not something a single-step skill has visibility into.
|
|
351
|
+
|
|
352
|
+
**Input**: The complete, sliced, validated model (Steps 1–10) plus this session's own record of decisions made along the way — assumptions added beyond the literal brief, sequencing corrections, business rules deliberately encoded as scenarios rather than events, read-model sharing choices, and any cross-context/integration gaps found (e.g. during Step 6 or Step 9).
|
|
353
|
+
|
|
354
|
+
**Output to carry forward**: One MARKDOWN node per chapter, placed in that chapter's first column, containing the full modeling reasoning for that bounded context in as much detail as the session actually has to give — not a boilerplate template filled in thinly.
|
|
355
|
+
|
|
356
|
+
**Gate**: Every chapter on the board has exactly one reasoning MARKDOWN node in its first column, non-empty, written after the model for that chapter was already complete (so it can describe the *finished* shape, not a plan).
|
|
357
|
+
|
|
358
|
+
**Mechanics** — a chapter has no `feedback` lane by default; add one first, then place a MARKDOWN node in it:
|
|
359
|
+
|
|
360
|
+
1. **Add a feedback lane** (once per chapter, skip if one already exists — check `meta.timelineData.rows` for `type === "feedback"` first):
|
|
361
|
+
|
|
362
|
+
**Prefer MCP:**
|
|
363
|
+
```
|
|
364
|
+
mcp__eventmodelers__add_lane { "boardId": "$BOARD_ID", "timelineId": "$CHAPTER_ID", "type": "feedback", "label": "Notes" }
|
|
365
|
+
```
|
|
366
|
+
|
|
367
|
+
**Fallback (no MCP):**
|
|
368
|
+
```bash
|
|
369
|
+
curl -s -X POST "$BASE_URL/api/org/$ORG_ID/boards/$BOARD_ID/timelines/$CHAPTER_ID/lanes" \
|
|
370
|
+
-H "x-token: $TOKEN" -H "x-board-id: $BOARD_ID" -H "x-user-id: orchestrator" \
|
|
371
|
+
-H "Content-Type: application/json" \
|
|
372
|
+
-d '{"type":"feedback","label":"Notes"}'
|
|
373
|
+
# → { laneId, type, label, index, totalLanes }
|
|
374
|
+
```
|
|
375
|
+
|
|
376
|
+
2. **Resolve the first column's ID** — the leftmost entry in `meta.timelineData.columns` (same chapter fetch used throughout this workflow for row/column lookups).
|
|
377
|
+
|
|
378
|
+
3. **Create the MARKDOWN node**, `cellId = "<feedbackLaneId>-<firstColumnId>"`:
|
|
379
|
+
|
|
380
|
+
**Prefer MCP:**
|
|
381
|
+
```
|
|
382
|
+
mcp__eventmodelers__submit_node_events {
|
|
383
|
+
"boardId": "$BOARD_ID",
|
|
384
|
+
"events": [{
|
|
385
|
+
"id": "<event-uuid>", "eventType": "node:created", "nodeId": "<node-uuid>",
|
|
386
|
+
"boardId": "$BOARD_ID", "timestamp": 1234567890,
|
|
387
|
+
"chapterId": "$CHAPTER_ID", "cellId": "<feedbackLaneId>-<firstColumnId>",
|
|
388
|
+
"meta": { "type": "MARKDOWN", "title": "Modeling Reasoning — <Chapter Name>", "description": "<full markdown body>" }
|
|
389
|
+
}]
|
|
390
|
+
}
|
|
391
|
+
```
|
|
392
|
+
|
|
393
|
+
**Fallback (no MCP):**
|
|
394
|
+
```bash
|
|
395
|
+
curl -s -X POST "$BASE_URL/api/org/$ORG_ID/boards/$BOARD_ID/nodes/events" \
|
|
396
|
+
-H "x-token: $TOKEN" -H "x-board-id: $BOARD_ID" -H "x-user-id: orchestrator" \
|
|
397
|
+
-H "Content-Type: application/json" \
|
|
398
|
+
-d '[{"id":"<event-uuid>","eventType":"node:created","nodeId":"<node-uuid>","boardId":"<BOARD_ID>",
|
|
399
|
+
"timestamp":1234567890,"chapterId":"<CHAPTER_ID>","cellId":"<feedbackLaneId>-<firstColumnId>",
|
|
400
|
+
"meta":{"type":"MARKDOWN","title":"Modeling Reasoning — <Chapter Name>","description":"<full markdown body>"}}]'
|
|
401
|
+
```
|
|
402
|
+
|
|
403
|
+
The note's body lives in **`meta.description`** as plain markdown source — headings, lists, bold, code fences, tables all render. **Not `meta.content`** — that field is accepted and stored without error but never rendered by the board UI, producing a visibly empty note; this was caught by comparing against a note authored directly in the UI, so treat it as confirmed, not a guess. There is no separate render/sketch call (unlike SCREEN/HTML_SCREEN) and no `fields[]` array on this element type.
|
|
404
|
+
|
|
405
|
+
**What the note should actually contain** — write for the next person (or next session) who opens this board cold, not for whoever just built it:
|
|
406
|
+
- **Scope**: what business process this chapter covers, and its stream roots (identity keys).
|
|
407
|
+
- **Assumptions added beyond the literal brief** — anything invented to fill a gap the requirements left open, and why (e.g. adding a resolution event so a state isn't a one-way trap door).
|
|
408
|
+
- **Business rules deliberately encoded as scenarios, not new events** — so a reader doesn't mistake a missing event for an oversight.
|
|
409
|
+
- **Sequencing or design corrections made mid-workflow** — e.g. a column reorder because an event's original placement implied the wrong causality.
|
|
410
|
+
- **Read model design rationale** — especially where one read model deliberately serves several screens/automations, so it doesn't read as a missing 1:1 mapping.
|
|
411
|
+
- **Any cross-context or integration gaps found** (Step 6 Conway's Law, or discovered incidentally, e.g. a same-timeline connection constraint blocking a needed cross-chapter data dependency) — state the finding and the viable resolutions, matching whatever TASK/QUESTION comment was also posted on the affected node.
|
|
412
|
+
- **Closing summary**: element counts and the validation verdict for this chapter's slice of the model.
|
|
413
|
+
|
|
414
|
+
If a chapter's story is genuinely simple, say so briefly rather than padding — but for any chapter with real design decisions behind it, this note is the place those decisions survive past the session that made them.
|
|
415
|
+
|
|
416
|
+
---
|
|
417
|
+
|
|
334
418
|
## Final Output
|
|
335
419
|
|
|
336
420
|
A complete, sliced event model consisting of:
|
|
@@ -344,10 +428,11 @@ A complete, sliced event model consisting of:
|
|
|
344
428
|
- Completeness verification
|
|
345
429
|
- Validation report with readiness verdict
|
|
346
430
|
- Slice definitions marking every independently deployable feature boundary
|
|
431
|
+
- A Modeling Reasoning MARKDOWN node in each chapter's first column, documenting the design decisions, assumptions, and any integration gaps behind that chapter's model
|
|
347
432
|
|
|
348
433
|
### Optional Follow-on Skills
|
|
349
434
|
|
|
350
|
-
These skills are not part of the
|
|
435
|
+
These skills are not part of the 11-step main path but extend the model for
|
|
351
436
|
specific needs:
|
|
352
437
|
|
|
353
438
|
- **`eventmodeling-designing-event-models`** — Use when stream identity,
|
|
@@ -364,12 +449,14 @@ specific needs:
|
|
|
364
449
|
## Quality Checklist
|
|
365
450
|
|
|
366
451
|
- [ ] No elements stranded at 0,0 — every EVENT, COMMAND, READMODEL, SCREEN, and AUTOMATION has a valid `cellId` in its chapter
|
|
367
|
-
- [ ] All
|
|
452
|
+
- [ ] All 11 modeling steps completed — no step skipped without explicit reason
|
|
368
453
|
- [ ] Every COMMAND, READMODEL, and AUTOMATION has a matching slice definition on the board
|
|
454
|
+
- [ ] Every chapter has a Modeling Reasoning MARKDOWN node in its first column, written after that chapter's model was complete
|
|
369
455
|
- [ ] Role Catalog exists with named human roles and system processors
|
|
370
456
|
- [ ] Every command is attributed to a specific role from the Role Catalog
|
|
371
457
|
- [ ] Every read model satisfies at least one UI or processor query need
|
|
372
458
|
- [ ] At least one Given-When-Then scenario exists per command
|
|
459
|
+
- [ ] At least one view scenario (GWT or storyline) exists per READMODEL — not just per command
|
|
373
460
|
- [ ] Completeness check shows no unresolved field traceability gaps
|
|
374
461
|
- [ ] Validation returns PASS or PASS WITH WARNINGS with all critical issues resolved
|
|
375
462
|
- [ ] Interview trail in `.trogonai/` updated with status of each completed step
|
package/stacks/modeling-kit/templates/.claude/skills/eventmodeling-storyboarding-events/SKILL.md
CHANGED
|
@@ -559,6 +559,17 @@ When placing screens on the board, follow these alignment rules:
|
|
|
559
559
|
|
|
560
560
|
> **Do not create standalone screen columns that are disconnected from commands or read models.** Every screen must either share its column with the command it submits, or be placed one column to the right of the read model it displays.
|
|
561
561
|
|
|
562
|
+
### View screens with multiple data regions get one copy per region
|
|
563
|
+
|
|
564
|
+
If a view screen has more than one visually distinct data region (a stats row, a list below it, a summary card next to a detail panel, etc.), **do not design it as a single screen fed by one screen-wide read model.** Each region gets its own screen copy and, in Step 5, its own read model in its own column:
|
|
565
|
+
|
|
566
|
+
- Render the **same full screen layout** once per region.
|
|
567
|
+
- In each copy, the region that copy is meant to foreground stays normal; every other region is visually de-emphasized — `filter: blur(3px); opacity: 0.45; pointer-events: none;` on the wrapper of the non-relevant region(s) reads clearly in an HTML_SCREEN render (Bulma classes still apply normally underneath the blur).
|
|
568
|
+
- Title each copy after the region, e.g. `"Librarian Dashboard — Statistics"` and `"Librarian Dashboard — Recently Added"`, not both just `"Librarian Dashboard"`.
|
|
569
|
+
- Place each copy in its own column, one column to the right of the read model that will feed it (per the table above) — this is normally a different column per copy, since each region typically has a different natural source event.
|
|
570
|
+
|
|
571
|
+
This is why the copies matter even though the underlying screen looks the same: it keeps each `READMODEL → SCREEN` connection narrow and forward (see `eventmodeling-identifying-outputs`'s "One read model per screen region" section), instead of one wide read model forced to straddle far-apart source events and pushed into a single column that can't sit correctly relative to every region's own event.
|
|
572
|
+
|
|
562
573
|
### Placing Automations
|
|
563
574
|
|
|
564
575
|
When a processor or system actor reacts to events automatically (no human interaction), place an **AUTOMATION** node in the actor row instead of a SCREEN. Automations go in the same column as the COMMAND they trigger and the READMODEL that feeds them.
|
package/stacks/modeling-kit/templates/.claude/skills/eventmodeling-validating-event-models/SKILL.md
CHANGED
|
@@ -80,6 +80,7 @@ Verify each swimlane has:
|
|
|
80
80
|
### 2. Consistency Checks
|
|
81
81
|
|
|
82
82
|
- [ ] **Event-Stream Mapping**: Every event belongs to exactly one lane
|
|
83
|
+
- [ ] **Single Command Issuer**: Every command is issued by exactly one SCREEN or AUTOMATION — never two. Check each COMMAND node's inbound edges; more than one SCREEN/AUTOMATION wired into the same command is a CRITICAL violation (commonly an auto-connect artifact — see `place-element` Step 7c)
|
|
83
84
|
- [ ] **Command Outcomes**: Every command produces events OR documents rejection
|
|
84
85
|
- [ ] **Deterministic Projections**: State can only be derived one way from events
|
|
85
86
|
- [ ] **No Side Effects in Projections**: Pure state reconstruction logic
|
|
@@ -192,6 +193,7 @@ Format findings as comments:
|
|
|
192
193
|
| Orphaned events | Events no one listens to | Link to projections or commands |
|
|
193
194
|
| No read models | Commands reading query/read models for validation | Add separate query read models; keep command state minimal |
|
|
194
195
|
| Circular dependencies | Projection A depends on B, B on A | Redesign stream boundaries |
|
|
196
|
+
| Command issued by multiple things | COMMAND node has 2+ inbound SCREEN/AUTOMATION edges | Keep the deliberate same-column issuer, remove the rest via `set_connection` (`action: "remove"`) — see `place-element` Step 7c |
|
|
195
197
|
|
|
196
198
|
## Key Principles for Event Sourcing
|
|
197
199
|
|
|
@@ -233,6 +235,7 @@ A model is **ready for code generation** if:
|
|
|
233
235
|
- [ ] State projection is deterministic from events
|
|
234
236
|
- [ ] Commands validate against current state only
|
|
235
237
|
- [ ] Each command either produces events or rejects (no silent failures)
|
|
238
|
+
- [ ] **No command has more than one inbound SCREEN/AUTOMATION edge (a command is never issued by more than one thing)**
|
|
236
239
|
- [ ] Event causality/command-event mapping is clear
|
|
237
240
|
- [ ] State transitions are documented
|
|
238
241
|
- [ ] No direct references between lanes
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: eventmodeling-validating-event-models-checklist
|
|
3
|
-
description: "Validate event-sourced CQRS models against
|
|
3
|
+
description: "Validate event-sourced CQRS models against 17 architectural checks across 7 phases. Identifies anti-patterns and confirms compliance with event sourcing principles. Use when reviewing event models for production readiness or after completing event modeling steps. Do not use for: reviewing incomplete or in-progress models (use eventmodeling-validating-event-models), or for elaborating new scenarios (use eventmodeling-elaborating-scenarios)."
|
|
4
4
|
allowed-tools:
|
|
5
5
|
- Write
|
|
6
6
|
- Bash
|
|
@@ -12,7 +12,7 @@ allowed-tools:
|
|
|
12
12
|
|
|
13
13
|
Prefer `mcp__eventmodelers__*` tools when available (registered by the `connect` skill) — the curl blocks below are the fallback for sessions without MCP connected.
|
|
14
14
|
|
|
15
|
-
**Purpose**: Validate any event-sourced CQRS event model against
|
|
15
|
+
**Purpose**: Validate any event-sourced CQRS event model against 17 architectural checks across 7 phases. Identifies anti-patterns and confirms compliance with event sourcing principles.
|
|
16
16
|
|
|
17
17
|
**Applies To**: Any domain - e-commerce, banking, SaaS, marketplace, healthcare, etc.
|
|
18
18
|
|
|
@@ -24,7 +24,7 @@ Prefer `mcp__eventmodelers__*` tools when available (registered by the `connect`
|
|
|
24
24
|
|
|
25
25
|
**What It Does**:
|
|
26
26
|
1. Reads current board state (EVENT, COMMAND, READMODEL nodes) as input
|
|
27
|
-
2. Systematically applies
|
|
27
|
+
2. Systematically applies 17 validation checks across 7 phases
|
|
28
28
|
2. Identifies violations of event sourcing principles (domain-agnostic)
|
|
29
29
|
3. Flags anti-patterns (calculations as events, non-entity streams, etc.)
|
|
30
30
|
4. Verifies read model/event distinction
|
|
@@ -59,12 +59,13 @@ Use the board nodes as the model input. After the checklist, use `handle-comment
|
|
|
59
59
|
|
|
60
60
|
## Validation Phases (Domain-Agnostic)
|
|
61
61
|
|
|
62
|
-
### Phase 1: Event Stream & Command Handler State Validation (
|
|
62
|
+
### Phase 1: Event Stream & Command Handler State Validation (4 checks)
|
|
63
63
|
- Check 1.1: Each event belongs to exactly one stream
|
|
64
64
|
- Check 1.2: Each command handler owns its own [CommandHandler]State class
|
|
65
65
|
- Check 1.3: No hard dependencies between command handlers (orchestrated via events only)
|
|
66
|
+
- Check 1.4: Each command is issued by exactly one thing — no COMMAND node has more than one inbound SCREEN/AUTOMATION edge
|
|
66
67
|
|
|
67
|
-
**Anti-pattern to catch**: Sharing state across handlers or treating state as persistent aggregate
|
|
68
|
+
**Anti-pattern to catch**: Sharing state across handlers or treating state as persistent aggregate; a command wired from two issuers (commonly a `place-element` auto-connect artifact where the command's own column holds an AUTOMATION and the previous column's SCREEN also gets wired in — see `learn-eventmodelers-api` §3 auto-connect "Known gap")
|
|
68
69
|
|
|
69
70
|
### Phase 2: Event Quality Validation (3 checks)
|
|
70
71
|
- Check 2.1: Events represent domain facts, not calculations
|
|
@@ -204,6 +205,24 @@ Reconstruct [CommandHandler]State on-demand
|
|
|
204
205
|
|
|
205
206
|
**Why**: State is derived from events, never stored. Events are source of truth. This enables consistent replay, audit trails, and time-travel debugging.
|
|
206
207
|
|
|
208
|
+
### 5. Command With Multiple Issuers
|
|
209
|
+
```
|
|
210
|
+
ANTI-PATTERN:
|
|
211
|
+
FlagLoanOverdue (COMMAND) has two inbound edges:
|
|
212
|
+
- "Flag Overdue Loans" (AUTOMATION, same column)
|
|
213
|
+
- "Adjust Due Date" (SCREEN, previous column)
|
|
214
|
+
- Result: unclear who/what actually triggers the command; validation and UI-vs-automation
|
|
215
|
+
authority checks (e.g. Role & Actor Attribution) can no longer be answered
|
|
216
|
+
|
|
217
|
+
CORRECT:
|
|
218
|
+
FlagLoanOverdue (COMMAND) has exactly one inbound edge, from the AUTOMATION that owns it.
|
|
219
|
+
If the SCREEN's user genuinely needs to trigger the same outcome, that's a second,
|
|
220
|
+
distinctly-named command (or the SCREEN issuing it directly, with the automation removed) —
|
|
221
|
+
not two issuers sharing one command.
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
**Why**: A command is never issued by more than one thing. Each command represents one specific trigger's decision to act — collapsing two triggers onto one command node hides which actor is actually responsible, and usually means either a naming/slice-boundary mistake or a stray auto-connect edge (`learn-eventmodelers-api` §3, "Known gap"). Fix by removing the extra edge via `set_connection` (`action: "remove"`), not by keeping both.
|
|
225
|
+
|
|
207
226
|
---
|
|
208
227
|
|
|
209
228
|
## Questions to Ask During Validation
|
|
@@ -233,7 +252,7 @@ Reconstruct [CommandHandler]State on-demand
|
|
|
233
252
|
## Success Criteria
|
|
234
253
|
|
|
235
254
|
**Model is validated when**:
|
|
236
|
-
- All
|
|
255
|
+
- All 17 checks pass (or have documented workarounds)
|
|
237
256
|
- No critical anti-patterns identified
|
|
238
257
|
- All 3 final questions answer YES
|
|
239
258
|
- Event sourcing principles clearly upheld
|
|
@@ -306,7 +325,7 @@ Running the checklist after Step 2 prevents wasting time on later steps if core
|
|
|
306
325
|
|
|
307
326
|
## Checklist Questions by Domain
|
|
308
327
|
|
|
309
|
-
The skill applies the same
|
|
328
|
+
The skill applies the same 17 checks regardless of domain. Here's how to think about it in different contexts:
|
|
310
329
|
|
|
311
330
|
**E-commerce domain**:
|
|
312
331
|
- Events: OrderCreated, OrderConfirmed, PaymentAuthorized, OrderShipped
|
|
@@ -338,7 +357,7 @@ The principle is the same across all domains: **immutable facts as events, calcu
|
|
|
338
357
|
|
|
339
358
|
## Quality Checklist
|
|
340
359
|
|
|
341
|
-
- [ ] All
|
|
360
|
+
- [ ] All 17 checks evaluated — no check skipped without documented justification
|
|
342
361
|
- [ ] Every FAIL result includes the specific event, handler, or stream that violated the check
|
|
343
362
|
- [ ] Anti-patterns identified by name with the exact model element that triggered the flag
|
|
344
363
|
- [ ] Final verdict is one of: PASS / PASS WITH WARNINGS / FAIL — no ambiguous outcomes
|
|
@@ -359,6 +378,6 @@ The principle is the same across all domains: **immutable facts as events, calcu
|
|
|
359
378
|
|
|
360
379
|
## Validation Checklist Reference
|
|
361
380
|
|
|
362
|
-
The
|
|
381
|
+
The 17-point checklist is defined in the **Validation Phases** section above.
|
|
363
382
|
Each check includes the anti-pattern to catch and questions to ask when evaluating your model.
|
|
364
383
|
|
|
@@ -446,6 +446,34 @@ Response: `{ "hashes": { "<event-uuid>": "<hash>" } }`
|
|
|
446
446
|
|
|
447
447
|
---
|
|
448
448
|
|
|
449
|
+
## Step 7c — Verify the command has exactly one issuer
|
|
450
|
+
|
|
451
|
+
**A command is never issued by more than one thing.** Run this check whenever `elementType` is `SCREEN`, `AUTOMATION`, or `COMMAND` — placing any of these can trigger the server's fire-and-forget auto-connect (`learn-eventmodelers-api` §3), which wires `SCREEN→COMMAND` and `AUTOMATION→COMMAND` edges to type-compatible neighbors in the node's own column and the previous column.
|
|
452
|
+
|
|
453
|
+
**Why this can go wrong**: auto-connect only skips the previous column's SCREEN when the COMMAND's own column already has a SCREEN — it does not check for an AUTOMATION there. So a COMMAND whose own column holds an AUTOMATION, with a SCREEN sitting in the previous column, ends up wired from *both* — the automation (same column) and the screen (previous column) — and now looks issued by two things.
|
|
454
|
+
|
|
455
|
+
After placing, resolve the relevant COMMAND node (the one just placed, or the one in the same/adjacent column as the SCREEN/AUTOMATION just placed) and inspect its edges:
|
|
456
|
+
|
|
457
|
+
**Prefer MCP:**
|
|
458
|
+
```
|
|
459
|
+
mcp__eventmodelers__get_node { "boardId": "<BOARD_ID>", "nodeId": "<COMMAND_NODE_ID>" }
|
|
460
|
+
```
|
|
461
|
+
|
|
462
|
+
Count inbound edges where `target === COMMAND_NODE_ID` and the source node is type `SCREEN` or `AUTOMATION`.
|
|
463
|
+
|
|
464
|
+
- **0 or 1 such edge** → fine, nothing to do.
|
|
465
|
+
- **2 or more** → keep the edge whose source sits in the COMMAND's own column (the deliberate, same-slice issuer) and remove every other one:
|
|
466
|
+
|
|
467
|
+
```
|
|
468
|
+
mcp__eventmodelers__set_connection { "boardId": "<BOARD_ID>", "source": "<extra-issuer-node-id>", "target": "<COMMAND_NODE_ID>", "action": "remove" }
|
|
469
|
+
```
|
|
470
|
+
|
|
471
|
+
If it's not clear which edge is the deliberate one (e.g. neither source sits in the COMMAND's own column), do not guess — leave both edges and post a `QUESTION` comment on the COMMAND node via `handle-comment` instead, describing the ambiguity.
|
|
472
|
+
|
|
473
|
+
**Fallback (no MCP)**: there is no documented single-purpose REST endpoint for edge removal outside `/nodes/events`. Connect MCP via the `connect` skill first; if that's genuinely not possible, skip the auto-fix and post a `QUESTION` comment on the COMMAND node flagging the double issuer for manual resolution instead of fabricating a payload.
|
|
474
|
+
|
|
475
|
+
---
|
|
476
|
+
|
|
449
477
|
## Step 8 — Report back
|
|
450
478
|
|
|
451
479
|
Tell the user:
|