@milehimikey/em 1.17.0 → 1.18.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (183) hide show
  1. package/.claude/skills/event-modeling/SKILL.md +1 -1
  2. package/.claude/skills/event-modeling-conform/SKILL.md +1 -1
  3. package/.claude/skills/event-modeling-design/SKILL.md +46 -5
  4. package/.claude/skills/event-modeling-discover/SKILL.md +43 -8
  5. package/.claude/skills/event-modeling-discover/reference/extract.md +6 -1
  6. package/.claude/skills/event-modeling-discover/reference/prd-rubric.md +102 -0
  7. package/.claude/skills/event-modeling-discover/reference/prd-spec-path.md +171 -0
  8. package/.claude/skills/event-modeling-engagement/SKILL.md +8 -3
  9. package/.claude/skills/event-modeling-implement/SKILL.md +1 -1
  10. package/.claude/skills/event-modeling-implement/reference/implement.md +8 -0
  11. package/.claude/skills/event-modeling-review/SKILL.md +1 -1
  12. package/.claude/skills/event-modeling-shared/reference/em-dsl.md +90 -5
  13. package/.claude/skills/event-modeling-shared/reference/methodology.md +268 -0
  14. package/.claude/skills/event-modeling-shared/reference/slice-doc-schema.md +96 -2
  15. package/.claude/skills/event-modeling-shared/templates/slice.md +11 -3
  16. package/.claude-plugin/marketplace.json +2 -2
  17. package/dist/catalog/docModelConsistencyValidate.js +160 -0
  18. package/dist/catalog/docModelConsistencyValidate.js.map +1 -1
  19. package/dist/catalog/invariantsValidate.d.ts +2 -0
  20. package/dist/catalog/invariantsValidate.js +67 -1
  21. package/dist/catalog/invariantsValidate.js.map +1 -1
  22. package/dist/catalog/scenarioQualityValidate.d.ts +4 -0
  23. package/dist/catalog/scenarioQualityValidate.js +148 -0
  24. package/dist/catalog/scenarioQualityValidate.js.map +1 -0
  25. package/dist/catalog/scenarios.d.ts +5 -0
  26. package/dist/catalog/scenarios.js +80 -0
  27. package/dist/catalog/scenarios.js.map +1 -1
  28. package/dist/catalog/sliceReadyValidate.d.ts +8 -0
  29. package/dist/catalog/sliceReadyValidate.js +35 -3
  30. package/dist/catalog/sliceReadyValidate.js.map +1 -1
  31. package/dist/catalog/sliceSections.d.ts +3 -2
  32. package/dist/catalog/sliceSections.js +99 -9
  33. package/dist/catalog/sliceSections.js.map +1 -1
  34. package/dist/cli/ciInit.d.ts +4 -11
  35. package/dist/cli/ciInit.js +24 -6
  36. package/dist/cli/ciInit.js.map +1 -1
  37. package/dist/cli/coverage.d.ts +9 -0
  38. package/dist/cli/coverage.js +18 -0
  39. package/dist/cli/coverage.js.map +1 -1
  40. package/dist/cli/defer.d.ts +28 -0
  41. package/dist/cli/defer.js +87 -4
  42. package/dist/cli/defer.js.map +1 -1
  43. package/dist/cli/engagement.d.ts +2 -2
  44. package/dist/cli/engagement.js +24 -1
  45. package/dist/cli/engagement.js.map +1 -1
  46. package/dist/cli/markImplemented.d.ts +14 -10
  47. package/dist/cli/markImplemented.js +49 -1
  48. package/dist/cli/markImplemented.js.map +1 -1
  49. package/dist/cli/prdTriage.d.ts +109 -0
  50. package/dist/cli/prdTriage.js +378 -0
  51. package/dist/cli/prdTriage.js.map +1 -0
  52. package/dist/cli/render-inputs.d.ts +2 -0
  53. package/dist/cli/render-inputs.js +4 -0
  54. package/dist/cli/render-inputs.js.map +1 -1
  55. package/dist/cli/revise.d.ts +2 -0
  56. package/dist/cli/revise.js +7 -2
  57. package/dist/cli/revise.js.map +1 -1
  58. package/dist/cli/shape.d.ts +27 -0
  59. package/dist/cli/shape.js +77 -0
  60. package/dist/cli/shape.js.map +1 -0
  61. package/dist/cli/sliceIndex.d.ts +6 -1
  62. package/dist/cli/sliceIndex.js +28 -13
  63. package/dist/cli/sliceIndex.js.map +1 -1
  64. package/dist/cli/sliceNew.d.ts +4 -1
  65. package/dist/cli/sliceNew.js +26 -9
  66. package/dist/cli/sliceNew.js.map +1 -1
  67. package/dist/cli/status.d.ts +21 -1
  68. package/dist/cli/status.js +25 -3
  69. package/dist/cli/status.js.map +1 -1
  70. package/dist/cli/storylineSuggest.d.ts +26 -0
  71. package/dist/cli/storylineSuggest.js +252 -0
  72. package/dist/cli/storylineSuggest.js.map +1 -0
  73. package/dist/cli/upgrade.d.ts +15 -3
  74. package/dist/cli/upgrade.js +426 -7
  75. package/dist/cli/upgrade.js.map +1 -1
  76. package/dist/cli/upgradeReshape.d.ts +60 -0
  77. package/dist/cli/upgradeReshape.js +425 -0
  78. package/dist/cli/upgradeReshape.js.map +1 -0
  79. package/dist/cli.js +286 -29
  80. package/dist/cli.js.map +1 -1
  81. package/dist/emit/dot.d.ts +2 -0
  82. package/dist/emit/dot.js +44 -8
  83. package/dist/emit/dot.js.map +1 -1
  84. package/dist/emit/engagementJson.d.ts +1 -1
  85. package/dist/emit/engagementJson.js +3 -1
  86. package/dist/emit/engagementJson.js.map +1 -1
  87. package/dist/emit/json.d.ts +21 -1
  88. package/dist/emit/json.js +26 -2
  89. package/dist/emit/json.js.map +1 -1
  90. package/dist/emit/queryJson.d.ts +1 -1
  91. package/dist/emit/queryJson.js +3 -1
  92. package/dist/emit/queryJson.js.map +1 -1
  93. package/dist/emit/statusJson.d.ts +1 -1
  94. package/dist/emit/statusJson.js +6 -1
  95. package/dist/emit/statusJson.js.map +1 -1
  96. package/dist/emit/theme.d.ts +1 -0
  97. package/dist/emit/theme.js +13 -0
  98. package/dist/emit/theme.js.map +1 -1
  99. package/dist/emit/validateJson.d.ts +5 -2
  100. package/dist/emit/validateJson.js +36 -2
  101. package/dist/emit/validateJson.js.map +1 -1
  102. package/dist/layout/grid.d.ts +5 -1
  103. package/dist/layout/grid.js +93 -2
  104. package/dist/layout/grid.js.map +1 -1
  105. package/dist/mcp/server.js +46 -11
  106. package/dist/mcp/server.js.map +1 -1
  107. package/dist/model/edges.d.ts +7 -0
  108. package/dist/model/edges.js +33 -16
  109. package/dist/model/edges.js.map +1 -1
  110. package/dist/model/model.d.ts +33 -0
  111. package/dist/model/model.js +87 -3
  112. package/dist/model/model.js.map +1 -1
  113. package/dist/model/queryIndex.js +2 -2
  114. package/dist/model/queryIndex.js.map +1 -1
  115. package/dist/model/refs.js +8 -5
  116. package/dist/model/refs.js.map +1 -1
  117. package/dist/model/rules.d.ts +149 -0
  118. package/dist/model/rules.js +144 -0
  119. package/dist/model/rules.js.map +1 -1
  120. package/dist/model/shapeLint.d.ts +12 -0
  121. package/dist/model/shapeLint.js +170 -0
  122. package/dist/model/shapeLint.js.map +1 -0
  123. package/dist/model/storylines.d.ts +51 -0
  124. package/dist/model/storylines.js +188 -0
  125. package/dist/model/storylines.js.map +1 -0
  126. package/dist/model/validate.d.ts +6 -1
  127. package/dist/model/validate.js +317 -14
  128. package/dist/model/validate.js.map +1 -1
  129. package/dist/parser/againSugar.d.ts +30 -0
  130. package/dist/parser/againSugar.js +105 -0
  131. package/dist/parser/againSugar.js.map +1 -0
  132. package/dist/parser/ast.d.ts +33 -0
  133. package/dist/parser/lexer.d.ts +2 -0
  134. package/dist/parser/lexer.js +4 -0
  135. package/dist/parser/lexer.js.map +1 -1
  136. package/dist/parser/parser.js +284 -4
  137. package/dist/parser/parser.js.map +1 -1
  138. package/dist/query/format.js +1 -0
  139. package/dist/query/format.js.map +1 -1
  140. package/dist/query/verbs.d.ts +4 -0
  141. package/dist/query/verbs.js +30 -3
  142. package/dist/query/verbs.js.map +1 -1
  143. package/dist/render/drawEdges.js +100 -4
  144. package/dist/render/drawEdges.js.map +1 -1
  145. package/dist/render/render.js +20 -2
  146. package/dist/render/render.js.map +1 -1
  147. package/dist/render/sliceDiagram.js +5 -5
  148. package/dist/render/sliceDiagram.js.map +1 -1
  149. package/dist/render/storylineBands.d.ts +11 -0
  150. package/dist/render/storylineBands.js +80 -0
  151. package/dist/render/storylineBands.js.map +1 -0
  152. package/dist/render/storylineDiagram.d.ts +14 -0
  153. package/dist/render/storylineDiagram.js +142 -0
  154. package/dist/render/storylineDiagram.js.map +1 -0
  155. package/dist/roster/check.d.ts +32 -0
  156. package/dist/roster/check.js +92 -0
  157. package/dist/roster/check.js.map +1 -0
  158. package/dist/roster/context.d.ts +3 -0
  159. package/dist/roster/context.js +23 -0
  160. package/dist/roster/context.js.map +1 -0
  161. package/dist/roster/init.d.ts +15 -0
  162. package/dist/roster/init.js +52 -0
  163. package/dist/roster/init.js.map +1 -0
  164. package/dist/roster/roster.d.ts +79 -0
  165. package/dist/roster/roster.js +298 -0
  166. package/dist/roster/roster.js.map +1 -0
  167. package/package.json +1 -1
  168. package/plugin/.claude-plugin/plugin.json +2 -2
  169. package/plugin/shared/reference/em-dsl.md +90 -5
  170. package/plugin/shared/reference/methodology.md +268 -0
  171. package/plugin/shared/reference/slice-doc-schema.md +96 -2
  172. package/plugin/shared/templates/slice.md +11 -3
  173. package/plugin/skills/conform/SKILL.md +1 -1
  174. package/plugin/skills/design/SKILL.md +46 -5
  175. package/plugin/skills/discover/SKILL.md +43 -8
  176. package/plugin/skills/discover/reference/extract.md +6 -1
  177. package/plugin/skills/discover/reference/prd-rubric.md +103 -0
  178. package/plugin/skills/discover/reference/prd-spec-path.md +172 -0
  179. package/plugin/skills/engagement/SKILL.md +8 -3
  180. package/plugin/skills/event-modeling/SKILL.md +1 -1
  181. package/plugin/skills/implement/SKILL.md +1 -1
  182. package/plugin/skills/implement/reference/implement.md +8 -0
  183. package/plugin/skills/review/SKILL.md +1 -1
