@milehimikey/em 1.11.0 → 1.12.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 (92) 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 +13 -5
  4. package/.claude/skills/event-modeling-discover/SKILL.md +1 -1
  5. package/.claude/skills/event-modeling-implement/SKILL.md +12 -6
  6. package/.claude/skills/event-modeling-implement/reference/implement.md +159 -18
  7. package/.claude/skills/event-modeling-review/SKILL.md +1 -1
  8. package/.claude/skills/event-modeling-shared/reference/em-dsl.md +17 -6
  9. package/.claude/skills/event-modeling-shared/reference/slice-doc-schema.md +39 -0
  10. package/.claude/skills/event-modeling-shared/templates/constitution.md +8 -1
  11. package/dist/catalog/build.js +42 -1
  12. package/dist/catalog/build.js.map +1 -1
  13. package/dist/catalog/coverage.d.ts +15 -0
  14. package/dist/catalog/coverage.js +34 -0
  15. package/dist/catalog/coverage.js.map +1 -0
  16. package/dist/catalog/docJoin.d.ts +11 -2
  17. package/dist/catalog/docJoin.js +26 -4
  18. package/dist/catalog/docJoin.js.map +1 -1
  19. package/dist/catalog/docModelConsistencyValidate.js +1 -1
  20. package/dist/catalog/docModelConsistencyValidate.js.map +1 -1
  21. package/dist/catalog/noteBindingValidate.js +18 -0
  22. package/dist/catalog/noteBindingValidate.js.map +1 -1
  23. package/dist/catalog/orphanedSliceDocValidate.d.ts +8 -1
  24. package/dist/catalog/orphanedSliceDocValidate.js +33 -6
  25. package/dist/catalog/orphanedSliceDocValidate.js.map +1 -1
  26. package/dist/catalog/pages.d.ts +16 -1
  27. package/dist/catalog/pages.js +16 -4
  28. package/dist/catalog/pages.js.map +1 -1
  29. package/dist/catalog/sliceReadyValidate.d.ts +12 -1
  30. package/dist/catalog/sliceReadyValidate.js +4 -4
  31. package/dist/catalog/sliceReadyValidate.js.map +1 -1
  32. package/dist/cli/agentsMd.js +9 -3
  33. package/dist/cli/agentsMd.js.map +1 -1
  34. package/dist/cli/ciInit.js +3 -0
  35. package/dist/cli/ciInit.js.map +1 -1
  36. package/dist/cli/conformScope.js +10 -3
  37. package/dist/cli/conformScope.js.map +1 -1
  38. package/dist/cli/coverage.d.ts +34 -9
  39. package/dist/cli/coverage.js +61 -24
  40. package/dist/cli/coverage.js.map +1 -1
  41. package/dist/cli/markImplemented.js +12 -1
  42. package/dist/cli/markImplemented.js.map +1 -1
  43. package/dist/cli/ratify.js +17 -1
  44. package/dist/cli/ratify.js.map +1 -1
  45. package/dist/cli/reratify.js +12 -1
  46. package/dist/cli/reratify.js.map +1 -1
  47. package/dist/cli/review.js +12 -1
  48. package/dist/cli/review.js.map +1 -1
  49. package/dist/cli/sliceIndex.js +9 -4
  50. package/dist/cli/sliceIndex.js.map +1 -1
  51. package/dist/cli/sliceLink.js +16 -0
  52. package/dist/cli/sliceLink.js.map +1 -1
  53. package/dist/cli/status.d.ts +11 -0
  54. package/dist/cli/status.js +17 -1
  55. package/dist/cli/status.js.map +1 -1
  56. package/dist/cli.js +63 -31
  57. package/dist/cli.js.map +1 -1
  58. package/dist/emit/coverageJson.d.ts +2 -2
  59. package/dist/emit/coverageJson.js +6 -2
  60. package/dist/emit/coverageJson.js.map +1 -1
  61. package/dist/emit/json.d.ts +24 -1
  62. package/dist/emit/json.js +50 -2
  63. package/dist/emit/json.js.map +1 -1
  64. package/dist/emit/statusJson.d.ts +1 -1
  65. package/dist/emit/statusJson.js +6 -1
  66. package/dist/emit/statusJson.js.map +1 -1
  67. package/dist/emit/validateJson.d.ts +4 -3
  68. package/dist/emit/validateJson.js +10 -3
  69. package/dist/emit/validateJson.js.map +1 -1
  70. package/dist/mcp/server.js +37 -19
  71. package/dist/mcp/server.js.map +1 -1
  72. package/dist/model/continuation.d.ts +23 -0
  73. package/dist/model/continuation.js +49 -0
  74. package/dist/model/continuation.js.map +1 -0
  75. package/dist/model/queryIndex.js +1 -1
  76. package/dist/model/queryIndex.js.map +1 -1
  77. package/dist/model/rules.d.ts +6 -0
  78. package/dist/model/rules.js +6 -0
  79. package/dist/model/rules.js.map +1 -1
  80. package/dist/model/sliceDocIndex.d.ts +12 -5
  81. package/dist/model/sliceDocIndex.js +18 -7
  82. package/dist/model/sliceDocIndex.js.map +1 -1
  83. package/dist/render/sliceStatus.d.ts +3 -3
  84. package/dist/render/sliceStatus.js +27 -29
  85. package/dist/render/sliceStatus.js.map +1 -1
  86. package/dist/templates.d.ts +1 -1
  87. package/dist/templates.js +8 -1
  88. package/dist/templates.js.map +1 -1
  89. package/dist/util/localDate.d.ts +2 -0
  90. package/dist/util/localDate.js +17 -0
  91. package/dist/util/localDate.js.map +1 -0
  92. package/package.json +1 -1
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: event-modeling
3
- em-version: 1.11.0
3
+ em-version: 1.12.0
4
4
  description: >-
