@milehimikey/em 0.1.0 → 0.2.0
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/.claude/skills/event-modeling/SKILL.md +184 -0
- package/.claude/skills/event-modeling/reference/em-dsl.md +193 -0
- package/.claude/skills/event-modeling/reference/methodology.md +177 -0
- package/.claude/skills/event-modeling/templates/live.html +53 -0
- package/.claude/skills/event-modeling/templates/model-readme.md +28 -0
- package/.claude/skills/event-modeling/templates/slice.md +70 -0
- package/.claude/skills/event-modeling/templates/state.md +34 -0
- package/README.md +26 -0
- package/dist/cli.js +25 -2
- package/dist/cli.js.map +1 -1
- package/dist/emit/theme.js +2 -3
- package/dist/emit/theme.js.map +1 -1
- package/package.json +3 -2
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: event-modeling
|
|
3
|
+
description: >-
|
|
4
|
+
Guide a user and their team through building rigorous Event Models with the `em` CLI, using
|
|
5
|
+
the 7 steps of Event Modeling and the 4 patterns (State Change, State View, Automation,
|
|
6
|
+
Translation), and produce implementation-ready slice design documents. Use when the user wants
|
|
7
|
+
to event-model a business process or system, do event modeling / event storming, design slices,
|
|
8
|
+
build or edit an `.em` model, or run the em tool. Works in resumable phases via an argument:
|
|
9
|
+
`discover` (steps 1-4), `model` (steps 5-7), `slice` (deep slice specs), plus `watch` and
|
|
10
|
+
`validate`. With no argument it resumes from the saved state file.
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
# Event Modeling with `em`
|
|
14
|
+
|
|
15
|
+
You are facilitating an Event Modeling session. Your job is to **extract an accurate model from
|
|
16
|
+
the user through Socratic questioning** — never to invent the domain. You drive the `em` CLI to
|
|
17
|
+
render the model live, and you produce implementation-ready slice design docs.
|
|
18
|
+
|
|
19
|
+
Read `reference/methodology.md` (the 7 steps + 4 patterns) and `reference/em-dsl.md` (DSL syntax
|
|
20
|
+
+ validation rules) before doing real work — they are the source of truth. Templates live in
|
|
21
|
+
`templates/`.
|
|
22
|
+
|
|
23
|
+
## Operating principles (every phase)
|
|
24
|
+
|
|
25
|
+
- **Socratic, one question at a time.** Ask focused questions; use `AskUserQuestion` for tight
|
|
26
|
+
multiple-choice decisions. Never assume a domain fact — extract it. Prefer "who/why/what-if/
|
|
27
|
+
what-must-always-be-true/how-do-you-know" over yes-no.
|
|
28
|
+
- **Don't guess — park it.** Unresolved items go into the Open Questions list in
|
|
29
|
+
`.event-modeling.md`, not into invented model content.
|
|
30
|
+
- **Happy path first; branches belong to slicing.** Steps 1-7 build the **happy-path spine**.
|
|
31
|
+
Alternate, unhappy, and exception-path events (rejections, removals, cancellations,
|
|
32
|
+
expirations, declines) are **not** enumerated as a separate discover/model task — they
|
|
33
|
+
surface during the `slice` phase as each slice's alternate/error flows and rule-boundary
|
|
34
|
+
scenarios, and any new branch events get added to the `.em` then. Never list "draft the
|
|
35
|
+
branch events" as a discover/model to-do or ask the user to do it before slicing.
|
|
36
|
+
- **Reflect and re-render.** After each meaningful increment, update the `.em` file, re-render,
|
|
37
|
+
and show the user what changed. Encourage running the live view (`watch`) so a team can follow.
|
|
38
|
+
- **Keep the `.em` structural; put depth in `note` docs.** The diagram holds flow; invariants,
|
|
39
|
+
fields, and scenarios live in `slices/*.md` linked via `note "slices/<name>.md"`.
|
|
40
|
+
- **No UI in headless/API models.** If the system is headless (clients call an API, not screens),
|
|
41
|
+
use no `ui`/`persona`: writes are `external translation → command → event` (name the inbound
|
|
42
|
+
adapter for the caller/role); reads are `read model → read translation` (an API query that
|
|
43
|
+
triggers **no** command — the analog of `view → ui`). **Repeat read models** across slices so the
|
|
44
|
+
timeline flows left-to-right — put each repeat **right after the event that feeds it**, sourcing
|
|
45
|
+
only that one adjacent event, so every arrow is short (a read model far from its source events
|
|
46
|
+
draws long arrows that look like a forbidden read→read link). Repeats render cleanly — em only
|
|
47
|
+
warns on a duplicate name that's *referenced* by `from`/`arrow`. Slice order matters: a reaction
|
|
48
|
+
must be directly followed by its command slice, and a read slice must not be (see `em-dsl.md`).
|
|
49
|
+
See `reference/methodology.md` (State View) and `reference/em-dsl.md`.
|
|
50
|
+
- **Validate continuously.** Run `em validate` and fix errors/warnings as you go (see DSL ref).
|
|
51
|
+
- **Save state at the end of every session** so work resumes cleanly.
|
|
52
|
+
|
|
53
|
+
## Preconditions (run first)
|
|
54
|
+
|
|
55
|
+
1. Check the tool: `em --version`. If missing, tell the user to run `npm i -g @milehimikey/em`
|
|
56
|
+
and stop until installed.
|
|
57
|
+
2. Locate the model. Look for an existing `<dir>/.event-modeling.md` and `*.em` in the working
|
|
58
|
+
directory (or a `models/` subfolder). If found, read the state file. If not, and the phase
|
|
59
|
+
needs one, ask the user for the model name and where to create it.
|
|
60
|
+
3. Parse the argument (`$ARGUMENTS`) to pick the phase below. With **no argument**, read the
|
|
61
|
+
state file and resume the recorded phase/step; if no model exists, propose starting `discover`.
|
|
62
|
+
|
|
63
|
+
## Project layout this skill creates
|
|
64
|
+
|
|
65
|
+
```
|
|
66
|
+
<model-name>/
|
|
67
|
+
<model-name>.em # the model (slice docs linked via note "...")
|
|
68
|
+
<model-name>.svg # render target for em watch
|
|
69
|
+
live.html # auto-refresh viewer (copy of templates/live.html, SVG_FILE set)
|
|
70
|
+
README.md # overview + slice index (from templates/model-readme.md)
|
|
71
|
+
.event-modeling.md # resumable state (from templates/state.md)
|
|
72
|
+
slices/<slice-name>.md # one rich slice doc per slice (from templates/slice.md)
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
When creating a new model, scaffold the directory, copy the templates in (filling the
|
|
76
|
+
placeholders), and set `SVG_FILE` in the copied `live.html` to `<model-name>.svg`. You may use
|
|
77
|
+
`em init` for a starter `.em`, but usually you'll build it up from the discovery conversation
|
|
78
|
+
instead. Fill template placeholders — never leave `{{...}}` in delivered files.
|
|
79
|
+
|
|
80
|
+
---
|
|
81
|
+
|
|
82
|
+
## Phase: `discover` — steps 1-4
|
|
83
|
+
|
|
84
|
+
Goal: a draft model of events, storyboard, commands, and views. Loose is OK; structure comes next.
|
|
85
|
+
|
|
86
|
+
1. **Brainstorm events (step 1).** Ask the user to name everything that happens, as past-tense
|
|
87
|
+
facts. Capture a flat list. Probe for missed state changes.
|
|
88
|
+
2. **Plot / storyboard (step 2).** Order the events into the narrative. Identify **personas**
|
|
89
|
+
(actors) and the **UI** screens at each step. Add `persona` declarations and `ui` elements.
|
|
90
|
+
3. **Inputs (step 3).** For each event, find the **command** that causes it (imperative name).
|
|
91
|
+
Form `command → event` slices (State Change pattern).
|
|
92
|
+
4. **Outputs (step 4).** Identify the **read models / views** consumers need and wire them with
|
|
93
|
+
`from "Event"` (State View pattern). In a **headless/API** model there is no UI — a read model is
|
|
94
|
+
consumed by an **external read translation** (the API query, triggers no command), and read
|
|
95
|
+
models are **repeated** in each slice where they're read so the timeline flows (see
|
|
96
|
+
`reference/methodology.md`).
|
|
97
|
+
|
|
98
|
+
End of phase: write/refresh the `.em`, render it, update `.event-modeling.md` (steps done,
|
|
99
|
+
decisions, open questions, slice inventory). Tell the user they can stop here and resume with
|
|
100
|
+
`/event-modeling model`.
|
|
101
|
+
|
|
102
|
+
## Phase: `model` — steps 5-7
|
|
103
|
+
|
|
104
|
+
Goal: a structurally complete, **validated** model with correct patterns and swimlanes.
|
|
105
|
+
|
|
106
|
+
1. **Swimlanes & patterns (step 5).** Group events into **contexts** (bounded contexts /
|
|
107
|
+
aggregates) — ask which events share a consistency boundary and who owns them. Classify each
|
|
108
|
+
slice as one of the 4 patterns. **Split every automation _and every translation_ across two
|
|
109
|
+
slices** (the reaction — processor/translation — plus its read model in one slice; the
|
|
110
|
+
triggered command + its event in the *next* slice). A translation or automation is a reaction:
|
|
111
|
+
it **triggers a command and never records an event directly**. Add `translation` slices for
|
|
112
|
+
external inputs (externally triggered: external → translation → command → event) and for the
|
|
113
|
+
system reacting to its own state (internally triggered: read model → translation → command →
|
|
114
|
+
event).
|
|
115
|
+
2. **Elaborate scenarios — first pass (step 6).** For each slice, capture the happy-path
|
|
116
|
+
Given/When/Then and the obvious invariants as short notes. (The full spec is the `slice` phase.)
|
|
117
|
+
3. **Evaluate completeness (step 7).** Walk the model: every command emits an event, every view
|
|
118
|
+
has a source, no orphan events, every UI is reachable, automations **and translations** split
|
|
119
|
+
correctly (each reaction triggers a command — none wired straight to an event). Run
|
|
120
|
+
`em validate` and resolve all errors and warnings — but check the reaction→command→event split
|
|
121
|
+
by hand, since `em validate` does **not** flag a translation/automation that emits an event
|
|
122
|
+
directly (see `reference/em-dsl.md`).
|
|
123
|
+
|
|
124
|
+
End of phase: render, update state, suggest `/event-modeling slice` to write implementation specs.
|
|
125
|
+
|
|
126
|
+
## Phase: `slice` — deep slice documents
|
|
127
|
+
|
|
128
|
+
Goal: implementation-ready specs. Go slice by slice (let the user pick order, or follow the
|
|
129
|
+
timeline). Check the state file's slice inventory for what's already done.
|
|
130
|
+
|
|
131
|
+
This is also where **branch / unhappy-path events** are discovered and added to the model — as a
|
|
132
|
+
slice's alternate/error flows surface (a rejection, removal, cancellation, decline, expiry), add
|
|
133
|
+
the corresponding event/slice to the `.em` and re-render. The happy-path spine from earlier
|
|
134
|
+
phases is the starting point, not the finished event set.
|
|
135
|
+
|
|
136
|
+
For each slice:
|
|
137
|
+
1. Hold a Socratic deep-dive to fill every section of `templates/slice.md`: intent, trigger/actor,
|
|
138
|
+
command + field table (types & rules), event(s) + payload (mark immutable facts), invariants
|
|
139
|
+
(give each a stable ID), Given/When/Then scenarios (happy path + rule boundaries + edge cases),
|
|
140
|
+
alternate/error flows (retries, idempotency, compensations), read models affected, open
|
|
141
|
+
questions. Park anything unresolved rather than guessing.
|
|
142
|
+
2. Write the doc to `slices/<slice-name>.md` (kebab-case the slice name).
|
|
143
|
+
3. Wire it into the `.em`: add `note "slices/<slice-name>.md"` to the slice's primary element
|
|
144
|
+
(the command for State Change, the view for State View, the processor for Automation, the
|
|
145
|
+
translation for Translation).
|
|
146
|
+
4. Update `README.md`'s slice index and the state file's slice inventory (`draft` → `ready`).
|
|
147
|
+
5. Re-render and `em validate`.
|
|
148
|
+
|
|
149
|
+
## Phase: `watch` — live team view
|
|
150
|
+
|
|
151
|
+
1. Ensure `live.html` exists in the model dir (copy from `templates/live.html`) with `SVG_FILE`
|
|
152
|
+
set to `<model-name>.svg`.
|
|
153
|
+
2. Start the watcher in the background: `em watch <model-name>.em -o <model-name>.svg`
|
|
154
|
+
(run_in_background). It re-renders the SVG on every save.
|
|
155
|
+
3. Tell the user to open `live.html` in a browser and share their screen — it auto-refreshes ~1s,
|
|
156
|
+
so the team sees the model evolve as you edit during the session. (`em` has no built-in server;
|
|
157
|
+
the HTML viewer provides the auto-reload.)
|
|
158
|
+
|
|
159
|
+
## Phase: `validate`
|
|
160
|
+
|
|
161
|
+
Run `em validate <model-name>.em` and walk through each diagnostic with the user, explaining the
|
|
162
|
+
rule (see `reference/em-dsl.md`) and proposing the fix (e.g. split an automation's command into
|
|
163
|
+
the next slice, add a missing `from`, give a command its event). Apply fixes on agreement.
|
|
164
|
+
|
|
165
|
+
Then do one check `em validate` can't: scan every `translation`/`processor`/`automation` slice and
|
|
166
|
+
confirm none contains an `event` — each reaction must trigger a `command` in the next slice
|
|
167
|
+
(`reaction → command → event`). If you find a reaction wired straight to an event, split it into
|
|
168
|
+
two slices and route it through a command.
|
|
169
|
+
|
|
170
|
+
---
|
|
171
|
+
|
|
172
|
+
## em command reference (quick)
|
|
173
|
+
|
|
174
|
+
```bash
|
|
175
|
+
em --version
|
|
176
|
+
em init <name>.em # optional starter scaffold
|
|
177
|
+
em validate <name>.em # check rules; exit 0 if clean/warnings only
|
|
178
|
+
em render <name>.em -o <name>.svg # render (svg/png/pdf by extension)
|
|
179
|
+
em render <name>.em --emit-dot # inspect generated Graphviz DOT
|
|
180
|
+
em watch <name>.em -o <name>.svg # re-render on save (run in background)
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
Always finish a working session by: re-rendering, running `em validate`, and updating
|
|
184
|
+
`.event-modeling.md` with the current phase/step, decisions, and open questions.
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
# `em` DSL Reference & Cheatsheet
|
|
2
|
+
|
|
3
|
+
The `em` tool (`@milehimikey/em`) is a slice-first text DSL rendered to a strict Graphviz
|
|
4
|
+
swimlane grid. Use this reference so the models you generate are **valid** and render cleanly.
|
|
5
|
+
Keep `.em` files focused on **structure**; put deep design in markdown linked via `note`.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## CLI
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
em init [file] # scaffold a starter model.em (default: model.em); -f to overwrite
|
|
13
|
+
em render <file> -o out.svg # render (extension picks format: svg, png, pdf)
|
|
14
|
+
em render <file> --emit-dot # print the Graphviz DOT instead of rendering
|
|
15
|
+
em render <file> --keep-empty-lanes # keep the API lane even when empty
|
|
16
|
+
em watch <file> -o out.svg # re-render on every save (file-based; no HTTP server)
|
|
17
|
+
em validate <file> # check event-modeling rules; exit 0 if only warnings/clean
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
Install if missing: `npm i -g @milehimikey/em`. PNG works with no system deps; PDF needs
|
|
21
|
+
`rsvg-convert`.
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
## Grammar
|
|
26
|
+
|
|
27
|
+
```
|
|
28
|
+
model "Name" # diagram title
|
|
29
|
+
|
|
30
|
+
persona Name # a UI swimlane row (actor)
|
|
31
|
+
context Name # an event swimlane row (bounded context / aggregate)
|
|
32
|
+
|
|
33
|
+
slice "Name" { # one vertical time step (a column)
|
|
34
|
+
ui Free Text @Persona # screen; @Persona picks its row (defaults to first/"User")
|
|
35
|
+
command Free Text # state-changing request (API band)
|
|
36
|
+
view Free Text from "Event A", "Event B" # read model fed by event(s)
|
|
37
|
+
event Free Text @Context # recorded fact; @Context picks its row (defaults to "Domain")
|
|
38
|
+
processor Free Text from "View" # automation; aliases: automation | saga | translation
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
arrow From Element -> To Element # explicit cross-slice edge (overrides inferred flow)
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
### Element kinds (8 keywords, nothing else)
|
|
45
|
+
| Keyword | Band | Meaning | Tag | Extra clauses |
|
|
46
|
+
|---|---|---|---|---|
|
|
47
|
+
| `ui` | persona | screen / interface | `@Persona` | `note`, `{ fields }` |
|
|
48
|
+
| `command` | API | state-changing request | — | `note`, `{ fields }` |
|
|
49
|
+
| `view` | API | read model / projection | — | `from "Event"…`, `note`, `{ fields }` |
|
|
50
|
+
| `event` | context | recorded fact (past tense) | `@Context` | `note`, `{ fields }` |
|
|
51
|
+
| `processor` / `automation` / `saga` / `translation` | automation | system reaction / adapter | — | `from "…"`, `note`, `{ fields }` |
|
|
52
|
+
|
|
53
|
+
### Clauses
|
|
54
|
+
- **Tags:** `@Persona` only on `ui`; `@Context` only on `event`. Undeclared tags auto-create a row.
|
|
55
|
+
- **`from "X"`** on views/automations declares the source(s); names are quoted, comma-separated.
|
|
56
|
+
Matching is case-insensitive and whitespace-normalized.
|
|
57
|
+
- **`note "path.md"`** on ANY element links a markdown doc. Relative to the `.em` file. Renders as
|
|
58
|
+
a clickable marker in SVG and a legend entry in PNG/PDF. **This is how slice docs attach.**
|
|
59
|
+
- **Fields:** `command Place Order { orderId: UUID, items: LineItem[], customerId }` — inline or
|
|
60
|
+
one-per-line. Types are free text (no semantic checking). Keep these light; full field specs
|
|
61
|
+
with rules live in the slice doc.
|
|
62
|
+
- **Comments:** `# ...` anywhere outside quotes (full-line or trailing).
|
|
63
|
+
|
|
64
|
+
### Swimlane band order (top → bottom)
|
|
65
|
+
Header row → **Automation** (only if used) → **persona** rows (in declared order) → **API**
|
|
66
|
+
(commands + views share this lane) → **context** rows (in declared order).
|
|
67
|
+
|
|
68
|
+
### Colors (for orientation)
|
|
69
|
+
UI = white, command = blue, event = amber/orange, view = green, automation = gray.
|
|
70
|
+
|
|
71
|
+
---
|
|
72
|
+
|
|
73
|
+
## Pattern → DSL mapping
|
|
74
|
+
|
|
75
|
+
```em
|
|
76
|
+
# 1. State Change: UI -> Command -> Event
|
|
77
|
+
slice "Place Order" {
|
|
78
|
+
ui Checkout @Customer
|
|
79
|
+
command Place Order
|
|
80
|
+
event Order Placed @Order
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
# 2. State View: Event(s) -> Read Model -> UI
|
|
84
|
+
slice "Open Orders" {
|
|
85
|
+
view Open Orders from "Order Placed"
|
|
86
|
+
ui Order List @Customer
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
# 3. Automation: split across TWO slices
|
|
90
|
+
slice "Orders To Fulfill" {
|
|
91
|
+
view Orders To Fulfill from "Order Placed"
|
|
92
|
+
processor Fulfillment Service
|
|
93
|
+
}
|
|
94
|
+
slice "Ship Order" { # the triggered command goes in the NEXT slice
|
|
95
|
+
command Ship Order
|
|
96
|
+
event Order Shipped @Shipping
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
# 4a. Translation (external trigger): external input -> translation -> command -> event
|
|
100
|
+
slice "Carrier Webhook" {
|
|
101
|
+
translation Carrier Adapter # inbound from outside the model; no internal `from`
|
|
102
|
+
}
|
|
103
|
+
slice "Confirm Delivery" { # the triggered command goes in the NEXT slice
|
|
104
|
+
command Confirm Delivery
|
|
105
|
+
event Delivery Confirmed @Shipping
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
# 4b. Translation (internal trigger): read model -> translation -> command -> event
|
|
109
|
+
slice "Quotes To Sync" {
|
|
110
|
+
view Accepted Quotes from "Quote Accepted"
|
|
111
|
+
translation CRM Sync # reacts to our own state via the read model in this slice
|
|
112
|
+
}
|
|
113
|
+
slice "Record Sync" {
|
|
114
|
+
command Record Crm Sync
|
|
115
|
+
event Quote Synced @Quote
|
|
116
|
+
}
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
A `translation` (like a `processor`) is a **reaction**: it triggers a command and never carries an
|
|
120
|
+
`event` in its own slice. Same two-slice split as the Automation pattern above.
|
|
121
|
+
|
|
122
|
+
### Headless / API systems (no UI) & repeated read models
|
|
123
|
+
|
|
124
|
+
When the system is headless (no UI — clients call an API), drop `ui`/`persona` entirely:
|
|
125
|
+
|
|
126
|
+
```em
|
|
127
|
+
# Write: external translation -> command -> event (name the inbound adapter for the caller/role)
|
|
128
|
+
slice "Create Quote (request)" { translation SalesRep BU Create Quote }
|
|
129
|
+
slice "Create Quote" { command Create Quote event QuoteCreated @Quote }
|
|
130
|
+
|
|
131
|
+
# Read: event(s) -> read model -> READ translation (the external caller's API query, replaces UI)
|
|
132
|
+
slice "Read Quote — created" { view Quote from "QuoteCreated" translation SalesRep BU Read Quote }
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
- A **read translation triggers no command** (it returns data outbound) — it is the headless analog
|
|
136
|
+
of `view → ui`, *not* a reaction. Only *reaction* translations/processors trigger commands. A read
|
|
137
|
+
slice has no `command` and no `event`, so `em validate` stays quiet about it.
|
|
138
|
+
- **Repeat the read model** in every slice where it's read so the timeline flows left-to-right.
|
|
139
|
+
Recurring `view`/`translation` names **render cleanly and stay warning-free** as long as nothing
|
|
140
|
+
references the repeated name via `from`/`arrow` (the duplicate-name warning fires only for a
|
|
141
|
+
*referenced* duplicate, resolving to the first). **Wire each event to a read model exactly once:**
|
|
142
|
+
a repeated instance's `from` lists only the **new** events since the previous instance (not
|
|
143
|
+
cumulative), or the event draws a duplicate arrow to the same read model at every repeat. (An
|
|
144
|
+
event may still feed several *different* read models, once each.)
|
|
145
|
+
- **Keep arrows span-1: put each repeat right after its feeding event.** Place a read-model instance
|
|
146
|
+
immediately after the event that updates it, sourcing only that single adjacent event. A read
|
|
147
|
+
model far from its source events draws long arrows that cross the read-model row and read as
|
|
148
|
+
read→read (an Event Modeling violation) — repeat the read model next to each event instead.
|
|
149
|
+
|
|
150
|
+
### Slice-ordering gotchas (edge inference)
|
|
151
|
+
|
|
152
|
+
`em` infers cross-slice arrows positionally, so **slice order matters**:
|
|
153
|
+
- A **reaction** (`processor`/`translation` that triggers a command) wires to the command in the
|
|
154
|
+
**immediately next** slice. So a reaction slice must be *directly* followed by its command slice —
|
|
155
|
+
don't insert a read slice between them.
|
|
156
|
+
- A **read** slice (read model → read translation, no command) must **not** be immediately followed
|
|
157
|
+
by a `command` slice, or the read translation will be mis-wired to that command. Put reads after a
|
|
158
|
+
command+event slice, or before another read / a reaction / an inbound `(request)` slice.
|
|
159
|
+
|
|
160
|
+
---
|
|
161
|
+
|
|
162
|
+
## `em validate` rules (design to satisfy these)
|
|
163
|
+
|
|
164
|
+
**Errors (must fix):**
|
|
165
|
+
1. **Band collision** — two elements of the same band in one slice (e.g. two `command`s, or two
|
|
166
|
+
`ui`s in the *same* persona row). Split them into separate slices/personas.
|
|
167
|
+
2. **Unknown event source** — `view X from "Event"` where the event doesn't exist anywhere.
|
|
168
|
+
3. **Unknown read-model source** — `processor X from "View"` where the view doesn't exist.
|
|
169
|
+
4. **Arrow endpoint mismatch** — `arrow A -> B` where A or B matches no element name.
|
|
170
|
+
|
|
171
|
+
**Warnings (should fix):**
|
|
172
|
+
1. **Automation/translation shares slice with its command** — both `automation`/`processor` and
|
|
173
|
+
`translation` are reactions; put the triggered command in the *next* slice.
|
|
174
|
+
2. **Command without event** — every command should record at least one event.
|
|
175
|
+
3. **Read model without source** — add `from "Event"` or place the view in a slice with an event.
|
|
176
|
+
4. **Duplicate name** — the same name defined N times; references resolve to the first. Rename.
|
|
177
|
+
|
|
178
|
+
**Design rules that keep models valid:**
|
|
179
|
+
- One element per band per slice (multiple personas/contexts are fine — they're different rows).
|
|
180
|
+
- Every `command` slice includes its `event`. Every `view` has a `from` source.
|
|
181
|
+
- Automations **and translations** are always two slices: the reaction (plus its read model, if
|
|
182
|
+
internally triggered) in one slice, the triggered `command` + its `event` in the next. A
|
|
183
|
+
translation/automation slice **never contains an `event`** — reactions trigger commands, not
|
|
184
|
+
events. Externally-triggered reactions start from outside the model (no `from`);
|
|
185
|
+
internally-triggered ones read a **view** (read model) — `from "X"` must resolve to a view.
|
|
186
|
+
- Name everything uniquely and consistently.
|
|
187
|
+
- Events are past tense; commands are imperative; views name the thing shown.
|
|
188
|
+
|
|
189
|
+
> ⚠️ **Validator blind spot:** `em validate` does **not** flag a translation/automation that emits
|
|
190
|
+
> an event directly (e.g. `translation T` and `event E` in the same slice with no command). It only
|
|
191
|
+
> warns when a reaction shares a slice with a *command*. So enforce the two-slice
|
|
192
|
+
> `reaction → command → event` split **by construction** — never rely on `em validate` to catch a
|
|
193
|
+
> reaction wired straight to an event.
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
# Event Modeling — Methodology Reference
|
|
2
|
+
|
|
3
|
+
This is the authoritative reference for the **7 steps** and **4 patterns** of Event Modeling
|
|
4
|
+
(Adam Dymitruk's method). Consult it while running any phase of the `event-modeling` skill.
|
|
5
|
+
Do not invent domain facts — use the Socratic prompts to extract them from the user.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Core idea
|
|
10
|
+
|
|
11
|
+
An event model is a **timeline of state changes** told as a story. Information only moves
|
|
12
|
+
three ways:
|
|
13
|
+
|
|
14
|
+
1. **Into** the system via a **command** (a request to change state) → produces an **event**.
|
|
15
|
+
2. **Out of** the system via a **read model / view** (a projection of past events) → shown on a **UI** or consumed by an automation.
|
|
16
|
+
3. There is **no other way** for information to flow. Every box on the diagram is one of:
|
|
17
|
+
UI screen, command, event, read model, or automation/processor.
|
|
18
|
+
|
|
19
|
+
**Automations and translations are not exceptions to rule 1.** They are *reactions* — a processor
|
|
20
|
+
or an adapter that wakes up, decides something must change, and **issues a command** to do it. They
|
|
21
|
+
never record an event themselves. A reaction box always points at a **command** (in the next
|
|
22
|
+
slice), and that command produces the event. If you ever see a translation or automation wired
|
|
23
|
+
straight to an event, the model is wrong.
|
|
24
|
+
|
|
25
|
+
Events are **immutable past-tense facts** ("Order Placed", "Payment Captured"). They are the
|
|
26
|
+
spine of the model. Everything else hangs off the events.
|
|
27
|
+
|
|
28
|
+
The unit of delivery is a **slice**: a thin vertical cut through the swimlanes that delivers
|
|
29
|
+
one of the four patterns. A slice is what a developer implements and tests in isolation.
|
|
30
|
+
|
|
31
|
+
---
|
|
32
|
+
|
|
33
|
+
## The 4 patterns
|
|
34
|
+
|
|
35
|
+
Every slice is exactly one of these. In `em`, each maps to a specific shape (see `em-dsl.md`).
|
|
36
|
+
|
|
37
|
+
### 1. State Change (Command pattern)
|
|
38
|
+
**UI → Command → Event.** A user (or automation) submits a command; the system validates it
|
|
39
|
+
against invariants and records one or more events.
|
|
40
|
+
- `em` shape: `slice { ui X @Persona command Y event Z @Context }`
|
|
41
|
+
- This is where **invariants** live — the command is rejected if a rule is violated.
|
|
42
|
+
- Socratic prompts: *"What does the user do here? What request are they making? What must be
|
|
43
|
+
true for it to succeed? What fact gets recorded when it does? What gets rejected and why?"*
|
|
44
|
+
|
|
45
|
+
### 2. State View (View pattern)
|
|
46
|
+
**Event(s) → Read Model → UI.** Past events are projected into a read model that a screen
|
|
47
|
+
displays. Read-only; changes no state.
|
|
48
|
+
- `em` shape: `slice { view V from "Event A", "Event B" ui Screen @Persona }`
|
|
49
|
+
- **Headless / API systems (no UI):** when there is no UI, a read model is consumed by an
|
|
50
|
+
**external caller querying the API**, modeled as a **read translation** — the outbound analog of
|
|
51
|
+
`→ ui`. Shape: `slice { view V from "..." translation <Caller> Read <V> }`. A read translation
|
|
52
|
+
**triggers no command** (it returns data); it is *not* a reaction, so the
|
|
53
|
+
`reaction → command → event` rule does **not** apply to it. (Contrast: a *reaction* translation in
|
|
54
|
+
pattern 4 triggers a command.)
|
|
55
|
+
- **Repeat read models across the timeline.** A read model is drawn fresh in **every** slice where
|
|
56
|
+
it is read — after the events that update it, before the actions that consume it — so the diagram
|
|
57
|
+
shows state flowing left-to-right (the information-completeness staircase). The same read-model
|
|
58
|
+
name recurring is intentional and **renders cleanly**: `em validate` only warns about a duplicate
|
|
59
|
+
name that is *also referenced* by a `from`/`arrow` (it resolves to the first), so a repeated read
|
|
60
|
+
model that nothing references by name produces **no warning**. **Wire each event to a read model
|
|
61
|
+
exactly once:** a repeated instance's `from` lists only the **new** events since the previous
|
|
62
|
+
instance (not cumulative) — otherwise an event draws a duplicate arrow to the same read model at
|
|
63
|
+
every repeat. (An event may still feed several *different* read models, once each.)
|
|
64
|
+
- **Place each repeat immediately after its feeding event (span-1).** The cleanest staircase puts a
|
|
65
|
+
read-model instance right after each event that updates it, sourcing **only that one adjacent
|
|
66
|
+
event** — every `event → read model` arrow is then short and forward-flowing. A read model sitting
|
|
67
|
+
far from its source events (e.g. a "list/queue" read placed early but fed by late events) draws
|
|
68
|
+
long arrows that cross the read-model row and *look* like read→read connections — which Event
|
|
69
|
+
Modeling forbids. The fix is never a `view → view` edge; it's to repeat the read model next to each
|
|
70
|
+
event that feeds it.
|
|
71
|
+
- Socratic prompts: *"What does the consumer need to see to make their next decision? Which past
|
|
72
|
+
events provide that information? Is this a screen or an API read? What's the freshness/consistency
|
|
73
|
+
expectation?"*
|
|
74
|
+
|
|
75
|
+
### 3. Automation (Processor pattern)
|
|
76
|
+
**Read Model → Processor → (next slice) Command → Event.** The system reacts on its own: a
|
|
77
|
+
processor watches a read model (a "to-do list" of work) and **issues a command** — it never
|
|
78
|
+
records an event directly.
|
|
79
|
+
- `em` shape: **two slices.** First `slice { view "Todo" from "..." processor P }`, then the
|
|
80
|
+
**next** `slice { command C event E @Context }`. The processor band holds no event; the command
|
|
81
|
+
in the next slice produces it. Keeping the triggered command in the same slice as the processor
|
|
82
|
+
is a validation warning — always split it.
|
|
83
|
+
- Socratic prompts: *"What should happen without a human? What condition triggers it? What work
|
|
84
|
+
list does the processor watch? What command does it fire? What if it fails or retries?"*
|
|
85
|
+
|
|
86
|
+
### 4. Translation
|
|
87
|
+
**Boundary crossing → command → event.** An adapter translates data across a boundary (an external
|
|
88
|
+
system, or another bounded context) into the model's own language. Like an automation, a
|
|
89
|
+
translation is a *reaction*: it **triggers a command**, never records an event directly. Two
|
|
90
|
+
trigger forms:
|
|
91
|
+
- **Externally triggered:** `external input → translation → command → event`. The trigger comes
|
|
92
|
+
from outside the model, so the translation has no internal `from`.
|
|
93
|
+
- **Internally triggered** (reacting to the modeled system's own state, e.g. pushing data out):
|
|
94
|
+
`read model → translation → command → event`. The translation reads a **view** via `from`.
|
|
95
|
+
- `em` shape: **two slices**, exactly like an automation. First
|
|
96
|
+
`slice { [view "Source" from "..."] translation T [from "Source"] }`, then the **next**
|
|
97
|
+
`slice { command C event E @Context }`. The translation band never carries an event.
|
|
98
|
+
- Socratic prompts: *"What boundary are we crossing, and which way? What outside system or context
|
|
99
|
+
feeds us (or do we feed)? In what format? How do we know its data is trustworthy/complete? What
|
|
100
|
+
internal **command** does it trigger, and what event does that command record?"*
|
|
101
|
+
|
|
102
|
+
---
|
|
103
|
+
|
|
104
|
+
## The 7 steps
|
|
105
|
+
|
|
106
|
+
Run them in order. The skill groups them into phases: **discover = 1–4**, **model = 5–7**,
|
|
107
|
+
and a dedicated **slice** phase deepens step 6 to implementation-ready specs.
|
|
108
|
+
|
|
109
|
+
### Step 1 — Brainstorm Events *(discover)*
|
|
110
|
+
List the domain **events** as past-tense facts, unordered at first. Go wide; capture every
|
|
111
|
+
state change anyone can think of. No commands, no UI yet — just facts.
|
|
112
|
+
- Prompts: *"What are all the things that happen in this process? Say each as something that
|
|
113
|
+
already occurred. What changed when that happened?"*
|
|
114
|
+
- Output: a flat list of candidate `event` names.
|
|
115
|
+
|
|
116
|
+
### Step 2 — The Plot / Storyboard *(discover)*
|
|
117
|
+
Put the events in **timeline order** to form the narrative. Identify the **personas** (actors)
|
|
118
|
+
and sketch the **UI** screens that move the story forward. This is the storyboard.
|
|
119
|
+
- Prompts: *"In what order do these happen? Who is on screen at each step? What screen are they
|
|
120
|
+
looking at? What's the happy-path story start to finish?"*
|
|
121
|
+
- Output: ordered events + `persona` list + `ui` screens per step.
|
|
122
|
+
|
|
123
|
+
### Step 3 — Inputs (Commands) *(discover)*
|
|
124
|
+
For each event, identify the **command** that causes it (State Change pattern). Name the intent,
|
|
125
|
+
not the outcome ("Place Order", not "Order Placed").
|
|
126
|
+
- Prompts: *"What action produces this event? Who or what issues it? Could it be refused?"*
|
|
127
|
+
- Output: `command → event` pairs.
|
|
128
|
+
|
|
129
|
+
### Step 4 — Outputs (Read Models) *(discover)*
|
|
130
|
+
Identify the **read models / views** each UI and automation needs (State View pattern). Wire
|
|
131
|
+
each view to the events that feed it.
|
|
132
|
+
- Prompts: *"What information does this screen show? Which past events supply it? Does any
|
|
133
|
+
automation need a work list derived from events?"*
|
|
134
|
+
- Output: `view` elements with their `from "Event"` sources.
|
|
135
|
+
|
|
136
|
+
### Step 5 — Swimlanes & Apply the Patterns *(model)*
|
|
137
|
+
Organize elements into swimlanes: one **persona** row per actor, one **context** (bounded
|
|
138
|
+
context / aggregate) row per event family. Classify every slice as one of the **4 patterns** and
|
|
139
|
+
wire them correctly — especially **split both automations and translations across two slices**
|
|
140
|
+
(the reaction in one, the command + event in the next). A translation or automation never records
|
|
141
|
+
an event directly; it triggers a command.
|
|
142
|
+
- Prompts: *"Which events belong together as one consistency boundary (aggregate)? Who owns this
|
|
143
|
+
data? Is this slice a state change, a view, an automation, or a translation?"*
|
|
144
|
+
- Output: a structurally complete model with swimlanes and pattern-correct slices.
|
|
145
|
+
|
|
146
|
+
### Step 6 — Elaborate Scenarios *(model first pass, slice deep pass)*
|
|
147
|
+
For each slice, write **Given / When / Then** scenarios and surface **invariants**, **critical
|
|
148
|
+
fields**, and **alternate / error flows**. In the `model` phase do a light first pass; the
|
|
149
|
+
dedicated `slice` phase writes the full rich spec (see `templates/slice.md`).
|
|
150
|
+
- Prompts: *"Given what starting state, when this command/trigger fires, then what event(s)
|
|
151
|
+
result? What must always be true? What are the failure paths? Which fields are essential and
|
|
152
|
+
what are their rules?"*
|
|
153
|
+
- Output: rich slice docs linked into the `.em` via `note "slices/<name>.md"`.
|
|
154
|
+
|
|
155
|
+
### Step 7 — Evaluate Completeness *(model)*
|
|
156
|
+
Walk the whole model with stakeholders and check for loose ends:
|
|
157
|
+
- Every **command** produces at least one **event**.
|
|
158
|
+
- Every **view** has at least one source event.
|
|
159
|
+
- Every **event** is shown somewhere or consumed by something (no orphan facts).
|
|
160
|
+
- Every **UI** is reachable and leads somewhere.
|
|
161
|
+
- Automations **and** translations are split correctly (reaction → command → event, never a
|
|
162
|
+
reaction wired straight to an event); translations cover every external input.
|
|
163
|
+
- Run `em validate` and resolve all errors and warnings. Note: `em validate` does **not** catch a
|
|
164
|
+
translation/automation that emits an event directly — verify the two-slice split by hand.
|
|
165
|
+
- Prompts: *"Is there any event nobody sees? Any screen with no way in or out? Any command that
|
|
166
|
+
records nothing? Any external system we haven't translated? Any translation or automation that
|
|
167
|
+
records an event instead of triggering a command?"*
|
|
168
|
+
|
|
169
|
+
---
|
|
170
|
+
|
|
171
|
+
## Socratic stance (applies throughout)
|
|
172
|
+
|
|
173
|
+
- Ask **one focused question at a time**; never assume a domain fact — extract it.
|
|
174
|
+
- Prefer "why", "what if", "who", "what must always be true", "how do you know" over yes/no.
|
|
175
|
+
- Mirror the model back after each increment and **re-render** so the team sees it evolve.
|
|
176
|
+
- Park unresolved questions in the state file instead of guessing.
|
|
177
|
+
- Name things crisply: events past-tense, commands imperative, views as the thing-shown.
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<!--
|
|
3
|
+
Live viewer for an em event model. Auto-reloads the rendered SVG so a team on a shared
|
|
4
|
+
screen sees updates from `em watch` without manual refresh.
|
|
5
|
+
|
|
6
|
+
Usage:
|
|
7
|
+
1) In one terminal: em watch MODEL.em -o MODEL.svg
|
|
8
|
+
2) Set SVG_FILE below to the SVG filename (same folder as this file).
|
|
9
|
+
3) Open this file in a browser (open live.html) and share the screen.
|
|
10
|
+
|
|
11
|
+
The SVG is embedded via <object> so note "..." links inside it stay clickable.
|
|
12
|
+
-->
|
|
13
|
+
<html lang="en">
|
|
14
|
+
<head>
|
|
15
|
+
<meta charset="utf-8" />
|
|
16
|
+
<title>Event Model — Live</title>
|
|
17
|
+
<style>
|
|
18
|
+
:root { color-scheme: light dark; }
|
|
19
|
+
body { margin: 0; font-family: system-ui, sans-serif; background: #fafafa; }
|
|
20
|
+
header {
|
|
21
|
+
display: flex; align-items: center; gap: .75rem;
|
|
22
|
+
padding: .5rem .9rem; background: #1f2933; color: #fff; font-size: 14px;
|
|
23
|
+
}
|
|
24
|
+
header .dot { width: 9px; height: 9px; border-radius: 50%; background: #34c759; }
|
|
25
|
+
header .stamp { margin-left: auto; opacity: .7; font-variant-numeric: tabular-nums; }
|
|
26
|
+
#stage { padding: 1rem; }
|
|
27
|
+
object { width: 100%; height: calc(100vh - 60px); border: 0; }
|
|
28
|
+
</style>
|
|
29
|
+
</head>
|
|
30
|
+
<body>
|
|
31
|
+
<header>
|
|
32
|
+
<span class="dot"></span>
|
|
33
|
+
<span>Event Model — live (auto-refresh)</span>
|
|
34
|
+
<span class="stamp" id="stamp">—</span>
|
|
35
|
+
</header>
|
|
36
|
+
<div id="stage">
|
|
37
|
+
<object id="svg" type="image/svg+xml"></object>
|
|
38
|
+
</div>
|
|
39
|
+
<script>
|
|
40
|
+
// Change this to your model's SVG filename:
|
|
41
|
+
const SVG_FILE = "model.svg";
|
|
42
|
+
const obj = document.getElementById("svg");
|
|
43
|
+
const stamp = document.getElementById("stamp");
|
|
44
|
+
function reload() {
|
|
45
|
+
// cache-bust so the freshly written SVG is fetched each tick
|
|
46
|
+
obj.setAttribute("data", SVG_FILE + "?t=" + Date.now());
|
|
47
|
+
stamp.textContent = new Date().toLocaleTimeString();
|
|
48
|
+
}
|
|
49
|
+
reload();
|
|
50
|
+
setInterval(reload, 1000);
|
|
51
|
+
</script>
|
|
52
|
+
</body>
|
|
53
|
+
</html>
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# {{Model Name}}
|
|
2
|
+
|
|
3
|
+
{{One-paragraph description of the business process(es) this event model covers.}}
|
|
4
|
+
|
|
5
|
+
## Live view
|
|
6
|
+
While modeling, run the live view so the team can watch the diagram update:
|
|
7
|
+
|
|
8
|
+
```bash
|
|
9
|
+
em watch {{model-name}}.em -o {{model-name}}.svg # re-renders on every save
|
|
10
|
+
# then open live.html in a browser (auto-refreshes ~1s; share the screen)
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
Static render: `em render {{model-name}}.em -o {{model-name}}.svg`
|
|
14
|
+
|
|
15
|
+
## Patterns legend
|
|
16
|
+
- **State Change** — UI → Command → Event
|
|
17
|
+
- **State View** — Event(s) → Read Model → UI
|
|
18
|
+
- **Automation** — Read Model → Processor → (next slice) Command → Event
|
|
19
|
+
- **Translation** — External input (or Read Model) → Translation → (next slice) Command → Event
|
|
20
|
+
|
|
21
|
+
## Slices
|
|
22
|
+
<!-- Keep this index in sync as slice docs are written. -->
|
|
23
|
+
| # | Slice | Pattern | Design doc |
|
|
24
|
+
|---|-------|---------|------------|
|
|
25
|
+
| 1 | {{Slice Name}} | {{Pattern}} | [slices/{{slice-name}}.md](slices/{{slice-name}}.md) |
|
|
26
|
+
|
|
27
|
+
## Status
|
|
28
|
+
See [`.event-modeling.md`](.event-modeling.md) for current phase, decisions, and open questions.
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
Rich slice design document. One per slice, stored in <model>/slices/<slice-name>.md and
|
|
3
|
+
linked from the .em model with: note "slices/<slice-name>.md" on the slice's defining element.
|
|
4
|
+
Fill every section through Socratic questioning. Leave "Open Questions" rather than guessing.
|
|
5
|
+
Replace the bracketed placeholders; delete guidance comments before finishing.
|
|
6
|
+
-->
|
|
7
|
+
|
|
8
|
+
# Slice: {{Slice Name}}
|
|
9
|
+
|
|
10
|
+
- **Pattern:** {{State Change | State View | Automation | Translation}}
|
|
11
|
+
- **Swimlane:** {{Persona/Actor}} → {{Context/Aggregate}}
|
|
12
|
+
- **Status:** {{draft | reviewed | ready-to-implement}}
|
|
13
|
+
|
|
14
|
+
## Intent
|
|
15
|
+
{{Why this slice exists — the user or business goal it serves, in one or two sentences.}}
|
|
16
|
+
|
|
17
|
+
## Trigger & Actor
|
|
18
|
+
{{Who or what initiates this slice and under what circumstances. For automations, the watched
|
|
19
|
+
read model and the triggering condition. For translations, state the trigger form: externally
|
|
20
|
+
triggered (the external system/source feeding us) or internally triggered (the read model whose
|
|
21
|
+
state we react to). Either way, name the command this reaction triggers — reactions never record
|
|
22
|
+
an event directly.}}
|
|
23
|
+
|
|
24
|
+
## Command / Input
|
|
25
|
+
<!-- For State Change, and the command half of an Automation or Translation (reactions trigger a
|
|
26
|
+
command in the next slice). Omit for pure State View slices. -->
|
|
27
|
+
**Command:** `{{Command Name}}`
|
|
28
|
+
|
|
29
|
+
| Field | Type | Required | Rules / Validation |
|
|
30
|
+
|-------|------|----------|--------------------|
|
|
31
|
+
| {{field}} | {{Type}} | {{yes/no}} | {{constraints, formats, ranges}} |
|
|
32
|
+
|
|
33
|
+
## Event(s) Emitted
|
|
34
|
+
<!-- The immutable facts recorded. List each event and its payload. -->
|
|
35
|
+
**Event:** `{{Event Name}}` → context `{{Context}}`
|
|
36
|
+
|
|
37
|
+
| Field | Type | Immutable Fact? | Source / Notes |
|
|
38
|
+
|-------|------|-----------------|----------------|
|
|
39
|
+
| {{field}} | {{Type}} | {{yes/no}} | {{where the value comes from}} |
|
|
40
|
+
|
|
41
|
+
## Read Model / View
|
|
42
|
+
<!-- For State View slices, and any read model this slice produces or feeds. -->
|
|
43
|
+
- **View:** `{{View Name}}` built from events: {{"Event A", "Event B"}}
|
|
44
|
+
- **Consumed by:** {{which UI screen or automation}}
|
|
45
|
+
- **Freshness / consistency expectation:** {{real-time | eventual | on-demand}}
|
|
46
|
+
|
|
47
|
+
## Invariants / Business Rules
|
|
48
|
+
<!-- What must ALWAYS hold. Give each a stable ID so tests and code can reference it. -->
|
|
49
|
+
- **INV-1:** {{rule that the command enforces; violation ⇒ rejection}}
|
|
50
|
+
- **INV-2:** {{...}}
|
|
51
|
+
|
|
52
|
+
## Scenarios (Given / When / Then)
|
|
53
|
+
<!-- The executable specification. Cover the happy path AND the key rule boundaries. -->
|
|
54
|
+
- **Happy path** — Given {{starting state / prior events}}, When {{command/trigger}},
|
|
55
|
+
Then {{event(s) recorded}} and {{resulting read-model change}}.
|
|
56
|
+
- **Rejected (INV-1)** — Given {{state}}, When {{command}}, Then {{rejected with reason}}; no event.
|
|
57
|
+
- **{{Edge case}}** — Given {{...}}, When {{...}}, Then {{...}}.
|
|
58
|
+
|
|
59
|
+
## Alternate & Error Flows
|
|
60
|
+
<!-- Failure paths, retries, compensations, timeouts, idempotency. -->
|
|
61
|
+
- {{e.g. external call fails → retry policy / compensating event}}
|
|
62
|
+
- {{idempotency: what happens if the command/event arrives twice?}}
|
|
63
|
+
|
|
64
|
+
## Dependencies & Read Models Affected
|
|
65
|
+
- **Upstream events this slice relies on:** {{...}}
|
|
66
|
+
- **Downstream read models / slices affected:** {{...}}
|
|
67
|
+
|
|
68
|
+
## Open Questions
|
|
69
|
+
<!-- Park unresolved items here instead of guessing. Mirror them into .event-modeling.md. -->
|
|
70
|
+
- [ ] {{question}}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
Resumable progress state for an event model. Stored as <model>/.event-modeling.md
|
|
3
|
+
The skill reads this on `/event-modeling` (no arg) to resume where you left off.
|
|
4
|
+
Keep it current at the end of every working session.
|
|
5
|
+
-->
|
|
6
|
+
|
|
7
|
+
# Event Modeling Progress — {{Model Name}}
|
|
8
|
+
|
|
9
|
+
- **Model file:** `{{model-name}}.em`
|
|
10
|
+
- **Current phase:** {{discover | model | slice}}
|
|
11
|
+
- **Current step:** {{1–7, see methodology}}
|
|
12
|
+
- **Last updated:** {{YYYY-MM-DD}}
|
|
13
|
+
|
|
14
|
+
## Steps completed
|
|
15
|
+
- [ ] 1. Brainstorm events
|
|
16
|
+
- [ ] 2. Plot / storyboard (personas + UI)
|
|
17
|
+
- [ ] 3. Inputs (commands)
|
|
18
|
+
- [ ] 4. Outputs (read models)
|
|
19
|
+
- [ ] 5. Swimlanes & apply patterns
|
|
20
|
+
- [ ] 6. Elaborate scenarios
|
|
21
|
+
- [ ] 7. Evaluate completeness (`em validate` clean)
|
|
22
|
+
|
|
23
|
+
## Decisions log
|
|
24
|
+
<!-- Resolved choices, with the reasoning, so they aren't re-litigated. -->
|
|
25
|
+
- {{YYYY-MM-DD}}: {{decision}} — {{why}}
|
|
26
|
+
|
|
27
|
+
## Open questions / parking lot
|
|
28
|
+
<!-- Unresolved items to bring back to the user. Never guess these. -->
|
|
29
|
+
- [ ] {{question}}
|
|
30
|
+
|
|
31
|
+
## Slice inventory
|
|
32
|
+
| Slice | Pattern | Doc status |
|
|
33
|
+
|-------|---------|------------|
|
|
34
|
+
| {{Slice Name}} | {{State Change/View/Automation/Translation}} | {{none / draft / ready}} |
|
package/README.md
CHANGED
|
@@ -178,6 +178,32 @@ npm test # vitest
|
|
|
178
178
|
npx tsx src/cli.ts <command> ... # run straight from source
|
|
179
179
|
```
|
|
180
180
|
|
|
181
|
+
## AI Assistant (Claude Code)
|
|
182
|
+
|
|
183
|
+
`em` ships a **Claude Code skill** that guides you and your team through Event Modeling using the 7-step process and 4 design patterns, producing implementation-ready slice specifications. The AI drives the model — asking focused questions, never guessing domain facts — while `em validate` keeps the diagram honest.
|
|
184
|
+
|
|
185
|
+
**Install the skill into your project:**
|
|
186
|
+
|
|
187
|
+
```bash
|
|
188
|
+
em skill install # copies the skill into .claude/skills/event-modeling/
|
|
189
|
+
em skill install --force # overwrite an existing installation
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
Then, in Claude Code, run:
|
|
193
|
+
|
|
194
|
+
```
|
|
195
|
+
/event-modeling # start (or resume) a guided session
|
|
196
|
+
/event-modeling discover # steps 1–4: brainstorm events, timeline, commands, read models
|
|
197
|
+
/event-modeling model # steps 5–7: swimlanes, patterns, completeness check
|
|
198
|
+
/event-modeling slice # deep implementation specs, one per slice
|
|
199
|
+
/event-modeling watch # open a live browser view for team modeling sessions
|
|
200
|
+
/event-modeling validate # run em validate and resolve all diagnostics
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
Sessions are **resumable** — the skill checkpoints progress in `.event-modeling.md` so you can continue across conversations.
|
|
204
|
+
|
|
205
|
+
For a complete worked example (a headless CPQ system with ~50 slices), see the [em-with-ai repository](https://github.com/milehimikey/em-with-ai).
|
|
206
|
+
|
|
181
207
|
## License
|
|
182
208
|
|
|
183
209
|
[MIT](LICENSE) © milehimikey
|
package/dist/cli.js
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
// SPDX-License-Identifier: MIT
|
|
3
3
|
import { readFileSync, writeFileSync, existsSync } from "node:fs";
|
|
4
|
-
import {
|
|
4
|
+
import { cp, mkdir } from "node:fs/promises";
|
|
5
|
+
import { basename, dirname, extname, join, resolve } from "node:path";
|
|
6
|
+
import { fileURLToPath } from "node:url";
|
|
5
7
|
import { Command } from "commander";
|
|
6
8
|
import { compile } from "./pipeline.js";
|
|
7
9
|
import { ParseError } from "./parser/parser.js";
|
|
@@ -13,7 +15,7 @@ const program = new Command();
|
|
|
13
15
|
program
|
|
14
16
|
.name("em")
|
|
15
17
|
.description("Event Modeling CLI — slice-first DSL rendered as a strict Graphviz grid")
|
|
16
|
-
.version("0.
|
|
18
|
+
.version("0.2.0");
|
|
17
19
|
program
|
|
18
20
|
.command("init")
|
|
19
21
|
.description("scaffold a starter .em model")
|
|
@@ -105,6 +107,27 @@ program
|
|
|
105
107
|
if (diagnostics.length === 0)
|
|
106
108
|
console.log("ok — no issues");
|
|
107
109
|
});
|
|
110
|
+
const skill = program
|
|
111
|
+
.command("skill")
|
|
112
|
+
.description("manage Claude Code skills bundled with em");
|
|
113
|
+
skill
|
|
114
|
+
.command("install")
|
|
115
|
+
.description("copy the event-modeling skill into .claude/skills/event-modeling/")
|
|
116
|
+
.option("-f, --force", "overwrite an existing installation")
|
|
117
|
+
.action(async (opts) => {
|
|
118
|
+
const pkgDir = dirname(fileURLToPath(import.meta.url));
|
|
119
|
+
const src = join(pkgDir, "..", ".claude", "skills", "event-modeling");
|
|
120
|
+
const dest = join(process.cwd(), ".claude", "skills", "event-modeling");
|
|
121
|
+
if (existsSync(dest) && !opts.force) {
|
|
122
|
+
console.log(`skill already installed at ${dest}`);
|
|
123
|
+
console.log("re-run with --force to overwrite");
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
await mkdir(join(process.cwd(), ".claude", "skills"), { recursive: true });
|
|
127
|
+
await cp(src, dest, { recursive: true });
|
|
128
|
+
console.log(`installed event-modeling skill → ${dest}`);
|
|
129
|
+
console.log("in Claude Code, run /event-modeling to start a guided session");
|
|
130
|
+
});
|
|
108
131
|
program.parseAsync().catch((e) => {
|
|
109
132
|
reportError(e);
|
|
110
133
|
process.exit(1);
|
package/dist/cli.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AACA,+BAA+B;AAC/B,OAAO,EAAE,YAAY,EAAE,aAAa,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AAClE,OAAO,EAAE,QAAQ,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;
|
|
1
|
+
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
|
package/dist/emit/theme.js
CHANGED
|
@@ -8,7 +8,7 @@ const STYLES = {
|
|
|
8
8
|
automation: { fill: "#DBDBDB", stroke: "#5F6368", fontColor: "#202124" },
|
|
9
9
|
processor: { fill: "#DBDBDB", stroke: "#5F6368", fontColor: "#202124" },
|
|
10
10
|
saga: { fill: "#DBDBDB", stroke: "#5F6368", fontColor: "#202124" },
|
|
11
|
-
translation: { fill: "#
|
|
11
|
+
translation: { fill: "#DBDBDB", stroke: "#5F6368", fontColor: "#202124" },
|
|
12
12
|
};
|
|
13
13
|
export function styleFor(kind) {
|
|
14
14
|
return STYLES[kind];
|
|
@@ -25,9 +25,8 @@ export function edgeColorFor(kind) {
|
|
|
25
25
|
case "automation":
|
|
26
26
|
case "processor":
|
|
27
27
|
case "saga":
|
|
28
|
-
return "#5F6368";
|
|
29
28
|
case "translation":
|
|
30
|
-
return "#
|
|
29
|
+
return "#5F6368";
|
|
31
30
|
default:
|
|
32
31
|
return "#3C4043";
|
|
33
32
|
}
|
package/dist/emit/theme.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"theme.js","sourceRoot":"","sources":["../../src/emit/theme.ts"],"names":[],"mappings":"AAAA,+BAA+B;AAC/B,qCAAqC;AAUrC,MAAM,MAAM,GAAmC;IAC7C,EAAE,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,SAAS,EAAE,SAAS,EAAE;IAChE,OAAO,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,SAAS,EAAE,SAAS,EAAE;IACrE,IAAI,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,SAAS,EAAE,SAAS,EAAE;IAClE,KAAK,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,SAAS,EAAE,SAAS,EAAE;IACnE,UAAU,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,SAAS,EAAE,SAAS,EAAE;IACxE,SAAS,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,SAAS,EAAE,SAAS,EAAE;IACvE,IAAI,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,SAAS,EAAE,SAAS,EAAE;IAClE,WAAW,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,SAAS,EAAE,SAAS,EAAE;CAC1E,CAAC;AAEF,MAAM,UAAU,QAAQ,CAAC,IAAiB;IACxC,OAAO,MAAM,CAAC,IAAI,CAAC,CAAC;AACtB,CAAC;AAED,uDAAuD;AACvD,MAAM,UAAU,YAAY,CAAC,IAA6B;IACxD,QAAQ,IAAI,EAAE,CAAC;QACb,KAAK,SAAS;YACZ,OAAO,SAAS,CAAC;QACnB,KAAK,MAAM;YACT,OAAO,SAAS,CAAC;QACnB,KAAK,OAAO;YACV,OAAO,SAAS,CAAC;QACnB,KAAK,YAAY,CAAC;QAClB,KAAK,WAAW,CAAC;QACjB,KAAK,MAAM
|
|
1
|
+
{"version":3,"file":"theme.js","sourceRoot":"","sources":["../../src/emit/theme.ts"],"names":[],"mappings":"AAAA,+BAA+B;AAC/B,qCAAqC;AAUrC,MAAM,MAAM,GAAmC;IAC7C,EAAE,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,SAAS,EAAE,SAAS,EAAE;IAChE,OAAO,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,SAAS,EAAE,SAAS,EAAE;IACrE,IAAI,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,SAAS,EAAE,SAAS,EAAE;IAClE,KAAK,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,SAAS,EAAE,SAAS,EAAE;IACnE,UAAU,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,SAAS,EAAE,SAAS,EAAE;IACxE,SAAS,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,SAAS,EAAE,SAAS,EAAE;IACvE,IAAI,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,SAAS,EAAE,SAAS,EAAE;IAClE,WAAW,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,MAAM,EAAE,SAAS,EAAE,SAAS,EAAE,SAAS,EAAE;CAC1E,CAAC;AAEF,MAAM,UAAU,QAAQ,CAAC,IAAiB;IACxC,OAAO,MAAM,CAAC,IAAI,CAAC,CAAC;AACtB,CAAC;AAED,uDAAuD;AACvD,MAAM,UAAU,YAAY,CAAC,IAA6B;IACxD,QAAQ,IAAI,EAAE,CAAC;QACb,KAAK,SAAS;YACZ,OAAO,SAAS,CAAC;QACnB,KAAK,MAAM;YACT,OAAO,SAAS,CAAC;QACnB,KAAK,OAAO;YACV,OAAO,SAAS,CAAC;QACnB,KAAK,YAAY,CAAC;QAClB,KAAK,WAAW,CAAC;QACjB,KAAK,MAAM,CAAC;QACZ,KAAK,aAAa;YAChB,OAAO,SAAS,CAAC;QACnB;YACE,OAAO,SAAS,CAAC;IACrB,CAAC;AACH,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@milehimikey/em",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "AI-friendly Event Modeling — a slice-first text DSL rendered to a strict, self-contained Graphviz grid (SVG/PNG)",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -10,7 +10,8 @@
|
|
|
10
10
|
"files": [
|
|
11
11
|
"dist",
|
|
12
12
|
"README.md",
|
|
13
|
-
"LICENSE"
|
|
13
|
+
"LICENSE",
|
|
14
|
+
".claude"
|
|
14
15
|
],
|
|
15
16
|
"scripts": {
|
|
16
17
|
"build": "tsc -p tsconfig.json",
|