@milehimikey/em 1.3.0 → 1.5.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.
Files changed (37) hide show
  1. package/.claude/skills/event-modeling/SKILL.md +99 -22
  2. package/.claude/skills/event-modeling/reference/conform.md +230 -0
  3. package/.claude/skills/event-modeling/reference/em-dsl.md +93 -26
  4. package/.claude/skills/event-modeling/reference/extract.md +12 -5
  5. package/.claude/skills/event-modeling/reference/methodology.md +103 -21
  6. package/.claude/skills/event-modeling/templates/conformance-report.md +69 -0
  7. package/.claude/skills/event-modeling/templates/model-readme.md +9 -0
  8. package/.claude/skills/event-modeling/templates/slice.md +15 -1
  9. package/.claude/skills/event-modeling/templates/state.md +1 -0
  10. package/README.md +17 -4
  11. package/dist/cli/changelog-git.d.ts +53 -0
  12. package/dist/cli/changelog-git.js +112 -0
  13. package/dist/cli/changelog-git.js.map +1 -0
  14. package/dist/cli.js +103 -5
  15. package/dist/cli.js.map +1 -1
  16. package/dist/emit/changelog.d.ts +57 -0
  17. package/dist/emit/changelog.js +143 -0
  18. package/dist/emit/changelog.js.map +1 -0
  19. package/dist/emit/diffJson.d.ts +17 -0
  20. package/dist/emit/diffJson.js +74 -0
  21. package/dist/emit/diffJson.js.map +1 -0
  22. package/dist/emit/json.d.ts +1 -0
  23. package/dist/emit/json.js +1 -1
  24. package/dist/emit/json.js.map +1 -1
  25. package/dist/model/edges.js +7 -16
  26. package/dist/model/edges.js.map +1 -1
  27. package/dist/model/validate.js +154 -0
  28. package/dist/model/validate.js.map +1 -1
  29. package/dist/render/drawEdges.d.ts +3 -1
  30. package/dist/render/drawEdges.js +119 -7
  31. package/dist/render/drawEdges.js.map +1 -1
  32. package/dist/render/render.js +5 -3
  33. package/dist/render/render.js.map +1 -1
  34. package/dist/render/svgGeometry.d.ts +13 -0
  35. package/dist/render/svgGeometry.js +47 -0
  36. package/dist/render/svgGeometry.js.map +1 -1
  37. package/package.json +3 -2
@@ -5,11 +5,13 @@ 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, 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
12
- `validate`. With no argument it resumes from the saved state file.
8
+ build or edit an `.em` model, run the em tool, extract / reverse-engineer a current-state
9
+ event model from an existing system or codebase (event-driven or legacy/procedural), or check
10
+ a ratified model for drift against the codebase that implements it. Works in resumable phases
11
+ via an argument: `discover` (steps 1-4, greenfield), `extract` (current-state model of an
12
+ existing system), `model` (steps 5-7), `slice` (deep slice specs), `conform` (drift check
13
+ against the codebase), plus `watch` and `validate`. With no argument it resumes from the saved
14
+ state file.
13
15
  ---
14
16
 
15
17
  # Event Modeling with `em`
@@ -46,11 +48,31 @@ validation rules) before doing real work — they are the source of truth. Templ
46
48
  triggers **no** command — the analog of `view → ui`). **Repeat read models** across slices so the
47
49
  timeline flows left-to-right — put each repeat **right after the event that feeds it**, sourcing
48
50
  only that one adjacent event, so every arrow is short (a read model far from its source events
49
- draws long arrows that look like a forbidden read→read link). Repeats render cleanly — em only
50
- warns on a duplicate name that's *referenced* by `from`/`arrow`. Slice order matters: a reaction
51
- must be directly followed by its command slice, and a read slice must not be immediately
51
+ draws a long arrow whose head lands columns away, making the *write* slice read as dangling —
52
+ keep a sub-flow that detours into another context together, not parked at the end of the model).
53
+ Declare every instance after the first with
54
+ **`view X again from "..."`** — `again` instances are exempt from the duplicate-name warning even
55
+ when referenced, and each reference resolves to the right instance. Slice order matters: a
56
+ reaction must be directly followed by its command slice, and a read slice must not be immediately
52
57
  followed by a command slice (see `em-dsl.md`).
53
58
  See `reference/methodology.md` (State View) and `reference/em-dsl.md`.
59
+ - **Never connect two instances of one read model.** The repeat is a timeline device, not a flow:
60
+ continuity is implied by the shared name, and the events arriving at each instance are what show
61
+ it changing. An arrow between instances says the read model feeds itself, and is an error.
62
+ - **Every slice is a COMPLETE pattern, never a half-slice.** A State Change is
63
+ `ui → command → event`; a State View is `event → read model → ui` (or `→ reaction`). So: a
64
+ command needs something that **triggers** it (the `ui` it's issued from, or the reaction in the
65
+ slice before it), its event needs a **reader**, and that read model needs a **consumer** of its
66
+ own. A command nothing points at is a write nobody can start; an event nothing projects is a
67
+ write nobody can see; a read model nothing displays is information dropped on the floor. Write
68
+ the whole chain together rather than sweeping up dangling ends later. Reactions don't count as
69
+ readers of *events* — they read views. **Every instance** of a repeated read model needs its own
70
+ consumer: if you repeat a view next to an event just to keep the arrow short, bring its screen
71
+ along, or don't add the instance.
72
+ - **Only six connections are legal:** `ui → command`, `command → event`, `event → read model`,
73
+ `read model → ui`, `read model → reaction`, `reaction → command`. Reaching for anything else —
74
+ above all `command → read model` (the CQRS violation) or `read model → command` — means the model
75
+ is missing an **element**, not an arrow.
54
76
  - **Validate continuously.** Run `em validate` and fix errors/warnings as you go (see DSL ref).