5
5
  Use when the user runs `/event-modeling` (with or without a phase name), or wants to resume an
6
6
  event-modeling session without specifying which part of the process applies. Reads the model's
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: event-modeling-conform
3
- em-version: 1.11.0
3
+ em-version: 1.12.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: event-modeling-design
3
- em-version: 1.11.0
3
+ em-version: 1.12.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
@@ -40,8 +40,11 @@ Goal: a structurally complete, **validated** model with correct patterns and swi
40
40
  slice, none skipped: `em slice new "<slice name>" --pattern <state-change|state-view|
41
41
  automation|translation> --swimlane "<Persona> → <Context>" --wire <model>.em` writes
42
42
  `slices/<slice-name>.md` at `status: draft` (the same command, flags, and `--wire`
43
- note-binding the `slice` phase's step 2 below documents in full). Then, for each freshly
44
- scaffolded doc, hold a first-pass Socratic pass and write the happy-path Given/When/Then into
43
+ note-binding the `slice` phase's step 2 below documents in full). A slice whose only element
44
+ is a `view X again` instance is a **continuation** (MIL-208), not a slice to scaffold —
45
+ `--wire` refuses it and names the originating slice holding `X`'s first declaration; its
46
+ scenarios go into that doc instead. Then, for each freshly scaffolded doc, hold a first-pass
47
+ Socratic pass and write the happy-path Given/When/Then into
45
48
  its `## Scenarios (Given / When / Then)` section and the obvious invariants known so far into
46
49
  its `## Invariants / Business Rules` section — **into the doc, never into the state file.**
47
50
  Keep this pass shallow (no field tables, alternate/error flows, or NFRs yet — that's the full
@@ -115,6 +118,9 @@ For each slice:
115
118
  "slices/<slice-name>.md"` line straight into the `.em`, onto the slice's primary element (the
116
119
  command for State Change, the view for State View, the processor for Automation, the
117
120
  translation for Translation), matched by export key — see step 4 for when it refuses instead.
121
+ A slice whose sole candidate view is a later `view X again` instance is a **continuation**
122
+ (MIL-208): `--wire` refuses it and names the originating slice holding `X`'s first
123
+ declaration — write this slice's scenarios into that doc instead of scaffolding a new one.
118
124
  Both `--pattern` and `--swimlane` are required; an invalid `--pattern` is refused with the
119
125
  valid choices listed. Never hand-type this block, and never fall back to a placeholder
120
126
  pattern/swimlane to dodge the flags. Then fill in every judgment section below the stub from
@@ -154,5 +160,7 @@ Before the project's first slice is implemented it also needs a ratified impleme
154
160
  constitution (stack, code style, testing norms, NFR baselines, review norms) — the
155
161
  `event-modeling-implement` skill elicits it, see its `reference/implement.md` §7.
156
162
 
157
- End of phase: every slice that should exist has a doc, `README.md`'s Slices table is current,
158
- and the model validates clean. Suggest `event-modeling-implement` for each ratified slice.
163
+ End of phase: every slice that should exist has a doc — except a continuation slice (MIL-208, a
164
+ later `view X again` instance), which has none by design and is documented by the originating
165
+ slice instead — `README.md`'s Slices table is current, and the model validates clean. Suggest
166
+ `event-modeling-implement` for each ratified slice.
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: event-modeling-discover
3
- em-version: 1.11.0
3
+ em-version: 1.12.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
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: event-modeling-implement
3
- em-version: 1.11.0
3
+ em-version: 1.12.0
4
4
  description: >-
5
5
  Use when building a single ratified event-modeling slice (status: ready-to-implement) into
6
6
  merged, tested code — the one phase where the agent builds rather than facilitates. Gates on
@@ -16,11 +16,17 @@ the agent builds rather than facilitates.
16
16
  **Read `reference/implement.md` before doing any implement work and follow it as the
17
17
  contract.** In short: gate on `em validate <model>.em --slice-ready <key>` (stop and hand
18
18
  back if it fails — never edit the doc to make the gate pass); read the project's implementation
19
- constitution before writing code and stay inside its rules; treat the slice doc as the
20
- spec (read-only, except the merge-time `status`/`implementedIn` flip); surface every gap to
21
- the user instead of deciding it silently; cover every `INV-<MNEMONIC>-n` and every scenario with
22
- tests; in spec-kit projects, allocate via em-sdd-bridge (redirect mode) and never run
23
- `/speckit.specify`.
19
+ constitution before writing code and stay inside its rules; before a project's first slice PR,
20
+ land a one-time foundation PR holding every model event (plus the shared interface shell) so
21
+ slices only ever import contracts, never invent or reach into a sibling's; build in dependency
22
+ order from `em query upstream`, not timeline order; treat the slice doc as
23
+ the spec (read-only, except the merge-time `status`/`implementedIn` flip); implement exactly one
24
+ slice doc per PR — a `covers:` doc's two keys ship together in that one PR, an `again` view
25
+ instance gets its own; ship the endpoint a
26
+ `ui`-triggered command or `ui`-consumed view obligates, and surface — never invent — one the
27
+ model doesn't show; surface every gap to the user instead of deciding it silently; cover every
28
+ `INV-<MNEMONIC>-n` and every scenario with tests; in spec-kit projects, allocate via
29
+ em-sdd-bridge (redirect mode) and never run `/speckit.specify`.
24
30
 
25
31
  **Gate: no constitution, no first slice.** The constitution is the project's ratified house rules
26
32
  — stack and architectural shape (including which implementation skill each slice pattern routes
@@ -26,18 +26,22 @@ em validate <model>.em --slice-ready <slice-key> --json
26
26
  ```
27
27
 
28
28
  `<slice-key>` is the slice's export key — the kebab-case slug of its name (`"Place Order"` →
29
- `place-order`). Read the JSON document's `ready` field — don't infer it from the exit code or
30
- any printed text. `ready: true` means: the slice has a doc bound via `note "slices/<key>.md"`,
31
- its frontmatter is usable, `status: ready-to-implement`, every `## Open Questions` checkbox is
32
- checked, and no status/version/link incoherence is flagged — `gates` names each of those 4
33
- conditions individually (`docBound`/`frontmatterUsable`/`statusReady`/
34
- `noUncheckedOpenQuestions`) if you need to say which one is blocking.
35
-
36
- **`ready: false` means stop.** Report which `gates` entries are `false` (and any `diagnostics`
37
- entries concerning this slice) and hand the slice back to the humans. Never make the gate pass
38
- yourself — checking an open-question box, flipping `status`, or editing frontmatter are
39
- ratification decisions, and ratification happens in a facilitated session, not in an
40
- implementation branch.
29
+ `place-order`). Don't guess it or hand-slug the name yourself: list every slice's key with
30
+ `em export <model>.em | jq -r '.model.slices[].key'`. Read the JSON document's `ready` field —
31
+ don't infer it from the exit code or any printed text. `ready: true` means: the slice has a doc
32
+ bound via `note "slices/<key>.md"`, its frontmatter is usable, `status: ready-to-implement`,
33
+ every `## Open Questions` checkbox is checked, and no status/version/link incoherence is flagged
34
+ — `gates` names each of those 4 conditions individually (`docBound`/`frontmatterUsable`/
35
+ `statusReady`/`noUncheckedOpenQuestions`) if you need to say which one is blocking.
36
+
37
+ **`ready: false` means stop** — with one named exception: a slice already `status: implemented`
38
+ with `driftSignal: in-sync` also reads `ready: false` (its `statusReady` gate fails because
39
+ nothing is newly ratified), and that specific case is re-implementation/repair, not a blocker —
40
+ see §3's third mode before treating it as one. For every other `false`, report which `gates`
41
+ entries are `false` (and any `diagnostics` entries concerning this slice) and hand the slice
42
+ back to the humans. Never make the gate pass yourself — checking an open-question box, flipping
43
+ `status`, or editing frontmatter are ratification decisions, and ratification happens in a
44
+ facilitated session, not in an implementation branch.
41
45
 
