@milehimikey/em 1.1.0 → 1.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 +64 -81
- package/.claude/skills/event-modeling/reference/em-dsl.md +23 -18
- package/.claude/skills/event-modeling/reference/extract.md +123 -0
- package/.claude/skills/event-modeling/reference/methodology.md +23 -0
- package/.claude/skills/event-modeling/templates/live.html +65 -13
- package/.claude/skills/event-modeling/templates/model-readme.md +9 -6
- package/.claude/skills/event-modeling/templates/slice.md +2 -4
- package/.claude/skills/event-modeling/templates/state.md +16 -11
- package/README.md +44 -199
- package/dist/cli.js +21 -227
- package/dist/cli.js.map +1 -1
- package/dist/model/validate.d.ts +0 -2
- package/dist/model/validate.js +1 -1
- package/dist/model/validate.js.map +1 -1
- package/dist/render/serve.d.ts +20 -0
- package/dist/render/serve.js +202 -0
- package/dist/render/serve.js.map +1 -0
- package/package.json +4 -5
- package/.claude/skills/event-modeling/reference/frontmatter.md +0 -129
- package/dist/config.d.ts +0 -15
- package/dist/config.js +0 -60
- package/dist/config.js.map +0 -1
- package/dist/migrate/migrate.d.ts +0 -26
- package/dist/migrate/migrate.js +0 -276
- package/dist/migrate/migrate.js.map +0 -1
- package/dist/model/deriveSliceFields.d.ts +0 -21
- package/dist/model/deriveSliceFields.js +0 -60
- package/dist/model/deriveSliceFields.js.map +0 -1
- package/dist/model/frontmatter.d.ts +0 -21
- package/dist/model/frontmatter.js +0 -38
- package/dist/model/frontmatter.js.map +0 -1
- package/dist/model/sliceDoc.d.ts +0 -56
- package/dist/model/sliceDoc.js +0 -28
- package/dist/model/sliceDoc.js.map +0 -1
- package/dist/model/validateSliceDocs.d.ts +0 -7
- package/dist/model/validateSliceDocs.js +0 -195
- package/dist/model/validateSliceDocs.js.map +0 -1
- package/dist/slice/generate.d.ts +0 -17
- package/dist/slice/generate.js +0 -61
- package/dist/slice/generate.js.map +0 -1
- package/dist/slice/query.d.ts +0 -42
- package/dist/slice/query.js +0 -96
- package/dist/slice/query.js.map +0 -1
- package/dist/slice/sync.d.ts +0 -18
- package/dist/slice/sync.js +0 -104
- package/dist/slice/sync.js.map +0 -1
- package/dist/util/findModel.d.ts +0 -1
- package/dist/util/findModel.js +0 -27
- package/dist/util/findModel.js.map +0 -1
|
@@ -5,8 +5,10 @@ description: >-
|
|
|
5
5
|
the 7 steps of Event Modeling and the 4 patterns (State Change, State View, Automation,
|
|
6
6
|
Translation), and produce implementation-ready slice design documents. Use when the user wants
|
|
7
7
|
to event-model a business process or system, do event modeling / event storming, design slices,
|
|
8
|
-
build or edit an `.em` model,
|
|
9
|
-
|
|
8
|
+
build or edit an `.em` model, run the em tool, or extract / reverse-engineer a current-state
|
|
9
|
+
event model from an existing system or codebase (event-driven or legacy/procedural). Works in
|
|
10
|
+
resumable phases via an argument: `discover` (steps 1-4, greenfield), `extract` (current-state
|
|
11
|
+
model of an existing system), `model` (steps 5-7), `slice` (deep slice specs), plus `watch` and
|
|
10
12
|
`validate`. With no argument it resumes from the saved state file.
|
|
11
13
|
---
|
|
12
14
|
|
|
@@ -16,10 +18,9 @@ You are facilitating an Event Modeling session. Your job is to **extract an accu
|
|
|
16
18
|
the user through Socratic questioning** — never to invent the domain. You drive the `em` CLI to
|
|
17
19
|
render the model live, and you produce implementation-ready slice design docs.
|
|
18
20
|
|
|
19
|
-
Read `reference/methodology.md` (the 7 steps + 4 patterns)
|
|
20
|
-
validation rules)
|
|
21
|
-
|
|
22
|
-
source of truth. Templates live in `templates/`.
|
|
21
|
+
Read `reference/methodology.md` (the 7 steps + 4 patterns) and `reference/em-dsl.md` (DSL syntax and
|
|
22
|
+
validation rules) before doing real work — they are the source of truth. Templates live in
|
|
23
|
+
`templates/`.
|
|
23
24
|
|
|
24
25
|
## Operating principles (every phase)
|
|
25
26
|
|
|
@@ -59,14 +60,9 @@ source of truth. Templates live in `templates/`.
|
|
|
59
60
|
2. Locate the model. Look for an existing `<dir>/.event-modeling.md` and `*.em` in the working
|
|
60
61
|
directory (or a `models/` subfolder). If found, read the state file. If not, and the phase
|
|
61
62
|
needs one, ask the user for the model name and where to create it.
|
|
62
|
-
3.
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
`em migrate <model-dir>` before continuing (it refuses a dirty git tree by default — commit
|
|
66
|
-
first, or pass `--force`). Don't hand-edit old-format docs around this; migrate once, then
|
|
67
|
-
proceed normally.
|
|
68
|
-
4. Parse the argument (`$ARGUMENTS`) to pick the phase below. With **no argument**, read the
|
|
69
|
-
state file and resume the recorded phase/step; if no model exists, propose starting `discover`.
|
|
63
|
+
3. Parse the argument (`$ARGUMENTS`) to pick the phase below. With **no argument**, read the
|
|
64
|
+
state file and resume the recorded phase/step; if no model exists, propose starting `discover`
|
|
65
|
+
(greenfield) or `extract` (modeling an existing system).
|
|
70
66
|
|
|
71
67
|
## Project layout this skill creates
|
|
72
68
|
|
|
@@ -74,32 +70,24 @@ source of truth. Templates live in `templates/`.
|
|
|
74
70
|
<model-name>/
|
|
75
71
|
<model-name>.em # the model (slice docs linked via note "...")
|
|
76
72
|
<model-name>.svg # render target for em watch
|
|
77
|
-
live.html # auto-refresh viewer (copy of templates/live.html,
|
|
73
|
+
live.html # file:// auto-refresh viewer (copy of templates/live.html, verbatim)
|
|
78
74
|
README.md # overview + slice index (from templates/model-readme.md)
|
|
79
|
-
.event-modeling.md # resumable state (from templates/state.md
|
|
80
|
-
|
|
81
|
-
slices/<slice-id>.md # one rich slice doc per slice, scaffolded via `em slice new`
|
|
75
|
+
.event-modeling.md # resumable state (from templates/state.md)
|
|
76
|
+
slices/<slice-name>.md # one rich slice doc per slice (from templates/slice.md)
|
|
82
77
|
```
|
|
83
78
|
|
|
84
|
-
When creating a new model, scaffold the directory
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
use `em slice new` (see the `slice` phase below), which injects the required frontmatter and
|
|
90
|
-
respects a project's `em.config.json` custom template if one is configured.
|
|
91
|
-
|
|
92
|
-
Ask the **Regulatory scope** session input (state.md) up front, alongside Scope line / PRD
|
|
93
|
-
reference / Headless-API-model: does this domain have compliance requirements (PCI-DSS, SOX,
|
|
94
|
-
HIPAA, GDPR, ...) or none? This decides whether the `slice` phase asks about the `compliance:`
|
|
95
|
-
frontmatter block per slice (see `reference/frontmatter.md`) — don't ask about compliance at all
|
|
96
|
-
for a model with no regulatory scope.
|
|
79
|
+
When creating a new model, scaffold the directory and copy the templates in (filling the
|
|
80
|
+
placeholders). Copy `live.html` **verbatim** — it takes the SVG name from its URL query
|
|
81
|
+
(`live.html?svg=<model-name>.svg`), so there's nothing to edit. You may use `em init` for a
|
|
82
|
+
starter `.em`, but usually you'll build it up from the discovery conversation instead. Fill
|
|
83
|
+
template placeholders — never leave `{{...}}` in delivered files.
|
|
97
84
|
|
|
98
85
|
---
|
|
99
86
|
|
|
100
87
|
## Phase: `discover` — steps 1-4
|
|
101
88
|
|
|
102
89
|
Goal: a draft model of events, storyboard, commands, and views. Loose is OK; structure comes next.
|
|
90
|
+
**Existing system?** Use `extract` instead (next section) — discover is for greenfield modeling.
|
|
103
91
|
|
|
104
92
|
1. **Brainstorm events (step 1).** Ask the user to name everything that happens, as past-tense
|
|
105
93
|
facts. Probe for missed state changes. For each candidate, apply the **"is it an event?"
|
|
@@ -124,6 +112,27 @@ End of phase: write/refresh the `.em`, render it, update `.event-modeling.md` (s
|
|
|
124
112
|
decisions, open questions, slice inventory). Tell the user they can stop here and resume with
|
|
125
113
|
`/event-modeling model`.
|
|
126
114
|
|
|
115
|
+
## Phase: `extract` — current-state model of an existing system
|
|
116
|
+
|
|
117
|
+
Goal: a faithful **current-state** model extracted from an existing system — the as-is sibling
|
|
118
|
+
of `discover` (it replaces discover; the model then proceeds to `model` as usual).
|
|
119
|
+
|
|
120
|
+
**Read `reference/extract.md` before doing any extract work** — it carries a stance override
|
|
121
|
+
that inverts methodology step 1 (capture how the system behaves *today*; never model desired
|
|
122
|
+
state).
|
|
123
|
+
|
|
124
|
+
- Two source modes, detected from the system itself and confirmed with the user:
|
|
125
|
+
**event-driven** (an event vocabulary already exists — schemas, topics, handlers) and
|
|
126
|
+
**procedural/monolith** (no event vocabulary — synthesize candidate events from behavior
|
|
127
|
+
and docs).
|
|
128
|
+
- Converge via a ~7-round confirm-and-clarify loop (one concern per round; render from round 2,
|
|
129
|
+
validate from round 4 — see the playbook).
|
|
130
|
+
- **Current-state-only:** park unknowns as `# TBD` comments in the `.em`, mirrored in the state
|
|
131
|
+
file's Open Questions — never guess intended design.
|
|
132
|
+
|
|
133
|
+
End of phase: validated as-is model; state file updated (source mode, rounds, decisions, slice
|
|
134
|
+
inventory); then chain to `/event-modeling model` (steps 5-7).
|
|
135
|
+
|
|
127
136
|
## Phase: `model` — steps 5-7
|
|
128
137
|
|
|
129
138
|
Goal: a structurally complete, **validated** model with correct patterns and swimlanes.
|
|
@@ -159,54 +168,36 @@ the corresponding event/slice to the `.em` and re-render. The happy-path spine f
|
|
|
159
168
|
phases is the starting point, not the finished event set.
|
|
160
169
|
|
|
161
170
|
For each slice:
|
|
162
|
-
1.
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
one; the shipped default otherwise) ready to fill in.
|
|
166
|
-
2. Hold a Socratic deep-dive to fill every body section: intent, trigger/actor, command + field
|
|
167
|
-
table (types & rules), event(s) + payload (mark immutable facts), invariants (give each a
|
|
168
|
-
stable ID), Given/When/Then scenarios (happy path + rule boundaries + edge cases),
|
|
171
|
+
1. Hold a Socratic deep-dive to fill every section of `templates/slice.md`: intent, trigger/actor,
|
|
172
|
+
command + field table (types & rules), event(s) + payload (mark immutable facts), invariants
|
|
173
|
+
(give each a stable ID), Given/When/Then scenarios (happy path + rule boundaries + edge cases),
|
|
169
174
|
alternate/error flows (retries, idempotency, compensations), read models affected, open
|
|
170
|
-
questions. Park anything unresolved rather than guessing.
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
for
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
3. Wire it into the `.em`: add `note "slices/<id>.md"` to the slice's primary element (the command
|
|
178
|
-
for State Change, the view for State View, the processor for Automation, the translation for
|
|
179
|
-
Translation).
|
|
180
|
-
4. Run `em slice sync <id>` — it discovers the linked element from that `note` and fills in the
|
|
181
|
-
generated frontmatter fields (`commands`/`events`/`readModels`/`contexts`/`personas`, and
|
|
182
|
-
`triggers`/`triggeredBy` for automation/translation). **Re-run `em slice sync <id>` (or
|
|
183
|
-
`--all`) after any later `.em` edit that touches this slice's elements** — generated fields
|
|
184
|
-
drift otherwise, and `em validate` will flag it.
|
|
185
|
-
5. When the doc is solid, move it forward: `em slice update <id> --status reviewed` (and later
|
|
186
|
-
`--status ready-to-implement`). Update `README.md`'s slice index.
|
|
187
|
-
6. Re-render and `em validate` (validates the `.em` **and** every slice doc's frontmatter by
|
|
188
|
-
default).
|
|
175
|
+
questions. Park anything unresolved rather than guessing.
|
|
176
|
+
2. Write the doc to `slices/<slice-name>.md` (kebab-case the slice name).
|
|
177
|
+
3. Wire it into the `.em`: add `note "slices/<slice-name>.md"` to the slice's primary element
|
|
178
|
+
(the command for State Change, the view for State View, the processor for Automation, the
|
|
179
|
+
translation for Translation).
|
|
180
|
+
4. Update `README.md`'s slice index and the state file's slice inventory (`draft` → `ready-to-implement`).
|
|
181
|
+
5. Re-render and `em validate`.
|
|
189
182
|
|
|
190
183
|
## Phase: `watch` — live team view
|
|
191
184
|
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
185
|
+
**Recommended (instant push, no polling):** start the watcher with `--serve` in the background:
|
|
186
|
+
`em watch <model-name>.em -o <model-name>.svg --serve` (run_in_background). It re-renders on every
|
|
187
|
+
save and pushes an instant reload to the browser over Server-Sent Events. Tell the user to open the
|
|
188
|
+
URL it prints (e.g. `http://localhost:5173/?svg=<model-name>.svg`) and share their screen — updates
|
|
189
|
+
appear the moment you save, with no idle churn between edits.
|
|
190
|
+
|
|
191
|
+
**No-server fallback (`file://`):** if the user prefers not to run a server, start the plain watcher
|
|
192
|
+
`em watch <model-name>.em -o <model-name>.svg` (run_in_background) and have them open the copied
|
|
193
|
+
`live.html?svg=<model-name>.svg` in a browser. It polls every ~2s and reloads without flicker. No
|
|
194
|
+
editing of `live.html` is needed — the `?svg=` query picks the model.
|
|
199
195
|
|
|
200
196
|
## Phase: `validate`
|
|
201
197
|
|
|
202
198
|
Run `em validate <model-name>.em` and walk through each diagnostic with the user, explaining the
|
|
203
|
-
rule (see `reference/em-dsl.md`
|
|
204
|
-
|
|
205
|
-
- `.em`-level fixes: split an automation's command into the next slice, add a missing `from`, give
|
|
206
|
-
a command its event.
|
|
207
|
-
- Slice-doc-level fixes: no frontmatter block → run `em migrate` (pre-1.0 model); "out of sync
|
|
208
|
-
with .em" → run `em slice sync <id>`; orphan doc → wire a `note "slices/<id>.md"` onto the
|
|
209
|
-
right element, then sync.
|
|
199
|
+
rule (see `reference/em-dsl.md`) and proposing the fix (e.g. split an automation's command into
|
|
200
|
+
the next slice, add a missing `from`, give a command its event). Apply fixes on agreement.
|
|
210
201
|
|
|
211
202
|
Then do one check `em validate` can't: scan every `translation`/`processor`/`automation` slice and
|
|
212
203
|
confirm none contains an `event` — each reaction must trigger a `command` in the next slice
|
|
@@ -220,20 +211,12 @@ two slices and route it through a command.
|
|
|
220
211
|
```bash
|
|
221
212
|
em --version
|
|
222
213
|
em init <name>.em # optional starter scaffold
|
|
223
|
-
em validate <name>.em # check
|
|
214
|
+
em validate <name>.em # check rules; exit 0 if clean/warnings only
|
|
224
215
|
em render <name>.em -o <name>.svg # render (svg/png/pdf by extension)
|
|
225
216
|
em render <name>.em --emit-dot # inspect generated Graphviz DOT
|
|
226
217
|
em watch <name>.em -o <name>.svg # re-render on save (run in background)
|
|
227
|
-
|
|
228
|
-
em slice new "<Slice Name>" --pattern <pattern> # scaffold slices/<id>.md with frontmatter
|
|
229
|
-
em slice sync <id> # after wiring note "slices/<id>.md" into the .em
|
|
230
|
-
em slice update <id> --status <status> # move a slice's lifecycle status forward
|
|
231
|
-
em slice list / show <id> / search "<query>" # query slice docs by frontmatter (--format json)
|
|
232
|
-
em slice search "<q>" --format json --full # ...with every field, not just the summary row
|
|
233
|
-
em migrate <model-dir> # one-time upgrade from a pre-1.0 model
|
|
218
|
+
em watch <name>.em -o <name>.svg --serve # + live viewer with instant push-reload (--port N)
|
|
234
219
|
```
|
|
235
220
|
|
|
236
|
-
See `reference/frontmatter.md` for the full slice-doc frontmatter schema and CLI reference.
|
|
237
|
-
|
|
238
221
|
Always finish a working session by: re-rendering, running `em validate`, and updating
|
|
239
222
|
`.event-modeling.md` with the current phase/step, decisions, and open questions.
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
<!-- DSL behavior change? Update BOTH docs/dsl.md and .claude/skills/event-modeling/reference/em-dsl.md -->
|
|
2
|
+
|
|
1
3
|
# `em` DSL Reference & Cheatsheet
|
|
2
4
|
|
|
3
5
|
The `em` tool (`@milehimikey/em`) is a slice-first text DSL rendered to a strict Graphviz
|
|
@@ -13,28 +15,14 @@ em init [file] # scaffold a starter model.em (default: model.em)
|
|
|
13
15
|
em render <file> -o out.svg # render (extension picks format: svg, png, pdf)
|
|
14
16
|
em render <file> --emit-dot # print the Graphviz DOT instead of rendering
|
|
15
17
|
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
|
|
18
|
+
em watch <file> -o out.svg # re-render on every save (file-based)
|
|
19
|
+
em watch <file> -o out.svg --serve # + localhost live viewer, instant SSE push-reload (--port N)
|
|
17
20
|
em validate <file> # check event-modeling rules; exit 0 if only warnings/clean
|
|
18
21
|
```
|
|
19
22
|
|
|
20
23
|
Install if missing: `npm i -g @milehimikey/em`. PNG works with no system deps; PDF needs
|
|
21
24
|
`rsvg-convert`.
|
|
22
25
|
|
|
23
|
-
**Slice docs** (frontmatter, lifecycle, search — see `reference/frontmatter.md` for the schema):
|
|
24
|
-
|
|
25
|
-
```bash
|
|
26
|
-
em slice new "Slice Name" [--pattern state-change] [--id <id>] # scaffold slices/<id>.md
|
|
27
|
-
em slice sync <id> # after wiring note "slices/<id>.md" into the .em
|
|
28
|
-
em slice sync --all # re-sync every doc in the model directory
|
|
29
|
-
em slice list [--status <s>] [--pattern <p>] [--context <c>] [--tag <t>] [--format json] [--full]
|
|
30
|
-
em slice show <id> [--body] [--format json]
|
|
31
|
-
em slice search "<query>" [--pattern <p>] [--status <s>] [--context <c>] [--tag <t>] [--format json] [--full]
|
|
32
|
-
em slice update <id> [--status <s>] [--bump-version]
|
|
33
|
-
em migrate <model-dir> [--dry-run] [--report <path>] # one-time upgrade from a pre-1.0 model
|
|
34
|
-
```
|
|
35
|
-
|
|
36
|
-
`em validate <file>` also validates every doc in `slices/` by default (`--skip-slices` to opt out).
|
|
37
|
-
|
|
38
26
|
---
|
|
39
27
|
|
|
40
28
|
## Grammar
|
|
@@ -49,6 +37,7 @@ slice "Name" { # one vertical time step (a column)
|
|
|
49
37
|
ui Free Text @Persona # screen; @Persona picks its row (defaults to first/"User")
|
|
50
38
|
command Free Text # state-changing request (API band)
|
|
51
39
|
view Free Text from "Event A", "Event B" # read model fed by event(s)
|
|
40
|
+
view Free Text again from "Event C" # later instance of an evolving read model (see Clauses)
|
|
52
41
|
event Free Text @Context # recorded fact; @Context picks its row (defaults to "Domain")
|
|
53
42
|
processor Free Text from "View" # automation; aliases: automation | saga | translation
|
|
54
43
|
}
|
|
@@ -68,7 +57,16 @@ arrow From Element -> To Element # explicit cross-slice edge (overrides infer
|
|
|
68
57
|
### Clauses
|
|
69
58
|
- **Tags:** `@Persona` only on `ui`; `@Context` only on `event`. Undeclared tags auto-create a row.
|
|
70
59
|
- **`from "X"`** on views/automations declares the source(s); names are quoted, comma-separated.
|
|
71
|
-
Matching is case-insensitive and whitespace-normalized.
|
|
60
|
+
Matching is case-insensitive and whitespace-normalized. A `from` may never point at an event or
|
|
61
|
+
view that first appears in a LATER slice (forward-only timeline — validation error).
|
|
62
|
+
- **`again`** (views only): `view <Name> again [from "Event", …]` declares a later instance of an
|
|
63
|
+
already-declared read model — the forward-only device for a view that evolves as later events
|
|
64
|
+
land. Instances are ONE logical view: the first declaration owns the `note` binding; each
|
|
65
|
+
instance's `from` lists only the NEW events landing at that point (not cumulative); instances are
|
|
66
|
+
linked left-to-right with a continuity arrow; a reaction (`from "View"`) reads the nearest
|
|
67
|
+
instance at-or-before its own slice. `again` with no earlier declaration is a validation error.
|
|
68
|
+
Use `again` (not a plain repeated `view` name) whenever the view is referenced by a
|
|
69
|
+
`from`/`arrow` — plain repeats are only warning-free while unreferenced.
|
|
72
70
|
- **`note "path.md"`** on ANY element links a markdown doc. Relative to the `.em` file. Renders as
|
|
73
71
|
a clickable marker in SVG and a legend entry in PNG/PDF. **This is how slice docs attach.**
|
|
74
72
|
- **Fields:** `command Place Order { orderId: UUID, items: LineItem[], customerId }` — inline or
|
|
@@ -156,7 +154,9 @@ slice "Read Quote — created" { view Quote from "QuoteCreated" translation S
|
|
|
156
154
|
*referenced* duplicate, resolving to the first). **Wire each event to a read model exactly once:**
|
|
157
155
|
a repeated instance's `from` lists only the **new** events since the previous instance (not
|
|
158
156
|
cumulative), or the event draws a duplicate arrow to the same read model at every repeat. (An
|
|
159
|
-
event may still feed several *different* read models, once each.)
|
|
157
|
+
event may still feed several *different* read models, once each.) When a repeated view **must be
|
|
158
|
+
referenced** — e.g. a reaction reads it via `from` — declare its later instances with
|
|
159
|
+
`view X again` instead (see Clauses); instances resolve properly and draw continuity arrows.
|
|
160
160
|
- **Keep arrows span-1: put each repeat right after its feeding event.** Place a read-model instance
|
|
161
161
|
immediately after the event that updates it, sourcing only that single adjacent event. A read
|
|
162
162
|
model far from its source events draws long arrows that cross the read-model row and read as
|
|
@@ -185,6 +185,11 @@ slice "Read Quote — created" { view Quote from "QuoteCreated" translation S
|
|
|
185
185
|
2. **Unknown event source** — `view X from "Event"` where the event doesn't exist anywhere.
|
|
186
186
|
3. **Unknown read-model source** — `processor X from "View"` where the view doesn't exist.
|
|
187
187
|
4. **Arrow endpoint mismatch** — `arrow A -> B` where A or B matches no element name.
|
|
188
|
+
5. **Backward timeline** ("time flows left to right") — an event feeding a view instance in an
|
|
189
|
+
EARLIER slice (fix: add a `view X again` instance where the event lands and move the source
|
|
190
|
+
there), a reaction reading a view before any instance of it exists, or an explicit backward
|
|
191
|
+
`arrow`.
|
|
192
|
+
6. **`again` without an earlier declaration** — declare the view plainly the first time it appears.
|
|
188
193
|
|
|
189
194
|
**Warnings (should fix):**
|
|
190
195
|
1. **Automation/translation shares slice with its command** — both `automation`/`processor` and
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
# Extract Phase — Current-State Model of an Existing System
|
|
2
|
+
|
|
3
|
+
Read this **before doing any extract work**. It governs the `extract` phase and **overrides
|
|
4
|
+
`methodology.md` step 1's stance** for the duration of extraction: you are capturing how the
|
|
5
|
+
system behaves **today**, not modeling what the business needs. Everything else in the
|
|
6
|
+
methodology — the 4 patterns, the "is it an event?" test, the Socratic stance, the two-slice
|
|
7
|
+
reaction split — still applies.
|
|
8
|
+
|
|
9
|
+
## Stance override — current-state-only
|
|
10
|
+
|
|
11
|
+
- Capture the system **as it is**, faithfully, warts included. Never invent future or desired
|
|
12
|
+
state. Improvement ideas that surface go to the state file's Open Questions or Decisions log
|
|
13
|
+
— never into the model.
|
|
14
|
+
- Methodology step 1's caution ("model the process the business **needs**, not how the current
|
|
15
|
+
system happens to work — existing system behavior is a common source of fake events") is
|
|
16
|
+
**deliberately suspended**: here the existing system *is* the subject.
|
|
17
|
+
- The **"is it an event?" filter still applies.** Derived values (totals, calculated fields)
|
|
18
|
+
and telemetry/activity (clicks, page views, logins) are still not events, even when the
|
|
19
|
+
current system stores or emits them as such. Park each rejection in the Decisions log with
|
|
20
|
+
why, so it isn't re-proposed.
|
|
21
|
+
- **Never guess.** Unknown or ambiguous current behavior is parked (see TBD convention below),
|
|
22
|
+
not filled in with what the design "probably" intends.
|
|
23
|
+
|
|
24
|
+
## Setup
|
|
25
|
+
|
|
26
|
+
1. Run the SKILL.md preconditions (tool check, model location) and scaffold the standard
|
|
27
|
+
project layout if no model exists yet.
|
|
28
|
+
2. **Agree the scope line first**: which system, which part, what's out of bounds. Write it to
|
|
29
|
+
the state file's Session inputs.
|
|
30
|
+
3. Record in the state file: `Source mode` (after detection, below) and
|
|
31
|
+
`Existing system refs` — the repo paths, event-schema/topic locations, and docs you will
|
|
32
|
+
extract from.
|
|
33
|
+
|
|
34
|
+
## Mode detection
|
|
35
|
+
|
|
36
|
+
Inspect the system with your own tools (Grep/Read over the codebase and docs), then confirm
|
|
37
|
+
the mode with the user:
|
|
38
|
+
|
|
39
|
+
- **Event-driven** — the system already has an event vocabulary. Signals: event
|
|
40
|
+
schemas/classes, topics/streams/queues, message or CDC handlers, an event store, pub/sub
|
|
41
|
+
wiring. Extraction *lifts* the model from that vocabulary.
|
|
42
|
+
- **Procedural / monolith** — no event vocabulary. Signals: transaction scripts, service
|
|
43
|
+
methods mutating rows, CRUD controllers, procedural runbooks/docs. Extraction *synthesizes*
|
|
44
|
+
candidate events from the state changes the behavior implies.
|
|
45
|
+
|
|
46
|
+
Mixed systems happen: pick the dominant mode, note the exception areas in the state file.
|
|
47
|
+
|
|
48
|
+
## Sourcing by mode
|
|
49
|
+
|
|
50
|
+
- **Event-driven:** inventory the emitted/consumed event types; take past-tense business facts
|
|
51
|
+
directly; then filter — technical/infrastructure messages (retries, heartbeats, envelope
|
|
52
|
+
wrappers) and derived/telemetry types are dropped (log why). Keep the system's own names
|
|
53
|
+
where they are honest past-tense facts; propose a cleaner past-tense name only when the
|
|
54
|
+
original is technical (record the mapping in the Decisions log).
|
|
55
|
+
- **Procedural:** for each significant operation, ask "what business fact has become true when
|
|
56
|
+
this completes?" — those are the candidate events. Screens and tables are *evidence* of
|
|
57
|
+
state changes, not events themselves; a table row's lifecycle (created/updated/closed) often
|
|
58
|
+
implies several events. Apply the event filter hard here — procedural systems are where
|
|
59
|
+
derived values and activity records masquerade as facts.
|
|
60
|
+
|
|
61
|
+
## The confirm-and-clarify loop (~7 rounds)
|
|
62
|
+
|
|
63
|
+
One concern per round. After every round: mirror what you extracted back to the user, update
|
|
64
|
+
the `.em`, and (from R2) re-render so they see the model grow. Convergence beats coverage —
|
|
65
|
+
it is fine to finish a round with `# TBD`s still parked.
|
|
66
|
+
|
|
67
|
+
- **R1 — Candidate events.** Extract (event-driven) or synthesize (procedural) the past-tense
|
|
68
|
+
facts; apply the event test; user confirms/renames/rejects. Ambiguous candidates → `# TBD`.
|
|
69
|
+
- **R2 — Timeline order.** Order the confirmed events into the as-is narrative. Identify the
|
|
70
|
+
*actual* actors/callers and the *real* UI or API surfaces (headless systems: no
|
|
71
|
+
`ui`/`persona` — use write/read translations per `em-dsl.md`). **First `em render` here.**
|
|
72
|
+
- **R3 — Commands / inputs.** For each event, the request that actually produces it today
|
|
73
|
+
(imperative name). Unclear trigger → `# TBD`, not a plausible guess.
|
|
74
|
+
- **R4 — Read models / outputs.** The projections/queries the system actually serves; wire
|
|
75
|
+
`from "Event"` (repeat read models span-1 per `em-dsl.md`). **Start running `em validate`**
|
|
76
|
+
each round from here.
|
|
77
|
+
- **R5 — Boundaries & reactions.** Current automations and integrations as-is: schedulers,
|
|
78
|
+
listeners, outbound syncs, inbound webhooks. Model each as a reaction with the two-slice
|
|
79
|
+
`reaction → command → event` split and the slice-ordering rules in `em-dsl.md` — even when
|
|
80
|
+
the current code wires it as one procedural step (note the as-built shape in the slice's
|
|
81
|
+
`# TBD`/Decisions if it differs).
|
|
82
|
+
- **R6 — Gap & TBD reconciliation.** Walk every parked `# TBD` with the user. Resolve only
|
|
83
|
+
what they can confirm **as a current-state fact**; everything else stays parked. Explicitly
|
|
84
|
+
refuse to resolve a TBD by inventing desired behavior.
|
|
85
|
+
- **R7 — Convergence.** Final `em render` + `em validate` (fix errors and warnings; remember
|
|
86
|
+
the validator's blind spot — check the reaction split by hand). User confirms the model is a
|
|
87
|
+
faithful picture of today. Mark extraction complete in the state file and hand off.
|
|
88
|
+
|
|
89
|
+
Seven rounds is the default shape, not a quota — collapse rounds for a small system, repeat
|
|
90
|
+
one for a sprawling one. Record any deviation in the Decisions log.
|
|
91
|
+
|
|
92
|
+
## TBD parking convention
|
|
93
|
+
|
|
94
|
+
- Inline `.em` comment on its own line directly above (or trailing) the element it concerns:
|
|
95
|
+
`# TBD: <specific question about current behavior>`. Comments are native DSL syntax — they
|
|
96
|
+
render and validate cleanly.
|
|
97
|
+
- Mirror every `# TBD` 1:1 as a checkbox in the state file's **Open questions / parking lot**,
|
|
98
|
+
and remove both together when resolved.
|
|
99
|
+
- A `# TBD` is only ever resolved by a confirmed current-state fact — never replaced by a
|
|
100
|
+
guessed element.
|
|
101
|
+
|
|
102
|
+
## Completion & handoff
|
|
103
|
+
|
|
104
|
+
Extraction is complete when: the model renders; `em validate` is clean (or remaining warnings
|
|
105
|
+
are consciously accepted in the Decisions log); every unknown is a parked `# TBD`; and the
|
|
106
|
+
user has confirmed the model reflects today's behavior. Then:
|
|
107
|
+
|
|
108
|
+
1. State file: set phase progress (extraction rounds checked), log the completion decision,
|
|
109
|
+
seed the **Slice inventory** (every slice, pattern, doc status `none`).
|
|
110
|
+
2. The extracted model **replaces `discover`** — suggest `/event-modeling model` next
|
|
111
|
+
(steps 5-7: swimlanes, patterns, completeness). Desired-state changes belong *after* the
|
|
112
|
+
as-is model is agreed — as normal `model`/`slice` evolution, clearly separated from
|
|
113
|
+
extraction.
|
|
114
|
+
|
|
115
|
+
## Anti-patterns
|
|
116
|
+
|
|
117
|
+
- **Inventing desired state** — the cardinal sin of extraction; park it instead.
|
|
118
|
+
- **Importing screens/tables as events** — they are evidence of state changes, not facts.
|
|
119
|
+
- **Guessing an unclear trigger or reaction** instead of parking a `# TBD`.
|
|
120
|
+
- **Cleaning up the domain while extracting** — renames and restructures beyond honest
|
|
121
|
+
past-tense naming are `model`-phase work, done with the user after the as-is picture exists.
|
|
122
|
+
- **Skipping render/validate until the end** — the loop depends on the user seeing the model
|
|
123
|
+
grow round by round.
|
|
@@ -121,6 +121,9 @@ state change anyone can think of. No commands, no UI yet — just facts.
|
|
|
121
121
|
User Clicked Button, User Viewed Page, User Logged In.
|
|
122
122
|
- Model the process the business **needs**, not how the current system happens to work. Existing
|
|
123
123
|
system behavior is a common source of fake events — screens and tables leaking in as facts.
|
|
124
|
+
(**Extract mode inverts this**: in the skill's `extract` phase the existing system *is* the
|
|
125
|
+
subject and this caution is deliberately suspended — see the "Extract" section below and
|
|
126
|
+
`reference/extract.md`. The derived-value and telemetry filters still apply there.)
|
|
124
127
|
- Output: a flat list of candidate `event` names.
|
|
125
128
|
|
|
126
129
|
### Step 2 — The Plot / Storyboard *(discover)*
|
|
@@ -178,6 +181,26 @@ Walk the whole model with stakeholders and check for loose ends:
|
|
|
178
181
|
|
|
179
182
|
---
|
|
180
183
|
|
|
184
|
+
## Extract: current-state models of existing systems
|
|
185
|
+
|
|
186
|
+
The `extract` phase derives a model from an **existing** system instead of a greenfield
|
|
187
|
+
conversation. It is discover's as-is sibling: its confirm-and-clarify rounds converge the same
|
|
188
|
+
step 1–4 outputs (events, timeline, commands, read models) — *derived from the system*, then
|
|
189
|
+
confirmed with the user — after which the model enters `model` (steps 5–7) as usual.
|
|
190
|
+
|
|
191
|
+
The stance inversion, stated authoritatively: **extraction captures how the system behaves
|
|
192
|
+
today — faithfully, warts included — and never invents future or desired state.** Step 1's
|
|
193
|
+
anti-current-system caution is suspended for the duration; the "is it an event?" filter is
|
|
194
|
+
not — derived values and telemetry are still rejected even when the current system emits them.
|
|
195
|
+
Unknown or ambiguous current behavior is parked (`# TBD` in the `.em`, mirrored in the state
|
|
196
|
+
file's Open Questions), never guessed. Desired-state improvements belong to the `model` and
|
|
197
|
+
`slice` phases, after the as-is picture is agreed.
|
|
198
|
+
|
|
199
|
+
Procedure, source modes (event-driven vs. procedural synthesis), and the round-by-round loop:
|
|
200
|
+
`reference/extract.md`.
|
|
201
|
+
|
|
202
|
+
---
|
|
203
|
+
|
|
181
204
|
## Socratic stance (applies throughout)
|
|
182
205
|
|
|
183
206
|
- Ask **one focused question at a time**; never assume a domain fact — extract it.
|
|
@@ -3,10 +3,22 @@
|
|
|
3
3
|
Live viewer for an em event model. Auto-reloads the rendered SVG so a team on a shared
|
|
4
4
|
screen sees updates from `em watch` without manual refresh.
|
|
5
5
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
6
|
+
Two ways to use it:
|
|
7
|
+
|
|
8
|
+
A) Push (recommended, instant, no polling):
|
|
9
|
+
em watch MODEL.em -o MODEL.svg --serve
|
|
10
|
+
then open the URL it prints (http://localhost:5173/?svg=MODEL.svg). You don't need
|
|
11
|
+
this file at all in that mode — the server serves its own push-based viewer.
|
|
12
|
+
|
|
13
|
+
B) This file, over file:// (no server):
|
|
14
|
+
1) In one terminal: em watch MODEL.em -o MODEL.svg
|
|
15
|
+
2) Open this file with the SVG name in the query:
|
|
16
|
+
open "live.html?svg=MODEL.svg" (macOS)
|
|
17
|
+
No editing required — the ?svg= param picks the model.
|
|
18
|
+
|
|
19
|
+
Under file:// the browser gives no reliable "did the file change?" signal (fetch/HEAD is
|
|
20
|
+
blocked), so this fallback polls and reloads on a gentle interval. It double-buffers the
|
|
21
|
+
swap so a shared screen never flashes. For zero idle work and instant updates, use --serve.
|
|
10
22
|
|
|
11
23
|
The SVG is embedded via <object> so note "..." links inside it stay clickable.
|
|
12
24
|
-->
|
|
@@ -37,17 +49,57 @@
|
|
|
37
49
|
<object id="svg" type="image/svg+xml"></object>
|
|
38
50
|
</div>
|
|
39
51
|
<script>
|
|
40
|
-
//
|
|
41
|
-
const SVG_FILE = "model.svg";
|
|
42
|
-
|
|
52
|
+
// SVG filename from ?svg=<name>.svg (defaults to model.svg) — one file serves any model.
|
|
53
|
+
const SVG_FILE = new URLSearchParams(location.search).get("svg") || "model.svg";
|
|
54
|
+
// Poll cadence for the file:// fallback. Tune freely; --serve makes polling unnecessary.
|
|
55
|
+
const REFRESH_INTERVAL_MS = 2000;
|
|
56
|
+
|
|
57
|
+
const stage = document.getElementById("stage");
|
|
43
58
|
const stamp = document.getElementById("stamp");
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
59
|
+
let current = document.getElementById("svg");
|
|
60
|
+
let lastModified = null;
|
|
61
|
+
|
|
62
|
+
// Best-effort change check — only meaningful when served over http(s). Under file://
|
|
63
|
+
// fetch/HEAD is blocked, so this throws and we fall back to reloading every tick.
|
|
64
|
+
async function changed() {
|
|
65
|
+
if (!location.protocol.startsWith("http")) return true;
|
|
66
|
+
try {
|
|
67
|
+
const res = await fetch(SVG_FILE, { method: "HEAD", cache: "no-store" });
|
|
68
|
+
const tag = res.headers.get("last-modified") || res.headers.get("etag");
|
|
69
|
+
if (tag && tag === lastModified) return false;
|
|
70
|
+
lastModified = tag;
|
|
71
|
+
return true;
|
|
72
|
+
} catch {
|
|
73
|
+
return true; // can't tell — reload to be safe
|
|
74
|
+
}
|
|
48
75
|
}
|
|
49
|
-
|
|
50
|
-
|
|
76
|
+
|
|
77
|
+
// Double-buffer: load into a hidden <object>, swap on load, so the shared screen
|
|
78
|
+
// never flashes white during a reload.
|
|
79
|
+
function swap() {
|
|
80
|
+
const next = document.createElement("object");
|
|
81
|
+
next.type = "image/svg+xml";
|
|
82
|
+
next.style.cssText = current.style.cssText;
|
|
83
|
+
next.addEventListener("load", () => {
|
|
84
|
+
stage.replaceChild(next, current);
|
|
85
|
+
current = next;
|
|
86
|
+
stamp.textContent = new Date().toLocaleTimeString();
|
|
87
|
+
}, { once: true });
|
|
88
|
+
next.setAttribute("data", SVG_FILE + "?t=" + Date.now());
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// Self-scheduling loop: schedule the next tick only after this one resolves, so a slow
|
|
92
|
+
// reload can never overlap the next tick.
|
|
93
|
+
async function tick() {
|
|
94
|
+
try {
|
|
95
|
+
if (await changed()) swap();
|
|
96
|
+
} finally {
|
|
97
|
+
setTimeout(tick, REFRESH_INTERVAL_MS);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
swap();
|
|
102
|
+
setTimeout(tick, REFRESH_INTERVAL_MS);
|
|
51
103
|
</script>
|
|
52
104
|
</body>
|
|
53
105
|
</html>
|
|
@@ -6,10 +6,13 @@
|
|
|
6
6
|
While modeling, run the live view so the team can watch the diagram update:
|
|
7
7
|
|
|
8
8
|
```bash
|
|
9
|
-
em watch {{model-name}}.em -o {{model-name}}.svg # re-
|
|
10
|
-
# then open
|
|
9
|
+
em watch {{model-name}}.em -o {{model-name}}.svg --serve # re-render + instant push-reload
|
|
10
|
+
# then open the URL it prints (http://localhost:5173/?svg={{model-name}}.svg) and share the screen
|
|
11
11
|
```
|
|
12
12
|
|
|
13
|
+
No server? Run `em watch {{model-name}}.em -o {{model-name}}.svg` and open
|
|
14
|
+
`live.html?svg={{model-name}}.svg` in a browser (polls ~2s; share the screen).
|
|
15
|
+
|
|
13
16
|
Static render: `em render {{model-name}}.em -o {{model-name}}.svg`
|
|
14
17
|
|
|
15
18
|
## Patterns legend
|
|
@@ -19,10 +22,10 @@ Static render: `em render {{model-name}}.em -o {{model-name}}.svg`
|
|
|
19
22
|
- **Translation** — External input (or Read Model) → Translation → (next slice) Command → Event
|
|
20
23
|
|
|
21
24
|
## Slices
|
|
22
|
-
<!-- Keep this index in sync as slice docs are written
|
|
23
|
-
| # | Slice | Pattern |
|
|
24
|
-
|
|
25
|
-
| 1 | {{Slice Name}} | {{Pattern}} |
|
|
25
|
+
<!-- Keep this index in sync as slice docs are written. -->
|
|
26
|
+
| # | Slice | Pattern | Design doc |
|
|
27
|
+
|---|-------|---------|------------|
|
|
28
|
+
| 1 | {{Slice Name}} | {{Pattern}} | [slices/{{slice-name}}.md](slices/{{slice-name}}.md) |
|
|
26
29
|
|
|
27
30
|
## Status
|
|
28
31
|
See [`.event-modeling.md`](.event-modeling.md) for current phase, decisions, and open questions.
|
|
@@ -3,15 +3,13 @@ Rich slice design document. One per slice, stored in <model>/slices/<slice-name>
|
|
|
3
3
|
linked from the .em model with: note "slices/<slice-name>.md" on the slice's defining element.
|
|
4
4
|
Fill every section through Socratic questioning. Leave "Open Questions" rather than guessing.
|
|
5
5
|
Replace the bracketed placeholders; delete guidance comments before finishing.
|
|
6
|
-
|
|
7
|
-
This is the body template only — `em slice new` injects the required frontmatter block above
|
|
8
|
-
this content (id, pattern, status, version, sliceElement, and the generated search fields).
|
|
9
|
-
Pattern and status live there now, not as prose here — see reference/frontmatter.md.
|
|
10
6
|
-->
|
|
11
7
|
|
|
12
8
|
# Slice: {{Slice Name}}
|
|
13
9
|
|
|
10
|
+
- **Pattern:** {{State Change | State View | Automation | Translation}}
|
|
14
11
|
- **Swimlane:** {{Persona/Actor}} → {{Context/Aggregate}}
|
|
12
|
+
- **Status:** {{draft | reviewed | ready-to-implement}}
|
|
15
13
|
|
|
16
14
|
## Intent
|
|
17
15
|
{{Why this slice exists — the user or business goal it serves, in one or two sentences.}}
|