@milehimikey/em 1.13.1 → 1.14.1

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 (235) hide show
  1. package/.claude/agents/em-critic.md +26 -0
  2. package/.claude/agents/em-implementer.md +34 -0
  3. package/.claude/agents/em-reviewer.md +31 -0
  4. package/.claude/agents/em-validator.md +27 -0
  5. package/.claude/skills/event-modeling/SKILL.md +50 -11
  6. package/.claude/skills/event-modeling-conform/SKILL.md +1 -1
  7. package/.claude/skills/event-modeling-design/SKILL.md +127 -18
  8. package/.claude/skills/event-modeling-discover/SKILL.md +1 -1
  9. package/.claude/skills/event-modeling-engagement/SKILL.md +192 -0
  10. package/.claude/skills/event-modeling-engagement/reference/engagement.md +143 -0
  11. package/.claude/skills/event-modeling-implement/SKILL.md +1 -1
  12. package/.claude/skills/event-modeling-implement/reference/implement.md +38 -11
  13. package/.claude/skills/event-modeling-review/SKILL.md +2 -2
  14. package/.claude/skills/event-modeling-shared/reference/em-dsl.md +279 -189
  15. package/.claude/skills/event-modeling-shared/reference/operating-principles.md +61 -0
  16. package/.claude/skills/event-modeling-shared/reference/slice-doc-schema.md +116 -5
  17. package/.claude/skills/event-modeling-shared/templates/constitution.md +18 -1
  18. package/.claude/skills/event-modeling-shared/templates/slice.md +64 -39
  19. package/.claude/skills/event-modeling-shared/templates/state.md +5 -1
  20. package/.claude-plugin/marketplace.json +14 -0
  21. package/README.md +25 -11
  22. package/dist/catalog/apiFirst.d.ts +28 -0
  23. package/dist/catalog/apiFirst.js +49 -0
  24. package/dist/catalog/apiFirst.js.map +1 -0
  25. package/dist/catalog/docJoin.d.ts +13 -0
  26. package/dist/catalog/docJoin.js +11 -0
  27. package/dist/catalog/docJoin.js.map +1 -1
  28. package/dist/catalog/docModelConsistencyValidate.js +44 -4
  29. package/dist/catalog/docModelConsistencyValidate.js.map +1 -1
  30. package/dist/catalog/invariantsValidate.d.ts +4 -0
  31. package/dist/catalog/invariantsValidate.js +102 -0
  32. package/dist/catalog/invariantsValidate.js.map +1 -0
  33. package/dist/catalog/scenarios.d.ts +15 -0
  34. package/dist/catalog/scenarios.js +97 -0
  35. package/dist/catalog/scenarios.js.map +1 -0
  36. package/dist/catalog/sliceDoc.d.ts +8 -0
  37. package/dist/catalog/sliceDoc.js +9 -0
  38. package/dist/catalog/sliceDoc.js.map +1 -1
  39. package/dist/catalog/sliceReadyValidate.d.ts +31 -4
  40. package/dist/catalog/sliceReadyValidate.js +66 -8
  41. package/dist/catalog/sliceReadyValidate.js.map +1 -1
  42. package/dist/catalog/sliceSections.d.ts +55 -0
  43. package/dist/catalog/sliceSections.js +317 -0
  44. package/dist/catalog/sliceSections.js.map +1 -0
  45. package/dist/cli/agentsMd.js +7 -1
  46. package/dist/cli/agentsMd.js.map +1 -1
  47. package/dist/cli/api.d.ts +90 -0
  48. package/dist/cli/api.js +318 -0
  49. package/dist/cli/api.js.map +1 -0
  50. package/dist/cli/ciInit.d.ts +82 -21
  51. package/dist/cli/ciInit.js +468 -64
  52. package/dist/cli/ciInit.js.map +1 -1
  53. package/dist/cli/conformScope.d.ts +28 -0
  54. package/dist/cli/conformScope.js +67 -0
  55. package/dist/cli/conformScope.js.map +1 -1
  56. package/dist/cli/coverage.d.ts +9 -0
  57. package/dist/cli/coverage.js +60 -8
  58. package/dist/cli/coverage.js.map +1 -1
  59. package/dist/cli/defer.d.ts +55 -0
  60. package/dist/cli/defer.js +244 -0
  61. package/dist/cli/defer.js.map +1 -0
  62. package/dist/cli/engagement.d.ts +210 -0
  63. package/dist/cli/engagement.js +444 -0
  64. package/dist/cli/engagement.js.map +1 -0
  65. package/dist/cli/engagementFile.d.ts +86 -0
  66. package/dist/cli/engagementFile.js +255 -0
  67. package/dist/cli/engagementFile.js.map +1 -0
  68. package/dist/cli/metrics.d.ts +10 -0
  69. package/dist/cli/metrics.js +49 -1
  70. package/dist/cli/metrics.js.map +1 -1
  71. package/dist/cli/pluginPin.d.ts +55 -0
  72. package/dist/cli/pluginPin.js +188 -0
  73. package/dist/cli/pluginPin.js.map +1 -0
  74. package/dist/cli/ratify.d.ts +27 -2
  75. package/dist/cli/ratify.js +112 -13
  76. package/dist/cli/ratify.js.map +1 -1
  77. package/dist/cli/reratify.d.ts +4 -2
  78. package/dist/cli/reratify.js +41 -16
  79. package/dist/cli/reratify.js.map +1 -1
  80. package/dist/cli/scopeInputs.d.ts +48 -0
  81. package/dist/cli/scopeInputs.js +176 -0
  82. package/dist/cli/scopeInputs.js.map +1 -0
  83. package/dist/cli/skillCheck.d.ts +15 -1
  84. package/dist/cli/skillCheck.js +22 -1
  85. package/dist/cli/skillCheck.js.map +1 -1
  86. package/dist/cli/skillDirs.d.ts +5 -0
  87. package/dist/cli/skillDirs.js +13 -1
  88. package/dist/cli/skillDirs.js.map +1 -1
  89. package/dist/cli/skillSync.d.ts +4 -0
  90. package/dist/cli/skillSync.js +23 -0
  91. package/dist/cli/skillSync.js.map +1 -1
  92. package/dist/cli/sliceNew.d.ts +11 -7
  93. package/dist/cli/sliceNew.js +91 -18
  94. package/dist/cli/sliceNew.js.map +1 -1
  95. package/dist/cli/sliceSync.d.ts +50 -0
  96. package/dist/cli/sliceSync.js +170 -0
  97. package/dist/cli/sliceSync.js.map +1 -0
  98. package/dist/cli/status.d.ts +24 -1
  99. package/dist/cli/status.js +20 -1
  100. package/dist/cli/status.js.map +1 -1
  101. package/dist/cli/systemInputs.d.ts +53 -10
  102. package/dist/cli/systemInputs.js +186 -11
  103. package/dist/cli/systemInputs.js.map +1 -1
  104. package/dist/cli/upgrade.d.ts +16 -3
  105. package/dist/cli/upgrade.js +366 -30
  106. package/dist/cli/upgrade.js.map +1 -1
  107. package/dist/cli/usageLog.d.ts +4 -2
  108. package/dist/cli/usageLog.js +4 -2
  109. package/dist/cli/usageLog.js.map +1 -1
  110. package/dist/cli.js +599 -94
  111. package/dist/cli.js.map +1 -1
  112. package/dist/emit/api.d.ts +29 -0
  113. package/dist/emit/api.js +220 -0
  114. package/dist/emit/api.js.map +1 -0
  115. package/dist/emit/apiCheckJson.d.ts +3 -0
  116. package/dist/emit/apiCheckJson.js +21 -0
  117. package/dist/emit/apiCheckJson.js.map +1 -0
  118. package/dist/emit/codeownersJson.d.ts +3 -0
  119. package/dist/emit/codeownersJson.js +19 -0
  120. package/dist/emit/codeownersJson.js.map +1 -0
  121. package/dist/emit/diffJson.d.ts +1 -1
  122. package/dist/emit/diffJson.js +8 -1
  123. package/dist/emit/diffJson.js.map +1 -1
  124. package/dist/emit/engagementJson.d.ts +8 -0
  125. package/dist/emit/engagementJson.js +46 -0
  126. package/dist/emit/engagementJson.js.map +1 -0
  127. package/dist/emit/glossaryJson.d.ts +1 -1
  128. package/dist/emit/glossaryJson.js +4 -1
  129. package/dist/emit/glossaryJson.js.map +1 -1
  130. package/dist/emit/json.d.ts +17 -1
  131. package/dist/emit/json.js +30 -1
  132. package/dist/emit/json.js.map +1 -1
  133. package/dist/emit/metricsJson.d.ts +1 -1
  134. package/dist/emit/metricsJson.js +4 -1
  135. package/dist/emit/metricsJson.js.map +1 -1
  136. package/dist/emit/queryJson.d.ts +1 -1
  137. package/dist/emit/queryJson.js +7 -1
  138. package/dist/emit/queryJson.js.map +1 -1
  139. package/dist/emit/scopeJson.d.ts +12 -0
  140. package/dist/emit/scopeJson.js +26 -0
  141. package/dist/emit/scopeJson.js.map +1 -0
  142. package/dist/emit/skillCheckJson.d.ts +3 -2
  143. package/dist/emit/skillCheckJson.js +10 -2
  144. package/dist/emit/skillCheckJson.js.map +1 -1
  145. package/dist/emit/statusJson.d.ts +1 -1
  146. package/dist/emit/statusJson.js +15 -1
  147. package/dist/emit/statusJson.js.map +1 -1
  148. package/dist/emit/systemJson.d.ts +15 -8
  149. package/dist/emit/systemJson.js +28 -13
  150. package/dist/emit/systemJson.js.map +1 -1
  151. package/dist/emit/validateJson.d.ts +3 -3
  152. package/dist/emit/validateJson.js +10 -4
  153. package/dist/emit/validateJson.js.map +1 -1
  154. package/dist/mcp/server.d.ts +1 -1
  155. package/dist/mcp/server.js +163 -31
  156. package/dist/mcp/server.js.map +1 -1
  157. package/dist/model/diff.d.ts +4 -1
  158. package/dist/model/diff.js +44 -8
  159. package/dist/model/diff.js.map +1 -1
  160. package/dist/model/glossary.d.ts +2 -0
  161. package/dist/model/glossary.js +1 -0
  162. package/dist/model/glossary.js.map +1 -1
  163. package/dist/model/model.d.ts +55 -2
  164. package/dist/model/model.js +57 -0
  165. package/dist/model/model.js.map +1 -1
  166. package/dist/model/qualifiedRef.d.ts +18 -0
  167. package/dist/model/qualifiedRef.js +26 -0
  168. package/dist/model/qualifiedRef.js.map +1 -1
  169. package/dist/model/queryIndex.d.ts +9 -1
  170. package/dist/model/queryIndex.js +18 -2
  171. package/dist/model/queryIndex.js.map +1 -1
  172. package/dist/model/rules.d.ts +143 -2
  173. package/dist/model/rules.js +162 -3
  174. package/dist/model/rules.js.map +1 -1
  175. package/dist/model/validate.d.ts +40 -0
  176. package/dist/model/validate.js +147 -1
  177. package/dist/model/validate.js.map +1 -1
  178. package/dist/parser/ast.d.ts +31 -3
  179. package/dist/parser/parser.js +148 -14
  180. package/dist/parser/parser.js.map +1 -1
  181. package/dist/query/format.js +1 -1
  182. package/dist/query/format.js.map +1 -1
  183. package/dist/query/verbs.d.ts +6 -0
  184. package/dist/query/verbs.js +6 -2
  185. package/dist/query/verbs.js.map +1 -1
  186. package/dist/system/adaptation.d.ts +14 -0
  187. package/dist/system/adaptation.js +49 -0
  188. package/dist/system/adaptation.js.map +1 -0
  189. package/dist/system/codeowners.d.ts +77 -0
  190. package/dist/system/codeowners.js +242 -0
  191. package/dist/system/codeowners.js.map +1 -0
  192. package/dist/system/manifest.d.ts +10 -5
  193. package/dist/system/manifest.js +54 -26
  194. package/dist/system/manifest.js.map +1 -1
  195. package/dist/system/migrateManifest.d.ts +28 -0
  196. package/dist/system/migrateManifest.js +155 -0
  197. package/dist/system/migrateManifest.js.map +1 -0
  198. package/dist/system/scope.d.ts +46 -0
  199. package/dist/system/scope.js +132 -0
  200. package/dist/system/scope.js.map +1 -0
  201. package/dist/system/verify.d.ts +50 -13
  202. package/dist/system/verify.js +179 -58
  203. package/dist/system/verify.js.map +1 -1
  204. package/dist/templates.d.ts +2 -2
  205. package/dist/templates.js +23 -2
  206. package/dist/templates.js.map +1 -1
  207. package/package.json +5 -2
  208. package/plugin/.claude-plugin/plugin.json +18 -0
  209. package/plugin/agents/em-critic.md +27 -0
  210. package/plugin/agents/em-implementer.md +35 -0
  211. package/plugin/agents/em-reviewer.md +32 -0
  212. package/plugin/agents/em-validator.md +28 -0
  213. package/plugin/shared/reference/em-dsl.md +871 -0
  214. package/plugin/shared/reference/methodology.md +329 -0
  215. package/plugin/shared/reference/operating-principles.md +222 -0
  216. package/plugin/shared/reference/slice-doc-schema.md +574 -0
  217. package/plugin/shared/templates/conformance-report.md +85 -0
  218. package/plugin/shared/templates/constitution.md +136 -0
  219. package/plugin/shared/templates/model-readme.md +46 -0
  220. package/plugin/shared/templates/slice.md +251 -0
  221. package/plugin/shared/templates/state.md +77 -0
  222. package/plugin/skills/conform/SKILL.md +93 -0
  223. package/plugin/skills/conform/reference/conform.md +428 -0
  224. package/plugin/skills/design/SKILL.md +298 -0
  225. package/plugin/skills/discover/SKILL.md +90 -0
  226. package/plugin/skills/discover/reference/extract.md +147 -0
  227. package/plugin/skills/engagement/SKILL.md +193 -0
  228. package/plugin/skills/engagement/reference/engagement.md +144 -0
  229. package/plugin/skills/event-modeling/SKILL.md +132 -0
  230. package/plugin/skills/implement/SKILL.md +61 -0
  231. package/plugin/skills/implement/reference/implement.md +439 -0
  232. package/plugin/skills/review/SKILL.md +74 -0
  233. package/dist/emit/typespec.d.ts +0 -26
  234. package/dist/emit/typespec.js +0 -356
  235. package/dist/emit/typespec.js.map +0 -1