42
46
  ## 2. Read the spec
43
47
 
@@ -68,18 +72,42 @@ Then read `slices/<slice-key>.md` end to end — every section is load-bearing:
68
72
  | `## Non-Functional Requirements` | Authz, PII, performance — implement or consciously surface, never skip |
69
73
  | `## Dependencies & Read Models Affected` | The blast radius — what else to check before and after |
70
74
 
75
+ **Interface obligations follow from the model, not from the doc's prose.** The DSL already
76
+ encodes what's reachable — see `em-dsl.md`'s "Headless / API systems & repeated read models"
77
+ section — and the routing from it is mechanical, not a technical choice you make while coding:
78
+
79
+ - A **command with a `ui` trigger** ships the endpoint that dispatches it as part of this slice
80
+ — shape, verb, status codes, and error mapping follow the constitution's interface conventions
81
+ (§7). Because: the model already said this command is reachable from outside; leaving it
82
+ reachable only through an internal gateway silently narrows what the model ratified.
83
+ - A **view with a `ui` consumer** ships the query endpoint as part of this slice, same
84
+ conventions.
85
+ - **No `ui`** on the trigger or the consumer (a command dispatched by a processor or a
86
+ translation, a view read only by a reaction) means internal-only: no endpoint. If one seems
87
+ needed anyway, that's a gap to surface (§4), not a technical choice to make mid-implementation
88
+ — adding a route the model doesn't show is exactly the kind of silent divergence §4 exists to
89
+ catch.
90
+ - **`public` events and views** are the model's published integration surface: their shape is a
91
+ contract, not an implementation detail. `em validate --list-public` lists them; `em typespec`
92
+ is the POC generator for a downstream schema. Changing a public element's shape is a ratified
93
+ delta, never an implementation-time choice.
94
+
71
95
  For timeline context, read the slice's surroundings in the `.em` (what triggers it, what
72
96
  consumes its output) — the slice's own diagram (`slices/<slice-key>.svg`) shows its canonical
73
97
  pattern shape. For machine-readable facts, use `em export <model>.em --slice <slice-key>`
74
98
  rather than parsing the DSL or the doc's frontmatter yourself: it returns just this slice's
75
99
  object — `pattern`, element refs, fields, and the joined doc metadata (`slice.doc.status`,
76
100
  `version`, `driftSignal`) — without piping the whole model's export to find one `slice.doc`.
101
+ When the view repeats later via `again` (MIL-208), that same call on the *originating* slice
102
+ also returns `alsoReads: [{ event, atSlice }]` — the union of every event the read model
103
+ consumes across all its later continuations, in timeline order — so implementing it never means
104
+ walking each continuation by hand to assemble the full event list.
77
105
 
78
106
  If the project ships pattern-specific implementation skills (for example an Axon/DCB skill
79
107
  set keyed on a slice doc's `pattern:` frontmatter), route by the slice's pattern and follow
80
108
  those.
81
109
 
82
- ## 3. Two modes: first version vs. ratified delta
110
+ ## 3. Three modes: first version, ratified delta, re-implementation/repair
83
111
 
84
112
  Check the doc's `version:` and `## Delta` section (or `em export`'s `slice.doc.driftSignal`):
85
113
 