@@ -89,6 +89,68 @@ over an omission — it reports). `implementedIn` and the lineage keys are the g
89
89
  optional ones; `implementedIn`'s required-once-implemented rule in the table below is
90
90
  convention, not parser-enforced.
91
91
 
92
+ ## Who may ratify
93
+
94
+ The schema records *who* signed (`reviewedBy`, `ratifiedBy`) but, by default, accepts any
95
+ non-empty name: who is allowed to sign is the host repo's decision. A host that wants em to
96
+ enforce it supplies an optional **roster**, `ratifiers.yaml`, beside the model. See
97
+ [examples/order-fulfillment/ratifiers.yaml](https://github.com/milehimikey/em/blob/main/examples/order-fulfillment/ratifiers.yaml).
98
+
99
+ ```yaml
100
+ rosterSchemaVersion: "1.0"
101
+ ratifiers:
102
+ - name: Aaron McKay
103
+ contexts: [Product, Attribute] # omit = the whole model
104
+ - name: Mike Key
105
+ - name: Dana Lee
106
+ models: [pim] # omit = every model the roster governs
107
+ reviewers: # optional; same shape, checked by `em slice review --by`
108
+ - name: Mike Key
109
+ ```
110
+
111
+ - **Schema.** `rosterSchemaVersion` is required (`"1.0"`); at least one of `ratifiers:` /
112
+ `reviewers:` is required; each is a non-empty list of `{ name, contexts?, models? }`. Unknown
113
+ keys are errors (`roster-invalid`). A missing `ratifiers:` / `reviewers:` key leaves that role
114
+ unrestricted.
115
+ - **Placement.** The nearest `ratifiers.yaml` walking up from the model's directory, stopping at
116
+ the nearest directory holding `.git` (or the filesystem root). If no `.git` exists anywhere
117
+ above the model, only the model's own directory is searched. `--roster <path>` on `ratify`,
118
+ `review`, `validate --slice-ready`, `roster check` overrides discovery; a path that does not
119
+ exist is an error, never "no roster".
120
+ - **A slice's contexts** are the contexts of its **events** plus the contexts of the events its
121
+ **views read** - each view instance's **own** `from`, never a union across the read model's
122
+ instances (adding a later `again` instance never changes an earlier slice's contexts). A
123
+ continuation slice also takes its originating instance's own `from`. A slice with neither has no
124
+ context and is permitted only by entries without `contexts`.
125
+ A continuation fed from a different context than its originating view (e.g. `tool-shows-on-loan` =
126
+ Loan + Tool) therefore needs a ratifier permitted for every one of those contexts, so a team with
127
+ one lead per context needs a joint or whole-model roster entry for such slices.
128
+ - **An entry permits a slice** when it applies to the model and either has no `contexts` or lists
129
+ **every** context of the slice: a cross-context slice needs a ratifier who may sign for all of
130
+ them (a leading `@` is tolerated in `contexts`).
131
+ - **Per-model scoping.** `models: [<modelKey>]` (the key `em export` reports as `model.key`) limits
132
+ an entry to those models; an entry without it applies to every model the roster governs. Context
133
+ names are matched **per model** against that model's declared contexts, so a shared root roster
134
+ needs `models:` whenever the models' ratifiers differ. A `models:` key the enclosing
135
+ `system.yaml` does not list is `roster-invalid`.
136
+ - **Validation against the model.** A `contexts:` entry the model does not declare (case-sensitive)
137
+ is `roster-invalid`, naming the context; only entries that apply to the model are checked.
138
+ - **File absent: today's behaviour**, no warning. **File present but invalid: fail closed** -
139
+ `ratify` and `review` refuse, `--slice-ready` and `roster check` report `roster-invalid`.
140
+ - **`grandfathered (unsigned before em 1.14)`** is em's own migration marker (`em upgrade`'s
141
+ `ratified-signoff`), not a signer: exempt from the `--slice-ready` check and from `roster check`
142
+ (which counts it) until the slice is re-ratified - but only the **exact** string, and only on a
143
+ doc with no `ratifiedHash` (the state `em upgrade` leaves). `grandfathered by X`, or the marker on
144
+ a doc that carries a hash, is a signer and is checked.
145
+ - **Where it is enforced.** At write time by `em slice ratify` / `em slice review` (the refusal
146
+ lists the permitted names); at check time by `em validate --slice-ready <key>`
147
+ (`ratified-by-not-in-roster`, error - one `ready-to-implement` slice, ratifier only) and by
148
+ `em roster check <model>` (every doc, `ratifiedBy` on `ready-to-implement` and `implemented`,
149
+ `reviewedBy` on any), the command `em ci init` wires into CI when a roster exists - see
150
+ [ci.md](ci.md#ratifier-roster-gate). `em status` reports `roster: present|absent|invalid`.
151
+ - `em roster init <model>` seeds the file from the names already in the docs
152
+ ([cli.md](https://github.com/milehimikey/em/blob/main/docs/cli.md#em-roster-init-file)).
153
+
92
154
  ## Required vs optional, by `status`
93
155
 
94
156
  | Key | draft | reviewed | ready-to-implement | implemented |
@@ -262,6 +324,21 @@ the union of events every later `again` instance of that view reads via `from`,
262
324
  order — so implementing the read model from one export never means walking every `again`
263
325
  slice's doc by hand to assemble the full event list.
264
326
 
327
+ The generated `em-slice-view` region follows the same rule (GH #280, 1.18.0): its `built from
328
+ events:` line lists the union of `from` across the originating instance and every `again`
329
+ instance (originating first, then timeline order, deduped), and each field's `from event` note
330
+ resolves against that union (each later event annotated `(via "<slice>")` with its instance's slice) — whichever instance's slice the doc is bound to. `em slice sync`
331
+ rewrites existing view regions accordingly.
332
+
333
+ **Synthesized continuations (GH #287).** A `view X again` line written inside a command's own slice
334
+ is lifted into a continuation column right after it. That column is a continuation like any other
335
+ (`continuationOf` names the view's originating slice, `alsoReads` counts its events) but exists
336
+ only as sugar over the host slice's text: its export key is `<host-slice-key>--again` (one column per host slice, its lifted lines stacked),
337
+ it never has a doc, and `em slice index` lists no row for it. A slice key never contains `/` (the slug generator emits single dashes only, so `--` cannot occur in a hand-written key); the `/` in an element ref `<slice-key>/<kind>.<slug>` is always the first and only one. A hand-written continuation slice
338
+ may also hold several `again` views, and may omit its name (auto-named `"<View> View: <Event>"`).
339
+ When one slice stacks views of different read models, `continuationOf` names the originating
340
+ slice of the FIRST view; the others are listed in their originating slices' `alsoReads` (with this slice's key as `atSlice`).
341
+
265
342
  **Authoring convention:** a continuation's behavior belongs in the originating doc's own
266
343
  `## Scenarios (Given / When / Then)` section — one scenario per event a later `again` instance
267
344
  feeds — rather than in a doc of its own; no new frontmatter is needed to record it.
@@ -398,7 +475,7 @@ re-run `em slice sync`.
398
475
  |---|---|---|
399
476
  | `em-slice-command` | `## Command / Input` | `**Command:** \`Name\`` + `\| Field \| Type \| Required \| Rules / Validation \|` (Required is `no` for a `name?: Type` field) |
400
477
  | `em-slice-event` | `## Event(s) Emitted` | `**Event:** \`Name\` → context \`Ctx\`` + `\| Field \| Type \| Immutable Fact? \| Source / Notes \|` (Source: the same-slice command field it is copied from, `assigned` for a system-assigned field, else `{{ }}`) |
401
- | `em-slice-view` | `## Read Model / View` | `- **View:** \`Name\` built from events: …` + `\| Field \| Type \| Source / Notes \|` (Source: the source event carrying the field, `Derived` for a `derived` field, else `{{ }}`) |
478
+ | `em-slice-view` | `## Read Model / View` | `- **View:** \`Name\` built from events: …` (the union across the originating and every `again` instance; each later event is annotated `(via "<slice>")` with the slice of the instance that adds it) + `\| Field \| Type \| Source / Notes \|` (Source: the source event, from that union, carrying the field, `Derived` for a `derived` field, `Collection of \`T\`, fed by …` for a `T[] derived from …` field — followed by one neutral Given/Then scenario stub per named event under the table, to copy into `## Scenarios` and complete there (the region itself stays read-only) — else `{{ }}`) |
402
479
  | `em-slice-invariants` | `## Invariants / Business Rules` | one `- **INV-X** — rule` line per model-declared invariant of the slice's commands and events |
403
480
 
404
481
  A slice with several elements of one kind gets one region per element, the name suffixed with
@@ -437,12 +514,22 @@ case is a block:
437
514
  - **Given:**
438
515
  - a basket whose total is 0
439
516
  - **When:**
440
- - the customer submits the order
517
+ - the customer issues PlaceOrder
441
518
  - **Then:**
442
519
  - the command is rejected with a validation error
443
520
  - no event is recorded
444
521
  ```
445
522
 
523
+ **Scenario quality (citation convention).** A scenario is tied to exactly one command: its When
524
+ names the slice's command (a reaction slice: the command it triggers), its Then names an event
525
+ the slice records or a rejection/error, and it **cites the `INV-…` ids it exercises** anywhere in
526
+ the block (the title above, or a Given/When/Then bullet). Every invariant declared for the slice
527
+ (an `invariant` line in the model, or the doc's Invariants section) should be cited by at least one
528
+ scenario. `em validate` advises on all three — `scenario-when-not-command`,
529
+ `scenario-then-not-outcome`, `invariant-without-scenario` — see
530
+ [validation.md](https://github.com/milehimikey/em/blob/main/docs/validation.md#scenario-quality). Older bullet-format scenarios are checked too; a doc with no scenarios is never
531
+ flagged.
532
+
446
533
  A `### Scenario: <title>` heading, then the three column-0 bullets `- **Given:**`, `- **When:**`,
447
534
  `- **Then:**`, each with nested ` - ` items (text after the label on the same line counts as a
448
535
  first item). `em export --json` publishes the parsed blocks as `slice.doc.scenarios`
@@ -521,6 +608,13 @@ one it is asking about:
521
608
  slice shipped, and which" is `shippedRecordOf()` (`src/catalog/sliceDoc.ts`); `em export`
522
609
  publishes its answer as `slice.doc.shipped`.
523
610
 
611
+ **Reading a PR that shows `status: draft`.** When `mark-implemented` opened the next version
612
+ (`status: draft`, `version: <N+1>`), the diff of that slice doc does not say "never marked": the
613
+ `shippedVersion: <N>` / `implementedIn:` lines **are** the record that v<N> was implemented, and
614
+ `status:` is the state of v<N+1>. The command prints a notice naming both
615
+ ([cli.md](https://github.com/milehimikey/em/blob/main/docs/cli.md#em-slice-mark-implemented-file-slice-key-pr-url)), and `em status` lists the slice
616
+ as `implemented (v<N>) · draft v<N+1> open` rather than a bare `draft`.
617
+
524
618
  So a shipped slice can be a `draft` again. `em slice revise <key>`
525
619
  ([cli.md](https://github.com/milehimikey/em/blob/main/docs/cli.md#em-slice-revise-file-slice-key)) opens v<N+1> as `status: draft`, bumps
526
620
  `version:`, clears the prior version's review and sign-off, and leaves `implementedIn`/`shipped*`
@@ -210,15 +210,23 @@ an event directly.}}
210
210
  --json` → slice.doc.scenarios). Cover the happy path AND the key rule boundaries, one block
211
211
  per case: a `### Scenario: <title>` heading, then exactly the three column-0 bullets
212
212
  `- **Given:**`, `- **When:**`, `- **Then:**`, each with its beats as nested ` - ` bullets.
213
- A block missing any of the three is flagged `slice-doc/structured-section-malformed`. -->
213
+ A block missing any of the three is flagged `slice-doc/structured-section-malformed`.
214
+ Quality (advisory warnings): each scenario is tied to exactly ONE command — its When names
215
+ this slice's command (a reaction slice: the reaction, its view, or the command it triggers); its Then names an event (or a stated no-op)
216
+ this slice records, or a rejection/error; and it CITES the INV ids it exercises (in the
217
+ title or any bullet). Every INV declared for the slice should be cited by some scenario
218
+ (`scenario-when-not-command`, `scenario-then-not-outcome`, `invariant-without-scenario`). -->
214
219
  ### Scenario: Happy path
215
220
  - **Given:**
216
221
  - {{starting state / prior events}}
217
222
  - **When:**
218
- - {{command/trigger}}
223
+ - {{the command, by name}}
219
224
  - **Then:**
220
- - {{event(s) recorded}}
225
+ - {{the event(s) recorded, by name}}
221
226
  - {{resulting read-model change}}
227
+ <!-- Add one block per rule boundary, citing the INV id in the title, e.g.
228
+ `### Scenario: Non-positive total is rejected (INV-CHK-1)` — When names the command, Then
229
+ names the rejection. -->
222
230
 
223
231
  ## Alternate & Error Flows
224
232
  <!-- Failure paths, retries, compensations, timeouts, idempotency. -->
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: conform
3
- em-version: 1.17.0
3
+ em-version: 1.18.0
4
4
  description: >-
5
5
  Use when checking a ratified event model (and its slice docs) for drift against the codebase
6
6
  that implements it — advisory only, never a gate, never an unprompted edit — or when walking
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: design
3
- em-version: 1.17.0
3
+ em-version: 1.18.0
4
4
  description: >-
5
5
  Use when structuring a draft event model into swimlanes and the four patterns (State Change,
6
6
  State View, Automation, Translation), evaluating a model's structural completeness, or writing
@@ -66,7 +66,13 @@ evidence"). In short:
66
66
 
67
67
  Goal: a structurally complete, **validated** model with correct patterns and swimlanes.
68
68
 
69
- 1. **Swimlanes & patterns (step 5).** Group events into **contexts** (bounded contexts /
69
+ 1. **Swimlanes & patterns (step 5).** **Name the storylines first**: the one main storyline (the
70
+ end-to-end journey) and the secondary ones (retire a thing, extend an attribute, reorganize).
71
+ Wrap each one's slices in a `storyline "Name" [source "url"] { slice … }` block (grammar and the
72
+ optional `after "<storyline>"` timeline clause: `em-dsl.md`) so the diagram gets a band per
73
+ story and `em render --storyline "Name"` can show one alone; slices keep their file order, and
74
+ a model can adopt storylines incrementally (`slice-outside-storyline` is only an advisory
75
+ warning) — but name them before you slice, not after. Then group events into **contexts** (bounded contexts /
70
76
  aggregates) — ask which events share a consistency boundary and who owns them. Classify each
71
77
  slice as one of the 4 patterns. **Share the slice between every automation or translation and
72
78
  the command it triggers** (the reaction — processor/translation — together with the command
@@ -75,7 +81,21 @@ Goal: a structurally complete, **validated** model with correct patterns and swi
75
81
  directly**. Add `translation` slices for external inputs (externally triggered: external →
76
82
  translation → command → event, no slice before) and for the system reacting to its own state
77
83
  (internally triggered: read model in the slice before → translation → command → event).
78
- 2. **Elaborate scenarios — first pass (step 6).** Scaffold every slice's doc now, one per
84
+ 2. **Storyline review — before any slice doc is scaffolded.** Read the timeline as a story, not a schema
85
+ (`${CLAUDE_PLUGIN_ROOT}/shared/reference/methodology.md`, "The story, not the schema"; em repo
86
+ `docs/storyline.md`). Name the **main storyline** (end-to-end happy path) and every
87
+ **secondary storyline** (retire, extend, reorganize), and group the slices under them; one
88
+ lifecycle block per entity (N × create/update/archive, one trailing view each) is the CRUD
89
+ shape — reshape it with the "Reshaping an existing model" checklist. Then the **intent test**
90
+ per command (would a business person say this name at this step in the story? an endpoint-shaped
91
+ name fails), the **question test** per view (write the question it answers; no question, no
92
+ read model; a child view only when its query needs what the parent view lacks), and the
93
+ **conditional-invariant signal** (invariants or fields that apply only "if `<field>` is set"
94
+ mean two intents in one command: split it). The `timeline-entity-clustered`,
95
+ `command-second-intent` and `automation-split-by-outcome` lints are the mechanical half; the
96
+ 1.18.0 design exit gate is `em validate --shape`. Shape the model first, scaffold docs after: a rename,
97
+ split or merge from the reshape would orphan docs already wired with `--wire`. The `slice` phase is unchanged (timeline order).
98
+ 3. **Elaborate scenarios — first pass (step 6).** Scaffold every slice's doc now, one per
79
99
  slice, none skipped: `em slice new "<slice name>" --pattern <state-change|state-view|
80
100
  automation|translation> --swimlane "<Persona> → <Context>" --wire <model>.em` writes
81
101
  `slices/<slice-name>.md` at `status: draft` (the same command, flags, and `--wire`
@@ -96,7 +116,7 @@ Goal: a structurally complete, **validated** model with correct patterns and swi
96
116
  the one-command fast path: a near-free stub per undocumented slice, wired, in slice order
97
117
  (MIL-184) — skip the per-slice `em slice new` calls above and deepen individual docs later
98
118
  when the team is ready.
99
- 3. **Evaluate completeness (step 7).** Walk the model: every slice is a **complete** pattern, not a
119
+ 4. **Evaluate completeness (step 7).** Walk the model: every slice is a **complete** pattern, not a
100
120
  half-slice — **every command has something that triggers it**, every command emits an event,
101
121
  **every event is read by a read model**, **every read model has a consumer**, every view has a
102
122
  source, every UI is reachable, every connection is
@@ -125,6 +145,19 @@ Goal: a structurally complete, **validated** model with correct patterns and swi
125
145
  session and stage `contracts/<model key>.tsp` with the model edit — the one derived artifact
126
146
  a design session produces, always by the command, never by hand.
127
147
 
148
+ 5. **Shape exit gate (GH #282).** The model phase ends on the shape lints, not on your judgement
149
+ of the storyline: run `em validate <model>.em --shape` (add `--json` to read it; exit code is
150
+ always 0, so read the output). Every finding is either **fixed** in the model, or **accepted**
151
+ as one bullet in `.event-modeling.md` `## Decisions log`:
152
+ `- YYYY-MM-DD: shape-accepted: <rule> <refs> — <reason>`
153
+ The gate passes only when **every remaining finding, waived or not,** has such a bullet. A
154
+ baseline passed as `--waive-file <path>` (lines `<rule-code> <slice-key>`) only silences the CLI
155
+ count for an estate; it never replaces the bullet. Re-run until each remaining finding has its
156
+ bullet. **Refuse to move to the slice phase otherwise** (`em state set-phase slice` waits): the
157
+ same kind of exit gate as asserting the generated contract above. Do not accept a finding
158
+ without asking the user for the reason. The gate runs after doc scaffolding, so update or retire
159
+ wired docs for renamed, merged or split slices.
160
+
128
161
  End of phase: render, update state (`em state set-phase slice` — the mechanical marker the state
129
162
  machine expects), and stop. Every slice already has a draft doc ready to receive the deep spec,
130
163
  but slicing is deliberate work the team starts when it's ready for it, not this phase's automatic
@@ -140,9 +173,17 @@ events already being settled, so working out of order means re-litigating an ear
140
173
  contract mid-spec. Check `README.md`'s Slices table for what's already done (run `em slice index
141
174
  <model-name>.em` first if it looks stale).
142
175
 
176
+ **Outcomes stay in the slice.** When the deep dive surfaces failure, retry or timeout, add the
177
+ outcome events (`Fulfilment Failed`, `Fulfilment Retry Scheduled`, ...) and their Given/When/Then
178
+ scenarios to the SAME slice, with retry policy and failure handling as invariants in its doc —
179
+ never a sibling slice per control-flow branch (1.18.0 adds the lint `automation-split-by-outcome`). A new slice
180
+ only when a different actor or a different trigger is involved. Canon: Dilger, "one business
181
+ action… two different possible outcomes" and "Linearize it!"; Dymitruk, "a few of these stories
182
+ to show how a command can and cannot succeed".
183
+
143
184
  This is also where **branch / unhappy-path events** are discovered and added to the model — as a
144
185
  slice's alternate/error flows surface (a rejection, removal, cancellation, decline, expiry), add
145
- the corresponding event/slice to the `.em` and re-render. The happy-path spine from earlier
186
+ the outcome event to the same slice unless a different actor or trigger is involved, then re-render. The happy-path spine from earlier
146
187
  phases is the starting point, not the finished event set. **Every new event needs its reader too**
147
188
  — a rejection or cancellation that nothing projects will warn, and usually the missing piece is
148
189
  real (someone has to see that the request was declined).
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: discover
3
- em-version: 1.17.0
3
+ em-version: 1.18.0
4
4
  description: >-
5
5
  Use when starting a brand-new event model from scratch — greenfield discovery: brainstorming
6
6
  past-tense events, storyboarding the happy path, finding the commands and read models — or when
@@ -22,28 +22,40 @@ layout, the Socratic/validation discipline every phase follows) and
22
22
  work — they are the source of truth. Run the preconditions there first.
23
23
 
24
24
  **Existing system?** Use the `extract` phase below. **New feature inside an existing codebase,
25
- not a whole-system extraction?** Stay in `discover`, but before locking in command/event names,
26
- Grep/Read adjacent real sources (OpenAPI specs, DB migrations, existing DTOs/event classes in
27
- sibling contexts) rather than guessing conventions — same check the `slice` phase applies to
28
- field tables (`em:design`).
25
+ not a whole-system extraction?** Stay in `discover`, but **only after the storyboard exists**
26
+ (step 2) and before locking in command/event names, Grep/Read adjacent real sources (OpenAPI
27
+ specs, DB migrations, existing DTOs/event classes in sibling contexts) rather than guessing
28
+ conventions — same check the `slice` phase applies to field tables (`em:design`).
29
+ Those sources fill operations no walkthrough touches; they never supply the story.
29
30
 
30
31
  ## Phase: `discover` — steps 1-4
31
32
 
32
33
  Goal: a draft model of events, storyboard, commands, and views. Loose is OK; structure comes next.
33
34
 
34
- 1. **Brainstorm events (step 1).** Ask the user to name everything that happens, as past-tense
35
+ 1. **Brainstorm events (step 1).** Draw the candidate events **from the walkthroughs and
36
+ journeys** (ask for them first; no walkthrough → *"what does success look like, start to
37
+ finish?"*): *"what happened, in the story's words?"* — business terms, never table or endpoint
38
+ vocabulary. Ask the user to name everything that happens, as past-tense
35
39
  facts. Probe for missed state changes. For each candidate, apply the **"is it an event?"
36
40
  test** (`methodology.md` step 1 — the "would this wake the CEO at 3am" heuristic):
37
41
  reject derived values (belong in a read model) and telemetry/activity (not business state).
38
42
  Don't silently drop rejections — park them in the state file's Decisions log with why, so
39
43
  they aren't re-proposed later. Capture the survivors as a flat list.
40
- 2. **Plot / storyboard (step 2).** Order the events into the narrative. Identify **personas**
44
+ 2. **Plot / storyboard (step 2).** Start from the story, not the schema: ask for the spec's
45
+ **walkthroughs**, use cases and user journeys — or *"what does success look like, start to
46
+ finish?"* — and build the timeline from those (`methodology.md`, "The story, not the schema").
47
+ Name the **main storyline** (the end-to-end happy path) and each **secondary storyline**
48
+ (retire, extend, reorganize) before ordering anything; never one lifecycle block per entity
49
+ (the CRUD shape). The OpenAPI/migrations/resource reference comes **after** the storyboard,
50
+ only to fill operations no walkthrough touches. Order the events into the narrative. Identify **personas**
41
51
  (actors) and the **UI** screens at each step. Add `persona` declarations and `ui` elements.
42
52
  In a **headless/API** model there are no human personas or screens — identify the external
43
53
  **callers** at each step instead; declare one persona per caller/role, and its `ui` boxes are
44
54
  the API calls (see `methodology.md`).
45
55
  3. **Inputs (step 3).** For each event, find the **command** that causes it (imperative name).
46
- Form `command → event` slices (State Change pattern). Every command needs a **trigger** in the
56
+ Form `command → event` slices (State Change pattern). Apply the **intent test**: name the
57
+ command for the intent a business person would say at this step of the story, not the
58
+ endpoint that carries it (`methodology.md`, "The story, not the schema"). Every command needs a **trigger** in the
47
59
  same breath: the screen the user issues it from (a `ui` in the slice), or — for the Automation
48
60
  and Translation patterns — the reaction that triggers it, also in this slice. A command
49
61
  nothing points at is a write nobody can start. *"Who does this, and where are they when they
@@ -66,6 +78,29 @@ slice already reads "no doc yet" — no slice docs exist until the `slice` phase
66
78
  Tell the user they can stop here and resume with `/em:event-modeling` (or invoke
67
79
  `em:design` directly).
68
80
 
81
+ ## Path: PRD or spec in (a document is the input)
82
+
83
+ Use this path when the user hands you a **PRD, requirements document or API spec** instead of
84
+ starting from a blank page. **Entry:** `/em:discover` (plugin) or `/em:event-modeling discover`
85
+ (repo skills), with the opening message **`Discover from this PRD/spec: <path>`**. **Read `reference/prd-spec-path.md`
86
+ and `reference/prd-rubric.md` before doing any work on it** — the path is a triage, not a
87
+ transcription: a document describes the system its authors imagined (resources, endpoints,
88
+ stack, quality targets), and a model copied from it comes out as CRUD-per-entity.
89
+
90
+ The steps, in order (detail in the reference): **0** pull the walkthroughs and name the main and
91
+ secondary **storylines** (the resource/endpoint reference is consulted last); **1** split into
92
+ single-claim statements, each quoting the source verbatim; **2** classify each against the
93
+ shipped rubric, plus the independent *"is this about the business, independent of how it's
94
+ built?"* check; **3** group by reaction (shared trigger + acting system = one slice; failure,
95
+ retry and timeout become outcome events and scenarios of that slice, never new slices); **4**
96
+ rewrite, don't drop (implementation details become business language linked to their source;
97
+ real tech choices and NFRs go to a constraints list that feeds the constitution); **5** record
98
+ `discovery/prd-triage.json` and run `em discover triage-check discovery/prd-triage.json`
99
+ (MCP: `discover_triage_check`) until it passes; **6** every low-confidence or disagreeing
100
+ statement becomes a Socratic question — never an invented answer. Rejections and parked
101
+ statements go in the state file's Decisions log with why, as in step 1. Then continue with
102
+ `discover` steps 1-4 on the surviving groups, with the storylines as the timeline's spine.
103
+
69
104
  ## Phase: `extract` — current-state model of an existing system
70
105
 
71
106
  Goal: a faithful **current-state** model extracted from an existing system — the as-is sibling
@@ -77,7 +77,12 @@ it is fine to finish a round with `# TBD`s still parked.
77
77
 
78
78
  - **R1 — Candidate events.** Extract (event-driven) or synthesize (procedural) the past-tense
79
79
  facts; apply the event test; user confirms/renames/rejects. Ambiguous candidates → `# TBD`.
80
- - **R2 — Timeline order.** Order the confirmed events into the as-is narrative. Identify the
80
+ - **R2 — Timeline order.** First **name the as-is process(es)** — which business journey does the
81
+ system run today, and what is its outcome? Take it from the system's walkthroughs, user journeys
82
+ and docs, and from the people who use it — not from the OpenAPI/migrations/DTO grep, which fills
83
+ operations no journey touches and comes **after** (`methodology.md`, "The story, not the
84
+ schema"). Name the main storyline and each secondary one, then order the confirmed events into
85
+ that as-is narrative, never one block per entity. Identify the
81
86
  *actual* actors/callers and the *real* UI or API surfaces (headless systems: still `ui`/`persona`
82
87
  — one persona per caller/role, its `ui` boxes are the API calls, per `em-dsl.md`). **First
83
88
  `em render` here.**
@@ -0,0 +1,103 @@
1
+ <!-- GENERATED FILE from .claude/skills/event-modeling-discover/reference/prd-rubric.md by scripts/build-plugin.ts — do not hand-edit; run npm run docs:generate -->
2
+ # PRD/spec triage rubric — version 1.0
3
+
4
+ The shipped classification rubric for the PRD/spec path (`prd-spec-path.md`, step 2). It is data:
5
+ `em discover triage-check` rejects a `discovery/prd-triage.json` whose `rubricVersion` is not the
6
+ version below. Do not edit it per project; a changed rubric ships with a new `rubricVersion` in a
7
+ new `em` release.
8
+
9
+ ## The independent check
10
+
11
+ Run on every statement in addition to its category: **"Is this about the business, independent of
12
+ how it is built?"** Record the answer as `aboutBusiness` (true/false), answering it separately from
13
+ the category. The table's `aboutBusiness` column is what the category *expects*.
14
+
15
+ ## Categories
16
+
17
+ | Category id | What it is | aboutBusiness expected | Allowed outcomes |
18
+ |---|---|---|---|
19
+ | `business-fact` | Something that happens or is true in the business; candidate event or field. | true | modeled, rewritten, parked, question |
20
+ | `user-intent` | What an actor wants to accomplish; candidate command with its trigger. | true | modeled, rewritten, parked, question |
21
+ | `information-need` | What an actor needs to see or know to act; candidate read model. | true | modeled, rewritten, parked, question |
22
+ | `actor` | A person, role or calling system; candidate persona. | true | modeled, parked, question |
23
+ | `business-rule` | A constraint the business imposes; candidate invariant or scenario. | true | modeled, rewritten, parked, question |
24
+ | `external-system` | A system outside the boundary; candidate translation slice or constraint. | either | modeled, rewritten, constraint, parked, question |
25
+ | `implementation-detail` | How it is built. Rewritten in business language when it implies a business fact; a real tech choice goes to the constraints list. | false | rewritten, constraint, parked, question |
26
+ | `nfr` | Performance, availability, security, compliance: feeds the constitution, never a slice. | either | constraint, parked, question |
27
+ | `goal-background` | Why the product exists; context for the main storyline, not a slice. | true | parked, rewritten, question |
28
+ | `question` | An open question in the source; becomes a Socratic question, never an invented answer. | either | question, parked |
29
+ | `resource-or-endpoint` | Describes an API resource or endpoint. Rewritten to the intent it serves (one endpoint may carry several); never promoted to a command as-is. | false | rewritten, parked, question |
30
+
31
+ ## Outcomes
32
+
33
+ `modeled` (joins a slice group as stated), `rewritten` (joins a group in business language, the
34
+ original linked through `quote`), `constraint` (constraints list), `parked` (a `reason`; also a
35
+ Decisions-log entry), `question` (a Socratic `question` for the human). `question` and `parked`
36
+ are allowed for every category: a doubt can always be asked, and an explicitly deferred intent is
37
+ legitimate when its `reason` says so.
38
+
39
+ ## Rules the checker enforces
40
+
41
+ All of these are checked by `em discover triage-check`; write the record so they hold.
42
+
43
+ 1. **Disagreement is a question.** If `aboutBusiness` contradicts the category's expectation
44
+ (a `business-fact` that names a table; an `implementation-detail` that states a business
45
+ fact), the outcome MUST be `question`. Put the proposed rewrite in the question text
46
+ ("Is this a business fact? proposed: Stock is decremented when a pick is confirmed."); the
47
+ human resolves it and a later pass records `rewritten`.
48
+ 2. **Low confidence is a question.** `confidence: low` means outcome `question`, whatever the category.
49
+ 3. **An endpoint is never modeled as-is.** `resource-or-endpoint` is `rewritten` (to the intent it
50
+ serves), `parked` or `question` — never `modeled`.
51
+ 4. **Only modeled and rewritten statements join a slice group.** They carry a declared `group` and
52
+ the `storyline` of that group. Every other outcome has `group: null` (and `storyline: null`),
53
+ except that a `question` may name the group it would join once answered (with that group's
54
+ storyline); that is a pointer only, never an anchor (rule 7).
55
+ 5. `rewritten` needs `rewrite`; `parked` needs `reason`; `question` needs `question`; every
56
+ `constraint` appears in `constraints[]`.
57
+ 6. **Provenance is checked, not declared.** Every statement says whether it came from a
58
+ `walkthrough` or from the `reference` (resource/endpoint reference, operational notes). A
59
+ `walkthrough` statement's quote must lie inside some storyline's span; a `reference`
60
+ statement's quote must lie outside every storyline span.
61
+ 7. **A group is either anchored or reference-only.** An anchored group names a storyline and has at
62
+ least one `modeled` or `rewritten` `walkthrough` statement whose quote lies inside that storyline's span (a `question` never anchors: a group anchored by questions only is an error). A **reference-only
63
+ group** has only `reference` statements and **no storyline**: `storyline: null` on the group and
64
+ on its statements. It never joins a walkthrough storyline. The report lists reference-only
65
+ groups outside every storyline — operations no walkthrough touches; check these are not CRUD fill.
66
+ 8. **One statement per claim.** A statement's normalised quote may not equal, contain or be contained in another statement's quote; the error names both.
67
+ 9. **Quotes are verbatim** (Markdown emphasis marks, smart quotes and whitespace aside) and at
68
+ least 12 characters. A storyline `quote` is the **full** walkthrough span, start to finish.
69
+
70
+ ## Rubric decisions
71
+
72
+ - `nfr` expects `aboutBusiness: either`: a retention or audit rule is a business NFR, so the check
73
+ must not force a disagreement on it.
74
+ - `external-system` also stays `either`: a system outside the boundary can be named in business
75
+ terms (the payment provider) or technical ones (a queue broker).
76
+
77
+ The checker cannot judge whether a classification is right, nor whether every claim in the
78
+ walkthrough has a statement; that is what the human review is for.
79
+
80
+ ## Machine-readable copy
81
+
82
+ The same data as JSON. The checker's built-in copy is asserted identical to this block, and the
83
+ table above is asserted to match it, by `test/prdTriage.test.ts`.
84
+
85
+ ```json
86
+ {
87
+ "rubricVersion": "1.0",
88
+ "independentCheck": "Is this about the business, independent of how it is built?",
89
+ "categories": [
90
+ { "id": "business-fact", "aboutBusiness": true, "outcomes": ["modeled", "rewritten", "parked", "question"], "meaning": "Something that happens or is true in the business; candidate event or field." },
91
+ { "id": "user-intent", "aboutBusiness": true, "outcomes": ["modeled", "rewritten", "parked", "question"], "meaning": "What an actor wants to accomplish; candidate command with its trigger." },
92
+ { "id": "information-need", "aboutBusiness": true, "outcomes": ["modeled", "rewritten", "parked", "question"], "meaning": "What an actor needs to see or know to act; candidate read model." },
93
+ { "id": "actor", "aboutBusiness": true, "outcomes": ["modeled", "parked", "question"], "meaning": "A person, role or calling system; candidate persona." },
94
+ { "id": "business-rule", "aboutBusiness": true, "outcomes": ["modeled", "rewritten", "parked", "question"], "meaning": "A constraint the business imposes; candidate invariant or scenario." },
95
+ { "id": "external-system", "aboutBusiness": "either", "outcomes": ["modeled", "rewritten", "constraint", "parked", "question"], "meaning": "A system outside the boundary; candidate translation slice or constraint." },
96
+ { "id": "implementation-detail", "aboutBusiness": false, "outcomes": ["rewritten", "constraint", "parked", "question"], "meaning": "How it is built. Rewritten in business language when it implies a business fact; a real tech choice goes to the constraints list." },
97
+ { "id": "nfr", "aboutBusiness": "either", "outcomes": ["constraint", "parked", "question"], "meaning": "Performance, availability, security, compliance: feeds the constitution, never a slice." },
98
+ { "id": "goal-background", "aboutBusiness": true, "outcomes": ["parked", "rewritten", "question"], "meaning": "Why the product exists; context for the main storyline, not a slice." },
99
+ { "id": "question", "aboutBusiness": "either", "outcomes": ["question", "parked"], "meaning": "An open question in the source; becomes a Socratic question, never an invented answer." },
100
+ { "id": "resource-or-endpoint", "aboutBusiness": false, "outcomes": ["rewritten", "parked", "question"], "meaning": "Describes an API resource or endpoint. Rewritten to the intent it serves (one endpoint may carry several); never promoted to a command as-is." }
101
+ ]
102
+ }
103
+ ```