@@ -0,0 +1,871 @@
1
+ <!-- GENERATED FILE from .claude/skills/event-modeling-shared/reference/em-dsl.md by scripts/build-plugin.ts — do not hand-edit; run npm run docs:generate -->
2
+ <!-- DSL behavior change? Update BOTH docs/dsl.md and .claude/skills/event-modeling-shared/reference/em-dsl.md -->
3
+
4
+ # `em` DSL Reference & Cheatsheet
5
+
6
+ The `em` tool (`@milehimikey/em`) is a slice-first text DSL rendered to a strict Graphviz
7
+ swimlane grid. Use this reference so the models you generate are **valid** and render cleanly.
8
+ Keep `.em` files focused on **structure**; put deep design in markdown linked via `note`.
9
+
10
+ ---
11
+
12
+ ## CLI
13
+
14
+ <!-- GENERATED:cli:start -- run `npm run docs:generate` to refresh, do not hand-edit -->
15
+ ```bash
16
+ em --version # print the installed em version
17
+ em init [file] # scaffold a starter .em model
18
+ em init [file] -f, --force # overwrite if the file exists
19
+ em scaffold <name> # scaffold a full project: <slug>/<slug>.em, README.md, .event-modeling.md, and constitution.md (the implementation constitution — skipped, with a note, in a spec-kit project, where `.specify/memory/constitution.md` IS that document) (see docs/cli.md — for just a starter .em, use `em init`; for a multi-model project, pass --under to nest it under a shared parent directory)
20
+ em scaffold <name> -f, --force # overwrite the directory's contents if it already exists
21
+ em scaffold <name> --under <dir> # parent directory to scaffold into — writes <dir>/<slug>/ instead of ./<slug>/, the supported multi-model layout (docs/cli.md, "Multi-model projects"): one directory per model, so each model's slices/ never collides with a sibling model's
22
+ em render <file> # transpile a model and render it (or emit DOT)
23
+ em render <file> -o, --out <path> # output path (extension picks the format)
24
+ em render <file> -T, --format <fmt> # output format (svg, png, pdf, ...)
25
+ em render <file> --slice <name> # render only this slice, redrawn in its own canonical pattern shape (default out: slices/<kebab-slug>.svg)
26
+ em render <file> --emit-dot # print the generated DOT instead of rendering
27
+ em render <file> --keep-empty-lanes # keep the API lane even when empty
28
+ em export <file> # export a versioned JSON snapshot of the normalized model
29
+ em export <file> -o, --out <path> # write to a file instead of stdout
30
+ em export <file> --slice <key> # export only this slice's object (pattern/fields/doc) instead of the whole model (export key, MIL-128) — refuses only if THIS slice has an error; an unrelated slice's breakage elsewhere in the model doesn't block it (see docs/cli.md)
31
+ em api generate <file> # write the TypeSpec contract for the model's public commands, events and views to <model dir>/contracts/<model key>.tsp
32
+ em api generate <file> -o, --out <path> # write to this path instead of <model dir>/contracts/<model key>.tsp
33
+ em api generate <file> --stdout # print the contract instead of writing it
34
+ em api check <file> # is the committed contract current? With --base, annotate each public-surface change since that revision additive or breaking (annotation only — exits 1 only when stale)
35
+ em api check <file> --base <rev> # git revision to diff the public surface against
36
+ em api check <file> --json # print a JSON document instead of the text report (see docs/cli.md)
37
+ em typespec <file> # DEPRECATED alias of `em api generate --stdout` (use em api generate)
38
+ em typespec <file> -o, --out <path> # write to a file instead of stdout
39
+ em diff <old> [new] # compare two models structurally (two files, or one file across git revisions)
40
+ em diff <old> [new] --from <rev> # diff <old> against this git revision instead of a second file
41
+ em diff <old> [new] --to <rev> # diff against this git revision instead of the current file (requires --from)
42
+ em diff <old> [new] --exit-code # exit 1 if the models differ, 0 if identical (git-diff convention)
43
+ em diff <old> [new] --json # print a JSON document instead of the text report (see docs/cli.md)
44
+ em glossary <files> # cross-model glossary of terms, with consistency checks across models (see docs/cli.md)
45
+ em glossary <files> --json # print the full glossary document instead of the text report
46
+ em glossary <files> -o, --out <path> # write the JSON document to a file instead of stdout (requires --json)
47
+ em glossary <files> --list-conflicts # print only the conflict lines, no summary
48
+ 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)
49
+ em catalog <files> # generate a browsable static HTML catalog site over one or more .em models (see docs/cli.md)
50
+ em catalog <files> -o, --out <dir> # output directory
51
+ em catalog <files> -T, --format <fmt> # diagram format embedded in the catalog (svg or png)
52
+ em catalog <files> --title <text> # catalog site title
53
+ em catalog <files> --keep-empty-lanes # keep the API lane even when empty
54
+ em slice new <name> # scaffold a fresh slices/<key>.md doc — the 5 frontmatter keys required at `status: draft`, the `# Slice:` heading and diagram-image stub, then every template section: the generated regions (command/event/view field tables, Invariants) filled from the model with --wire (template placeholders otherwise — `em slice sync` fills them), the authored sections (Intent, Scenarios, Open Questions, ...) with placeholder bullets (see .claude/skills/event-modeling-shared/reference/slice-doc-schema.md, templates/slice.md)
55
+ em slice new <name> --pattern <pattern> # slice pattern: state-change | state-view | automation | translation
56
+ em slice new <name> --swimlane <swimlane> # swimlane, e.g. "Persona → Context"
57
+ em slice new <name> -f, --force # overwrite the file if it already exists
58
+ em slice new <name> --wire <model-file> # also insert the `note "slices/<key>.md"` line onto the slice's primary element in this .em file (matched by export key), instead of just printing it to paste by hand (MIL-161)
59
+ em slice new <name> --stub # write a near-free stub instead: same 5 frontmatter keys, but a one-line placeholder body instead of the diagram-image stub and every judgment section (MIL-184) — deepen it later by re-running without --stub and -f
60
+ em slice sync <model-file> [key] # regenerate the GENERATED regions (command/event/view field tables, the Invariants list) of existing slice docs in place from the model, authored sections untouched (MIL-266); docs without regions are skipped with a note. --check never writes and exits 1 when a doc is stale
61
+ em slice sync <model-file> [key] --check # report ok / stale / no-regions per doc without writing; exit 1 when any is stale
62
+ em slice sync <model-file> [key] --json # print the machine-readable report (sliceSyncSchemaVersion 1.0)
63
+ em slice stub-all <file> # scaffold + wire a near-free stub (`em slice new --stub`'s content, MIL-184) for every slice in <file> with no resolvable doc — the fast path to status coloring for an exploratory/backbone model without hand-running `slice new` per slice. Skips a continuation slice (MIL-208, it has no doc of its own), an already-documented slice, and a slice whose pattern can't be classified
64
+ em slice stub-all <file> --status <status> # target status for every stub: draft | reviewed | ready-to-implement | implemented
65
+ em slice stub-all <file> --by <name> # identity for reviewedBy/ratifiedBy — required unless --status draft
66
+ em slice stub-all <file> --implemented-in <url> # PR/commit URL for implementedIn — required with --status implemented
67
+ em slice stub-all <file> --dry-run # list what would be stubbed/wired without writing anything
68
+ em slice index <file> # rewrite the model's sibling README.md's GENERATED Slices table from `em export`'s slice facts (key, pattern, doc status/implementedIn) — the hand-maintained table is deprecated
69
+ em slice index <file> --check # verify the table is current; exit non-zero on drift without writing (CI)
70
+ em slice mark-implemented <file> <slice-key> <pr-url> # flip a slice doc's frontmatter to `status: implemented` / `implementedIn: <pr-url>` — the one edit an implementing agent makes to a ratified doc at merge (MIL-103, replaces the em-sdd-bridge `em-sdd-mark-implemented` script; see reference/implement.md §6). Idempotent on the same URL; refuses to overwrite a different one; never touches `version:` or the doc body
71
+ em slice review <file> <slice-key> # flip a slice doc's frontmatter to `status: reviewed` and record `reviewedBy:`/`reviewedOn:` — the FIRST of the two human gates (MIL-201, docs/process.md#the-slice-lifecycle-gates): the room walked this slice and every open question it raised is resolved. Ratification is a separate, later gate (`em slice ratify`). Legal from `status: draft`; idempotent on the same --by/--on pair; refuses to overwrite a different one already recorded; refuses a doc already `ready-to-implement`/`implemented`; never touches `version:` or the doc body
72
+ em slice review <file> <slice-key> --by <name> # the reviewer's (or facilitator's) name
73
+ em slice review <file> <slice-key> --on <date> # review date, YYYY-MM-DD (default: today, local date)
74
+ em slice ratify <file> <slice-key> # flip a slice doc's frontmatter to `status: ready-to-implement` and record `ratifiedBy:`/`ratifiedOn:` — the handoff sign-off (MIL-165, docs/process.md#the-slice-lifecycle-gates) that makes who ratified, and when, a first-class recorded fact. The review gate is a check on the doc's current `status:`, not on whether `reviewedBy:`/`reviewedOn:` are populated: a doc at `reviewed` or `ready-to-implement` passes (the latter is where `em slice reratify` leaves a doc, so the follow-up ratify needs no --skip-review); any other status — a `draft` that never went through `em slice review` — is refused unless --skip-review is passed. Idempotent on the same --by/--on pair; refuses to overwrite a different one already recorded; never touches `version:` or the doc body
75
+ em slice ratify <file> <slice-key> --by <name> # the ratifier's name
76
+ em slice ratify <file> <slice-key> --on <date> # ratification date, YYYY-MM-DD (default: today, local date)
77
+ em slice ratify <file> <slice-key> --skip-review # ratify without a recorded review — prints a loud notice on stderr
78
+ em slice ratify <file> <slice-key> --meaning-unchanged # API-first sign-off (MIL-238): this version does not change what the public contract means — records `meaningConfirmed: true`; required (or --contract-change) on a slice touching a `public` element
79
+ em slice ratify <file> <slice-key> --contract-change <why> # API-first sign-off (MIL-238): a consumer must read this change differently, and why — records `contractChange: "<why>"`
80
+ em slice reratify <file> <slice-key> # bump `version:` and flip a shipped slice doc's frontmatter back to `status: ready-to-implement` — the re-ratification mechanical edit (MIL-161, mirrors `em slice mark-implemented`). Applies to a doc at `status: implemented`, or (MIL-258) to a `ready-to-implement` doc that is ratified but not yet shipped — there it bumps `version:` and clears the sign-off but leaves `status:` alone; a `ready-to-implement` doc with no `ratifiedBy:` refuses (awaiting `em slice ratify --by`). Clears any stale `ratifiedBy:`/`ratifiedOn:`/`reviewedBy:`/`reviewedOn:` (they describe the PRIOR version's review and sign-off) so a follow-up `em slice ratify --by` applies cleanly — and needs no fresh review; never touches `implementedIn:` or the doc body
81
+ em slice reratify <file> <slice-key> --meaning-unchanged # API-first sign-off (MIL-238): the new version does not change what the public contract means — records `meaningConfirmed: true`; required (or --contract-change) on a slice touching a `public` element
82
+ em slice reratify <file> <slice-key> --contract-change <why> # API-first sign-off (MIL-238): a consumer must read the new version differently, and why — records `contractChange: "<why>"`
83
+ em slice defer <file> <slice-key> <question> # record a question this version will not answer as deferred to a later version (MIL-275): rewrites the single unchecked `## Open Questions` item containing <question> as `- [x] <item> — v<current>: <decision>; deferred to v<n> (<date>[, <by>])` so `--slice-ready` passes without a guess, and mirrors it into `.event-modeling.md` (the parking lot and a dated Decisions-log bullet; the file must exist, it is never created). Idempotent; `em slice reratify` re-opens the item when it bumps to v<n>
84
+ em slice defer <file> <slice-key> <question> --until <version> # the version that must answer it, v<n> (greater than the doc's version)
85
+ em slice defer <file> <slice-key> <question> --decision <text> # what this version does instead
86
+ em slice defer <file> <slice-key> <question> --by <name> # who deferred it
87
+ em slice defer <file> <slice-key> <question> --on <date> # deferral date, YYYY-MM-DD (default: today, local date)
88
+ em slice conform <file> <slice-key> # record per-slice-per-version conformance certification: sets `conformedVersion:`/`conformedAt:`/`conformedOn:` on a slice doc (MIL-214) — the fact `driftSignal: in-sync` now depends on. Legal only for `status: implemented` with an `implementedIn:` link. Idempotent on the same (version, --at) pair; a different --at simply overwrites (a later re-certification is normal — there's no way for em to tell 'later' from 'earlier' for an arbitrary revision string, so there's no --force to reach for). Refuses when the newest conformance/*-findings.json matching --at still has an unruled finding in scope for this slice; --skip-findings-check overrides with a loud notice.
89
+ em slice conform <file> <slice-key> --at <rev> # the target-repo revision this certification sweep diffed against
90
+ em slice conform <file> <slice-key> --on <date> # certification date, YYYY-MM-DD (default: today, local date)
91
+ em slice conform <file> <slice-key> --skip-findings-check # certify even with unruled findings in scope — prints a loud notice on stderr
92
+ em changelog <file> # render a model's git history as a business-readable ledger (see docs/cli.md)
93
+ em changelog <file> --from <rev> # start the walk at this revision (inclusive)
94
+ em changelog <file> --to <rev> # end the walk at this revision (inclusive; default HEAD)
95
+ em changelog <file> -o, --out <path> # write to a file instead of stdout
96
+ em state read [dir] # print the state file's mechanical fields as JSON
97
+ em state set-phase <phase> [dir] # rewrite Current phase: (and Last updated:); --step also rewrites Current step:
98
+ em state set-phase <phase> [dir] --step <n> # also set Current step: to this value
99
+ em state set-conformance <revision> [dir] # rewrite Last conformance: (and Last updated:) in the exact format reference/conform.md parses. Refuses (MIL-214) while any `implemented` slice has an unruled conformance finding in the findings JSON beside --report (a `slice: null` finding blocks every slice); --partial records the marker with a ` (partial)` suffix instead, with a loud notice. Without a findings JSON beside --report: warns once and records the marker as given (migration path for a report predating `em conform-findings`)
100
+ em state set-conformance <revision> [dir] --report <path> # path to the conformance report just written
101
+ em state set-conformance <revision> [dir] --partial # record the marker as PARTIAL even though some in-scope findings are still unruled
102
+ em state set-review <date> [dir] # rewrite Last stakeholder review: (and Last updated:)
103
+ em state log-usage <file> # append one Usage log line — phase(s) touched + em validate's diagnostic categories hit, deduped and canonically formatted (MIL-161, docs/usage-data.md) — the mechanical half of 'save state at the end of every session' that used to be run-validate-then-hand-format; state file resolved next to <file>, same convention as em conform-scope
104
+ em state log-usage <file> --phases <list> # comma-separated phase(s) touched this session: discover, extract, model, slice, implement, conform, review, validate, watch, engagement
105
+ em model version bump <file> # bump the model's design version: write model-versions/v<N+1>.json (sibling of slices/, conformance/) and rewrite the state file's `Model version:` bullet (MIL-218). Refuses when the state file is missing (run `em scaffold` first), when --by is empty, and when nothing has changed since the current version (same model content hash AND same slice-version vector) unless --force — a no-op bump is the one case --force is right for.
106
+ em model version bump <file> --by <name> # the bumper's name
107
+ em model version bump <file> --on <date> # bump date, YYYY-MM-DD (default: today, local date)
108
+ em model version bump <file> --force # bump even though nothing has changed since the current version
109
+ em model version show <file> # print the model's current design version and the most recently certified version, if any (model-versions/*.json — MIL-218)
110
+ em model version show <file> --json # print a JSON document instead of text
111
+ em usage-report [root] # aggregate every .event-modeling.md's Usage log under [root] into phase/diagnostic-category tallies (MIL-161) — replaces docs/usage-data.md's hand-rolled grep/awk/sort pipeline; a logged line that doesn't match the canonical em state log-usage format is reported under unparseableLines rather than silently mistallied or dropped
112
+ em usage-report [root] --json # print a JSON document instead of the text report
113
+ em conform-scope <file> # mechanize conform phase step 1 (reference/conform.md): map the target repo's changed paths since Last conformance: to slices via each slice doc's implementedIn, JSON to stdout — --seed-asis also seeds the <model>-asis.em scratch model (see docs/cli.md)
114
+ em conform-scope <file> --repo <path> # path to (or inside) the target codebase's git repository
115
+ em conform-scope <file> --full # ignore Last conformance:/changed paths; scope every implemented slice
116
+ em conform-scope <file> --seed-asis # write <model>-asis.em as a byte copy of the canonical model and ensure it's gitignored
117
+ em conform-supersede <file> <report-path> # stamp a conformance report with a "superseded as of <rev>" banner once its findings have been ruled on (MIL-164, docs/process.md#what-ratified-means) — the companion step to run at ratification time so a reader following the report's file:line citations knows they describe an ancestor of the current model. Additive-only splice, never a rewrite of the report; idempotent on the same --as-of/--findings/--on stamp; refuses if the report doesn't exist. --locus/--by (MIL-214, both required together) additionally record the ruling itself — locus/resolvedBy/resolvedOn — on the findings named by --findings in the sibling conformance/<date>-findings.json, when one exists (warns once and stamps the banner only when it doesn't — the migration path for a report predating em conform-findings)
118
+ em conform-supersede <file> <report-path> --as-of <rev> # the revision this ruling was made against — same value passed to `em state set-conformance`
119
+ em conform-supersede <file> <report-path> --findings <spec> # which finding number(s) this stamps as ruled, e.g. "1-3" or "1,2,4"
120
+ em conform-supersede <file> <report-path> --on <date> # ruling date, YYYY-MM-DD (default: today, local date)
121
+ em conform-supersede <file> <report-path> --locus <locus> # who/what the named finding(s) say is wrong: model | doc | code | none (requires --by)
122
+ em conform-supersede <file> <report-path> --by <name> # who ruled on the named finding(s) (requires --locus)
123
+ em conform-findings check <path> # shape-validate a conformance/<date>-findings.json file (findingsSchemaVersion, required fields, enum values, ids sorted+unique, a ruled finding carries resolvedBy+resolvedOn) — exit 1 on shape errors. The conform skill writes this file directly alongside its report; this is how a headless run verifies what it just wrote.
124
+ em conform-findings check <path> --json # print a JSON document instead of the text report
125
+ em watch <file> # re-render on every save
126
+ em watch <file> -o, --out <path> # output path (extension picks the format)
127
+ em watch <file> -T, --format <fmt> # output format (svg, png, pdf, ...)
128
+ em watch <file> --keep-empty-lanes # keep the API lane even when empty
129
+ em watch <file> --serve # serve a live viewer with instant push-reload (no polling)
130
+ em watch <file> --port <n> # port for --serve (default 5173)
131
+ em validate <file> # check a model against event-modeling rules
132
+ em validate <file> --list-issues # print only open `issue` diagnostics (slice, element, line, text)
133
+ em validate <file> --list-divergences # print only accepted-divergence annotations (slice, element, line, text) — never fails the build
134
+ em validate <file> --list-public # print only commands, events and views marked `public` (slice, kind, name, line) — an integration-surface audit, never fails the build
135
+ 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)
136
+ 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, a recorded ratifiedBy, and — for a slice touching a `public` element — a current contracts/<model key>.tsp; exits non-zero if not ready (MIL-87, MIL-259, MIL-238)
137
+ em validate <file> --json # print a JSON document instead of text — works on a model WITH errors, unlike `em export` (MIL-128, see docs/cli.md); exit codes are unchanged
138
+ em migrate <file> # rewrite the old two-slice Automation/Translation shape into the merged single-slice shape MIL-120 made canonical (see docs/cli.md)
139
+ em migrate <file> --write # apply the rewrite to the file (default: dry run — report only, write nothing)
140
+ 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)
141
+ em ledger <file> --from <rev> # baseline revision
142
+ em ledger <file> --to <rev> # compare revision (default: current working tree)
143
+ em ledger <file> --waive <slice-key> # excuse a doc-content-without-version-bump finding for this slice key (repeatable; MIL-185, see docs/cli.md) — never waives a version regression or bump-without-content-change
144
+ em ledger <file> --json # print a JSON document instead of the text report (see docs/cli.md)
145
+ em coverage <file> # check that every INV-* invariant ID cited in an implemented slice doc is cited by a test under --tests <dir> (MIL-130/MIL-207) — mechanizes reference/implement.md's definition-of-done citation check; advisory by default, --strict for CI; --include-ready also counts ready-to-implement docs (forward-looking report); a continuation slice (an `again` view instance with no doc of its own, MIL-208) is excluded from the report — its invariants, if any, live in the originating slice's own doc; --slice <key> scopes the report to one slice whatever its status (MIL-255), the pre-merge definition-of-done check
146
+ em coverage <file> --tests <dir> # directory to scan recursively for test files citing invariant IDs
147
+ em coverage <file> --slice <key> # only this slice (export key), whether ready-to-implement or implemented — no --include-ready needed; --strict then fails on this slice's uncited invariants, or if the slice is not in scope (draft/reviewed/unbound: nothing checked) (MIL-255)
148
+ em coverage <file> --strict # exit non-zero if any invariant ID has zero citations (CI)
149
+ em coverage <file> --include-ready # also count ready-to-implement docs, not just implemented (MIL-207)
150
+ em coverage <file> --json # print a JSON document instead of the text report (see docs/cli.md)
151
+ em status <files> # deterministic state-of-the-system rollup over one or more .em models: slices by lifecycle status, driftSignal breakdown, invariant coverage totals (with --tests), open issue markers + unchecked Open Questions, and last-conformance commits-behind-HEAD (MIL-163, see docs/cli.md)
152
+ em status <files> --tests <dir> # directory to scan for INV-* test citations — enables invariant coverage totals
153
+ em status <files> --repo <path> # git repo to compute commits-behind-HEAD in (default: each model's own directory)
154
+ em status <files> --json # print a JSON document instead of the text report (see docs/cli.md)
155
+ em status <files> --md # print a markdown block suited for README embedding
156
+ em status <files> --badge # print a generated SVG badge
157
+ em status <files> -o, --out <path> # write output to a file instead of stdout
158
+ em freshness <file> # standalone freshness signal for one model's conformance record (MIL-164): "last conformed <rev> — N commits and M slice-PRs behind HEAD", computed from the same conform-scope machinery `em status`'s conformance clause uses — for when you want just this fact, no full state-of-the-system rollup (see docs/cli.md)
159
+ em freshness <file> --repo <path> # git repo to compute behind-HEAD in (default: the model's own directory)
160
+ em freshness <file> --json # print a JSON document instead of the text line
161
+ em metrics <file> # the pilot metrics named in advance by the register, computed from git history alone (MIL-170): ratification turnaround (reviewed -> ratified -> implemented), conform-cycle cadence + finding counts, and status-vs-reality disagreement over time — plus a fourth, reported as not computable from history (see docs/usage-data.md). `<file>` is an anchor .em file, used only to locate slices/, conformance/, and .event-modeling.md relative to it — same convention as em ledger; never parsed or compiled
162
+ em metrics <file> --from <rev> # baseline revision
163
+ em metrics <file> --to <rev> # compare revision (default: HEAD)
164
+ em metrics <file> --json # print a JSON document instead of the text report (see docs/cli.md)
165
+ em query consumers <files> # views/reactions consuming an event, plus their slices
166
+ em query consumers <files> --event <ref-or-name> # the event's export ref or display name
167
+ em query consumers <files> --json # print a JSON document instead of the text report
168
+ em query producers <files> # commands producing an event, plus their slices and ui triggers
169
+ em query producers <files> --event <ref-or-name> # the event's export ref or display name
170
+ em query producers <files> --json # print a JSON document instead of the text report
171
+ em query downstream <files> # transitive closure along legal edges from an element — impact analysis
172
+ em query downstream <files> --of <ref-or-name> # the starting element's export ref or display name
173
+ em query downstream <files> --depth <n> # limit traversal to n hops (default: unlimited)
174
+ em query downstream <files> --json # print a JSON document instead of the text report
175
+ em query upstream <files> # transitive closure against legal-edge direction from an element
176
+ em query upstream <files> --of <ref-or-name> # the starting element's export ref or display name
177
+ em query upstream <files> --depth <n> # limit traversal to n hops (default: unlimited)
178
+ em query upstream <files> --json # print a JSON document instead of the text report
179
+ em query slices <files> # filtered slice list — pattern/status/context/persona/tag filters AND-combine
180
+ em query slices <files> --pattern <p> # state-change | state-view | automation | translation | unclassified
181
+ em query slices <files> --status <s> # the slice's joined doc status (draft, reviewed, ready-to-implement, implemented, ...)
182
+ em query slices <files> --context <c> # match a slice with an event in this @Context
183
+ em query slices <files> --persona <p> # match a slice with a ui in this @Persona
184
+ em query slices <files> --tag <t> # match a slice with an event carrying this tag key
185
+ em query slices <files> --json # print a JSON document instead of the text report
186
+ em query invariant <files> # declaring slice + doc facts for one INV-* id, and (with --tests) its test citations
187
+ em query invariant <files> --id <inv-id> # the INV-* id to look up
188
+ em query invariant <files> --tests <dir> # directory to scan for test files citing this id
189
+ em query invariant <files> --json # print a JSON document instead of the text report
190
+ em query field <files> # one field's facts on an element — type, tag/assigned markers, renamed-from chain
191
+ em query field <files> --of <element-ref> # the element's export ref or display name
192
+ em query field <files> --name <field> # the field's name
193
+ em query field <files> --json # print a JSON document instead of the text report
194
+ em query path <files> # shortest path between two elements through the six legal connection types
195
+ em query path <files> --from <ref-or-name> # the starting element's export ref or display name
196
+ em query path <files> --to <ref-or-name> # the ending element's export ref or display name
197
+ em query path <files> --json # print a JSON document instead of the text report
198
+ em engagement new <file> <slug> # write <model dir>/engagements/<slug>.md — frontmatter (slices, parallel ceiling) plus the generated Ledger table; selection by --slices, --context, or --downstream-of (exactly one)
199
+ em engagement new <file> <slug> --slices <keys> # comma-separated slice export keys
200
+ em engagement new <file> <slug> --context <name> # every slice with an event in this context
201
+ em engagement new <file> <slug> --downstream-of <ref> # a slice key, element ref, or display name: it and everything downstream of it (loops-to excluded)
202
+ em engagement new <file> <slug> --parallel <n> # how many slices of one level may be in flight at once
203
+ em engagement new <file> <slug> --by <name> # who created the engagement (createdBy)
204
+ em engagement new <file> <slug> --force # overwrite an existing engagement file with this slug
205
+ em engagement plan <file> <slug> # the engagement's build plan: dependency levels over model.edges (loops-to excluded), and per slice its readiness, branch impl/<key>, base (main or impl/<upstream>), and any hold
206
+ em engagement plan <file> <slug> --json # print a JSON document instead of text (same document as the MCP engagement_plan tool)
207
+ em engagement set <file> <slug> <key> # the only write path to the Ledger: set one slice's state (and optionally branch/base/pr); idempotent; merged is terminal; --state held records a human hold
208
+ em engagement set <file> <slug> <key> --state <state> # one of: planned | building | validating | review | awaiting-merge | merged | held | gap
209
+ em engagement set <file> <slug> <key> --branch <name> # the slice's branch
210
+ em engagement set <file> <slug> <key> --base <name> # the branch the slice's PR targets
211
+ em engagement set <file> <slug> <key> --pr <url> # the slice's PR URL
212
+ em engagement status <file> <slug> # the Ledger joined with each slice doc's current status, plus whether the engagement is closable
213
+ em engagement status <file> <slug> --json # print a JSON document instead of text (same document as the MCP engagement_status tool)
214
+ em engagement close <file> <slug> # set status: closed — refuses unless every slice is merged (recorded or inferred) or gap
215
+ em system [target] # verify a system — every model's `consumes` refs resolved against the other models' `public` events/views, the cross-model half of "both ends of a flow" (MIL-194/MIL-235, see docs/cli.md). Reads system.yaml when given (or found in the directory), else discovers every *.em in the repo
216
+ em system [target] --json # print a JSON document instead of the text report (see docs/cli.md)
217
+ em system codeowners [target] # generate (or --check) the managed CODEOWNERS block that routes review: each model's team on its design directory, and every team that `consumes` a model's public surface on that model's contract file (MIL-234, see docs/ci.md and docs/cli.md)
218
+ em system codeowners [target] -o, --output <path> # the CODEOWNERS file to splice into (default: the first of CODEOWNERS, .github/CODEOWNERS, docs/CODEOWNERS that exists, else CODEOWNERS at the repo root)
219
+ em system codeowners [target] --check # verify the committed file already carries the generated block; never writes, exit 1 on drift (CI)
220
+ em system codeowners [target] --json # print a JSON document instead of the text report (see docs/cli.md)
221
+ em system scope [target] # check a change set against the seams (MIL-240): fail only when it alters a producer's public surface (or contract file) AND a consuming model's design dir together (`seam-crossing`); warn on other multi-model change sets. Files changed only by `Em-Upgrade:` commits are exempt. There is no override - review on the contract file is the gate (see docs/cli.md, docs/ci.md)
222
+ em system scope [target] --base <rev> # the change set committed since <rev> (merge-base form, <rev>...HEAD); CI passes the PR base
223
+ em system scope [target] --staged # also (or only) check what is staged right now; never exempted
224
+ em system scope [target] --json # print a JSON document instead of the text report (see docs/cli.md)
225
+ em contract # print the packaged implementation contract (reference/implement.md) to stdout — the agent-neutral discovery path for any agent that can run a shell, not just Claude Code (MIL-129); see docs/cli.md
226
+ em mcp # start an MCP (Model Context Protocol) server over stdio (MIL-21) — a structured, agent-facing alternative to shelling out to `em`; every tool mirrors a CLI `--json` surface byte-for-byte. docs/mcp.md isn't vendored into the skill bundle — for the handshake and the full, current tool table (the list changes as commands gain MCP parity, so it's not repeated here — MIL-187) see https://github.com/milehimikey/em/blob/main/docs/mcp.md. Equivalent to running the `em-mcp` bin directly
227
+ em skill install # copy the event-modeling skill bundle into .claude/skills/ (event-modeling, event-modeling-discover/-design/-implement/-conform/-review, event-modeling-shared); across a structural bundle change (e.g. the MIL-157 split of the old single event-modeling/ directory into this six-directory bundle), `em skill sync` is the migration path — --force now performs the same reconcile (MIL-180)
228
+ em skill install -f, --force # overwrite an existing installation, reconciling added/updated/removed files within the bundle's own managed directories the same way `em skill sync` does — never touching an unrelated sibling skill (MIL-180)
229
+ em skill install --no-agents-md # skip writing/updating the AGENTS.md agent-contract section (on by default, MIL-129)
230
+ em skill sync [path] # update the vendored .claude/skills/ event-modeling skill bundle in [path] to match the installed em package (overwrites unconditionally; local edits are never merged, MIL-93)
231
+ em skill sync [path] --no-agents-md # skip writing/updating the AGENTS.md agent-contract section (on by default, MIL-129)
232
+ em skill check [path] # check [path]'s em skills for drift against the installed em package: the em Claude Code plugin's pin (settings + this machine's marketplace registry) when the repo declares it, and the vendored .claude/skills/ bundle when present; exits non-zero on any mismatch (CI-ready, MIL-93, MIL-231)
233
+ em skill check [path] --json # print a JSON document instead of the text report (see docs/cli.md)
234
+ em skill check [path] --ci # also fail (exit 1) when the declared plugin is not registered on this machine (implied by CI=true in the environment); without it that finding prints the install commands on stderr and exits 0
235
+ em ci init <model> # install .github/workflows/em-ci.yml (PR gates: em validate, em slice index --check, em coverage --strict, em ledger, em skill check, em glossary --fail-on-conflicts, plus a push-triggered status-badge rebuild) and em-conform.yml (scheduled, advisory-only conformance cadence) — same install discipline as `em skill install`: marker-delimited, idempotent, --check for CI self-verification (MIL-166, see docs/ci.md). Give it a system.yaml (or its directory) instead of a model to cover every model in the system: one workflow, per-model jobs, a conform matrix (MIL-233)
236
+ em ci init <model> --tests <dir> # test directory the coverage/status steps scan for INV-* citations
237
+ em ci init <model> -f, --force # replace an existing workflow file that has no GENERATED markers, or a managed block generated for a different set of models
238
+ em ci init <model> --check # verify both files match the current preset; exit non-zero on drift without writing (CI)
239
+ em upgrade <file> # bring a model repo authored under an older em (1.6 forward) up to the installed version: an ordered list of mechanical steps (skill bundle, reaction shape, state-file bullets, generated CI blocks, constitution scaffold) plus a human list of things no command can safely decide by itself (MIL-219, see docs/upgrading.md). Dry-run by default; --apply makes one git commit per applicable step, refusing on a dirty working tree
240
+ em upgrade <file> --apply # apply every applicable mechanical step, one git commit each, then a final `Em version:` commit
241
+ em upgrade <file> --check # exit non-zero only on a hard incompatibility (an existing but unparseable state file, an un-migratable .em shape) — writes nothing; what CI runs; the final line says why
242
+ em upgrade <file> --json # print a JSON document instead of text (dry-run/--check only, never with --apply)
243
+ ```
244
+ <!-- GENERATED:cli:end -->
245
+
246
+ Install if missing: `npm i -g @milehimikey/em`. SVG, PNG, and PDF all work with no system
247
+ deps; only rarer formats (ps, eps, ...) need `rsvg-convert`.
248
+
249
+ ---
250
+
251
+ ## Grammar
252
+
253
+ ```
254
+ model "Name" [owner "Team"] # diagram title; optional owning team(s), quoted free text
255
+
256
+ persona Name # a UI swimlane row (actor)
257
+ context Name # an event swimlane row (bounded context / aggregate)
258
+
259
+ slice "Name" [source "url"] { # one vertical time step (a column); source is optional
260
+ ui Free Text @Persona # screen; @Persona picks its row (defaults to first/"User")
261
+ command Free Text # state-changing request (API band)
262
+ invariant INV-ORD-1 "Rule" # rule guarding the command/event just above (see Clauses)
263
+ view Free Text from "Event A", "Event B" # read model fed by event(s)
264
+ view Free Text again from "Event C" # later instance of an evolving read model (see Clauses)
265
+ event Free Text @Context # recorded fact; @Context picks its row (defaults to "Domain")
266
+ processor Free Text from "View" # automation; aliases: automation | saga | translation
267
+ translation Free Text consumes other-model:event.slug # binds another model's public event/view
268
+ }
269
+
270
+ arrow From Element -> To Element # explicit cross-slice edge (overrides inferred flow)
271
+
272
+ type Name { field: Type, ... } # named structured type, reusable from any field (see Named types)
273
+ ```
274
+
275
+ ### Element kinds (8 keywords, nothing else)
276
+ | Keyword | Band | Meaning | Tag | Extra clauses |
277
+ |---|---|---|---|---|
278
+ | `ui` | persona | screen / interface | `@Persona` | `note`, `issue`, `divergence`, `{ fields }` |
279
+ | `command` | API | state-changing request | — | `note`, `issue`, `divergence`, `renamed from`, `{ fields }`, standalone `invariant` line |
280
+ | `view` | API | read model / projection | — | `from "Event"…`, `note`, `issue`, `divergence`, `public`, `{ fields }` |
281
+ | `event` | context | recorded fact (past tense) | `@Context` | `note`, `issue`, `divergence`, `public`, `tag`, `renamed from`, `loops-to`, field-level `assigned`, `{ fields }`, standalone `invariant` line |
282
+ | `processor` / `automation` / `saga` / `translation` | automation | system reaction / adapter | — | `from "…"`, `note`, `issue`, `divergence`, `{ fields }`; `translation` only: `consumes` |
283
+
284
+ ### Clauses
285
+ - **Tags:** `@Persona` only on `ui`; `@Context` only on `event`. Undeclared tags auto-create a
286
+ row. Multi-word tags need no quoting — the tag captures everything after `@` to end of line
287
+ (`ui Ticket Queue @Customer Service` matches `persona Customer Service`).
288
+ - **`from "X"`** on views/automations declares the source(s); names are quoted, comma-separated.
289
+ Matching is case-insensitive and whitespace-normalized. A `from` may never point at an event or
290
+ view that first appears in a LATER slice (forward-only timeline — validation error).
291
+ Kinds never cross: a view's `from` resolves ONLY to events; a reaction's `from` resolves ONLY
292
+ to read models — naming an event on a reaction is a validation error even though the event
293
+ exists. Feed an event to a reaction by projecting it into a view first (the automation's
294
+ "to-do list") and pointing the reaction at that view. Name that view after the pending work
295
+ (`Payments To Process`), never after the triggering event — a view reusing the event's name
296
+ collides in the shared namespace and draws a duplicate-name warning.
297
+ - **`again`** (views only): `view <Name> again [from "Event", …]` declares a later instance of an
298
+ already-declared read model — the forward-only device for a view that evolves as later events
299
+ land. Instances are ONE logical view: the first declaration owns the `note` binding; each
300
+ instance's `from` lists only the NEW events landing at that point (not cumulative); a reaction
301
+ (`from "View"`) reads the nearest instance at-or-before its own slice. Instances are NEVER
302
+ connected to one another — continuity is implied by the shared name, and the events reaching each
303
+ instance are what show the view changing. `again` with no earlier declaration is a validation error.
304
+ Use `again` (not a plain repeated `view` name) whenever the view is referenced by a
305
+ `from`/`arrow` — plain repeats are only warning-free while unreferenced. A later instance is
306
+ also a **continuation** for the doc lifecycle (MIL-208): it stays a slice on the timeline but
307
+ is not a spec unit — no doc, no status, no ratification, no PR of its own; the originating
308
+ slice's doc carries its behavior and `em export`'s `alsoReads` carries the union of events it
309
+ reads. See `slice-doc-schema.md`'s "Continuations" section for the full contract.
310
+ - **`loops-to "View"`** (events only): marks an event as re-feeding an earlier read model — a
311
+ poll/retry loop where a later fact must re-trigger an automation that already ran (a lapsed
312
+ reservation re-notifying the next person waiting in line, and similar re-triggers). Written
313
+ after the event's name/`@Context`, either on the header line or trailing an
314
+ inline `{ fields }` block; repeatable (`loops-to "A" loops-to "B"`, or comma-joined). The
315
+ target must resolve to a `view` instance STRICTLY EARLIER on the timeline — the opposite of
316
+ `from`'s "nearest at-or-before" rule; pointing at the same slice or later is a validation
317
+ error naming `from` on a later `view … again` instance as the forward-flowing alternative,
318
+ and naming a view that doesn't exist is a separate unresolved-target error. A resolved
319
+ `loops-to` counts as the event being read for both-ends-of-a-flow completeness. **No arrow is
320
+ ever drawn** for it — drawing one backward would violate the timeline laws — instead the
321
+ event gets an indigo ↩ corner marker (bottom-left) and the legend gains a "Loops" section:
322
+ `↩ 1 <Event> re-feeds "<View>"` (the number identifies the event, not the target). `em
323
+ export` carries it as `loopsTo: { name, ref }[] | null` (`from`'s shape); `model.edges` gains
324
+ `{ from: <event ref>, to: <view ref>, source: "loops-to" }`; `em query`'s adjacency labels the
325
+ connection `"loops-to"` too, so `downstream`/`upstream` follow it (cycle-safely).
326
+ - **`note "path.md"`** on ANY element links a markdown doc. Relative to the `.em` file. Renders as
327
+ a clickable marker in SVG and a legend entry in PNG/PDF. **This is how slice docs attach.**
328
+ - **`issue "text"`** on ANY element flags an open question inline — the diagram-visible red
329
+ sticky note. Renders as a red corner marker (opposite corner from `note`, so both can coexist
330
+ on one element) plus a legend entry; `em validate` warns on every open issue. **Prefer this
331
+ over a `# TBD` comment for anything that should show up on the rendered diagram** — `# TBD` is
332
+ invisible once rendered, `issue` isn't.
333
+ - **`divergence "text"`** on ANY element records a reasoned, ratified deviation between this
334
+ element and its implementation — the *resolved* sibling of `issue` (lint-suppression-with-
335
+ rationale for the `conform` phase). Renders as a teal corner marker (bottom-right — distinct
336
+ from `note`'s top-right and `issue`'s top-left, so all three can coexist) plus a legend entry.
337
+ Raises **no** `em validate` warning by design; use `--list-divergences` to audit. `em diff
338
+ --json` carries it forward as `acceptedDivergence` on the affected change/removal entry, so
339
+ `conform` can cite an already-ratified deviation instead of re-flagging it as drift every run.
340
+ - **`public`** (events and views): marks the element as part of the published integration
341
+ surface — a published event contract (e.g. AsyncAPI), or a view whose read API/response
342
+ shape external consumers depend on — as opposed to an internal-only fact or projection.
343
+ Plain structural flag, no free text and no diagram marker (same posture as `again`). Write
344
+ it as the last token on the line; on an event, optionally right before a trailing
345
+ `@Context` — `event X @Context public` or `event X public @Context` both work. A `public`
346
+ element is exempt from the unread-event / unconsumed-view warnings (its consumer is outside
347
+ this model). `em export` carries it as `public: true`/`false`; `em diff` reports a flip as
348
+ `event marked public`/`event unmarked public` (the entry cites the element's kind, so a
349
+ view flip reads `view "X"`); `em validate --list-public` audits the whole public surface.
350
+ **Exposure of a read model (MIL-215) is three cases, no dedicated keyword:** a person/client
351
+ reads it → draw the `ui` that reads it (ships the endpoint); another model/system reads it →
352
+ `public` (a cross-model contract its consumer binds with `consumes`, verified by `em system`, not `em validate`); only
353
+ an automation reads it → nothing — internal by design, the read a reaction is required to
354
+ make, not an endpoint. `em validate --list-public` flags a `public` view with no `ui`/reaction
355
+ reading it anywhere in this model as an audit note ("no in-model reader"), not a warning — a
356
+ cross-model `public` view's reader legitimately lives outside this file (see "`em validate`
357
+ rules" below).
358
+ - **`consumes`** (translation only, MIL-235): `translation X consumes <modelKey>:<kind>.<slug>[, …]`
359
+ binds this boundary-crossing reaction to another model's `public` event or view. The binding is
360
+ declared on the consumer and has no version. `kind` is `event` or `view` (never `command`).
361
+ `modelKey` is the kebab-slug of the producer's `model "Name"`, and `slug` is the kebab-slug of
362
+ the element's name. There is no slice segment, so the producer can move the element between
363
+ slices without breaking consumers. Refs are unquoted and comma-separated. Write the clause before
364
+ or after `from "…"`, before or after a `{ … }` block, never inside the braces. On any other kind
365
+ it is a parse error: ``line N: `consumes` is only valid on translation — only a boundary-crossing
366
+ reaction binds to another model's public surface``. `em validate` checks the grammar only.
367
+ `em system` resolves the ref (`consumes-unknown-model` / `consumes-unknown-element`) and
368
+ derives `dangling-public-event` / `unbound-translation` from it. Two `public` elements of one
369
+ kind sharing a slug are a `public-name-not-unique` error, because a ref couldn't tell them
370
+ apart. Exported as `consumes: string[] | null`.
371
+ - **`owner`** (model header, MIL-235): `model "Checkout" owner "Storefront team"[, "…"]`. Quoted
372
+ free text, kept as written, exported as `model.owner: string[]`. It replaces a 1.0
373
+ `system.yaml`'s per-model `owner:`.
374
+ - **`tag`** (events only): declares a DCB (Dynamic Consistency Boundary) tag key. Three forms:
375
+ a trailing `tag` on a field line inside the event's `{ … }` block (`priceId: UUID tag`, or
376
+ typeless `priceId tag`) — an identity tag, key defaults to the field's own name; a bare field
377
+ whose ENTIRE text is just `tag` is a field named `tag`, not a clause. `tag <key> from a, b`
378
+ (composite, ≥2 bare/unquoted field names — unlike a view's quoted `from`). `tag <key>
379
+ external "text"` (external — the string is documentation only, never parsed). Composite/
380
+ external clauses are element-level: write them as a trailing clause on the event (same
381
+ family as `note`/`issue`), or as one or more standalone `tag ...` lines immediately following
382
+ the event inside the slice body (attaches to the most recently declared element, which must
383
+ be an event); multiple accumulate. `tag` anywhere on a command/view/`ui`/`type` field, or an
384
+ element-level `tag` clause on a non-event, is a parse error. `em export` carries every tag
385
+ key under the event's `tags` array (`{ key, kind, fields, description }`); `em validate`
386
+ flags a composite tag naming an unknown field and a duplicate tag key on one event.
387
+ - **`renamed from "Old1", "Old2"`** (events and commands only): records the prior name(s) an
388
+ element, or one of its fields, was known as — codegen metadata for converting
389
+ already-stored payloads instead of an upcaster chain. Element-level trails the element's own
390
+ name (same trailing-clause family as `@Context`/`public`); field-level trails a field's type,
391
+ or the bare name of a typeless field, inside the `{ … }` block — event/command fields only, a
392
+ parse error on a view/`ui`/automation-kind field or a `type` field. The list is quoted,
393
+ comma-separated, most-recent-old-name first. Inside one inline `{ … }` block, a bare quoted
394
+ field name immediately after a renamed field is read as a CONTINUATION of that list, not a
395
+ new field — give the quoted-name field a type, or write fields one per line, to avoid the
396
+ ambiguity. `em export` carries it as `renamedFrom: string[] | null` on both the element and
397
+ each field. **`em diff` does not read this clause** — a rename still reports as remove+add.
398
+ - **`assigned`** (event fields only): marks a field as system-assigned — set by the server/
399
+ handler, never supplied by the triggering command (a minted ID, a decision-time timestamp).
400
+ Trails a field's type, or the bare name of a typeless field (`orderId assigned`,
401
+ `placedAt: Instant assigned`) — same trailing-clause family as `tag`/`renamed from`, composes
402
+ with either in any order. A field whose ENTIRE text is just `assigned` is a field named
403
+ `assigned`, not a clause. `assigned` on a command/view/`ui`/`type` field is a parse error.
404
+ Excludes the field from `em validate`'s event ← command fields-completeness check (and thus
405
+ from `--slice-ready`, since the diagnostic never fires) without narrowing view ← event
406
+ tracing. `em export` carries it as `assigned: boolean` on the field, always present, same
407
+ `=== true` convention as `tag`. **Reach for this before echoing a server-set field onto the
408
+ command just to silence a fields-completeness warning** — that misrepresents what the client
409
+ actually sends.
410
+ - **`derived`** (view fields only, MIL-200): marks a field as computed from which events have
411
+ landed, rather than copied from a single source event's payload (a status stepping through
412
+ states as events arrive, a computed ordinal, an availability flag two events can flip).
413
+ Trails a field's type, or the bare name of a typeless field — bare `derived` (no traced
414
+ source) or traced `derived from "Event A", "Event B"` (the two-word form parsed before the
415
+ bare check, same discipline as `renamed from` vs. plain `from`). A field whose ENTIRE text is
416
+ just `derived` is a field named `derived`, not a clause. `derived`/`derived from` on a
417
+ command/event/`ui`/`type` field is a parse error. Excludes the field from
418
+ `fields-completeness/view-field-no-source` entirely (and thus from `--slice-ready`). The
419
+ traced form still checks something: every named event must resolve among the view's ACTUAL
420
+ sources (its own `from` list, or the same-slice events for a `from`-less view) — an
421
+ unresolved name is a validation error, `derived-from-unresolved`. `em export` carries it as
422
+ `derived: boolean` on the field (always present, same `=== true` convention as `tag`/
423
+ `assigned`) and `derivedFrom: string[] | null` (the traced event name(s), `null` on a bare
424
+ `derived` field). **Reach for this instead of dropping a computed field out of the `.em` and
425
+ into doc-only prose** — the marker keeps `em export`/the model aware the field exists (the
426
+ `doc-model-field-mismatch` doc↔model check does not compare view field tables at all today —
427
+ see the slice template's field-table guidance — so this is an export-side win specifically,
428
+ not yet a doc-drift check).
429
+ - **Fields:** `command Place Order { orderId: UUID, items: LineItem[], customerId }` — inline or
430
+ one-per-line. Types are free text (no semantic checking) UNLESS the type string names a
431
+ declared `type` (see Named types below), in which case it resolves to a structured
432
+ reference. Keep these light; full field specs with rules live in the slice doc.
433
+ - **`invariant`** (command/event only, MIL-265): a standalone line directly after the element,
434
+ `invariant INV-ORD-1 "Order total must be positive"` — ID, then an optional quoted rule
435
+ sentence (always write one). Several lines accumulate in order. Parse error after a
436
+ view/`ui`/reaction, before any element, or inside a `{ … }` block. The ID must match
437
+ `INV-<MNEMONIC>-<n>` (2-4 uppercase letters/digits, a number) — `invariants/malformed-id` and
438
+ `invariants/duplicate-id` are errors. **The model declares the ID and the rule; the slice doc's
439
+ Invariants section cites the ID and elaborates** (`- INV-ORD-1 — why, edge cases`), never
440
+ restating it as a `**INV-ORD-1:**` rule (`invariants/declared-in-both`). A model-declared ID
441
+ belongs to its element's slice for `em coverage`/`em query invariant`; doc-declared IDs (the
442
+ 1.13 style) still work as the fallback. `em export` carries `invariants: [{ id, rule }] | null`
443
+ per element.
444
+ - **Comments:** `# ...` anywhere outside quotes (full-line or trailing).
445
+ - **Quoted strings:** everything between a `"` and its matching `"` is literal — `{`, `}`, `#`
446
+ included, never re-interpreted as a field block or comment — so REST path-template
447
+ placeholders are safe as-is: `issue "PUT v3/widgets/{widgetId}/suspend"`. Escape a literal
448
+ `"` or `\` inside a string with `\"`/`\\`; no other backslash sequence is special. An
449
+ unterminated string (missing closing `"`) is a parse error naming the clause it broke in.
450
+ - **`source "url"` on a slice header** (the only slice-level clause — everything above is
451
+ per-element) links the slice back to the ticket/conversation it traces to, e.g.
452
+ `slice "Checkout" source "https://linear.app/team/issue/MIL-60" { ... }`. Exports as
453
+ `model.slices[].source` via `em export`, so an intake loop stays machine-traversable instead
454
+ of relying on prose. Purely metadata — no visual marker, not validated. Don't confuse it with
455
+ an element's `note "path.md"` (a markdown file link, not a URL).
456
+
457
+ ### Named types
458
+
459
+ `type Name { field: Type, ... }` declares a reusable structured shape at the top level (any
460
+ order relative to `persona`/`context`/`slice`, no clauses). Reference it from any field
461
+ anywhere — bare (`winner: QuoteAcceptedLine`) for a nested object, `Name[]` for an array
462
+ (`lines: QuoteAcceptedLine[]`). Resolution is opportunistic: a type string only becomes a
463
+ structured reference when it names a declared type (case/whitespace-insensitive match); every
464
+ other type string stays free text exactly as before, so declaring no `type` blocks changes
465
+ nothing. Recursion between declared types is allowed as a DAG (e.g. a diamond shape like
466
+ `Order` referencing `Address` twice) — a bare/singular self- or mutual-cycle
467
+ (`type Node { child: Node }`) is a validation error, but the same shape through an array
468
+ (`type Node { children: Node[] }`) is legal since the array can terminate at runtime; this is
469
+ how tree/recursive data (categories, org charts, comment threads, BOMs) gets expressed.
470
+ `em export` lists every declared type under `model.types[]` (stable `ref`, e.g.
471
+ `types/quote-accepted-line`) and adds an additive `typeRef` key to every field (declared-type
472
+ fields and ordinary element fields alike) — `{ name, ref, array }` when resolved, `null`
473
+ otherwise. `em diff` tracks types added/removed and field changes on surviving types.
474
+
475
+ ### Swimlane band order (top → bottom)
476
+ Header row → **Automation** (only if used) → **persona** rows (in declared order) → **API**
477
+ (commands + views share this lane) → **context** rows (in declared order).
478
+
479
+ ### Colors (for orientation)
480
+ UI = white, command = blue, event = amber/orange, view = green, automation = gray.
481
+
482
+ ---
483
+
484
+ ## Pattern → DSL mapping
485
+
486
+ ```em
487
+ # 1. State Change: UI -> Command -> Event
488
+ slice "Place Order" {
489
+ ui Checkout @Customer
490
+ command Place Order
491
+ event Order Placed @Order
492
+ }
493
+
494
+ # 2. State View: Event(s) -> Read Model -> UI
495
+ slice "Open Orders" {
496
+ view Open Orders from "Order Placed"
497
+ ui Order List @Customer
498
+ }
499
+
500
+ # 3. Automation: read model in the slice before, reaction + command + event together
501
+ slice "Orders To Fulfill" {
502
+ view Orders To Fulfill from "Order Placed"
503
+ }
504
+ slice "Ship Order" { # reaction, command, and event all share this one slice
505
+ processor Fulfillment Service from "Orders To Fulfill"
506
+ command Ship Order
507
+ event Order Shipped @Shipping
508
+ }
509
+ slice "Open Orders — shipped" {
510
+ view Open Orders again from "Order Shipped" # every event needs a reader
511
+ ui Order List @Customer # ...and every read model needs a consumer
512
+ }
513
+
514
+ # 4a. Translation (external trigger, no durable artifact): external call -> translation -> command -> event
515
+ slice "Confirm Delivery" {
516
+ translation Carrier Adapter # inbound from outside the model; no internal `from`
517
+ command Confirm Delivery
518
+ event Delivery Confirmed @Shipping
519
+ }
520
+ slice "Open Orders — delivered" {
521
+ view Open Orders again from "Delivery Confirmed"
522
+ ui Order List @Customer
523
+ }
524
+
525
+ # 4b. Translation (external trigger, durable artifact): persisted inbound queue -> translation -> command -> event
526
+ slice "Receive Carrier Event" { # ingest: persist the raw webhook before reacting to it
527
+ ui Webhook Endpoint @Carrier
528
+ command Receive Carrier Event
529
+ event Carrier Event Received @Shipping # scoped to @Shipping's fact, not the @Carrier caller
530
+ }
531
+ slice "Inbound Carrier Events" {
532
+ view Inbound Carrier Events from "Carrier Event Received" # the persisted inbound queue
533
+ }
534
+ slice "Acknowledge Carrier Event" {
535
+ translation Carrier Queue Adapter from "Inbound Carrier Events" # different producer than 4a's Carrier Adapter, so a distinct name
536
+ command Acknowledge Carrier Event
537
+ event Carrier Event Acknowledged @Shipping
538
+ }
539
+ slice "Inbound Carrier Events — processed" {
540
+ view Inbound Carrier Events again from "Carrier Event Acknowledged"
541
+ ui Delivery Board @Ops
542
+ }
543
+
544
+ # 4c. Translation (internal trigger, durable artifact): read model -> translation -> command -> event
545
+ slice "Accept Quote" {
546
+ ui Quote Screen @Customer # every command needs a trigger: a ui, or a reaction, in this slice
547
+ command Accept Quote
548
+ event Quote Accepted @Quote
549
+ }
550
+ slice "Quotes To Sync" {
551
+ view Accepted Quotes from "Quote Accepted"
552
+ }
553
+ slice "Record Sync" {
554
+ translation CRM Sync from "Accepted Quotes" # reacts to our own state via the read model
555
+ command Record Crm Sync
556
+ event Quote Synced @Quote
557
+ }
558
+ slice "Accepted Quotes — synced" {
559
+ view Accepted Quotes again from "Quote Synced"
560
+ ui Sync Status @Customer
561
+ }
562
+ ```
563
+
564
+ Trigger source (external/internal) and durable artifact (`view`-backed or not) are independent
565
+ axes — 4a and 4b are both "externally triggered" but only 4b has a queue, and a `view`-backed
566
+ translation reacts the same whether the view was filled by an outside system (4b) or the model's
567
+ own event (4c). An inbound message earns `event` status the same way any event does: scope it to
568
+ the context/lane whose fact it represents (e.g. `Carrier Event Received @Shipping` or its own
569
+ `Carrier` context), not by who committed it — that keeps it a legitimate boundary fact (à la an
570
+ Anti-Corruption-Layer event in DDD) instead of a technical artifact leaking foreign vocabulary
571
+ into the model.
572
+
573
+ A `translation` (like a `processor`) is a **reaction**: it triggers a command in the same slice —
574
+ same shape as the Automation pattern above, and the same shape `ui` already uses in State Change.
575
+ The reaction never records the event directly (there's no `reaction -> event` edge); the command
576
+ it triggers is what records it, and that event happens to live in the reaction's own slice now
577
+ because the command does.
578
+
579
+ Note the read slice closing each pattern: **every event must be read by some read model**
580
+ (warning 4 below). A command slice is not finished until the slice that projects its event
581
+ exists. Reactions don't count — they read *views*, not events.
582
+
583
+ ### Headless / API systems & repeated read models
584
+
585
+ A headless system (no screens — clients call an API) still uses `ui`/`persona`: a slice **is**
586
+ trigger → command → event, read vertically, so the trigger belongs *in* the slice, never split into
587
+ one of its own. Declare a persona per external caller/role and treat its `ui` boxes as API calls
588
+ instead of shipped screens — same two patterns (State Change, State View) as any other slice, no new
589
+ shape:
590
+
591
+ ```em
592
+ persona IntegratorAPI # API-flagged lane: its `ui` boxes are API calls, not shipped screens
593
+
594
+ slice "Create Quote" {
595
+ ui Create Quote @IntegratorAPI
596
+ command Create Quote
597
+ event QuoteCreated @Quote
598
+ }
599
+
600
+ slice "Read Quote — created" {
601
+ view Quote from "QuoteCreated"
602
+ ui Read Quote @IntegratorAPI
603
+ }
604
+ ```
605
+
606
+ - **`translation` stays reserved for genuine reactions and real external-system boundaries** — an
607
+ internal automation, or a webhook/adapter crossing into another system (see the Automation and
608
+ 4a/4b/4c Translation examples above, unchanged). It is **not** how you model a synchronous
609
+ request/response API call — that's Pattern 1 (State Change) with an API persona, exactly like the
610
+ `Create Quote` slice above.
611
+ - **Internal-only commands and views (no public route) carry no `ui` at all.** They follow the
612
+ ordinary Automation pattern already documented: the reaction shares the command's slice, and an
613
+ internal-only read model is consumed by that reaction (from the slice before), not by a screen
614
+ or an API query.
615
+ - **Repeat the read model** in every slice where it's read so the timeline flows left-to-right, and
616
+ **prefer `view X again`** for every instance after the first (see Clauses) — **each instance
617
+ carries its own consumer** (the `ui` that reads it, or a reaction), not just the last one. `again`
618
+ instances are exempt from the duplicate-name warning even when referenced, and each reference
619
+ resolves to the right instance — a plain repeat only stays warning-free while nothing references it by name, and
620
+ resolves to the *first* declaration when something does. **Wire each event to a read model exactly
621
+ once:** a repeated instance's `from` lists only the **new** events since the previous instance
622
+ (not cumulative), or the event draws a duplicate arrow to the same read model at every repeat. (An
623
+ event may still feed several *different* read models, once each.)
624
+ - **Instances are never joined to one another.** No arrow between two instances of one read model,
625
+ ever — an explicit one is a validation error. The repeat is a timeline device: continuity is
626
+ implied by the shared name, and the events arriving at each instance are what show it changing.
627
+ - **Keep arrows span-1: put each repeat right after its feeding event.** Place a read-model instance
628
+ immediately after the event that updates it, sourcing only that single adjacent event. The
629
+ renderer routes a long arrow *around* intervening boxes rather than through them, so it no longer
630
+ reads as a forbidden read→read link — but distance is still a real problem: the arrowhead lands
631
+ columns away from the event that produced it, so **the write slice reads as dangling**, as if
632
+ nothing consumed its event. You have to trace a line across the diagram to see the connection.
633
+ Keep the read model adjacent to its event, and keep a sub-flow that detours into another context
634
+ together rather than parking it at the end of the model.
635
+
636
+ What this means for whoever builds a slice, not just whoever draws it: a `ui` trigger or
637
+ consumer is an obligation to ship an endpoint, no `ui` means internal-only, and `public` marks
638
+ the published integration surface — see `reference/implement.md`'s "Interface obligations"
639
+ paragraph in the `event-modeling-implement` skill (§2).
640
+
641
+ ### Reaction wiring — no positional gotcha
642
+
643
+ A reaction's command is inferred from **same-slice presence**, exactly like `ui -> command`: put
644
+ `processor`/`translation` in the same slice as the command it triggers, in either order, and `em`
645
+ wires it correctly regardless of what any other slice contains or where it sits in the file. There's
646
+ no adjacency to get right and no reordering hazard — the read model it watches (if any) is named
647
+ explicitly via `from "View"`, resolved by name to the nearest instance at or before its own slice,
648
+ not by position either.
649
+
650
+ The one thing that still doesn't wire on its own: a `ui` never triggers a reaction — no pattern has
651
+ a `ui` wired to `processor`/`automation`/`saga`/`translation`, only to `command`. A `ui` left in a
652
+ reaction's slice renders with no outgoing edge, disconnected, and `em validate` warns on it — move
653
+ it to the slice that displays the read model, or drop it.
654
+
655
+ ---
656
+
657
+ ## `em validate` rules (design to satisfy these)
658
+
659
+ **Errors (must fix):**
660
+ 1. **Band collision** — two elements of the same band in one slice (e.g. two `command`s, or two
661
+ `ui`s in the *same* persona row). Split them into separate slices/personas.
662
+ 2. **Unknown event source** — `view X from "Event"` where the event doesn't exist anywhere.
663
+ 3. **Unknown read-model source** — `processor X from "View"` where the view doesn't exist.
664
+ 4. **Arrow endpoint mismatch** — `arrow A -> B` where A or B matches no element name.
665
+ 5. **Backward timeline** ("time flows left to right") — an event feeding a view instance in an
666
+ EARLIER slice (fix: add a `view X again` instance where the event lands and move the source
667
+ there), a reaction reading a view before any instance of it exists, or an explicit backward
668
+ `arrow`.
669
+ 6. **`again` without an earlier declaration** — declare the view plainly the first time it appears.
670
+ 7. **Illegal connection** — an `arrow` joining kinds the patterns don't connect. Only
671
+ `ui -> command`, `command -> event`, `event -> view`, `view -> ui`, `view -> reaction`, and
672
+ `reaction -> command` are allowed. A command straight to a view is the CQRS violation (an event
673
+ has to sit between them); a view straight to a command needs a reaction between them; and two
674
+ instances of one view are never connected at all. Inferred edges are always legal, so this only
675
+ ever fires on a hand-written `arrow`.
676
+ 8. **Cyclic type reference** — a declared `type` nesting itself with no array to terminate it
677
+ (`type Node { child: Node }`, or the same shape across several types). Break the cycle, or
678
+ route the self/mutual reference through an array (`children: Node[]`) if the data is
679
+ genuinely tree-shaped.
680
+ 9. **Lineage-ref errors** (slice-doc frontmatter, fs-aware) — a
681
+ `split-from`/`merged-from`/`superseded-by` ref with malformed `<slice-key>@v<N>` grammar, a
682
+ self-reference or cycle, a `superseded-by` naming a slice absent from the current model, or
683
+ a ref naming a version higher than the target's own `version:`. A `split-from`/`merged-from`
684
+ naming a key absent from the tree is deliberately silent — that's the normal state after a
685
+ real split/merge (see the four `lineage-*` codes in the table below).
686
+ 10. **`loops-to` not earlier, or unresolved** — an event's `loops-to "View"` naming a view at the
687
+ same slice or later (`loops-to-forward`; that's what `from` on a later `view … again`
688
+ instance is for), or naming a view that doesn't exist at all, or a name that resolves to a
689
+ non-view (`loops-to-unresolved`).
690
+ 11. **`derived from` names an unresolved event** — a view field's traced `derived from "Event
691
+ A", "Event B"` clause naming an event that isn't among the view's actual sources (its own
692
+ `from` list, or the same-slice events for a `from`-less view) — `derived-from-unresolved`.
693
+ Name one of the view's real sources, or drop the traced event.
694
+
695
+ **Warnings (should fix):**
696
+ 1. **Reaction with no command** — a `processor`/`automation`/`saga`/`translation` that triggers
697
+ nothing. It never records an event itself; add the command it issues in this same slice, or an
698
+ explicit `arrow` to one elsewhere.
699
+ 2. **`ui` shares slice with a reaction** — a `ui` only ever wires to a `command` a person issues; no
700
+ pattern has a `ui` triggering an automation/processor/saga/translation. Left in the reaction's
701
+ own slice it renders disconnected, with no edge, whether or not that slice also has the
702
+ reaction's own command. Move it to the slice that displays the read model, or drop it.
703
+ 3. **Command with no trigger** — nothing issues it. A command needs a `ui` in its slice, or the
704
+ reaction (automation/processor/saga/translation) that triggers it, *also* in this slice. The
705
+ input-side mirror of (5): a command nothing points at is a write nobody can start.
706
+ 4. **Command without event** — every command should record at least one event.
707
+ 5. **Event nobody reads** — the mirror of (4): recording an event no read model projects is a
708
+ write with no reader. Follow every write slice with the read slice that consumes its event.
709
+ Counts as read when a `view` names it in `from` (any `again` instance will do), when a view
710
+ with no `from` of its own sits in its slice, via an explicit `event -> view` arrow, or via a
711
+ resolved `loops-to "View"` clause (a loop-back is a real consumer too — an UNRESOLVED or
712
+ forward `loops-to` does not count, so the event still warns as unread alongside its own
713
+ `loops-to-forward`/`loops-to-unresolved` error). (Events marked `public` are exempt — their
714
+ reader is outside the model; same for a `public` view and the no-consumer warning.)
715
+ 6. **Read model without source** — add `from "Event"` or place the view in a slice with an event.
716
+ 7. **Duplicate name** — the same name defined N times; references resolve to the first. Rename.
717
+ (A duplicate `type` name always warns, unlike a duplicate element name — there's no
718
+ legitimate unreferenced-duplicate case for a named type.)
719
+ 8. **Open issue** — an element carries `issue "text"`; resolve the question, then remove the
720
+ clause. `em validate --list-issues` prints just these; `--fail-on-issues` (opt-in) makes CI
721
+ fail while any remain open.
722
+ 9. **View field with no source** — a `view` field whose name matches no field on any instance
723
+ of its source events. Only checked once BOTH the view and at least one source event declare
724
+ `{ fields }` — unless the field is marked `derived` (optionally `derived from "Event A",
725
+ "Event B"`, whose names must be among the view's sources), which exempts it entirely.
726
+ 10. **Event field not from a command** — an `event` field whose name matches no field on any
727
+ command in the same slice. Only checked once BOTH the event and at least one same-slice
728
+ command declare `{ fields }`. This is the payoff of the fields feature for slicing rigor:
729
+ once fields are written down, `em validate` checks that data flows forward consistently.
730
+ 11. **Event with no producing command** — a fact with no traceable cause; add the command that
731
+ records it (either order within the slice), or an explicit `arrow` from one.
732
+ 12. **Two same-named `translation`s reading different producers** — rename one; fires
733
+ unconditionally (unlike the duplicate-name warning), since the confusion is in reading
734
+ the model, not resolving a reference.
735
+ 13. **Frontmatter coherence** (slice docs, fs-aware) — `status: implemented` with no
736
+ `implementedIn` link at all. The re-ratification mismatch (status flipped back while
737
+ `implementedIn` names the prior PR) is deliberately never flagged — it's the drift
738
+ signal, not incoherence.
739
+ 14. **Doc-join problems** (`em export`/`--slice-ready`) — `binding-missing-file` (a
740
+ `note "slices/<key>.md"` binding pointing at a file that doesn't exist) and
741
+ `frontmatter-invalid` (the doc exists but lacks a frontmatter block or a required key).
742
+
743
+ `divergence "text"` is deliberately NOT in this list — it raises no warning at all, since it
744
+ records a deviation already reasoned through and accepted, not something to fix. Use
745
+ `em validate --list-divergences` to audit them on demand.
746
+
747
+ ### Full rule code reference
748
+
749
+ Every rule above (and every fs-aware check `em validate` layers on top — lineage, frontmatter
750
+ coherence) has a stable `code`, generated below from the same registry `em` itself validates
751
+ against (`src/model/rules.ts`) — a new rule shows up here the moment it's registered, whether or
752
+ not the prose above has caught up yet. `--slice-ready <key>`-only codes are excluded; see
753
+ `docs/cli.md#em-validate-file` in the
754
+ [em repository](https://github.com/milehimikey/em/blob/main/docs/cli.md#em-validate-file).
755
+
756
+ <!-- GENERATED:validate-rules:start -- run `npm run docs:generate` to refresh, do not hand-edit -->
757
+ | Code | Severity | Title | Fix |
758
+ |---|---|---|---|
759
+ | `arrow-backward` | error | Backward arrow | Restructure so the target comes later. |
760
+ | `arrow-unresolved-source` | error | Arrow source unresolved | Fix the arrow's source name. |
761
+ | `arrow-unresolved-target` | error | Arrow target unresolved | Fix the arrow's target name. |
762
+ | `binding-missing-file` | warning | Doc binding points at a missing file | Create the slice doc, or fix the `note` path. |
763
+ | `both-ends-of-a-flow/command-no-event` | warning | Command without event | Add the event this command records. |
764
+ | `both-ends-of-a-flow/command-untriggered` | warning | Command with no trigger | Add a `ui` or the reaction that issues it, both in this slice. |
765
+ | `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. |
766
+ | `both-ends-of-a-flow/event-unread` | warning | Event nobody reads | Project it into a view, or reconsider recording it. |
767
+ | `both-ends-of-a-flow/reaction-no-command` | warning | Reaction with no command | Add the command it triggers, in this slice, or an explicit arrow to one. |
768
+ | `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. |
769
+ | `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. |
770
+ | `code-spans-seam` | warning | Code change spans a seam | Keep one code module per model and change the two sides of a seam in separate change sets. |
771
+ | `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. |
772
+ | `consumer-not-adapted` | error | Consuming translation no longer matches the producer's public element | Update the translation's field block to the producer's current fields (a renamed field names its new name in the message). Additive producer changes never raise this — consumers tolerate unknown fields. |
773
+ | `consumes-unknown-element` | error | `consumes` names no public element of that model | Point the ref at an element the producer marks `public` (`em export` lists them), or ask the producer to publish it. |
774
+ | `consumes-unknown-model` | error | `consumes` names a model the system does not have | Fix the model key (the kebab-slug of the producer's `model "Name"`), or add that model to the system. |
775
+ | `continuation-has-own-doc` | warning | Continuation slice has its own doc | Fold this doc's scenarios into slices/<originating-key>.md and delete it; a later `view X again` instance is documented by the view's originating slice. |
776
+ | `cross-model-slice-doc-collision` | warning | Colliding slice doc path across models | Give each model its own directory (see docs/cli.md, "Multi-model projects"). |
777
+ | `dangling-public-event` | warning | Public event/view no seam consumes | Declare the seam that reads it, or drop `public` if nothing outside the model does. |
778
+ | `derived-from-unresolved` | error | `derived from` names an unknown event | Name one of the view's actual sources, or drop the traced event from `derived from`. |
779
+ | `doc-model-element-not-in-doc` | warning | Model element the doc doesn't mention | Add the matching marker to the doc, or remove the element from the model. |
780
+ | `doc-model-element-not-in-model` | warning | Doc names an element the model doesn't have | Add the element to the model, or fix/remove it in the doc. |
781
+ | `doc-model-field-mismatch` | warning | Doc/model field mismatch | Reconcile the field table with the model's fields — names and types. |
782
+ | `doc-model-pattern-mismatch` | warning | Doc/model pattern mismatch | Fix the doc's `pattern:` frontmatter to match the model, or restructure the slice to match the doc. |
783
+ | `duplicate-element-ref` | warning | Duplicate element ref | Rename the element so its export ref is unique. |
784
+ | `duplicate-model-key` | warning | Duplicate model key | Give each model a unique `model "Name"` so its key (and every cross-model ref) is stable. |
785
+ | `duplicate-name` | warning | Duplicate name | Rename one of the duplicates. |
786
+ | `duplicate-slice-name` | warning | Duplicate slice name | Rename the slice so its export key is unique. |
787
+ | `duplicate-type-name` | warning | Duplicate type name | Rename one of the duplicate `type` declarations. |
788
+ | `duplicate-type-ref` | warning | Duplicate type ref | Rename the type so its export ref is unique. |
789
+ | `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. |
790
+ | `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. |
791
+ | `frontmatter-coherence-implemented-without-link` | warning | Implemented without a link | Add `implementedIn` once the slice ships. |
792
+ | `frontmatter-invalid` | warning | Invalid or missing frontmatter | Add the required frontmatter keys, or add a frontmatter block. |
793
+ | `grid-collision` | error | Band collision | Split the colliding elements into separate slices. |
794
+ | `invariants/declared-in-both` | warning | Invariant declared in the model and in a slice doc | Keep the model's `invariant` line; in the doc, cite the ID and elaborate instead of restating it as a `**INV-…:**` rule. |
795
+ | `invariants/doc-cites-undeclared` | warning | Slice doc cites an undeclared invariant | Declare the ID in the model (or in a slice doc's Invariants section), or fix the citation. |
796
+ | `invariants/duplicate-id` | error | Duplicate invariant ID | Give each model-declared invariant its own ID — a rule is declared once, on the element it guards. |
797
+ | `invariants/malformed-id` | error | Malformed invariant ID | Rename the ID to the `INV-<MNEMONIC>-<n>` shape, e.g. `INV-ORD-1` (2-4 uppercase letters/digits, then a number). |
798
+ | `invariants/public-command-without-invariants` | warning | Public command declares no invariants | Add an `invariant INV-<MNEMONIC>-<n> "rule"` line after the command, or declare its rules in the slice doc's Invariants section. |
799
+ | `lineage-forward-dangling` | error | Dangling forward lineage ref | Fix the key, or remove the stale successor. |
800
+ | `lineage-ref-cycle` | error | Lineage cycle | Break the cycle — a slice can't be its own ancestor. |
801
+ | `lineage-ref-malformed` | error | Malformed lineage ref | Fix the value to `<slice-key>@v<N>`, or remove it. |
802
+ | `lineage-version-impossible` | error | Impossible lineage version | Fix the referenced version, or ratify the target slice first. |
803
+ | `loops-to-forward` | error | `loops-to` target not earlier on the timeline | Point `loops-to` at an earlier view, or use `from` on a later `view … again` instance instead. |
804
+ | `loops-to-unresolved` | error | `loops-to` target unresolved | Name a view that exists, or declare it before this event. |
805
+ | `model-version-stale` | warning | Model version stale | Run `em model version bump` to record the current model content/slice-version vector. |
806
+ | `multi-model-change-set` | warning | Change set touches more than one model | Prefer one model per change set. Unrelated models in one change set are fine; models joined by a seam are not, once a contract changes. |
807
+ | `note-binding-dangling` | warning | Dangling cross-slice note | Create the doc at that path, or fix/remove the note. |
808
+ | `note-binding-extra` | warning | Extra doc-binding note, ignored | Remove the note, or point it at the slice's actual bound doc. |
809
+ | `note-binding-unratified` | warning | Unratified cross-slice note | Add `covers: <this-slice-key>` to that doc's frontmatter, or correct the note's path. |
810
+ | `note-binding-unusable` | warning | Cross-slice note to a doc with unusable frontmatter | Fix that doc's frontmatter, or fix/remove the note. |
811
+ | `open-issue` | warning | Open issue | Resolve the question, then remove the `issue` clause. |
812
+ | `orphaned-slice-doc` | warning | Orphaned slice doc | Rename it to a current slice's key, add `covers:` (plus a `note` binding) to attach it to a live slice, or delete it. |
813
+ | `owner-not-a-handle` | warning | Model owner is not a CODEOWNERS handle | Write the owner as a GitHub user (`@alice`), a team (`@org/team`), or an email address, so `em system codeowners` can route review to it. |
814
+ | `public-field-type-unresolved` | error | Public field type unresolved | Give the field a type from the public type table (string, text, int, long, decimal, boolean, uuid, date, datetime, duration, bytes), `X[]` of one, or a declared `type` — or drop `public`. |
815
+ | `public-name-not-unique` | error | Two public elements of one kind share a name | Rename one of them (or drop `public` from one): a consumer's `consumes <model>:<kind>.<slug>` must name exactly one element. |
816
+ | `reaction-from-future-view` | error | Backward timeline (reaction reads a future view) | Declare the view in or before the reaction's slice. |
817
+ | `reaction-from-unresolved` | error | Unknown read-model source | Project the event into a view first, or fix the `from` reference. |
818
+ | `seam-consumer-not-reaction` | error | Seam consumer is not a reaction | Point `to` at a translation/automation element (or a slice containing exactly one). |
819
+ | `seam-crossing` | error | Change set alters a contract and its consumer together | Split the change set: land the producer's public-surface change first, then adapt the consuming model in a later change. Review on the contract file is the only override. |
820
+ | `seam-crossing-greenfield` | warning | New public surface lands with its first consumer | Nothing to fix: a public element no consumer was bound to at the base revision is new surface, not a crossing. Later changes to it are crossings. |
821
+ | `seam-duplicate` | warning | Duplicate seam | Remove the repeated `from`/`to` pair. |
822
+ | `seam-endpoint-unresolved` | error | Seam endpoint does not resolve | Fix the ref to an element the named model actually exports (`em export` lists every ref), or re-declare the seam after a rename. |
823
+ | `seam-source-not-public` | error | Seam source is not `public` | Mark the event/view `public` in its model, or point the seam at the element that is. |
824
+ | `slice-doc/structured-section-malformed` | warning | Malformed structured section | Balance the `<!-- GENERATED:em-slice-…:start -->`/`:end -->` markers (re-create the doc with `em slice new --force` if they are lost), restore the template's table header and run `em slice sync`, and give every `### Scenario:` block a Given, When and Then bullet. |
825
+ | `system-manifest-invalid` | error | Seam manifest invalid | Fix the manifest: `systemSchemaVersion: "2.0"`, `models:` with a readable `source` per model, and nothing else (`seams:`/`owner:` moved into the models — run `em upgrade`). |
826
+ | `system-manifest-outdated` | warning | Seam manifest is schema 1.0 | Run `em upgrade <model>.em --apply` once: it moves `seams:` into `consumes` clauses and `owner:` onto each model header, and rewrites the manifest to 2.0. |
827
+ | `system-model-key-mismatch` | error | Manifest model key differs from the export's `model.key` | Rename the manifest's `models:` key to the computed key the message prints. |
828
+ | `tag-composite-unknown-field` | error | Composite tag names an unknown field | Fix the field name, or add it to the event's fields. |
829
+ | `tag-duplicate-key` | error | Duplicate tag key | Rename one of the tags so every key on the event is unique. |
830
+ | `translation-name-collision` | warning | Translation name reused for different producers | Use a distinct name per producer to avoid confusion. |
831
+ | `type-cycle` | error | Cyclic type reference | Break the cycle, or route the self/mutual reference through an array. |
832
+ | `ui-shares-slice-with-automation` | warning | `ui` shares slice with a reaction | Move the `ui` to the slice that displays the read model, or drop it. |
833
+ | `unbound-translation` | warning | Externally-fed reaction no seam feeds | Declare the seam whose `to` is this reaction, or give it an in-model `from`. |
834
+ | `undeclared-seam-candidate` | warning | Looks connected across models, no seam declared | Declare the seam, or rename one side so the name match stops looking like a link. |
835
+ | `view-again-without-earlier` | error | `again` without an earlier declaration | Declare the view plainly the first time it appears. |
836
+ | `view-from-future-event` | error | Backward timeline (view reads a future event) | Move the source to a later `view X again` instance. |
837
+ | `view-from-unresolved` | error | Unknown event source | Fix the `from` reference to name an existing event. |
838
+ | `view-no-source` | warning | Read model without source | Add `from "Event"`, or place the view in a slice with an event. |
839
+ <!-- GENERATED:validate-rules:end -->
840
+
841
+ **Design rules that keep models valid:**
842
+ - One element per band per slice (multiple personas/contexts are fine — they're different rows).
843
+ - Every `command` slice includes its `event`. Every `view` has a `from` source.
844
+ - **Every `command` has a trigger.** A `ui` in its slice, or the reaction that triggers it, also in
845
+ its slice. A command nothing points at is a write nobody can start.
846
+ - **Every `event` has a reader.** A command slice isn't finished until the read slice that projects
847
+ its event exists. Reactions don't count — they read views, not events. Pair each write slice with
848
+ its read slice as you go rather than sweeping up dangling events at the end.
849
+ - **Every `view` has a consumer.** A `ui` in its slice, a reaction watching it, or (headless) a read
850
+ translation. Every *instance* of a repeat, not just the last — a bare `view X again` slice is a
851
+ half-slice, not a State View.
852
+ - **Only six connections are legal**, and only these are ever inferred:
853
+ `ui → command`, `command → event`, `event → view`, `view → ui`, `view → reaction`,
854
+ `reaction → command`. Anything else in an explicit `arrow` is an error — above all
855
+ `command → view` (the CQRS violation: an event has to sit between them) and `view → command`
856
+ (a reaction has to sit between them). If you reach for an arrow the patterns don't allow, the
857
+ model is missing an element, not an arrow.
858
+ - A reaction (`processor`/`automation`/`saga`/`translation`) shares its slice with the `command` it
859
+ triggers and that command's `event` — the same shape `ui` already uses in State Change. If it's
860
+ internally triggered, the **view** (read model) it watches lives in the slice before, named via
861
+ `from "X"` (which must resolve to a view, never an event directly). Externally-triggered reactions
862
+ start from outside the model — no `from` at all, and no slice before them.
863
+ - Name everything uniquely and consistently.
864
+ - Events are past tense; commands are imperative; views name the thing shown.
865
+
866
+ A reaction that emits an event with no command anywhere between them — `translation T` and
867
+ `event E` in the same slice, nothing issuing it — is exactly what
868
+ `both-ends-of-a-flow/reaction-no-command` (above) catches: any reaction with no `command` in its
869
+ own slice, and no explicit arrow to one, warns. Still route every reaction through a real command
870
+ by construction (`reaction → command → event`) rather than an explicit `arrow "Reaction" -> "Event"`
871
+ — that shape is its own `connection-legality` error (a reaction never records an event itself).