@@ -93,6 +121,26 @@ Check the doc's `version:` and `## Delta` section (or `em export`'s `slice.doc.d
93
121
  **Never regenerate merged code wholesale from the model** — after merge, code is owned by
94
122
  the people who've edited it (generated-then-owned); regeneration is legal only for slices
95
123
  whose code never merged.
124
+ - **Re-implementation / repair** (`status: implemented`, `implementedIn` set, `driftSignal:
125
+ in-sync` — `--slice-ready` reports `ready: false` purely because `statusReady` fails, with no
126
+ other gate false and no doc content changed): the doc hasn't changed, but the shipped code
127
+ needs rebuilding or fixing — deleted code, a reverted PR, a bug in behavior the doc already
128
+ specifies. **The doc is still the spec.** The gate's `false` here means "nothing new is
129
+ ratified," not "stop" — it is not license to self-initiate: proceed only when a human has
130
+ asked for this specific re-work. Scope the change to what the repair needs, exactly like a
131
+ delta (generated-then-owned still applies — don't regenerate the whole slice wholesale just
132
+ because the gate reads oddly); if what you'd change is really a change in behavior rather than
133
+ a repair of it, that's a ratified delta (the bullet above), not this mode — when unsure which
134
+ one you're in, ask the human rather than guess.
135
+
136
+ No lifecycle flip is needed unless `implementedIn` changes. If it does,
137
+ `em slice mark-implemented <model>.em <slice-key> <new-pr-url>` will **refuse** rather than
138
+ overwrite: a doc already `implemented` with a different `implementedIn` URL is left untouched
139
+ and the command reports the conflict (`src/cli/markImplemented.ts`) instead of silently
140
+ replacing provenance. That refusal is correct, not a bug to route around — never hand-edit the
141
+ frontmatter to force it through. Surface the conflict to the human instead: they decide whether
142
+ the new PR supersedes the recorded one (and re-run `mark-implemented` themselves, or ask you to,
143
+ once they've decided) or whether the existing link should stand.
96
144
 
97
145
  ## 4. Gaps: propose, never decide
98
146
 
@@ -114,6 +162,20 @@ undocumented ordering, a contradiction with adjacent code. The discipline:
114
162
 
115
163
  ## 5. Definition of done
116
164
 
165
+ **The unit is the slice doc: one slice doc = one PR.** A doc that `covers:` another slice (an
166
+ automation's to-do-list view and its reaction, MIL-121) is one spec, not two — it ships in one
167
+ PR, and `mark-implemented` runs for both keys against the same URL. A read model is one doc,
168
+ even when a `view X again` clause repeats it across later positions on the timeline (MIL-208):
169
+ the later instance is a **continuation** of the slice holding `X`'s first declaration, not a
170
+ spec unit of its own — no doc, no status, no ratification, no PR of its own. Its behavior
171
+ belongs in the originating doc's `## Scenarios` (one scenario per event the later instance
172
+ feeds), not a doc of its own; `em export --slice <originating-key>` lists the full union of
173
+ events the read model consumes across every position via `alsoReads` (§2). Never fold multiple
174
+ slice docs' implementations into one PR because they touch the same read model, ship together,
175
+ or seem small — see §10.
176
+
177
+ - The PR description cites exactly one slice doc (plus its `covers:` keys, if any) and lists
178
+ that doc's invariants alongside the tests that cover them.
117
179
  - Every `INV-<MNEMONIC>-n` invariant has at least one test that cites its ID — checked mechanically by
118
180
  `em coverage <model>.em --tests <dir>` (MIL-130); run it (add `--strict` in CI) rather than
119
181
  eyeballing citations by hand.
@@ -122,11 +184,15 @@ undocumented ordering, a contradiction with adjacent code. The discipline:
122
184
  - Alternate/error flows (idempotency included) are covered by tests.