55
77
  - **Save state at the end of every session** so work resumes cleanly.
56
78
 
@@ -78,13 +100,19 @@ validation rules) before doing real work — they are the source of truth. Templ
78
100
  README.md # overview + slice index (from templates/model-readme.md)
79
101
  .event-modeling.md # resumable state (from templates/state.md)
80
102
  slices/<slice-name>.md # one rich slice doc per slice (from templates/slice.md)
103
+ conformance/<date>-report.md
104
+ # conform-phase reports (from templates/conformance-report.md)
105
+ <model-name>-asis.em # conform-phase scratch model, regenerated per run — git-ignore this
81
106
  ```
82
107
 
83
108
  When creating a new model, scaffold the directory and copy the templates in (filling the
84
109
  placeholders). Copy `live.html` **verbatim** — it takes the SVG name from its URL query
85
110
  (`live.html?svg=<model-name>.svg`), so there's nothing to edit. You may use `em init` for a
86
111
  starter `.em`, but usually you'll build it up from the discovery conversation instead. Fill
87
- template placeholders — never leave `{{...}}` in delivered files.
112
+ template placeholders — never leave `{{...}}` in delivered files. The `conformance/` directory
113
+ and `<model-name>-asis.em` only appear once the `conform` phase runs; add the pattern `*-asis.em`
114
+ to the repository's `.gitignore` the first time one is created (see `reference/conform.md`) —
115
+ it's scratch, never committed.
88
116
 
89
117
  ---
90
118
 
@@ -109,12 +137,20 @@ phase applies to field tables, below.
109
137
  **callers** at each step instead; they become the inbound write translations and read
110
138
  translations (see `reference/methodology.md`).
111
139
  3. **Inputs (step 3).** For each event, find the **command** that causes it (imperative name).
112
- Form `command → event` slices (State Change pattern).
140
+ Form `command → event` slices (State Change pattern). Every command needs a **trigger** in the
141
+ same breath: the screen the user issues it from (a `ui` in the slice), or — for the Automation
142
+ and Translation patterns — the reaction in the slice before it. A command nothing points at is
143
+ a write nobody can start. *"Who does this, and where are they when they do it?"*
113
144
  4. **Outputs (step 4).** Identify the **read models / views** consumers need and wire them with
114
- `from "Event"` (State View pattern). In a **headless/API** model there is no UI — a read model is
145
+ `from "Event"` (State View pattern), each with the screen (or reaction) that consumes it — a
146
+ read model nothing displays is information dropped on the floor. In a **headless/API** model there is no UI — a read model is
115
147
  consumed by an **external read translation** (the API query, triggers no command), and read
116
- models are **repeated** in each slice where they're read so the timeline flows (see
117
- `reference/methodology.md`).
148
+ models are **repeated** in each slice where they're read (use `view X again` after the first) so
149
+ the timeline flows (see `reference/methodology.md`).
150
+ **Close the loop before leaving this step:** every event from step 3 must be read by at least one
151
+ read model. Walk the event list and ask, for each one, *"who looks at this, and what do they do
152
+ with it?"* An event with no answer is either missing its read model or shouldn't be recorded —
153
+ both are worth raising with the user rather than leaving dangling.
118
154
 
119
155
  End of phase: write/refresh the `.em`, render it, update `.event-modeling.md` (steps done,
120
156
  decisions, open questions) and `README.md`'s slice index. Tell the user they can stop here and
@@ -156,12 +192,17 @@ Goal: a structurally complete, **validated** model with correct patterns and swi
156
192
  event).
157
193
  2. **Elaborate scenarios — first pass (step 6).** For each slice, capture the happy-path
158
194
  Given/When/Then and the obvious invariants as short notes. (The full spec is the `slice` phase.)
159
- 3. **Evaluate completeness (step 7).** Walk the model: every command emits an event, every view
160
- has a source, no orphan events, every UI is reachable, automations **and translations** split
161
- correctly (each reaction triggers a command — none wired straight to an event). Run
162
- `em validate` and resolve all errors and warnings — but check the reaction→command→event split
163
- by hand, since `em validate` does **not** flag a translation/automation that emits an event
164
- directly (see `reference/em-dsl.md`).
195
+ 3. **Evaluate completeness (step 7).** Walk the model: every slice is a **complete** pattern, not a
196
+ half-slice — **every command has something that triggers it**, every command emits an event,
197
+ **every event is read by a read model**, **every read model has a consumer**, every view has a
198
+ source, every UI is reachable, every connection is
199
+ one of the six legal pairs, and automations **and translations** split correctly (each reaction
200
+ triggers a command — none wired straight to an event). Run `em validate` and resolve all errors
201
+ and warnings — but check the reaction→command→event split by hand, since `em validate` does
202
+ **not** flag a translation/automation that emits an event directly (see `reference/em-dsl.md`).
203
+ For an unread event, don't just bolt on a view to silence the warning: ask the user who looks at
204
+ this fact and what they do with it. The honest answers are "here's the read model we missed" or
205
+ "nobody — so why are we recording it", and both improve the model.
165
206
 
166
207
  End of phase: render, update state, suggest `/event-modeling slice` to write implementation specs.
167
208
 
@@ -173,7 +214,9 @@ timeline). Check `README.md`'s slice index for what's already done.
173
214
  This is also where **branch / unhappy-path events** are discovered and added to the model — as a
174
215
  slice's alternate/error flows surface (a rejection, removal, cancellation, decline, expiry), add
175
216
  the corresponding event/slice to the `.em` and re-render. The happy-path spine from earlier
176
- phases is the starting point, not the finished event set.
217
+ phases is the starting point, not the finished event set. **Every new event needs its reader too**
218
+ — a rejection or cancellation that nothing projects will warn, and usually the missing piece is
219
+ real (someone has to see that the request was declined).
177
220
 
178
221
  For each slice:
179
222
  1. Hold a Socratic deep-dive to fill every section of `templates/slice.md`: intent, trigger/actor,
@@ -194,6 +237,29 @@ For each slice:
194
237
  `ready-to-implement`, later `implemented` once shipped, with `Implemented in:` filled in).
195
238
  5. Re-render and `em validate`.
196
239
 
240
+ ## Phase: `conform` — drift check against the codebase
241
+
242
+ Goal: check a **ratified** model (and its slice docs) against the codebase that implements it,
243
+ and report where they've drifted — advisory only, never a gate, never an unprompted edit.
244
+
245
+ **Read `reference/conform.md` before doing any conform work** — it carries the full flow
246
+ (scope, evidence-first verification, `em diff --json`, classification, report) and the
247
+ stance guardrails (evidence-first, uncertainty is never drift, propose-don't-edit). It reuses
248
+ `extract`'s sourcing/mode rules for reading the target codebase rather than duplicating them.
249
+
250
+ In short: for each in-scope slice, gather code evidence *before* comparing to the model or doc;
251
+ write the as-is picture into a scratch model (`<model-name>-asis.em`, reusing the canonical
252
+ model's names wherever the code matches them); run `em diff <model-name>.em
253
+ <model-name>-asis.em --json` and let `em` decide the structural deltas; classify every finding
254
+ (real drift / model gap / internal inconsistency / uncertainty) with cited evidence; write
255
+ `conformance/<date>-report.md` with proposed `issue "conformance: …"` red notes; apply only the
256
+ proposals the user ratifies, then re-render and validate; update the state file's
257
+ `Last conformance:` marker.
258
+
259
+ End of phase: state file's `Last conformance:` marker updated, Decisions log entry if any
260
+ proposals were applied. Conform doesn't chain to another phase — it's a recurring loop, run
261
+ again whenever the codebase has moved.
262
+
197
263
  ## Phase: `watch` — live team view
198
264
 
199
265
  **Recommended (instant push, no polling):** start the watcher with `--serve` in the background:
@@ -210,8 +276,19 @@ editing of `live.html` is needed — the `?svg=` query picks the model.
210
276
  ## Phase: `validate`
211
277
 
212
278
  Run `em validate <model-name>.em` and walk through each diagnostic with the user, explaining the
213
- rule (see `reference/em-dsl.md`) and proposing the fix (e.g. split an automation's command into
214
- the next slice, add a missing `from`, give a command its event). Apply fixes on agreement.
279
+ rule (see `reference/em-dsl.md`) and proposing the fix. Apply fixes on agreement. Common ones:
280
+
281
+ | Diagnostic | Fix |
282
+ |---|---|
283
+ | automation/translation shares a slice with a command | Split the command into the next slice |
284
+ | **command has nothing that triggers it** | Add the `ui` it's issued from — or, if the system issues it, the reaction in the slice before it. Ask *"who does this, and where are they when they do it?"* |
285
+ | **read model has no consumer** | Add the screen that displays it, or the reaction that watches it. If it's a repeat added only to shorten an arrow and nothing looks at it there, drop the instance |
286
+ | read model has no source | Add the missing `from "Event"` |
287
+ | command produces no event | Give the command its event, or drop the command |
288
+ | **event is not read by any read model** | Add the read slice that projects it — but ask *who looks at this and what do they do with it* first. "Nobody" is a real answer, and then the question is why it's recorded at all |
289
+ | **illegal `arrow` kind pair** | Don't reroute it — the model is missing an element. `command → view` needs the event between them; `view → command` needs a reaction. Between two instances of one read model, just delete the arrow: the repeat needs no connection |
290
+ | `view X again` with no earlier declaration | Declare the view plainly the first time it appears |
291
+ | event feeding an earlier view instance | Add a `view X again` where the event lands and move the source there |
215
292
 
216
293
  For anything genuinely unresolved rather than a rule violation, prefer `issue "text"` on the
217
294
  relevant element over a `# TBD` comment — it shows up as a red marker on the rendered diagram and
