@milehimikey/em 1.6.0 → 1.7.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 +60 -12
- package/.claude/skills/event-modeling/reference/conform.md +30 -8
- package/.claude/skills/event-modeling/reference/em-dsl.md +145 -23
- package/.claude/skills/event-modeling/reference/extract.md +2 -1
- package/.claude/skills/event-modeling/reference/implement.md +172 -0
- package/.claude/skills/event-modeling/reference/methodology.md +5 -0
- package/.claude/skills/event-modeling/templates/conformance-report.md +15 -9
- package/.claude/skills/event-modeling/templates/model-readme.md +3 -3
- package/.claude/skills/event-modeling/templates/slice.md +70 -4
- package/.claude/skills/event-modeling/templates/state.md +6 -5
- package/README.md +1 -0
- package/dist/catalog/build.d.ts +5 -0
- package/dist/catalog/build.js +1 -3
- package/dist/catalog/build.js.map +1 -1
- package/dist/catalog/docJoin.d.ts +39 -0
- package/dist/catalog/docJoin.js +86 -0
- package/dist/catalog/docJoin.js.map +1 -0
- package/dist/catalog/driftSignal.d.ts +16 -0
- package/dist/catalog/driftSignal.js +24 -0
- package/dist/catalog/driftSignal.js.map +1 -0
- package/dist/catalog/frontmatterCoherenceValidate.d.ts +8 -0
- package/dist/catalog/frontmatterCoherenceValidate.js +52 -0
- package/dist/catalog/frontmatterCoherenceValidate.js.map +1 -0
- package/dist/catalog/lineageValidate.d.ts +9 -0
- package/dist/catalog/lineageValidate.js +161 -0
- package/dist/catalog/lineageValidate.js.map +1 -0
- package/dist/catalog/readSliceDoc.d.ts +2 -0
- package/dist/catalog/readSliceDoc.js +21 -0
- package/dist/catalog/readSliceDoc.js.map +1 -0
- package/dist/catalog/sliceDoc.d.ts +94 -3
- package/dist/catalog/sliceDoc.js +157 -7
- package/dist/catalog/sliceDoc.js.map +1 -1
- package/dist/catalog/sliceReadyValidate.d.ts +11 -0
- package/dist/catalog/sliceReadyValidate.js +85 -0
- package/dist/catalog/sliceReadyValidate.js.map +1 -0
- package/dist/cli/diff-inputs.d.ts +20 -0
- package/dist/cli/diff-inputs.js +75 -18
- package/dist/cli/diff-inputs.js.map +1 -1
- package/dist/cli/ledgerCheck.d.ts +38 -0
- package/dist/cli/ledgerCheck.js +116 -0
- package/dist/cli/ledgerCheck.js.map +1 -0
- package/dist/cli/skillCheck.d.ts +18 -0
- package/dist/cli/skillCheck.js +63 -0
- package/dist/cli/skillCheck.js.map +1 -0
- package/dist/cli/skillSync.d.ts +22 -0
- package/dist/cli/skillSync.js +69 -0
- package/dist/cli/skillSync.js.map +1 -0
- package/dist/cli/skillVersionCheck.d.ts +32 -0
- package/dist/cli/skillVersionCheck.js +145 -0
- package/dist/cli/skillVersionCheck.js.map +1 -0
- package/dist/cli.d.ts +3 -1
- package/dist/cli.js +199 -32
- package/dist/cli.js.map +1 -1
- package/dist/emit/diffJson.d.ts +1 -1
- package/dist/emit/diffJson.js +18 -14
- package/dist/emit/diffJson.js.map +1 -1
- package/dist/emit/json.d.ts +10 -25
- package/dist/emit/json.js +70 -103
- package/dist/emit/json.js.map +1 -1
- package/dist/emit/ledgerJson.d.ts +5 -0
- package/dist/emit/ledgerJson.js +25 -0
- package/dist/emit/ledgerJson.js.map +1 -0
- package/dist/emit/skillCheckJson.d.ts +5 -0
- package/dist/emit/skillCheckJson.js +32 -0
- package/dist/emit/skillCheckJson.js.map +1 -0
- package/dist/model/diff.d.ts +44 -1
- package/dist/model/diff.js +51 -9
- package/dist/model/diff.js.map +1 -1
- package/dist/model/model.d.ts +1 -1
- package/dist/model/refs.d.ts +23 -0
- package/dist/model/refs.js +75 -0
- package/dist/model/refs.js.map +1 -0
- package/dist/model/rules.d.ts +252 -0
- package/dist/model/rules.js +253 -0
- package/dist/model/rules.js.map +1 -0
- package/dist/model/validate.d.ts +23 -1
- package/dist/model/validate.js +184 -49
- package/dist/model/validate.js.map +1 -1
- package/dist/parser/ast.d.ts +3 -2
- package/dist/parser/parser.js +36 -14
- package/dist/parser/parser.js.map +1 -1
- package/dist/pipeline.d.ts +9 -0
- package/dist/pipeline.js +4 -2
- package/dist/pipeline.js.map +1 -1
- package/dist/render/sliceStatus.js +1 -1
- package/dist/render/sliceStatus.js.map +1 -1
- package/dist/util/isMainModule.d.ts +14 -0
- package/dist/util/isMainModule.js +33 -0
- package/dist/util/isMainModule.js.map +1 -0
- package/dist/util/walkDir.d.ts +4 -0
- package/dist/util/walkDir.js +28 -0
- package/dist/util/walkDir.js.map +1 -0
- package/package.json +5 -2
|
@@ -1,28 +1,32 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: event-modeling
|
|
3
|
+
em-version: 1.7.0
|
|
3
4
|
description: >-
|
|
4
5
|
Guide a user and their team through building rigorous Event Models with the `em` CLI, using
|
|
5
6
|
the 7 steps of Event Modeling and the 4 patterns (State Change, State View, Automation,
|
|
6
|
-
Translation), and produce implementation-ready slice
|
|
7
|
+
Translation), and produce implementation-ready slice docs. Use when the user wants
|
|
7
8
|
to event-model a business process or system, do event modeling / event storming, design slices,
|
|
8
9
|
build or edit an `.em` model, run the em tool, extract / reverse-engineer a current-state
|
|
9
10
|
event model from an existing system or codebase (event-driven or legacy/procedural), or check
|
|
10
11
|
a ratified model for drift against the codebase that implements it. Works in resumable phases
|
|
11
12
|
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), `
|
|
13
|
-
|
|
14
|
-
|
|
13
|
+
existing system), `model` (steps 5-7), `slice` (deep slice specs), `implement` (build one
|
|
14
|
+
ratified slice into code, following the bundled agent guide), `conform` (drift check
|
|
15
|
+
against the codebase), plus `watch`, `review` (stakeholder walkthrough), and `validate`.
|
|
16
|
+
With no argument it resumes from the saved state file.
|
|
15
17
|
---
|
|
16
18
|
|
|
17
19
|
# Event Modeling with `em`
|
|
18
20
|
|
|
19
21
|
You are facilitating an Event Modeling session. Your job is to **extract an accurate model from
|
|
20
22
|
the user through Socratic questioning** — never to invent the domain. You drive the `em` CLI to
|
|
21
|
-
render the model live, and you produce implementation-ready slice
|
|
23
|
+
render the model live, and you produce implementation-ready slice docs.
|
|
22
24
|
|
|
23
25
|
Read `reference/methodology.md` (the 7 steps + 4 patterns) and `reference/em-dsl.md` (DSL syntax and
|
|
24
26
|
validation rules) before doing real work — they are the source of truth. Templates live in
|
|
25
|
-
`templates/`.
|
|
27
|
+
`templates/`. References to `docs/*.md` throughout this skill mean the
|
|
28
|
+
[em repository's docs](https://github.com/milehimikey/em/tree/main/docs) — they are not
|
|
29
|
+
vendored alongside this skill.
|
|
26
30
|
|
|
27
31
|
## Operating principles (every phase)
|
|
28
32
|
|
|
@@ -79,8 +83,8 @@ validation rules) before doing real work — they are the source of truth. Templ
|
|
|
79
83
|
- **Validate continuously.** Run `em validate` and fix errors/warnings as you go (see DSL ref).
|
|
80
84
|
- **Save state at the end of every session** so work resumes cleanly. Also append one line to
|
|
81
85
|
the Usage log: the phase(s) touched and the `em validate` diagnostic *categories* that fired
|
|
82
|
-
(
|
|
83
|
-
content). This is the team's only usage signal today (`docs/usage-data.md`) — keep it cheap
|
|
86
|
+
(the exact fixed strings from `docs/usage-data.md`, e.g. "read model has no consumer" —
|
|
87
|
+
never the full message or domain content). This is the team's only usage signal today (`docs/usage-data.md`) — keep it cheap
|
|
84
88
|
and habitual, not a task to skip.
|
|
85
89
|
|
|
86
90
|
## Preconditions (run first)
|
|
@@ -226,6 +230,16 @@ phases is the starting point, not the finished event set. **Every new event need
|
|
|
226
230
|
real (someone has to see that the request was declined).
|
|
227
231
|
|
|
228
232
|
For each slice:
|
|
233
|
+
0. **Check for re-ratification first.** If `slices/<slice-name>.md` already exists with
|
|
234
|
+
`status: implemented`, this is a re-ratification, not fresh authoring: hold a Socratic
|
|
235
|
+
deep-dive on what changed (same rigor as step 1, scoped to the delta), update whichever
|
|
236
|
+
sections actually changed, then express the change as the `## Delta` section — fixed heading,
|
|
237
|
+
`### Added`/`Modified`/`Removed`/`Renamed` Requirement blocks each carrying its own scenarios
|
|
238
|
+
(see `templates/slice.md` and `docs/slice-doc-schema.md#delta-section-grammar-and-lifecycle`)
|
|
239
|
+
— **overwriting** whatever the section held before, never appending to it. Then continue at
|
|
240
|
+
step 2 to bump `version` and flip `status` back to `ready-to-implement`, leaving
|
|
241
|
+
`implementedIn` naming the prior version's PR (intended drift signal, not a bug — see the
|
|
242
|
+
schema doc). Otherwise, this is first-time authoring: continue at step 1.
|
|
229
243
|
1. Hold a Socratic deep-dive to fill every section of `templates/slice.md`: intent, trigger/actor,
|
|
230
244
|
command + field table (types & rules), event(s) + payload (mark immutable facts), invariants
|
|
231
245
|
(give each a stable ID), Given/When/Then scenarios (happy path + rule boundaries + edge cases),
|
|
@@ -235,19 +249,51 @@ For each slice:
|
|
|
235
249
|
adjacent real sources (OpenAPI specs, DB migrations, existing DTOs/event classes in sibling
|
|
236
250
|
contexts) before finalizing field names/types or invariants — don't guess a shape that's
|
|
237
251
|
already defined elsewhere.
|
|
238
|
-
2. Write the doc to `slices/<slice-name>.md` (kebab-case the slice name)
|
|
252
|
+
2. Write the doc to `slices/<slice-name>.md` (kebab-case the slice name), with the YAML
|
|
253
|
+
frontmatter block at the very top — all five of `schemaVersion`/`pattern`/`swimlane`/
|
|
254
|
+
`status`/`version` (kebab-case `pattern` value, `schemaVersion: 1`, `version` starting at
|
|
255
|
+
`1`; omitting any of the five makes the doc `frontmatter-invalid` to `em export` and the
|
|
256
|
+
readiness gate), plus `implementedIn` once shipped — the canonical, machine-read
|
|
257
|
+
metadata dialect (`- **Status:** ...` bullet lines are legacy/accepted input only; never
|
|
258
|
+
write new docs that way). When this doc exists because of a split, merge, or rename, add the
|
|
259
|
+
matching lineage key(s) too (`split-from`/`merged-from`/`superseded-by`, `<slice-key>@v<N>`
|
|
260
|
+
grammar — see `docs/slice-doc-schema.md` for the full schema). On a re-ratification (step 0),
|
|
261
|
+
bump `version` and flip `status` here instead of setting them fresh. Record the originating
|
|
239
262
|
need (ticket/conversation link) in the Intent section when one exists.
|
|
240
263
|
3. Render the slice's own diagram: `em render <model>.em --slice "<slice name>" -o
|
|
241
264
|
slices/<slice-name>.svg` (kebab-case, matching the doc's filename) — redraws just this slice
|
|
242
265
|
in its own canonical pattern shape, and add `` near the top of
|
|
243
|
-
the doc, right after the
|
|
266
|
+
the doc, right after the `# Slice:` heading (below the frontmatter block).
|
|
244
267
|
4. Wire it into the `.em`: add `note "slices/<slice-name>.md"` to the slice's primary element
|
|
245
268
|
(the command for State Change, the view for State View, the processor for Automation, the
|
|
246
269
|
translation for Translation).
|
|
247
|
-
5. Update `README.md`'s slice index — the one canonical slice table (`draft` →
|
|
248
|
-
`ready-to-implement`, later `implemented` once shipped, with
|
|
270
|
+
5. Update `README.md`'s slice index — the one canonical slice table (`draft` → `reviewed` →
|
|
271
|
+
`ready-to-implement`, later `implemented` once shipped, with its Implemented-in link
|
|
272
|
+
filled in).
|
|
249
273
|
6. Re-render and `em validate`.
|
|
250
274
|
|
|
275
|
+
When a slice is **ratified** — a human flips its `status` to `ready-to-implement` with every
|
|
276
|
+
open question resolved — it's ready to hand to an implementer. Confirm with
|
|
277
|
+
`em validate <model>.em --slice-ready <key>`, and point whoever (or whatever) implements it
|
|
278
|
+
at `reference/implement.md`.
|
|
279
|
+
|
|
280
|
+
## Phase: `implement` — build one ratified slice
|
|
281
|
+
|
|
282
|
+
Goal: turn a single `ready-to-implement` slice into merged, verified code — the one phase
|
|
283
|
+
where the agent builds rather than facilitates.
|
|
284
|
+
|
|
285
|
+
**Read `reference/implement.md` before doing any implement work and follow it as the
|
|
286
|
+
contract.** In short: gate on `em validate <model>.em --slice-ready <key>` (stop and hand
|
|
287
|
+
back if it fails — never edit the doc to make the gate pass); treat the slice doc as the
|
|
288
|
+
spec (read-only, except the merge-time `status`/`implementedIn` flip); surface every gap to
|
|
289
|
+
the user instead of deciding it silently; cover every INV-n and every scenario with tests;
|
|
290
|
+
in spec-kit projects, allocate via em-sdd-bridge (redirect mode) and never run
|
|
291
|
+
`/speckit.specify`.
|
|
292
|
+
|
|
293
|
+
End of phase: PR merged, slice doc flipped to `status: implemented` with `implementedIn`
|
|
294
|
+
filled, `README.md`'s slice index updated. Implement doesn't chain to another phase — run it
|
|
295
|
+
once per ratified slice; `conform` later checks the result against the model.
|
|
296
|
+
|
|
251
297
|
## Phase: `conform` — drift check against the codebase
|
|
252
298
|
|
|
253
299
|
Goal: check a **ratified** model (and its slice docs) against the codebase that implements it,
|
|
@@ -346,6 +392,7 @@ two slices and route it through a command.
|
|
|
346
392
|
|
|
347
393
|
## em command reference (quick)
|
|
348
394
|
|
|
395
|
+
<!-- GENERATED:cli-quick:start -- run `npm run docs:generate` to refresh, do not hand-edit -->
|
|
349
396
|
```bash
|
|
350
397
|
em --version
|
|
351
398
|
em init <name>.em # optional starter scaffold
|
|
@@ -357,6 +404,7 @@ em watch <name>.em -o <name>.svg # re-render on save (run in backgroun
|
|
|
357
404
|
em watch <name>.em -o <name>.svg --serve # + live viewer with instant push-reload (--port N)
|
|
358
405
|
# click "Review mode" for a slice-by-slice storyboard walkthrough
|
|
359
406
|
```
|
|
407
|
+
<!-- GENERATED:cli-quick:end -->
|
|
360
408
|
|
|
361
409
|
Always finish a working session by: re-rendering, running `em validate`, and updating
|
|
362
410
|
`.event-modeling.md` with the current phase/step, decisions, open questions, and a Usage log
|
|
@@ -34,10 +34,11 @@ difference is what you do with what you find: extract *builds* a model from the
|
|
|
34
34
|
|
|
35
35
|
1. A canonical `.em` model with a state file (`.event-modeling.md`), reachable the same way the
|
|
36
36
|
SKILL.md preconditions locate it.
|
|
37
|
-
2. The
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
tell the user and skip it rather than reporting "drift"
|
|
37
|
+
2. The slices you're about to check should be **implemented** — docs at `status: implemented`
|
|
38
|
+
(or the status the user says corresponds to shipped code) for whichever slices are in scope.
|
|
39
|
+
Checking a `draft` or `ready-to-implement` slice against code is a wasted walk — ratified or
|
|
40
|
+
not, nobody has built to it yet; tell the user and skip it rather than reporting "drift"
|
|
41
|
+
against it.
|
|
41
42
|
3. The codebase to check: the state file's **Existing system refs** (populated during
|
|
42
43
|
`extract`, if this model came from one) or ask the user for the repo path if not set. Record
|
|
43
44
|
it if missing. In a headless/scheduled run with no user to ask, default to the repository
|
|
@@ -56,7 +57,7 @@ named invariants, Given/When/Then scenarios). Conform checks all three surfaces
|
|
|
56
57
|
|---|---|---|---|
|
|
57
58
|
| **Structural** — `.em` ↔ code | slices/elements/fields/flow | `em` (deterministic) | `em diff --json` on canonical vs. an as-is scratch model |
|
|
58
59
|
| **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
|
|
60
|
+
| **Internal** — slice doc ↔ `.em` | the doc's frontmatter (`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
|
|
|
61
62
|
The internal surface is deterministically checkable in principle (structured doc sections +
|
|
62
63
|
`{ fields }` blocks) — promoting it to an `em validate` rule is a natural follow-up; file it as
|
|
@@ -74,7 +75,7 @@ authoritative.
|
|
|
74
75
|
Default: **diff-scoped.** Read the state file's `Last conformance:` marker (date + target-repo
|
|
75
76
|
revision). If it's set, run `git diff --name-only <recorded-revision>..HEAD` from the target
|
|
76
77
|
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, `
|
|
78
|
+
map those paths to slices (via each slice doc's known code locations, `implementedIn:` links,
|
|
78
79
|
or a quick Grep if those are stale) — that's your in-scope set. If the marker reads `never`
|
|
79
80
|
(first run) or the user passes `--full`, scope is every `implemented` slice.
|
|
80
81
|
|
|
@@ -112,6 +113,18 @@ For each slice in scope:
|
|
|
112
113
|
3). If it does, this is not a fresh finding to litigate: classify it as **Accepted
|
|
113
114
|
divergence** (step 4) and cite the annotation text as the evidence. This is the specific,
|
|
114
115
|
per-element, ratified counterpart to the idiom-level bullet above.
|
|
116
|
+
- **Unpropagated deltas (MIL-85):** before treating any slice-level finding as drift, check
|
|
117
|
+
that slice's `doc.driftSignal` in `em export --json` (see `docs/slice-doc-schema.md`'s
|
|
118
|
+
"`status` under re-ratification" section for the mechanics). `"unpropagated-delta"` means
|
|
119
|
+
the slice was re-ratified (a new
|
|
120
|
+
`version`, `status` flipped back off `implemented`) but `implementedIn` still names the
|
|
121
|
+
*prior* version's PR — the code you're evidence-walking is that prior version, on purpose.
|
|
122
|
+
Do not diff it against the newer doc content and do not report a mismatch as Real drift or
|
|
123
|
+
a Model gap; classify it as **Unpropagated delta** (step 4) instead and stop — the finding
|
|
124
|
+
is "this delta hasn't shipped yet," not a defect in the shipped code. `"implemented-
|
|
125
|
+
without-link"` (status: implemented, no link at all) is a genuine coherence problem, but
|
|
126
|
+
it's already surfaced by `em validate`'s `frontmatter-coherence-implemented-without-link`
|
|
127
|
+
warning — don't re-raise it here as a separate conform finding.
|
|
115
128
|
3. Write what you found into the scratch model (`<model-name>-asis.em`, see below). **Seed the
|
|
116
129
|
scratch model as a copy of the canonical `.em` and replace only the in-scope slices** with
|
|
117
130
|
the as-is picture, leaving out-of-scope slices byte-identical. This is not an optimization:
|
|
@@ -178,6 +191,12 @@ step 2 — sorts into exactly one class:
|
|
|
178
191
|
`.em`/`em export` for a spec/internal finding). This is what the annotation is *for*: a
|
|
179
192
|
reasoned, ratified deviation stops being re-reported as drift on every run. Cite the
|
|
180
193
|
annotation text, not fresh judgment — that judgment already happened when it was written.
|
|
194
|
+
- **Unpropagated delta** (MIL-85) — the slice's `doc.driftSignal` (`em export --json`) is
|
|
195
|
+
`"unpropagated-delta"`: a ratified version bump hasn't shipped yet, so `implementedIn` still
|
|
196
|
+
names prior work on purpose. Distinct from Accepted divergence — this is a structurally-
|
|
197
|
+
derived, deterministic signal (`status`/`implementedIn` coherence), not a human-authored
|
|
198
|
+
`divergence` annotation; keep the two separate so a reader can tell which mechanism produced
|
|
199
|
+
which finding. Cite `slice.doc.driftSignal` and `slice.doc.version` together as evidence.
|
|
181
200
|
- **Extraction uncertainty** — no evidence either way. Never reported as drift or a gap.
|
|
182
201
|
|
|
183
202
|
Every classification must cite the evidence recorded in step 2 (or the `em diff` entry, for
|
|
@@ -203,8 +222,11 @@ End of run: update the state file's `Last conformance:` marker, exact format:
|
|
|
203
222
|
```
|
|
204
223
|
|
|
205
224
|
The revision is the one you just diffed against, so the next run's scope starts from here —
|
|
206
|
-
step 1 parses this line, so keep the format exact.
|
|
207
|
-
|
|
225
|
+
step 1 parses this line, so keep the format exact. **The marker only advances with the human
|
|
226
|
+
in the loop**: in an interactive session, update it after walking the report with the user; a
|
|
227
|
+
headless/scheduled run writes the report but never the marker (nobody ratified the outcome),
|
|
228
|
+
so the next run re-walks the same span — see `docs/ci.md`. If any proposals were ratified and
|
|
229
|
+
applied, log a Decisions entry noting what changed and why.
|
|
208
230
|
|
|
209
231
|
## Conventions
|
|
210
232
|
|
|
@@ -10,20 +10,61 @@ Keep `.em` files focused on **structure**; put deep design in markdown linked vi
|
|
|
10
10
|
|
|
11
11
|
## CLI
|
|
12
12
|
|
|
13
|
+
<!-- GENERATED:cli:start -- run `npm run docs:generate` to refresh, do not hand-edit -->
|
|
13
14
|
```bash
|
|
14
|
-
em
|
|
15
|
-
em
|
|
16
|
-
em
|
|
17
|
-
em render <file>
|
|
18
|
-
em render <file> --
|
|
19
|
-
em
|
|
20
|
-
em
|
|
21
|
-
em
|
|
22
|
-
em
|
|
23
|
-
em
|
|
24
|
-
em
|
|
25
|
-
em
|
|
15
|
+
em --version # print the installed em version
|
|
16
|
+
em init [file] # scaffold a starter .em model
|
|
17
|
+
em init [file] -f, --force # overwrite if the file exists
|
|
18
|
+
em render <file> # transpile a model and render it (or emit DOT)
|
|
19
|
+
em render <file> -o, --out <path> # output path (extension picks the format)
|
|
20
|
+
em render <file> -T, --format <fmt> # output format (svg, png, pdf, ...)
|
|
21
|
+
em render <file> --slice <name> # render only this slice, redrawn in its own canonical pattern shape (default out: slices/<kebab-slug>.svg)
|
|
22
|
+
em render <file> --emit-dot # print the generated DOT instead of rendering
|
|
23
|
+
em render <file> --keep-empty-lanes # keep the API lane even when empty
|
|
24
|
+
em export <file> # export a versioned JSON snapshot of the normalized model
|
|
25
|
+
em export <file> -o, --out <path> # write to a file instead of stdout
|
|
26
|
+
em diff <old> [new] # compare two models structurally (two files, or one file across git revisions)
|
|
27
|
+
em diff <old> [new] --from <rev> # diff <old> against this git revision instead of a second file
|
|
28
|
+
em diff <old> [new] --to <rev> # diff against this git revision instead of the current file (requires --from)
|
|
29
|
+
em diff <old> [new] --exit-code # exit 1 if the models differ, 0 if identical (git-diff convention)
|
|
30
|
+
em diff <old> [new] --json # print a JSON document instead of the text report (see docs/cli.md)
|
|
31
|
+
em glossary <files> # cross-model glossary of terms, with consistency checks across models (see docs/cli.md)
|
|
32
|
+
em glossary <files> --json # print the full glossary document instead of the text report
|
|
33
|
+
em glossary <files> -o, --out <path> # write the JSON document to a file instead of stdout (requires --json)
|
|
34
|
+
em glossary <files> --list-conflicts # print only the conflict lines, no summary
|
|
35
|
+
em glossary <files> --fail-on-conflicts # exit non-zero if any cross-model term conflicts were found (opt-in — conflicts are warnings and don't block by default)
|
|
36
|
+
em catalog <files> # generate a browsable static HTML catalog site over one or more .em models (see docs/cli.md)
|
|
37
|
+
em catalog <files> -o, --out <dir> # output directory
|
|
38
|
+
em catalog <files> -T, --format <fmt> # diagram format embedded in the catalog (svg or png)
|
|
39
|
+
em catalog <files> --title <text> # catalog site title
|
|
40
|
+
em catalog <files> --keep-empty-lanes # keep the API lane even when empty
|
|
41
|
+
em changelog <file> # render a model's git history as a business-readable ledger (see docs/cli.md)
|
|
42
|
+
em changelog <file> --from <rev> # start the walk at this revision (inclusive)
|
|
43
|
+
em changelog <file> --to <rev> # end the walk at this revision (inclusive; default HEAD)
|
|
44
|
+
em changelog <file> -o, --out <path> # write to a file instead of stdout
|
|
45
|
+
em watch <file> # re-render on every save
|
|
46
|
+
em watch <file> -o, --out <path> # output path (extension picks the format)
|
|
47
|
+
em watch <file> -T, --format <fmt> # output format (svg, png, pdf, ...)
|
|
48
|
+
em watch <file> --keep-empty-lanes # keep the API lane even when empty
|
|
49
|
+
em watch <file> --serve # serve a live viewer with instant push-reload (no polling)
|
|
50
|
+
em watch <file> --port <n> # port for --serve (default 5173)
|
|
51
|
+
em validate <file> # check a model against event-modeling rules
|
|
52
|
+
em validate <file> --list-issues # print only open `issue` diagnostics (slice, element, line, text)
|
|
53
|
+
em validate <file> --list-divergences # print only accepted-divergence annotations (slice, element, line, text) — never fails the build
|
|
54
|
+
em validate <file> --list-public # print only events and views marked `public` (slice, kind, name, line) — an integration-surface audit, never fails the build
|
|
55
|
+
em validate <file> --fail-on-issues # exit non-zero if the model has any open `issue`s (opt-in — issues are warnings and don't block by default)
|
|
56
|
+
em validate <file> --slice-ready <key> # readiness gate for one slice (export key): status ready-to-implement, doc resolvable via note binding, zero unchecked Open Questions — exits non-zero if not ready (MIL-87)
|
|
57
|
+
em ledger <file> # check slice docs' version: field agrees with their content across two git revisions (opt-in CI check, MIL-89 — never part of `em validate`, see docs/ci.md)
|
|
58
|
+
em ledger <file> --from <rev> # baseline revision
|
|
59
|
+
em ledger <file> --to <rev> # compare revision (default: current working tree)
|
|
60
|
+
em ledger <file> --json # print a JSON document instead of the text report (see docs/cli.md)
|
|
61
|
+
em skill install # copy the event-modeling skill into .claude/skills/event-modeling/
|
|
62
|
+
em skill install -f, --force # overwrite an existing installation
|
|
63
|
+
em skill sync [path] # update the vendored .claude/skills/event-modeling/ copy in [path] to match the installed em package (overwrites unconditionally; local edits are never merged, MIL-93)
|
|
64
|
+
em skill check [path] # check the vendored .claude/skills/event-modeling/ copy in [path] for drift against the installed em package; exits non-zero on any mismatch (CI-ready, MIL-93)
|
|
65
|
+
em skill check [path] --json # print a JSON document instead of the text report (see docs/cli.md)
|
|
26
66
|
```
|
|
67
|
+
<!-- GENERATED:cli:end -->
|
|
27
68
|
|
|
28
69
|
Install if missing: `npm i -g @milehimikey/em`. PNG works with no system deps; PDF needs
|
|
29
70
|
`rsvg-convert`.
|
|
@@ -57,15 +98,23 @@ type Name { field: Type, ... } # named structured type, reusable from any f
|
|
|
57
98
|
|---|---|---|---|---|
|
|
58
99
|
| `ui` | persona | screen / interface | `@Persona` | `note`, `issue`, `divergence`, `{ fields }` |
|
|
59
100
|
| `command` | API | state-changing request | — | `note`, `issue`, `divergence`, `{ fields }` |
|
|
60
|
-
| `view` | API | read model / projection | — | `from "Event"…`, `note`, `issue`, `divergence`, `{ fields }` |
|
|
101
|
+
| `view` | API | read model / projection | — | `from "Event"…`, `note`, `issue`, `divergence`, `public`, `{ fields }` |
|
|
61
102
|
| `event` | context | recorded fact (past tense) | `@Context` | `note`, `issue`, `divergence`, `public`, `{ fields }` |
|
|
62
103
|
| `processor` / `automation` / `saga` / `translation` | automation | system reaction / adapter | — | `from "…"`, `note`, `issue`, `divergence`, `{ fields }` |
|
|
63
104
|
|
|
64
105
|
### Clauses
|
|
65
|
-
- **Tags:** `@Persona` only on `ui`; `@Context` only on `event`. Undeclared tags auto-create a
|
|
106
|
+
- **Tags:** `@Persona` only on `ui`; `@Context` only on `event`. Undeclared tags auto-create a
|
|
107
|
+
row. Multi-word tags need no quoting — the tag captures everything after `@` to end of line
|
|
108
|
+
(`ui Ticket Queue @Customer Service` matches `persona Customer Service`).
|
|
66
109
|
- **`from "X"`** on views/automations declares the source(s); names are quoted, comma-separated.
|
|
67
110
|
Matching is case-insensitive and whitespace-normalized. A `from` may never point at an event or
|
|
68
111
|
view that first appears in a LATER slice (forward-only timeline — validation error).
|
|
112
|
+
Kinds never cross: a view's `from` resolves ONLY to events; a reaction's `from` resolves ONLY
|
|
113
|
+
to read models — naming an event on a reaction is a validation error even though the event
|
|
114
|
+
exists. Feed an event to a reaction by projecting it into a view first (the automation's
|
|
115
|
+
"to-do list") and pointing the reaction at that view. Name that view after the pending work
|
|
116
|
+
(`Payments To Process`), never after the triggering event — a view reusing the event's name
|
|
117
|
+
collides in the shared namespace and draws a duplicate-name warning.
|
|
69
118
|
- **`again`** (views only): `view <Name> again [from "Event", …]` declares a later instance of an
|
|
70
119
|
already-declared read model — the forward-only device for a view that evolves as later events
|
|
71
120
|
land. Instances are ONE logical view: the first declaration owns the `note` binding; each
|
|
@@ -89,13 +138,16 @@ type Name { field: Type, ... } # named structured type, reusable from any f
|
|
|
89
138
|
Raises **no** `em validate` warning by design; use `--list-divergences` to audit. `em diff
|
|
90
139
|
--json` carries it forward as `acceptedDivergence` on the affected change/removal entry, so
|
|
91
140
|
`conform` can cite an already-ratified deviation instead of re-flagging it as drift every run.
|
|
92
|
-
- **`public`** (events
|
|
93
|
-
(e.g.
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
`
|
|
98
|
-
|
|
141
|
+
- **`public`** (events and views): marks the element as part of the published integration
|
|
142
|
+
surface — a published event contract (e.g. AsyncAPI), or a view whose read API/response
|
|
143
|
+
shape external consumers depend on — as opposed to an internal-only fact or projection.
|
|
144
|
+
Plain structural flag, no free text and no diagram marker (same posture as `again`). Write
|
|
145
|
+
it as the last token on the line; on an event, optionally right before a trailing
|
|
146
|
+
`@Context` — `event X @Context public` or `event X public @Context` both work. A `public`
|
|
147
|
+
element is exempt from the unread-event / unconsumed-view warnings (its consumer is outside
|
|
148
|
+
this model). `em export` carries it as `public: true`/`false`; `em diff` reports a flip as
|
|
149
|
+
`event marked public`/`event unmarked public` (the entry cites the element's kind, so a
|
|
150
|
+
view flip reads `view "X"`); `em validate --list-public` audits the whole public surface.
|
|
99
151
|
- **Fields:** `command Place Order { orderId: UUID, items: LineItem[], customerId }` — inline or
|
|
100
152
|
one-per-line. Types are free text (no semantic checking) UNLESS the type string names a
|
|
101
153
|
declared `type` (see Named types below), in which case it resolves to a structured
|
|
@@ -328,6 +380,12 @@ slice "Read Quote — created" {
|
|
|
328
380
|
(`type Node { child: Node }`, or the same shape across several types). Break the cycle, or
|
|
329
381
|
route the self/mutual reference through an array (`children: Node[]`) if the data is
|
|
330
382
|
genuinely tree-shaped.
|
|
383
|
+
9. **Lineage-ref errors** (slice-doc frontmatter, fs-aware) — a
|
|
384
|
+
`split-from`/`merged-from`/`superseded-by` ref with malformed `<slice-key>@v<N>` grammar, a
|
|
385
|
+
self-reference or cycle, a `superseded-by` naming a slice absent from the current model, or
|
|
386
|
+
a ref naming a version higher than the target's own `version:`. A `split-from`/`merged-from`
|
|
387
|
+
naming a key absent from the tree is deliberately silent — that's the normal state after a
|
|
388
|
+
real split/merge (see the four `lineage-*` codes in the table below).
|
|
331
389
|
|
|
332
390
|
**Warnings (should fix):**
|
|
333
391
|
1. **Automation/translation shares slice with its command** — both `automation`/`processor` and
|
|
@@ -342,8 +400,10 @@ slice "Read Quote — created" {
|
|
|
342
400
|
4. **Command without event** — every command should record at least one event.
|
|
343
401
|
5. **Event nobody reads** — the mirror of (4): recording an event no read model projects is a
|
|
344
402
|
write with no reader. Follow every write slice with the read slice that consumes its event.
|
|
345
|
-
Counts as read when a `view` names it in `from` (any `again` instance will do), when a
|
|
346
|
-
|
|
403
|
+
Counts as read when a `view` names it in `from` (any `again` instance will do), when a view
|
|
404
|
+
with no `from` of its own sits in its slice, or via an explicit `event -> view` arrow.
|
|
405
|
+
(Events marked `public` are exempt — their reader is outside the model; same for a
|
|
406
|
+
`public` view and the no-consumer warning.)
|
|
347
407
|
6. **Read model without source** — add `from "Event"` or place the view in a slice with an event.
|
|
348
408
|
7. **Duplicate name** — the same name defined N times; references resolve to the first. Rename.
|
|
349
409
|
(A duplicate `type` name always warns, unlike a duplicate element name — there's no
|
|
@@ -358,11 +418,73 @@ slice "Read Quote — created" {
|
|
|
358
418
|
command in the same slice. Only checked once BOTH the event and at least one same-slice
|
|
359
419
|
command declare `{ fields }`. This is the payoff of the fields feature for slicing rigor:
|
|
360
420
|
once fields are written down, `em validate` checks that data flows forward consistently.
|
|
421
|
+
11. **Event with no producing command** — a fact with no traceable cause; add the command that
|
|
422
|
+
records it (either order within the slice), or an explicit `arrow` from one.
|
|
423
|
+
12. **Two same-named `translation`s reading different producers** — rename one; fires
|
|
424
|
+
unconditionally (unlike the duplicate-name warning), since the confusion is in reading
|
|
425
|
+
the model, not resolving a reference.
|
|
426
|
+
13. **Frontmatter coherence** (slice docs, fs-aware) — `status: implemented` with no
|
|
427
|
+
`implementedIn` link at all. The re-ratification mismatch (status flipped back while
|
|
428
|
+
`implementedIn` names the prior PR) is deliberately never flagged — it's the drift
|
|
429
|
+
signal, not incoherence.
|
|
430
|
+
14. **Doc-join problems** (`em export`/`--slice-ready`) — `binding-missing-file` (a
|
|
431
|
+
`note "slices/<key>.md"` binding pointing at a file that doesn't exist) and
|
|
432
|
+
`frontmatter-invalid` (the doc exists but lacks a frontmatter block or a required key).
|
|
361
433
|
|
|
362
434
|
`divergence "text"` is deliberately NOT in this list — it raises no warning at all, since it
|
|
363
435
|
records a deviation already reasoned through and accepted, not something to fix. Use
|
|
364
436
|
`em validate --list-divergences` to audit them on demand.
|
|
365
437
|
|
|
438
|
+
### Full rule code reference
|
|
439
|
+
|
|
440
|
+
Every rule above (and every fs-aware check `em validate` layers on top — lineage, frontmatter
|
|
441
|
+
coherence) has a stable `code`, generated below from the same registry `em` itself validates
|
|
442
|
+
against (`src/model/rules.ts`) — a new rule shows up here the moment it's registered, whether or
|
|
443
|
+
not the prose above has caught up yet. `--slice-ready <key>`-only codes are excluded; see
|
|
444
|
+
`docs/cli.md#em-validate-file` in the
|
|
445
|
+
[em repository](https://github.com/milehimikey/em/blob/main/docs/cli.md#em-validate-file).
|
|
446
|
+
|
|
447
|
+
<!-- GENERATED:validate-rules:start -- run `npm run docs:generate` to refresh, do not hand-edit -->
|
|
448
|
+
| Code | Severity | Title | Fix |
|
|
449
|
+
|---|---|---|---|
|
|
450
|
+
| `arrow-backward` | error | Backward arrow | Restructure so the target comes later. |
|
|
451
|
+
| `arrow-unresolved-source` | error | Arrow source unresolved | Fix the arrow's source name. |
|
|
452
|
+
| `arrow-unresolved-target` | error | Arrow target unresolved | Fix the arrow's target name. |
|
|
453
|
+
| `automation-shares-slice-with-command` | warning | Automation shares slice with its command | Put the triggered command in the next slice. |
|
|
454
|
+
| `binding-missing-file` | warning | Doc binding points at a missing file | Create the slice doc, or fix the `note` path. |
|
|
455
|
+
| `both-ends-of-a-flow/command-no-event` | warning | Command without event | Add the event this command records. |
|
|
456
|
+
| `both-ends-of-a-flow/command-untriggered` | warning | Command with no trigger | Add a `ui` in this slice, or a reaction in the previous slice. |
|
|
457
|
+
| `both-ends-of-a-flow/event-unproduced` | warning | Event with no producing command | Add the command that records it, or an explicit arrow from one. |
|
|
458
|
+
| `both-ends-of-a-flow/event-unread` | warning | Event nobody reads | Project it into a view, or reconsider recording it. |
|
|
459
|
+
| `both-ends-of-a-flow/ui-unbacked` | warning | `ui` with no read model or command | Add a `view` it displays, or the command it triggers. |
|
|
460
|
+
| `both-ends-of-a-flow/view-unconsumed` | warning | Read model with no consumer | Add a `ui` or reaction that consumes it, or drop this instance. |
|
|
461
|
+
| `connection-legality/illegal-pair` | error | Illegal connection | Only ui→command→event→view→ui and view→reaction→command are legal — the message names the missing step. |
|
|
462
|
+
| `duplicate-element-ref` | warning | Duplicate element ref | Rename the element so its export ref is unique. |
|
|
463
|
+
| `duplicate-name` | warning | Duplicate name | Rename one of the duplicates. |
|
|
464
|
+
| `duplicate-slice-name` | warning | Duplicate slice name | Rename the slice so its export key is unique. |
|
|
465
|
+
| `duplicate-type-name` | warning | Duplicate type name | Rename one of the duplicate `type` declarations. |
|
|
466
|
+
| `duplicate-type-ref` | warning | Duplicate type ref | Rename the type so its export ref is unique. |
|
|
467
|
+
| `fields-completeness/event-field-no-source` | warning | Event field not from a command | Add the field to a command in the slice, or remove it from the event. |
|
|
468
|
+
| `fields-completeness/view-field-no-source` | warning | View field with no source | Add the field to a source event, or remove it from the view. |
|
|
469
|
+
| `frontmatter-coherence-implemented-without-link` | warning | Implemented without a link | Add `implementedIn` once the slice ships. |
|
|
470
|
+
| `frontmatter-invalid` | warning | Invalid or missing frontmatter | Add the required frontmatter keys, or add a frontmatter block. |
|
|
471
|
+
| `grid-collision` | error | Band collision | Split the colliding elements into separate slices. |
|
|
472
|
+
| `lineage-forward-dangling` | error | Dangling forward lineage ref | Fix the key, or remove the stale successor. |
|
|
473
|
+
| `lineage-ref-cycle` | error | Lineage cycle | Break the cycle — a slice can't be its own ancestor. |
|
|
474
|
+
| `lineage-ref-malformed` | error | Malformed lineage ref | Fix the value to `<slice-key>@v<N>`, or remove it. |
|
|
475
|
+
| `lineage-version-impossible` | error | Impossible lineage version | Fix the referenced version, or ratify the target slice first. |
|
|
476
|
+
| `open-issue` | warning | Open issue | Resolve the question, then remove the `issue` clause. |
|
|
477
|
+
| `reaction-from-future-view` | error | Backward timeline (reaction reads a future view) | Declare the view in or before the reaction's slice. |
|
|
478
|
+
| `reaction-from-unresolved` | error | Unknown read-model source | Project the event into a view first, or fix the `from` reference. |
|
|
479
|
+
| `translation-name-collision` | warning | Translation name reused for different producers | Use a distinct name per producer to avoid confusion. |
|
|
480
|
+
| `type-cycle` | error | Cyclic type reference | Break the cycle, or route the self/mutual reference through an array. |
|
|
481
|
+
| `ui-shares-slice-with-automation` | warning | `ui` shares slice with a reaction, no command | Move the `ui` to the read-model slice, or to the slice with the command this triggers. |
|
|
482
|
+
| `view-again-without-earlier` | error | `again` without an earlier declaration | Declare the view plainly the first time it appears. |
|
|
483
|
+
| `view-from-future-event` | error | Backward timeline (view reads a future event) | Move the source to a later `view X again` instance. |
|
|
484
|
+
| `view-from-unresolved` | error | Unknown event source | Fix the `from` reference to name an existing event. |
|
|
485
|
+
| `view-no-source` | warning | Read model without source | Add `from "Event"`, or place the view in a slice with an event. |
|
|
486
|
+
<!-- GENERATED:validate-rules:end -->
|
|
487
|
+
|
|
366
488
|
**Design rules that keep models valid:**
|
|
367
489
|
- One element per band per slice (multiple personas/contexts are fine — they're different rows).
|
|
368
490
|
- Every `command` slice includes its `event`. Every `view` has a `from` source.
|
|
@@ -124,7 +124,8 @@ accepted case, and each one is worth naming explicitly as a finding); every unkn
|
|
|
124
124
|
`# TBD`; and the user has confirmed the model reflects today's behavior. Then:
|
|
125
125
|
|
|
126
126
|
1. State file: set phase progress (extraction rounds checked), log the completion decision.
|
|
127
|
-
Seed `README.md`'s **slice index** (every slice, pattern, status
|
|
127
|
+
Seed `README.md`'s **slice index** (every slice, pattern, status "no doc yet" — no slice
|
|
128
|
+
docs exist until the `slice` phase writes them).
|
|
128
129
|
2. The extracted model **replaces `discover`** — suggest `/event-modeling model` next
|
|
129
130
|
(steps 5-7: swimlanes, patterns, completeness). Desired-state changes belong *after* the
|
|
130
131
|
as-is model is agreed — as normal `model`/`slice` evolution, clearly separated from
|