123
185
  - The build and full test suite are green; the model still validates (`em validate` in CI —
124
186
  you didn't touch the `.em`, so this only fails if something else broke).
125
- - Nothing between the slice doc and the code was committed as a source of truth (see §8 —
187
+ - Nothing between the slice doc and the code was committed as a source of truth (see §9 —
126
188
  work containers are ephemeral; generated or symlinked specs are renderings).
127
189
 
128
190
  ## 6. At merge: the lifecycle flip
129
191
 
192
+ The PR being merged here is the one doc's PR (§5) — its description cites exactly this slice doc
193
+ (plus any `covers:` keys) and nothing else, which is what makes the flip below unambiguous: one
194
+ PR, one `implementedIn` URL, applied to every key that PR's doc covers.
195
+
130
196
  When the PR merges, exactly **two** frontmatter fields change on the slice doc:
131
197
 
132
198
  ```yaml
@@ -165,7 +231,11 @@ parked in the document as open items, exactly like a slice doc's Open Questions:
165
231
  1. **Stack and architectural shape** — languages/runtimes, frameworks, the architectural pattern,
166
232
  where a slice's code lives, and the routing table: which implementation skill or approach each
167
233
  of the four slice patterns uses (for example, State Change slices → the `axon-*` skill set).
168
- That table is what you obey in §2 when you read a slice's `pattern:`.
234
+ That table is what you obey in §2 when you read a slice's `pattern:`. This is also where the
235
+ **interface conventions** question gets asked — resource style, error/rejection mapping,
236
+ versioning, auth at the edge — so the shape of every endpoint the interface obligations
237
+ paragraph (§2) and the foundation step (§8) require is decided once, here, instead of
238
+ invented per slice.
169
239
  2. **Code style** — formatter/linter, naming, module layout, and what "idiomatic" means here.
170
240
  3. **Testing norms** — which test levels each pattern requires, where invariant and scenario
171
241
  tests live, and the test-naming convention that lets `em coverage` trace `INV-<MNEMONIC>-n`
@@ -197,7 +267,76 @@ spec-kit's own placeholders is the team's call, and em's sections go in alongsid
197
267
  `em status` will then report `constitution: present` for the model. That's the whole mechanical
198
268
  check — em stores and locates this document, and never validates a word of it.
199
269
 
200
- ## 8. Spec-kit projects: the SDD adapter
270
+ ## 8. Foundation: contracts first
271
+
272
+ Once per project, before the first slice PR ever opens, land one **foundation PR** that turns
273
+ the model's events — and the cross-cutting shell the constitution names — into code, and
274
+ nothing else. Skip this and the failure is the same on every project that's skipped it: the
275
+ first slice PR ships fine, then the second one needs an event the first slice already defined
276
+ informally, and every PR after that either redefines it slightly differently or reaches into a
277
+ sibling slice's package to reuse it — the boundary the constitution drew is gone within a week.
278
+
279
+ 1. **What it contains.** Every event `em export <model>.em` lists in `events[]`, each placed in
280
+ the home the constitution's "where a slice's code lives" rule assigns to its *emitting*
281
+ slice — not a separate shared "events" module, so nothing about where an event lives depends
282
+ on this PR existing rather than the constitution. Fields come verbatim from the slice docs'
283
+ Event tables, with types mapped per the constitution's stack conventions. Add the
284
+ cross-cutting shell the constitution names: shared ports, and the request/response boundary
285
+ and error mapping the interface obligations above (§2) plug their endpoints into. **No
286
+ handlers, no projections — contracts only.** Because: a handler or a projection belongs to
287
+ the slice that owns the behavior behind it; folding one into the foundation PR turns a shared
288
+ contract into a pre-empted slice.
289
+ 2. **It merges before any slice PR opens.** Every slice branch is cut from `main` *after* the
290
+ foundation PR has landed there — never from a commit that predates it, and never alongside an
291
+ open, unmerged foundation PR. A slice PR never carries foundation files. Because: a slice PR
292
+ opened while the foundation is still in review shows a diff dominated by files the slice
293
+ didn't write and can't be held accountable for, and its merge depends on someone else's
294
+ review timeline instead of its own.
295
+ 3. **A slice never creates, edits, or reaches into another slice's code.** It *imports* events —
296
+ only events — from wherever the foundation placed them; a sibling slice's handlers, storage,
297
+ and other internals stay off limits. Because: the boundary the constitution drew only holds if
298
+ every slice actually stays inside it — a slice that quietly builds on a sibling's internals has
299
+ erased that boundary informally, one import at a time.
300
+ 4. **A ratified delta that adds or changes an event re-runs this step for that event, first.**
301
+ Land the code change for the new or changed event — in its emitting slice's home, following
302
+ rule 1 — before the slice's delta PR opens, exactly as if it were the first pass. Because: the
303
+ same contract-before-consumer ordering that applies once per project applies again every time
304
+ the contract itself changes; an event isn't a stable shared type just because it was one
305
+ yesterday.
306
+
307
+ ### Order of work
308
+
309
+ The foundation PR above answers most of "which slice next" by itself — once every event exists
310
+ as a contract, a read slice no longer has to wait for the write slice that will eventually
311
+ produce it. What's left is ordering the slice PRs against each other, and that ordering comes
312
+ from the dependency graph, not from where a slice sits on the timeline:
313
+
314
+ 1. **Foundation first removes most ordering constraints.** With every event already landed as a
315
+ contract (rules 1–4 above), a slice's remaining dependency is on other *slices*, not on
316
+ events existing at all — that's what turns "order every slice against every other slice"
317
+ into the short, per-pattern list below.
318
+ 2. **Then take dependency order from the graph, not the timeline.** Before starting a slice,
319
+ run `em query upstream <model>.em --of <slice's command-or-view ref>` — anything it returns
320
+ that's still `ready-to-implement` is a candidate to build first; `em query producers
321
+ <model>.em --event <name>` names the slices that own a specific event's producing commands
322
+ when you need to check one directly. Concretely, per pattern:
323
+ - **State Change** with no upstream is always startable — nothing but the foundation gates it.
324
+ - **State View** is startable as soon as the events it reads exist as contracts (the
325
+ foundation alone satisfies this), but build it *after* their producing slices have merged
326
+ when you can — its fixture tests then exercise real commands instead of hand-rolled event
327
+ fixtures.
328
+ - **Automation** goes after both the command it triggers and the to-do-list view it reads
329
+ (bound via `covers:` when the two are written as one doc) — a reaction can't be tested
330
+ against a command handler or a to-do list that doesn't exist yet.
331
+ - **Translation** goes after the command it triggers, same reasoning as an Automation's
332
+ triggered-command dependency.
333
+ 3. **Timeline order is the ratification order, not the build order.** Ratifying slices in
334
+ timeline order is deliberate — it's the order stakeholders reason about the business process
335
+ in. Carrying that same order into implementation is the mistake this section exists to head
336
+ off: build order comes from the dependency graph above, and the two orders routinely
337
+ disagree.
338
+
339
+ ## 9. Spec-kit projects: the SDD adapter
201
340
 
202
341
  If the repository uses spec-kit (a `.specify/` directory exists), do not hand-author spec-kit
203
342
  artifacts — allocate through **em-sdd-bridge**, redirect mode preferred:
@@ -229,7 +368,7 @@ plan/tasks as FEATURE_SPEC. The rules that keep redirect mode safe:
229
368
  No spec-kit (or any SDD tool)? Implement straight from the slice doc — it already contains
230
369
  everything a spec holds. Don't introduce an intermediate spec document of your own.
231
370
 
232
- ## 9. Never do
371
+ ## 10. Never do
233
372
 
234
373
  | Rule | Because |
235
374
  |---|---|
@@ -241,8 +380,10 @@ everything a spec holds. Don't introduce an intermediate spec document of your o
241
380
  | Never invent the house rules a first slice needs (stack, style, testing, NFR defaults) | They're the project's decision, ratified in the constitution — §7, not your judgment call |
242
381
  | Never regenerate merged code from the model | Generated-then-owned: post-merge code belongs to its owners |
243
382
  | Never commit an authored intermediate spec | The slice is the spec; anything between it and the code is a rendering |
383
+ | Never open a slice PR before the foundation PR has merged | The foundation is what makes an event a stable shared contract — a slice built ahead of it is building on a moving target (§8) |
384
+ | Never implement more than one slice doc in a PR | The PR is the durable record of one spec's implementation; `mark-implemented`, coverage, and conformance all key on one doc ↔ one `implementedIn` |
244
385
 
245
- ## 10. Afterward: the loop closes
386
+ ## 11. Afterward: the loop closes
246
387
 
247
388
  Implementation isn't the end of the slice's story. On a cadence — or whenever someone asks —
248
389
  the `conform` phase checks implemented slices against the code, using the `implementedIn` link
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: event-modeling-review
3
- em-version: 1.11.0
3
+ em-version: 1.12.0
4
4
  description: >-
5
5
  Use when starting em's live browser viewer (`em watch --serve`) so a team can watch a model
6
6
  update in real time, or facilitating a SCHEDULED stakeholder walkthrough that steps through
@@ -54,10 +54,10 @@ em slice index <file> --check # verify the table
54
54
  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
55
55
  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
56
56
  em slice review <file> <slice-key> --by <name> # the reviewer's (or facilitator's) name
57
- em slice review <file> <slice-key> --on <date> # review date, YYYY-MM-DD (default: today)
57
+ em slice review <file> <slice-key> --on <date> # review date, YYYY-MM-DD (default: today, local date)
58
58
  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. Refuses a doc that never passed the review gate (`em slice review`) 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
59
59
  em slice ratify <file> <slice-key> --by <name> # the ratifier's name
60
- em slice ratify <file> <slice-key> --on <date> # ratification date, YYYY-MM-DD (default: today)
60
+ em slice ratify <file> <slice-key> --on <date> # ratification date, YYYY-MM-DD (default: today, local date)
61
61
  em slice ratify <file> <slice-key> --skip-review # ratify without a recorded review — prints a loud notice on stderr
62
62
  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`). Only applies to a doc at `status: implemented`; 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
63
63
  em changelog <file> # render a model's git history as a business-readable ledger (see docs/cli.md)
@@ -81,7 +81,7 @@ em conform-scope <file> --seed-asis # write <model>-as
81
81
  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
82
82
  em conform-supersede <file> <report-path> --as-of <rev> # the revision this ruling was made against — same value passed to `em state set-conformance`
83
83
  em conform-supersede <file> <report-path> --findings <spec> # which finding number(s) this stamps as ruled, e.g. "1-3" or "1,2,4"
84
- em conform-supersede <file> <report-path> --on <date> # ruling date, YYYY-MM-DD (default: today)
84
+ em conform-supersede <file> <report-path> --on <date> # ruling date, YYYY-MM-DD (default: today, local date)
85
85
  em watch <file> # re-render on every save
86
86
  em watch <file> -o, --out <path> # output path (extension picks the format)
87
87
  em watch <file> -T, --format <fmt> # output format (svg, png, pdf, ...)
@@ -102,9 +102,10 @@ em ledger <file> --from <rev> # baseline revisio
102
102
  em ledger <file> --to <rev> # compare revision (default: current working tree)
103
103
  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
104
104
  em ledger <file> --json # print a JSON document instead of the text report (see docs/cli.md)
105
- em coverage <file> # check that every INV-* invariant ID cited in a ready-to-implement/implemented slice doc is cited by a test under --tests <dir> (MIL-130) — mechanizes reference/implement.md's definition-of-done citation check; advisory by default, --strict for CI
105
+ 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
106
106
  em coverage <file> --tests <dir> # directory to scan recursively for test files citing invariant IDs
107
107
  em coverage <file> --strict # exit non-zero if any invariant ID has zero citations (CI)
108
+ em coverage <file> --include-ready # also count ready-to-implement docs, not just implemented (MIL-207)
108
109
  em coverage <file> --json # print a JSON document instead of the text report (see docs/cli.md)
109
110
  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)