@@ -0,0 +1,230 @@
1
+ # Conform Phase — Drift Detection Against the Codebase
2
+
3
+ Read this **before doing any conform work**. It governs the `conform` phase: checking a
4
+ **ratified** model (and its slice docs) against the codebase that is supposed to implement it,
5
+ and reporting where they've drifted apart. Conform reuses `extract`'s sourcing/mode rules
6
+ (`reference/extract.md`) for reading the target codebase rather than duplicating them — the
7
+ difference is what you do with what you find: extract *builds* a model from the code; conform
8
+ *compares* code evidence against a model that already exists.
9
+
10
+ ## Stance — advisory-only, evidence-first
11
+
12
+ - **Advisory-only.** Conform never gates anything and never edits the canonical model or a
13
+ slice doc on its own initiative. It proposes; the user ratifies. There is no such thing as
14
+ an auto-applied conformance fix.
15
+ - **Evidence-first, not model-first.** For every slice you check, gather code evidence —
16
+ handler/event/projection/endpoint sites, invariant-enforcement sites, scenario-test sites —
17
+ **before** you compare anything to the model or doc. Read the doc and model first to know
18
+ *what to look for*, but write down what the code actually shows before you judge whether it
19
+ matches. Anchoring bias is the failure mode this rule exists to block: "the model says X" is
20
+ never allowed to substitute for "the code shows X at `path/to/File.kt:42`".
21
+ - **Uncertainty is never drift.** If you can't find evidence either way — the code path is
22
+ unclear, the test is ambiguous, you ran out of budget — that's an **uncertainty**, reported
23
+ in its own section, never folded into a drift or gap finding. A false positive here is worse
24
+ than a missed one; it's what burns the loop's credibility.
25
+ - **Anchored, not blind.** You read the canonical model's vocabulary before walking the code,
26
+ and you reuse its slice/element names wherever the code genuinely matches them. This trades
27
+ some independence for precision: naming a scratch model's elements from scratch (blind
28
+ extraction) would report wholesale slice add/remove noise from naming alone the moment your
29
+ synonym for an existing element doesn't match the model's — the exact false-drift failure
30
+ mode this phase exists to avoid. The guardrail against anchoring bias is the evidence-first
31
+ rule above, not independence from the model's vocabulary.
32
+
33
+ ## Preconditions
34
+
35
+ 1. A canonical `.em` model with a state file (`.event-modeling.md`), reachable the same way the
36
+ SKILL.md preconditions locate it.
37
+ 2. The model should be ratified for the slices you're about to check — `slice` docs at
38
+ `implemented` (or the status the user says corresponds to shipped code) for whichever slices
39
+ are in scope. Checking a `draft` or `ready-to-implement` slice against code is a wasted walk;
40
+ tell the user and skip it rather than reporting "drift" against a doc nobody built to yet.
41
+ 3. The codebase to check: the state file's **Existing system refs** (populated during
42
+ `extract`, if this model came from one) or ask the user for the repo path if not set. Record
43
+ it if missing. In a headless/scheduled run with no user to ask, default to the repository
44
+ the model lives in (if it contains the implementation) and state that assumption in the
45
+ report's run metadata.
46
+ 4. Confirm `em --version` works (same check as every other phase) — you'll run `em diff`. The
47
+ phase needs `em diff --json`; if the flag errors as unknown, the installed `em` predates it
48
+ — say so and stop rather than falling back to eyeballing the two models.
49
+
50
+ ## The three conformance surfaces
51
+
52
+ The model isn't just the `.em` — slice docs carry the detailed spec (field tables with rules,
53
+ named invariants, Given/When/Then scenarios). Conform checks all three surfaces that can drift:
54
+
55
+ | Surface | What drifts | Who checks | How |
56
+ |---|---|---|---|
57
+ | **Structural** — `.em` ↔ code | slices/elements/fields/flow | `em` (deterministic) | `em diff --json` on canonical vs. an as-is scratch model |
58
+ | **Spec** — slice doc ↔ code | field rules not enforced, named invariants (`INV-*`) without enforcement/test sites, GWT scenarios without test sites | you (judgment) | per-slice evidence walk: each checkable claim in the doc mapped to a code/test site by name (`INV-CHK-4` → its enforcement site + its test) |
59
+ | **Internal** — slice doc ↔ `.em` | the doc's header metadata (Pattern, Status) or its Command/Event/Read Model sections and field tables disagreeing with the model's own elements/fields | you (judgment, v1) | cross-check the doc's structured sections against the slice's model elements |
60
+
61
+ The internal surface is deterministically checkable in principle (structured doc sections +
62
+ `{ fields }` blocks) — promoting it to an `em validate` rule is a natural follow-up; file it as
63
+ its own issue rather than growing this phase's scope. For now it's agent judgment, same as spec.
64
+
65
+ **Internal inconsistency is its own class, not drift.** When a doc and the `.em` disagree with
66
+ each other, neither one is "the code" — you don't know which is stale. Report it as internal
67
+ inconsistency and let the user decide which side is right; don't guess by treating one as
68
+ authoritative.
69
+
70
+ ## Flow
71
+
72
+ ### 1. Scope
73
+
74
+ Default: **diff-scoped.** Read the state file's `Last conformance:` marker (date + target-repo
75
+ revision). If it's set, run `git diff --name-only <recorded-revision>..HEAD` from the target
76
+ repo's root to list the changed paths (`git log` alone prints commit subjects, not paths), then
77
+ map those paths to slices (via each slice doc's known code locations, `Implemented in:` links,
78
+ or a quick Grep if those are stale) — that's your in-scope set. If the marker reads `never`
79
+ (first run) or the user passes `--full`, scope is every `implemented` slice.
80
+
81
+ Cost containment here is cadence, not a trigger condition — diff-scoped-by-default is what
82
+ keeps a recurring conform run cheap; don't invent a smarter trigger.
83
+
84
+ ### 2. Anchored, evidence-first verification (per in-scope slice)
85
+
86
+ For each slice in scope:
87
+
88
+ 1. Read the slice's **doc** (`slices/<slice-name>.md`) alongside its elements in the `.em`.
89
+ This tells you the vocabulary to anchor on and the claims to check — not the answer.
90
+ 2. Walk the code and **record evidence before comparing**:
91
+ - **Structural:** the handler, event class(es), projection(s), endpoint(s) that implement
92
+ this slice.
93
+ - **Spec:** for every named invariant (`INV-*`) in the doc, its enforcement site and its
94
+ test site (by name/path, both or neither — a half-found invariant is an uncertainty, not
95
+ a pass). Scenarios are budgeted: map Given/When/Then scenarios to individual test sites
96
+ when the in-scope slice count is small (roughly ≤5) or the user asks for that depth;
97
+ otherwise record slice-level scenario-test presence and say so in the report — per-scenario
98
+ mapping across a full model roughly doubles the walk. Where the doc doesn't name
99
+ invariants/scenarios individually, fall back to slice-level test presence (some vs. none)
100
+ rather than inventing IDs the doc never gave you.
101
+ - **Legitimate non-materialized views:** an automation's watched view may have no
102
+ materialized projection in code — a direct event handler with criteria-sourced state is
103
+ the same pattern expressed in the code idiom (extract.md's R5 reaction rules). That is
104
+ NOT structural drift; note the idiom in the evidence log instead of reporting the view
105
+ as missing.
106
+ 3. Write what you found into the scratch model (`<model-name>-asis.em`, see below). **Seed the
107
+ scratch model as a copy of the canonical `.em` and replace only the in-scope slices** with
108
+ the as-is picture, leaving out-of-scope slices byte-identical. This is not an optimization:
109
+ `em diff` matches slices by name, so a scratch model containing *only* the in-scope slices
110
+ reports every other canonical slice as `slice-removed` plus one `element-removed` per
111
+ element — a phantom-removal flood on every diff-scoped run, which is the exact false drift
112
+ this phase exists to avoid. It also keeps the scratch model compilable (see step 3). Within
113
+ the slices you do rewrite, **reuse the canonical model's slice and element names wherever
114
+ the code genuinely matches them** — the canonical model is the vocabulary anchor. Give a
115
+ new name only to code behavior the model doesn't cover at all. Record the mapping decision
116
+ for every element you name this way (e.g. `CartItemAdded` code class ↔ `"Cart Item Added"`
117
+ model event) so it's auditable, not a silent judgment call. **Match the canonical model's field granularity:** declare `{ fields }`
118
+ in the scratch model only where the canonical model declares them (if the canonical model
119
+ puts fields on commands but not events, so does the scratch model — writing code-derived
120
+ event fields there would flood the diff with `field-added` noise). Schema claims at finer
121
+ granularity than the `.em` declares — e.g. event fields documented only in a slice doc's
122
+ table — are checked on the **spec surface** against the doc, not smuggled into the
123
+ structural diff.
124
+ 4. Cross-check the doc's header metadata and Command/Event/Read Model sections against the
125
+ slice's actual `.em` elements and fields for the internal surface — this doesn't need code
126
+ evidence, just doc-vs-`.em` reading. Narrative sections (Intent, Open Questions, notes)
127
+ count too when the contradiction is unambiguous — a stale sentence that flatly contradicts
128
+ the `.em` is an internal inconsistency; a vague or interpretable one is not a finding.
129
+
130
+ Do this for every in-scope slice before moving on to diffing. The evidence you record here is
131
+ what every finding in the report will cite — file paths, not vibes.
132
+
133
+ ### 3. Deterministic diff
134
+
135
+ First `em validate <model-name>-asis.em`. `em diff` refuses when either side has validation
136
+ *errors* (warnings are fine), so a scratch model that doesn't compile stops the run cold. The
137
+ usual cause is a slice that depends on one you rewrote incompletely — a `view X again` whose
138
+ first declaration you dropped, an automation `from` a read model that no longer exists, or an
139
+ `arrow` you wrote joining kinds the patterns don't connect. (An unread event is only a *warning*,
140
+ so it never blocks the diff — and in an as-is scratch model it's expected.)
141
+ Seeding from the canonical model (step 2) prevents this; if it still fails, **fix the scratch
142
+ model and report the failure as a tooling problem in the run metadata — never as drift.** A
143
+ model you couldn't compile is an uncertainty about your own scratch file, not a finding
144
+ against the codebase.
145
+
146
+ Then run `em diff <model-name>.em <model-name>-asis.em --json`. `em`, not you, decides what
147
+ structurally differs — don't re-derive by eye what the tool already computed. Parse the JSON
148
+ (`docs/cli.md`'s diff section documents the envelope: `diffSchemaVersion`, `counts`, `changes`,
149
+ `removals`, every optional `ChangeEntry` field explicit-`null` when unused) and use it as the
150
+ structural-surface findings directly.
151
+
152
+ ### 4. Classify
153
+
154
+ Every finding — structural deltas from step 3, plus spec- and internal-surface findings from
155
+ step 2 — sorts into exactly one class:
156
+
157
+ - **Real drift** — code evidence contradicts the model or doc.
158
+ - **Model gap** — code evidence shows behavior neither the model nor the doc covers.
159
+ - **Internal inconsistency** — the doc and the `.em` disagree with each other (surface 3 only).
160
+ - **Extraction uncertainty** — no evidence either way. Never reported as drift or a gap.
161
+
162
+ Every classification must cite the evidence recorded in step 2 (or the `em diff` entry, for
163
+ structural findings) — a finding with no citation isn't ready to report.
164
+
165
+ ### 5. Report + proposals
166
+
167
+ Write `conformance/<YYYY-MM-DD>-report.md` in the model directory, from
168
+ `templates/conformance-report.md`. For a real-drift or model-gap finding, propose a
169
+ ready-to-apply red note — `issue "conformance: <text>"` on the right element, written out in
170
+ the report exactly as it should be pasted into the `.em` — when a model-side marker is the
171
+ right fix; when the fix is purely doc wording (or the finding is an internal inconsistency
172
+ with no clear side to flag), omit the red note and say why instead. **You never edit the
173
+ canonical model or a slice doc unprompted** — walk the report with the user, apply only the
174
+ proposals they approve, then re-render and `em validate`.
175
+
176
+ End of run: update the state file's `Last conformance:` marker, exact format:
177
+
178
+ ```
179
+ - **Last conformance:** YYYY-MM-DD @ <target-repo revision> — report: conformance/YYYY-MM-DD-report.md
180
+ ```
181
+
182
+ The revision is the one you just diffed against, so the next run's scope starts from here —
183
+ step 1 parses this line, so keep the format exact. If any proposals were ratified and applied,
184
+ log a Decisions entry noting what changed and why.
185
+
186
+ ## Conventions
187
+
188
+ - **Scratch model:** `<model-name>-asis.em`, written **next to** the canonical model (same
189
+ directory as `.event-modeling.md`). Always seeded as a copy of the canonical model with only
190
+ the in-scope slices rewritten (step 2), never built up from nothing. It's regenerated every
191
+ conform run — add the pattern `*-asis.em` to the repository's `.gitignore` (create the entry
192
+ if there isn't one) so it never gets committed.
193
+ - **Report location:** `conformance/<YYYY-MM-DD>-report.md` in the model directory. One file
194
+ per run; don't overwrite a prior date's report.
195
+ - **Red note wording:** `issue "conformance: <what's wrong, plainly>"` — plain enough that
196
+ whoever ratifies it later understands the claim without re-reading the report.
197
+ - **Naming/matching rules:** same past-tense-event / imperative-command / thing-shown-view
198
+ naming discipline as the rest of the skill (`methodology.md`'s Socratic stance) — you're
199
+ matching code to existing names here, not inventing new ones except for genuinely uncovered
200
+ behavior.
201
+
202
+ ## Completion & handoff
203
+
204
+ Conform is complete when: every in-scope slice has been walked (step 2), the scratch model has
205
+ been diffed (step 3), every finding is classified with cited evidence (step 4), the report is
206
+ written (step 5), the state file's `Last conformance:` marker is updated, and the user has seen
207
+ the report and said which proposals (if any) to apply. Applied proposals get re-rendered and
208
+ re-validated before you call the run done.
209
+
210
+ Conform doesn't chain to another phase automatically — it's a recurring loop, not a step in
211
+ building the model. Suggest running it again next time the target codebase has moved, at
212
+ whatever cadence the user wants (see `docs/ci.md` for a scheduled-run recipe).
213
+
214
+ ## Anti-patterns
215
+
216
+ - **Blind extraction** — deriving the as-is scratch model with no sight of the canonical
217
+ model's vocabulary. Loses the anchor and produces naming-driven false drift.
218
+ - **A scratch model holding only the in-scope slices** — `em diff` reads every omitted
219
+ canonical slice as removed, so a diff-scoped run drowns in phantom removals. Seed from the
220
+ canonical model and rewrite only the slices you walked.
221
+ - **Reporting uncertainty as drift** — "I couldn't find the enforcement site" is an
222
+ uncertainty, never a finding against the model.
223
+ - **Editing the model or a slice doc without ratification** — conform proposes; the user
224
+ applies. No exceptions, even for an "obviously correct" fix.
225
+ - **Treating scenario/test absence as a build failure** — a missing test site is a spec-surface
226
+ finding to report and classify, not a reason to stop the run or block anything. Conform is
227
+ advisory; there's no gate here.
228
+ - **Re-deriving what `em diff` already decided** — once you have the JSON, use it; don't
229
+ eyeball the two `.em` files yourself looking for structural differences the tool already
230
+ computed for you.
@@ -64,9 +64,10 @@ arrow From Element -> To Element # explicit cross-slice edge (overrides infer
64
64
  - **`again`** (views only): `view <Name> again [from "Event", …]` declares a later instance of an
65
65
  already-declared read model — the forward-only device for a view that evolves as later events
66
66
  land. Instances are ONE logical view: the first declaration owns the `note` binding; each
67
- instance's `from` lists only the NEW events landing at that point (not cumulative); instances are
68
- linked left-to-right with a continuity arrow; a reaction (`from "View"`) reads the nearest
69
- instance at-or-before its own slice. `again` with no earlier declaration is a validation error.
67
+ instance's `from` lists only the NEW events landing at that point (not cumulative); a reaction
68
+ (`from "View"`) reads the nearest instance at-or-before its own slice. Instances are NEVER
69
+ connected to one another — continuity is implied by the shared name, and the events reaching each
70
+ instance are what show the view changing. `again` with no earlier declaration is a validation error.
70
71
  Use `again` (not a plain repeated `view` name) whenever the view is referenced by a
71
72
  `from`/`arrow` — plain repeats are only warning-free while unreferenced.
72
73
  - **`note "path.md"`** on ANY element links a markdown doc. Relative to the `.em` file. Renders as
@@ -106,7 +107,7 @@ slice "Open Orders" {
106
107
  ui Order List @Customer
107
108
  }
108
109
 
109
- # 3. Automation: split across TWO slices
110
+ # 3. Automation: split across TWO slices (plus the read slice that consumes the event)
110
111
  slice "Orders To Fulfill" {
111
112
  view Orders To Fulfill from "Order Placed"
112
113
  processor Fulfillment Service
@@ -115,6 +116,10 @@ slice "Ship Order" { # the triggered command goes in the NEXT slice
115
116
  command Ship Order
116
117
  event Order Shipped @Shipping
117
118
  }
119
+ slice "Open Orders — shipped" {
120
+ view Open Orders again from "Order Shipped" # every event needs a reader
121
+ ui Order List @Customer # ...and every read model needs a consumer
122
+ }
118
123
 
119
124
  # 4a. Translation (external trigger): external input -> translation -> command -> event
120
125
  slice "Carrier Webhook" {
@@ -124,8 +129,17 @@ slice "Confirm Delivery" { # the triggered command goes in the NEXT s
124
129
  command Confirm Delivery
125
130
  event Delivery Confirmed @Shipping
126
131
  }
132
+ slice "Open Orders — delivered" {
133
+ view Open Orders again from "Delivery Confirmed"
134
+ ui Order List @Customer
135
+ }
127
136
 
128
137
  # 4b. Translation (internal trigger): read model -> translation -> command -> event
138
+ slice "Accept Quote" {
139
+ ui Quote Screen @Customer # every command needs a trigger: a ui, or a reaction before it
140
+ command Accept Quote
141
+ event Quote Accepted @Quote
142
+ }
129
143
  slice "Quotes To Sync" {
130
144
  view Accepted Quotes from "Quote Accepted"
131
145
  translation CRM Sync # reacts to our own state via the read model in this slice
@@ -134,40 +148,66 @@ slice "Record Sync" {
134
148
  command Record Crm Sync
135
149
  event Quote Synced @Quote
136
150
  }
151
+ slice "Accepted Quotes — synced" {
152
+ view Accepted Quotes again from "Quote Synced"
153
+ ui Sync Status @Customer
154
+ }
137
155
  ```
138
156
 
139
157
  A `translation` (like a `processor`) is a **reaction**: it triggers a command and never carries an
140
158
  `event` in its own slice. Same two-slice split as the Automation pattern above.
141
159
 
160
+ Note the read slice closing each pattern: **every event must be read by some read model**
161
+ (warning 4 below). A command slice is not finished until the slice that projects its event
162
+ exists. Reactions don't count — they read *views*, not events.
163
+
142
164
  ### Headless / API systems (no UI) & repeated read models
143
165
 
144
166
  When the system is headless (no UI — clients call an API), drop `ui`/`persona` entirely:
145
167
 
168
+ A slice body is always multi-line — there is **no one-line `slice "X" { … }` form**, and writing
169
+ one is a parse error (`'slice' is not valid inside a slice`).
170
+
146
171
  ```em
147
172
  # Write: external translation -> command -> event (name the inbound adapter for the caller/role)
148
- slice "Create Quote (request)" { translation SalesRep BU Create Quote }
149
- slice "Create Quote" { command Create Quote event QuoteCreated @Quote }
173
+ slice "Create Quote (request)" {
174
+ translation SalesRep BU Create Quote
175
+ }
176
+ slice "Create Quote" {
177
+ command Create Quote
178
+ event QuoteCreated @Quote
179
+ }
150
180
 
151
- # Read: event(s) -> read model -> READ translation (the external caller's API query, replaces UI)
152
- slice "Read Quote — created" { view Quote from "QuoteCreated" translation SalesRep BU Read Quote }
181
+ # Read: event(s) -> read model -> READ translation (the external caller's API query, replaces UI).
182
+ # This slice is also what makes QuoteCreated a *read* event — without it the model warns.
183
+ slice "Read Quote — created" {
184
+ view Quote from "QuoteCreated"
185
+ translation SalesRep BU Read Quote
186
+ }
153
187
  ```
154
188
 
155
189
  - A **read translation triggers no command** (it returns data outbound) — it is the headless analog
156
190
  of `view → ui`, *not* a reaction. Only *reaction* translations/processors trigger commands. A read
157
191
  slice has no `command` and no `event`, so `em validate` stays quiet about it.
158
- - **Repeat the read model** in every slice where it's read so the timeline flows left-to-right.
159
- Recurring `view`/`translation` names **render cleanly and stay warning-free** as long as nothing
160
- references the repeated name via `from`/`arrow` (the duplicate-name warning fires only for a
161
- *referenced* duplicate, resolving to the first). **Wire each event to a read model exactly once:**
162
- a repeated instance's `from` lists only the **new** events since the previous instance (not
163
- cumulative), or the event draws a duplicate arrow to the same read model at every repeat. (An
164
- event may still feed several *different* read models, once each.) When a repeated view **must be
165
- referenced** — e.g. a reaction reads it via `from` — declare its later instances with
166
- `view X again` instead (see Clauses); instances resolve properly and draw continuity arrows.
192
+ - **Repeat the read model** in every slice where it's read so the timeline flows left-to-right, and
193
+ **prefer `view X again`** for every instance after the first (see Clauses). `again` instances are
194
+ exempt from the duplicate-name warning even when referenced, and each reference resolves to the
195
+ right instance — a plain repeat only stays warning-free while nothing references it by name, and
196
+ resolves to the *first* declaration when something does. **Wire each event to a read model exactly
197
+ once:** a repeated instance's `from` lists only the **new** events since the previous instance
198
+ (not cumulative), or the event draws a duplicate arrow to the same read model at every repeat. (An
199
+ event may still feed several *different* read models, once each.)
200
+ - **Instances are never joined to one another.** No arrow between two instances of one read model,
201
+ ever — an explicit one is a validation error. The repeat is a timeline device: continuity is
202
+ implied by the shared name, and the events arriving at each instance are what show it changing.
167
203
  - **Keep arrows span-1: put each repeat right after its feeding event.** Place a read-model instance
168
- immediately after the event that updates it, sourcing only that single adjacent event. A read
169
- model far from its source events draws long arrows that cross the read-model row and read as
170
- read→read (an Event Modeling violation) — repeat the read model next to each event instead.
204
+ immediately after the event that updates it, sourcing only that single adjacent event. The
205
+ renderer routes a long arrow *around* intervening boxes rather than through them, so it no longer
206
+ reads as a forbidden read→read link — but distance is still a real problem: the arrowhead lands
207
+ columns away from the event that produced it, so **the write slice reads as dangling**, as if
208
+ nothing consumed its event. You have to trace a line across the diagram to see the connection.
209
+ Keep the read model adjacent to its event, and keep a sub-flow that detours into another context
210
+ together rather than parking it at the end of the model.
171
211
 
172
212
  ### Slice-ordering gotchas (edge inference)
173
213
 
@@ -197,20 +237,33 @@ slice "Read Quote — created" { view Quote from "QuoteCreated" translation S
197
237
  there), a reaction reading a view before any instance of it exists, or an explicit backward
198
238
  `arrow`.
199
239
  6. **`again` without an earlier declaration** — declare the view plainly the first time it appears.
240
+ 7. **Illegal connection** — an `arrow` joining kinds the patterns don't connect. Only
241
+ `ui -> command`, `command -> event`, `event -> view`, `view -> ui`, `view -> reaction`, and
242
+ `reaction -> command` are allowed. A command straight to a view is the CQRS violation (an event
243
+ has to sit between them); a view straight to a command needs a reaction between them; and two
244
+ instances of one view are never connected at all. Inferred edges are always legal, so this only
245
+ ever fires on a hand-written `arrow`.
200
246
 
201
247
  **Warnings (should fix):**
202
248
  1. **Automation/translation shares slice with its command** — both `automation`/`processor` and
203
249
  `translation` are reactions; put the triggered command in the *next* slice.
204
- 2. **Command without event** — every command should record at least one event.
205
- 3. **Read model without source** — add `from "Event"` or place the view in a slice with an event.
206
- 4. **Duplicate name** — the same name defined N times; references resolve to the first. Rename.
207
- 5. **Open issue** — an element carries `issue "text"`; resolve the question, then remove the
250
+ 2. **Command with no trigger** — nothing issues it. A command needs a `ui` in its slice, or a
251
+ reaction (automation/processor/saga/translation) in the slice *before* it. The input-side mirror
252
+ of (4): a command nothing points at is a write nobody can start.
253
+ 3. **Command without event** — every command should record at least one event.
254
+ 4. **Event nobody reads** — the mirror of (3): recording an event no read model projects is a
255
+ write with no reader. Follow every write slice with the read slice that consumes its event.
256
+ Counts as read when a `view` names it in `from` (any `again` instance will do), when a
257
+ fieldless-`from` view sits in its slice, or via an explicit `event -> view` arrow.
258
+ 5. **Read model without source** — add `from "Event"` or place the view in a slice with an event.
259
+ 6. **Duplicate name** — the same name defined N times; references resolve to the first. Rename.
260
+ 7. **Open issue** — an element carries `issue "text"`; resolve the question, then remove the
208
261
  clause. `em validate --list-issues` prints just these; `--fail-on-issues` (opt-in) makes CI
209
262
  fail while any remain open.
210
- 6. **View field with no source** — a `view` field whose name matches no field on any instance
263
+ 8. **View field with no source** — a `view` field whose name matches no field on any instance
211
264
  of its source events. Only checked once BOTH the view and at least one source event declare
212
265
  `{ fields }`.
213
- 7. **Event field not from a command** — an `event` field whose name matches no field on any
266
+ 9. **Event field not from a command** — an `event` field whose name matches no field on any
214
267
  command in the same slice. Only checked once BOTH the event and at least one same-slice
215
268
  command declare `{ fields }`. This is the payoff of the fields feature for slicing rigor:
216
269
  once fields are written down, `em validate` checks that data flows forward consistently.
@@ -218,6 +271,20 @@ slice "Read Quote — created" { view Quote from "QuoteCreated" translation S
218
271
  **Design rules that keep models valid:**
219
272
  - One element per band per slice (multiple personas/contexts are fine — they're different rows).
220
273
  - Every `command` slice includes its `event`. Every `view` has a `from` source.
274
+ - **Every `command` has a trigger.** A `ui` in its slice, or a reaction in the slice *before* it.
275
+ A command nothing points at is a write nobody can start.
276
+ - **Every `event` has a reader.** A command slice isn't finished until the read slice that projects
277
+ its event exists. Reactions don't count — they read views, not events. Pair each write slice with
278
+ its read slice as you go rather than sweeping up dangling events at the end.
279
+ - **Every `view` has a consumer.** A `ui` in its slice, a reaction watching it, or (headless) a read
280
+ translation. Every *instance* of a repeat, not just the last — a bare `view X again` slice is a
281
+ half-slice, not a State View.
282
+ - **Only six connections are legal**, and only these are ever inferred:
283
+ `ui → command`, `command → event`, `event → view`, `view → ui`, `view → reaction`,
284
+ `reaction → command`. Anything else in an explicit `arrow` is an error — above all
285
+ `command → view` (the CQRS violation: an event has to sit between them) and `view → command`
286
+ (a reaction has to sit between them). If you reach for an arrow the patterns don't allow, the
287
+ model is missing an element, not an arrow.
221
288
  - Automations **and translations** are always two slices: the reaction (plus its read model, if
222
289
  internally triggered) in one slice, the triggered `command` + its `event` in the next. A
223
290
  translation/automation slice **never contains an `event`** — reactions trigger commands, not