110
111
  em status <files> --tests <dir> # directory to scan for INV-* test citations — enables invariant coverage totals
@@ -152,7 +153,7 @@ em query path <files> --json # print a JSON doc
152
153
  em system <manifest> # verify a seam manifest (system.yaml) against its models' exports — every `public` event/view bound to a reaction in another model, the cross-model half of "both ends of a flow" (MIL-194, see docs/cli.md)
153
154
  em system <manifest> --json # print a JSON document instead of the text report (see docs/cli.md)
154
155
  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
155
- em mcp # start an MCP (Model Context Protocol) server over stdio (MIL-21) — a structured, agent-facing alternative to shelling out to `em`; see docs/mcp.md for the full, current tool table (the list changes as commands gain MCP parity, so it's not repeated here — MIL-187). Equivalent to running the `em-mcp` bin directly
156
+ 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
156
157
  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)
157
158
  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)
158
159
  em skill install --no-agents-md # skip writing/updating the AGENTS.md agent-contract section (on by default, MIL-129)
@@ -224,7 +225,11 @@ type Name { field: Type, ... } # named structured type, reusable from any f
224
225
  connected to one another — continuity is implied by the shared name, and the events reaching each
225
226
  instance are what show the view changing. `again` with no earlier declaration is a validation error.
226
227
  Use `again` (not a plain repeated `view` name) whenever the view is referenced by a
227
- `from`/`arrow` — plain repeats are only warning-free while unreferenced.
228
+ `from`/`arrow` — plain repeats are only warning-free while unreferenced. A later instance is
229
+ also a **continuation** for the doc lifecycle (MIL-208): it stays a slice on the timeline but
230
+ is not a spec unit — no doc, no status, no ratification, no PR of its own; the originating
231
+ slice's doc carries its behavior and `em export`'s `alsoReads` carries the union of events it
232
+ reads. See `slice-doc-schema.md`'s "Continuations" section for the full contract.
228
233
  - **`loops-to "View"`** (events only): marks an event as re-feeding an earlier read model — a
229
234
  poll/retry loop where a later fact must re-trigger an automation that already ran (a lapsed
230
235
  reservation re-notifying the next person waiting in line, and similar re-triggers). Written
@@ -516,6 +521,11 @@ slice "Read Quote — created" {
516
521
  Keep the read model adjacent to its event, and keep a sub-flow that detours into another context
517
522
  together rather than parking it at the end of the model.
518
523
 
524
+ What this means for whoever builds a slice, not just whoever draws it: a `ui` trigger or
525
+ consumer is an obligation to ship an endpoint, no `ui` means internal-only, and `public` marks
526
+ the published integration surface — see `reference/implement.md`'s "Interface obligations"
527
+ paragraph in the `event-modeling-implement` skill (§2).
528
+
519
529
  ### Reaction wiring — no positional gotcha
520
530
 
521
531
  A reaction's command is inferred from **same-slice presence**, exactly like `ui -> command`: put
@@ -646,6 +656,7 @@ not the prose above has caught up yet. `--slice-ready <key>`-only codes are excl
646
656
  | `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. |
647
657
  | `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. |
648
658
  | `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. |
659
+ | `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. |
649
660
  | `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"). |
650
661
  | `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. |
651
662
  | `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`. |
@@ -180,6 +180,12 @@ reads a bound doc's canonical fields (`em export`'s `slice.doc`, `em validate --
180
180
  `implementedIn`/lineage/Open Questions, not the covered slice's own (nonexistent) file. A doc
181
181
  covers its own canonical slice key implicitly — `covers` never needs to list it.
182
182
 
183
+ `em catalog` (MIL-137) is one such consumer, via the same filename+frontmatter `covers:` scan
184
+ (`src/catalog/coverage.ts`), never the `note` clause itself: a covered slice's detail page shows
185
+ the covering doc's Status, but a "Documented as part of &lt;covering slice&gt;" banner instead
186
+ of the covering doc's body — linking to the covering slice's own catalog page rather than
187
+ inlining content authored under a different slice's name.
188
+
183
189
  `covers` is a plain list of slice keys (`<slice-key>`, e.g. `detect-unpaid-orders`), **not** the
184
190
  `<slice-key>@v<N>` ref grammar the lineage keys use above: coverage is a standing "this doc also
185
191
  serves that slice" declaration, with no version component — unlike lineage, which records a
@@ -195,6 +201,39 @@ the mismatch, as its own separate rule (MIL-126) — see
195
201
  that just doesn't ratify (`covers` doesn't list this slice back), and an extra note doing nothing
196
202
  in a slice that's already bound elsewhere, each get their own diagnostic there.
197
203
 
204
+ ## Continuations
205
+
206
+ Since `em` 1.12.0 (MIL-208): a `view X again` instance is a **continuation** of the slice
207
+ holding `X`'s first (originating) declaration — one read model, fed by more than one event over
208
+ the timeline — not a spec unit of its own. Unlike `covers` above, this is fully automatic and
209
+ structural: no frontmatter, no `note`, nothing to author. A slice is a continuation iff every
210
+ non-`ui` element it declares is a `view` with `again === true`; the doc join resolves it
211
+ straight through to the originating slice's own doc, exactly the way a ratified `covers:`
212
+ binding resolves a covered slice — same `doc.found`/`status`/`driftSignal`, plus a
213
+ `continuationOf: <originating-key>` field naming why. `em export`, `em status`, `em coverage`,
214
+ `em slice index`, `em catalog`, and the render pipeline's Slice Status legend all resolve a
215
+ continuation slice this way; `em slice ratify`/`review`/`reratify`/`mark-implemented` and
216
+ `em slice new --wire` refuse on one outright, naming the originating slice to act on instead —
217
+ a continuation slice has no status/ratification of its own to flip.
218
+
219
+ The originating slice's own `em export` object also gains `alsoReads: [{ event, atSlice }]` —
220
+ the union of events every later `again` instance of that view reads via `from`, in timeline
221
+ order — so implementing the read model from one export never means walking every `again`
222
+ slice's doc by hand to assemble the full event list.
223
+
224
+ **Authoring convention:** a continuation's behavior belongs in the originating doc's own
225
+ `## Scenarios (Given / When / Then)` section — one scenario per event a later `again` instance
226
+ feeds — rather than in a doc of its own; no new frontmatter is needed to record it.
227
+
228
+ **Legacy own doc wins.** A slice that would otherwise be a continuation but still carries its
229
+ own doc — a `note "slices/<again-key>.md"` binding it, or a stray `slices/<again-key>.md` file
230
+ left over from before this rule existed — keeps today's per-instance behavior unchanged: its own
231
+ doc, its own status, its own ratification. `em validate`'s `continuation-has-own-doc` warning
232
+ (see [validation.md](https://github.com/milehimikey/em/blob/main/docs/validation.md)) flags it either way, so an author can fold that doc's
233
+ scenarios into `slices/<originating-key>.md` and delete it. This is the migration path — an
234
+ existing model with per-`again`-instance docs keeps working exactly as before until each one is
235
+ folded in.
236
+
198
237
  ## Delta section: grammar and lifecycle
199
238
 
200
239
  `## Delta` is the body section that pairs with a re-ratification (see "`status` under
@@ -46,6 +46,9 @@ technical choice" an implementing agent would otherwise make alone.
46
46
  - **Architectural pattern:** {{e.g. event-sourced CQRS with vertical slices; layered service over
47
47
  a relational store; functional core / imperative shell}}
48
48
  - **Where a slice's code lives:** {{one package per slice, per context, ...}}
49
+ - **Interface conventions:** {{resource style — REST/RPC/GraphQL, etc.; error/rejection mapping;
50
+ versioning; auth at the edge — the shape every `ui`-triggered command or `ui`-consumed view's
51
+ endpoint follows, decided once here rather than invented per slice}}
49
52
 
50
53
  Routing — which implementation skill or approach each of the four slice patterns uses:
51
54
 
@@ -105,7 +108,11 @@ it is silent, these apply.
105
108
  - **Reviewers:** {{who — a team, a CODEOWNERS rule, the slice's ratifier}}
106
109
  - **What blocks a merge:** {{e.g. a failing suite, an uncovered invariant, a gap that belongs back
107
110
  at the model}}
108
- - **Branch / commit conventions:** {{...}}
111
+ - **Branch / commit conventions:** {{...}} — name only branch naming and commit-message style
112
+ here. **One slice doc = one PR is the implement contract's rule, not this document's to
113
+ amend** — a `covers:` doc ships as one PR for both keys; a read model is one doc, so a later
114
+ `again` instance is a continuation of the doc that first declared it, not a PR of its own. See
115
+ the agent guide's §5/§10.
109
116
  - **At merge:** run `em slice mark-implemented <model>.em <slice-key> <pr-url>` — the one edit an
110
117
  implementing agent makes to a ratified slice doc, run by {{the PR author | CI}}. Never bump
111
118
  `version:`; that moves only when a delta is ratified.
@@ -13,10 +13,12 @@
13
13
  import { existsSync, readFileSync } from "node:fs";
14
14
  import { copyFile, mkdir, writeFile } from "node:fs/promises";
15
15
  import { basename, dirname, extname, join } from "node:path";
16
+ import { continuationOf } from "../model/continuation.js";
16
17
  import { layoutDot, composeSvg, writeRendered } from "../render/render.js";
17
18
  import { buildSliceDiagram } from "../render/sliceDiagram.js";
18
19
  import { dedupe, kebabSlug } from "../util/slug.js";
19
20
  import { classifySlicePattern } from "./classify.js";
21
+ import { readCoverageDocs } from "./coverage.js";
20
22
  import { detectSliceDocCollisions } from "./modelCollisionValidate.js";
21
23
  import { parseSliceDoc } from "./sliceDoc.js";
22
24
  import { renderIndexPage, renderSlicePage } from "./pages.js";
@@ -51,6 +53,11 @@ export async function buildCatalog(inputs, opts) {
51
53
  if (refs.diagnostics.length > 0)
52
54
  diagnostics.push({ file, diagnostics: refs.diagnostics });
53
55
  const sliceSummaries = [];
56
+ // MIL-137: built lazily, only if some slice below actually lacks its own doc — same
57
+ // shared scan (src/catalog/coverage.ts) the render pipeline's Slice Status legend uses
58
+ // (src/render/sliceStatus.ts), so a covered slice never resolves to a different doc
59
+ // depending on which command asked.
60
+ let coverageDocs = null;
54
61
  for (let i = 0; i < model.slices.length; i++) {
55
62
  const slice = model.slices[i];
56
63
  const sliceKey = refs.sliceKeys[i];
@@ -63,7 +70,40 @@ export async function buildCatalog(inputs, opts) {
63
70
  // instead of silently rendering the first slice's doc content on both pages.
64
71
  const docRelPath = join("slices", `${sliceKey}.md`);
65
72
  const docPath = join(dirname(file), docRelPath);
66
- const doc = existsSync(docPath) ? parseSliceDoc(readFileSync(docPath, "utf8")) : null;
73
+ const ownDoc = existsSync(docPath) ? parseSliceDoc(readFileSync(docPath, "utf8")) : null;
74
+ // MIL-137: no own doc -> fall back to a sibling doc that ratifies coverage of this slice
75
+ // (MIL-121 `covers:`). The covering doc's status/pattern act as this slice's own for the
76
+ // index Status column and this page's header badge (same "one doc, one status" rule the
77
+ // render pipeline's Slice Status legend already applies), but its BODY is never inlined —
78
+ // the detail page shows a "documented as part of" banner linking to the covering slice's
79
+ // own page instead, never the covering doc's content rendered under a different slice's
80
+ // name.
81
+ let doc = ownDoc;
82
+ let coveredBy = null;
83
+ if (!ownDoc) {
84
+ coverageDocs ??= readCoverageDocs(join(dirname(file), "slices"));
85
+ const covering = coverageDocs.get(sliceKey);
86
+ if (covering) {
87
+ doc = covering.doc;
88
+ const coveringIndex = refs.sliceKeys.indexOf(covering.key);
89
+ coveredBy = { key: covering.key, name: coveringIndex >= 0 ? model.slices[coveringIndex].name : covering.key, kind: "covers" };
90
+ }
91
+ else {
92
+ // MIL-208: still no doc — check whether this slice is a CONTINUATION (an again-view-
93
+ // only slice) of another slice's own file, filename-convention resolution's own
94
+ // fallback (mirrors render/sliceStatus.ts's readSliceDocs). Checked last, after both
95
+ // the own-file and MIL-121 covers: lookups have already come up empty.
96
+ const continuation = continuationOf(model, refs, i);
97
+ if (continuation) {
98
+ const originatingPath = join(dirname(file), "slices", `${continuation.sliceKey}.md`);
99
+ if (existsSync(originatingPath)) {
100
+ doc = parseSliceDoc(readFileSync(originatingPath, "utf8"));
101
+ const originatingSlice = model.slices[continuation.sliceIndex];
102
+ coveredBy = { key: continuation.sliceKey, name: originatingSlice.name, kind: "continuation" };
103
+ }
104
+ }
105
+ }
106
+ }
67
107
  // Same sibling-file convention as the doc lookup above, one extension over: a
68
108
  // slice diagram authored by `em render --slice` (per the event-modeling skill)
69
109
  // is copied in as-is so the catalog stays self-contained; otherwise built
@@ -103,6 +143,7 @@ export async function buildCatalog(inputs, opts) {
103
143
  pattern,
104
144
  elementRefs,
105
145
  doc,
146
+ coveredBy,
106
147
  docExpectedPath: docRelPath,
107
148
  });
108
149
  await writeFile(join(slicesDir, `${sliceKey}.html`), page, "utf8");