@milehimikey/em 1.19.7 → 1.21.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 (206) 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-conform/reference/conform.md +29 -1
  4. package/.claude/skills/event-modeling-design/SKILL.md +33 -7
  5. package/.claude/skills/event-modeling-discover/SKILL.md +2 -2
  6. package/.claude/skills/event-modeling-discover/reference/extract.md +12 -0
  7. package/.claude/skills/event-modeling-engagement/SKILL.md +1 -1
  8. package/.claude/skills/event-modeling-implement/SKILL.md +7 -2
  9. package/.claude/skills/event-modeling-implement/reference/implement.md +73 -6
  10. package/.claude/skills/event-modeling-review/SKILL.md +19 -7
  11. package/.claude/skills/event-modeling-shared/reference/em-dsl.md +57 -5
  12. package/.claude/skills/event-modeling-shared/reference/methodology.md +51 -2
  13. package/.claude/skills/event-modeling-shared/reference/slice-doc-schema.md +2 -1
  14. package/.claude/skills/event-modeling-shared/templates/model-readme.md +10 -2
  15. package/.claude/skills/event-modeling-shared/templates/slice.md +12 -1
  16. package/.claude-plugin/marketplace.json +2 -2
  17. package/dist/catalog/build.js +6 -5
  18. package/dist/catalog/build.js.map +1 -1
  19. package/dist/catalog/coverage.d.ts +3 -0
  20. package/dist/catalog/coverage.js +12 -0
  21. package/dist/catalog/coverage.js.map +1 -1
  22. package/dist/catalog/docBadge.d.ts +16 -0
  23. package/dist/catalog/docBadge.js +50 -0
  24. package/dist/catalog/docBadge.js.map +1 -0
  25. package/dist/catalog/docLocate.d.ts +17 -0
  26. package/dist/catalog/docLocate.js +97 -0
  27. package/dist/catalog/docLocate.js.map +1 -0
  28. package/dist/catalog/docModelConsistencyValidate.js +9 -2
  29. package/dist/catalog/docModelConsistencyValidate.js.map +1 -1
  30. package/dist/catalog/frontmatterCoherenceValidate.js +16 -1
  31. package/dist/catalog/frontmatterCoherenceValidate.js.map +1 -1
  32. package/dist/catalog/invTokens.d.ts +49 -0
  33. package/dist/catalog/invTokens.js +94 -0
  34. package/dist/catalog/invTokens.js.map +1 -0
  35. package/dist/catalog/orphanedSliceDocValidate.js +15 -10
  36. package/dist/catalog/orphanedSliceDocValidate.js.map +1 -1
  37. package/dist/catalog/pages.js +48 -1
  38. package/dist/catalog/pages.js.map +1 -1
  39. package/dist/catalog/readSliceDoc.js +5 -4
  40. package/dist/catalog/readSliceDoc.js.map +1 -1
  41. package/dist/catalog/scenarioQualityValidate.js +2 -2
  42. package/dist/catalog/scenarioQualityValidate.js.map +1 -1
  43. package/dist/catalog/scenarios.d.ts +4 -0
  44. package/dist/catalog/scenarios.js +4 -1
  45. package/dist/catalog/scenarios.js.map +1 -1
  46. package/dist/catalog/sliceDoc.d.ts +54 -1
  47. package/dist/catalog/sliceDoc.js +99 -2
  48. package/dist/catalog/sliceDoc.js.map +1 -1
  49. package/dist/catalog/sliceDocRender.d.ts +3 -0
  50. package/dist/catalog/sliceDocRender.js +295 -0
  51. package/dist/catalog/sliceDocRender.js.map +1 -0
  52. package/dist/catalog/sliceSections.d.ts +18 -1
  53. package/dist/catalog/sliceSections.js +38 -4
  54. package/dist/catalog/sliceSections.js.map +1 -1
  55. package/dist/cli/coverage.d.ts +13 -23
  56. package/dist/cli/coverage.js +51 -91
  57. package/dist/cli/coverage.js.map +1 -1
  58. package/dist/cli/defer.js +3 -2
  59. package/dist/cli/defer.js.map +1 -1
  60. package/dist/cli/ledgerCheck.js +11 -8
  61. package/dist/cli/ledgerCheck.js.map +1 -1
  62. package/dist/cli/markImplemented.js +2 -2
  63. package/dist/cli/markImplemented.js.map +1 -1
  64. package/dist/cli/ratify.js +2 -2
  65. package/dist/cli/ratify.js.map +1 -1
  66. package/dist/cli/refsCheck.d.ts +52 -0
  67. package/dist/cli/refsCheck.js +129 -2
  68. package/dist/cli/refsCheck.js.map +1 -1
  69. package/dist/cli/render-inputs.d.ts +1 -1
  70. package/dist/cli/render-inputs.js +3 -2
  71. package/dist/cli/render-inputs.js.map +1 -1
  72. package/dist/cli/review.js +2 -2
  73. package/dist/cli/review.js.map +1 -1
  74. package/dist/cli/revise.js +2 -2
  75. package/dist/cli/revise.js.map +1 -1
  76. package/dist/cli/skillDirs.d.ts +22 -0
  77. package/dist/cli/skillDirs.js +24 -0
  78. package/dist/cli/skillDirs.js.map +1 -1
  79. package/dist/cli/sliceConform.d.ts +57 -1
  80. package/dist/cli/sliceConform.js +111 -10
  81. package/dist/cli/sliceConform.js.map +1 -1
  82. package/dist/cli/sliceIndex.d.ts +19 -3
  83. package/dist/cli/sliceIndex.js +81 -17
  84. package/dist/cli/sliceIndex.js.map +1 -1
  85. package/dist/cli/sliceNew.js +2 -0
  86. package/dist/cli/sliceNew.js.map +1 -1
  87. package/dist/cli/sliceStubAll.js +7 -2
  88. package/dist/cli/sliceStubAll.js.map +1 -1
  89. package/dist/cli/sliceSync.d.ts +12 -0
  90. package/dist/cli/sliceSync.js +42 -4
  91. package/dist/cli/sliceSync.js.map +1 -1
  92. package/dist/cli/status.d.ts +3 -4
  93. package/dist/cli/status.js +5 -4
  94. package/dist/cli/status.js.map +1 -1
  95. package/dist/cli/storylineSplit.d.ts +18 -0
  96. package/dist/cli/storylineSplit.js +110 -0
  97. package/dist/cli/storylineSplit.js.map +1 -0
  98. package/dist/cli/upgrade.js +39 -14
  99. package/dist/cli/upgrade.js.map +1 -1
  100. package/dist/cli/watchStoryline.d.ts +33 -0
  101. package/dist/cli/watchStoryline.js +65 -0
  102. package/dist/cli/watchStoryline.js.map +1 -0
  103. package/dist/cli.js +423 -71
  104. package/dist/cli.js.map +1 -1
  105. package/dist/emit/docJson.d.ts +99 -0
  106. package/dist/emit/docJson.js +197 -0
  107. package/dist/emit/docJson.js.map +1 -0
  108. package/dist/emit/json.d.ts +22 -1
  109. package/dist/emit/json.js +12 -1
  110. package/dist/emit/json.js.map +1 -1
  111. package/dist/emit/refsJson.d.ts +5 -3
  112. package/dist/emit/refsJson.js +5 -2
  113. package/dist/emit/refsJson.js.map +1 -1
  114. package/dist/mcp/server.js +43 -9
  115. package/dist/mcp/server.js.map +1 -1
  116. package/dist/model/continuation.d.ts +9 -0
  117. package/dist/model/continuation.js +30 -0
  118. package/dist/model/continuation.js.map +1 -1
  119. package/dist/model/edges.d.ts +1 -1
  120. package/dist/model/edges.js +8 -1
  121. package/dist/model/edges.js.map +1 -1
  122. package/dist/model/loadModel.d.ts +38 -0
  123. package/dist/model/loadModel.js +122 -0
  124. package/dist/model/loadModel.js.map +1 -0
  125. package/dist/model/model.d.ts +9 -4
  126. package/dist/model/model.js +4 -0
  127. package/dist/model/model.js.map +1 -1
  128. package/dist/model/rules.d.ts +31 -0
  129. package/dist/model/rules.js +31 -0
  130. package/dist/model/rules.js.map +1 -1
  131. package/dist/model/shapeLint.js +143 -0
  132. package/dist/model/shapeLint.js.map +1 -1
  133. package/dist/model/sliceDocIndex.js +13 -5
  134. package/dist/model/sliceDocIndex.js.map +1 -1
  135. package/dist/model/sliceDocPath.d.ts +19 -0
  136. package/dist/model/sliceDocPath.js +49 -0
  137. package/dist/model/sliceDocPath.js.map +1 -0
  138. package/dist/model/storylineMap.d.ts +20 -0
  139. package/dist/model/storylineMap.js +55 -0
  140. package/dist/model/storylineMap.js.map +1 -0
  141. package/dist/model/validate.d.ts +7 -0
  142. package/dist/model/validate.js +127 -0
  143. package/dist/model/validate.js.map +1 -1
  144. package/dist/parser/ast.d.ts +27 -0
  145. package/dist/parser/parser.d.ts +17 -1
  146. package/dist/parser/parser.js +142 -3
  147. package/dist/parser/parser.js.map +1 -1
  148. package/dist/pipeline.d.ts +16 -0
  149. package/dist/pipeline.js +19 -2
  150. package/dist/pipeline.js.map +1 -1
  151. package/dist/render/drawEdges.d.ts +5 -1
  152. package/dist/render/drawEdges.js +57 -4
  153. package/dist/render/drawEdges.js.map +1 -1
  154. package/dist/render/drawNotes.d.ts +4 -1
  155. package/dist/render/drawNotes.js +40 -10
  156. package/dist/render/drawNotes.js.map +1 -1
  157. package/dist/render/render.d.ts +11 -0
  158. package/dist/render/render.js +93 -10
  159. package/dist/render/render.js.map +1 -1
  160. package/dist/render/serve.d.ts +5 -0
  161. package/dist/render/serve.js +18 -2
  162. package/dist/render/serve.js.map +1 -1
  163. package/dist/render/sliceDiagram.js +3 -0
  164. package/dist/render/sliceDiagram.js.map +1 -1
  165. package/dist/render/sliceOverlay.d.ts +5 -1
  166. package/dist/render/sliceOverlay.js +24 -2
  167. package/dist/render/sliceOverlay.js.map +1 -1
  168. package/dist/render/sliceStatus.d.ts +8 -0
  169. package/dist/render/sliceStatus.js +22 -15
  170. package/dist/render/sliceStatus.js.map +1 -1
  171. package/dist/render/storylineBands.d.ts +0 -3
  172. package/dist/render/storylineBands.js +2 -22
  173. package/dist/render/storylineBands.js.map +1 -1
  174. package/dist/render/storylineDiagram.js +17 -69
  175. package/dist/render/storylineDiagram.js.map +1 -1
  176. package/dist/render/storylineMap.d.ts +18 -0
  177. package/dist/render/storylineMap.js +263 -0
  178. package/dist/render/storylineMap.js.map +1 -0
  179. package/dist/render/storylinePicker.d.ts +9 -0
  180. package/dist/render/storylinePicker.js +17 -0
  181. package/dist/render/storylinePicker.js.map +1 -0
  182. package/dist/render/svgGeometry.d.ts +9 -0
  183. package/dist/render/svgGeometry.js +16 -0
  184. package/dist/render/svgGeometry.js.map +1 -1
  185. package/dist/render/viewerHtml.js +1 -1
  186. package/dist/render/viewerHtml.js.map +1 -1
  187. package/dist/templates.d.ts +1 -1
  188. package/dist/templates.js +10 -2
  189. package/dist/templates.js.map +1 -1
  190. package/package.json +1 -1
  191. package/plugin/.claude-plugin/plugin.json +2 -2
  192. package/plugin/shared/reference/em-dsl.md +57 -5
  193. package/plugin/shared/reference/methodology.md +51 -2
  194. package/plugin/shared/reference/slice-doc-schema.md +2 -1
  195. package/plugin/shared/templates/model-readme.md +10 -2
  196. package/plugin/shared/templates/slice.md +12 -1
  197. package/plugin/skills/conform/SKILL.md +1 -1
  198. package/plugin/skills/conform/reference/conform.md +29 -1
  199. package/plugin/skills/design/SKILL.md +33 -7
  200. package/plugin/skills/discover/SKILL.md +2 -2
  201. package/plugin/skills/discover/reference/extract.md +12 -0
  202. package/plugin/skills/engagement/SKILL.md +1 -1
  203. package/plugin/skills/event-modeling/SKILL.md +1 -1
  204. package/plugin/skills/implement/SKILL.md +7 -2
  205. package/plugin/skills/implement/reference/implement.md +73 -6
  206. package/plugin/skills/review/SKILL.md +19 -7
@@ -218,7 +218,11 @@ drift into **CRUD laid on a timeline**; these rules stop that. Quoted lines are
218
218
  reorganize are **secondary storylines** (chapters; Dilger: "one linear flow from left to right. No
219
219
  exceptions… Linearize it!", "Model one process at a time, each with its own clear ending"). Never one
220
220
  lifecycle block per entity. Name the main and every secondary storyline; a `storyline "Name" { … }`
221
- block groups the slices (`dsl.md`, Storylines).
221
+ block groups the slices (`dsl.md`, Storylines). **A storyline is the unit a reader consumes, so it
222
+ is the unit an author edits too (GH #421):** the root model lists the storylines and their flow
223
+ (`storyline "Name" [after "Other"]`), and each story's slices live in `<slug>/<slug>.em` with its
224
+ docs in `<slug>/slices/`. Draft with inline blocks if that is faster, then `em storyline split
225
+ <root>.em` once the storylines are named — before docs are wired, so nothing moves twice.
222
226
  - **Build from walkthroughs first.** The story already lives in the spec's walkthroughs, use cases and
223
227
  user journeys. Build the timeline from those; the resource/API reference (OpenAPI, endpoint tables,
224
228
  migrations) fills only the operations no walkthrough touches, and is read last. Headless: a persona
@@ -316,12 +320,57 @@ drift into **CRUD laid on a timeline**; these rules stop that. Quoted lines are
316
320
  view carries ids; child with its own data → embedded in the parent view; child queried without the
317
321
  parent id → its own view. Aggregate rules decide write-side consistency, never read models. *(em
318
322
  ruling)*
323
+ - **Views as lookups: an event that creates a row carries what the row shows.** A view reads another
324
+ event only when that event adds, changes or removes a row the view already owns — never merely to
325
+ fill a column on a row some *other* event created. **Per-view check:** for each `from "E"` on a view,
326
+ ask *does E add, change or remove a row here? If it only fills a column, put the column on the event
327
+ that creates the row and drop E.* Generated models routinely come out wrong here — a wide fan-out of
328
+ "reference" events into every view that shows a name, and views keeping a lookup table ("every
329
+ neighbor's name and current email, every tool's name") beside their own rows. Fix: the decider
330
+ already read what it needed to enforce its own invariants (e.g. a checkout decider that reads Tool
331
+ Added and Neighbor Registered to validate the request already has `toolName`/`neighborName` in hand),
332
+ so have it assign those fields onto the event it records — the view then needs nothing beyond that
333
+ one event. A slice doc that needs a "Bookkeeping" paragraph (the view keeps, beyond its own rows, a
334
+ lookup of some other entity's fields) has this smell; see `docs/patterns.md#state-view` for the
335
+ worked overdue-notice example.
336
+ `view-lookup-source` is the mechanical, advisory half — it catches the cross-context cases with a few
337
+ false positives; a within-context lookup (`Tool Checked Out` read only to learn whose loan a later
338
+ event concerns) is invisible to it, so this check still has to be done by eye.
339
+ **Then split what is left into trigger and housekeeping** (Dilger: show the line from the event
340
+ that triggers the automation, not every event that touches its read model; GH #414). Of the
341
+ events a view legitimately reads, only the ones that **put work on the list** are the story — they
342
+ go in `from` and are drawn. Every event that merely **keeps a row current or removes it** (a due
343
+ date moved, an address changed, a loan returned) goes in `updates from`: still a source for
344
+ `derived from`, completeness and the generated built-from list, never an arrow. Write the to-do
345
+ list as `view Overdue Loans To Notify from "Tool Checked Out" updates from "Loan Extended",
346
+ "Neighbor Email Changed", "Tool Returned"`. The author chooses the triggers — `Holds To Fulfill`
347
+ keeps two (`from "Hold Placed", "Tool Returned"`) because both put a fulfillable hold on the
348
+ list. **Per-view check:** *which of these events is the reason this row appears? That is `from`;
349
+ everything else is `updates from`.* The render's "Kept current by" legend is the readback: if a
350
+ to-do view shows more than one or two inbound arrows, the split is missing. A State View that is
351
+ not a to-do list gets the same treatment — its screen's story is the event that creates the row.
319
352
  - **Write side: children belong in the parent's context** when the child's invariants are checks against
320
353
  the parent. A sub-resource schema shared across parent types is one event per parent on the parent's
321
354
  context, not a shared aggregate — check the spec's event reference, not its resource section. Where
322
355
  two slices record the same fact, repeat the event bare (same name, same context). *(em ruling)*
323
356
  - **Repeat the view after every command that changes it** (`again`, span-1 — see State View above), so
324
- the reader sees the information arrive rather than a view at the end.
357
+ the reader sees the information arrive rather than a view at the end. **A continuation names only
358
+ what changed:** `view X again from "E"` lists the event(s) that changed the view since the previous
359
+ instance — never the originating instance's whole source list again. Field sources are the union
360
+ across instances, so restating loses nothing and only draws the same arrows twice;
361
+ `again-restates-sources` (GH #424) is the mechanical half.
362
+ - **Storylines read as stories** (owner rulings 2026-11-03, after Dilger). (1) **A storyline that reads
363
+ another declares `after` it:** an event of story A feeding a view of story B means B comes after A
364
+ in the telling — write `storyline "B" after "A"` (or on a storyline between them); two stories that
365
+ really run in parallel read nothing from each other. `storyline-reads-without-after` (GH #420) is
366
+ the mechanical half. (2) **Review each story on its own render and on the canvas, never on the
367
+ full timeline:** `em render <root>.em --map` draws every storyline's real diagram as a frame with
368
+ the flow between stories (`after` thick, reads dashed and labelled by event); the full timeline is
369
+ for `em diff` and ordering checks, not for reading. A storyline render shows only the storyline —
370
+ nothing from outside it is drawn, so an inbound arrow you miss there is a connector on the canvas.
371
+ (3) **Housekeeping is not drawn** (`updates from`, above) and **continuations name only what
372
+ changed** (above): together these are what keeps a story's render to one line per cause. *(em
373
+ ruling, built on Dilger's "the line from the event that triggers it")*
325
374
  - **Outcomes are events of one slice** (Dilger: "One decision. One event. One state change."; one
326
375
  action with two outcomes is the legitimate exception): alternative events on one context in one slice
327
376
  (`event A | B @Ctx`, GH #278), failure as a scenario first, a chapter only when core (it changes what the user does next, or has
@@ -56,7 +56,7 @@ to decide `frontmatter-invalid` without re-deriving frontmatter-shape rules of i
56
56
  | `schemaVersion` | integer | `1` (current dialect version) | its *value* is not read back by any `em` command today (reserved) — but its *presence* is required by `hasUsableFrontmatter()`: a doc omitting it is `frontmatter-invalid` (see below) |
57
57
  | `pattern` | string | `state-change` \| `state-view` \| `automation` \| `translation` | authored/informational only — `em catalog` and `em export` both derive pattern from the `.em` AST instead (`em export`'s `slice.pattern`, schema `1.4`) |
58
58
  | `swimlane` | string | free text, `<Persona> → <Context>` | display-only |
59
- | `status` | string, case-insensitive | `draft` \| `reviewed` \| `ready-to-implement` \| `implemented` | written by `em slice review` (→ `reviewed`, MIL-201), `em slice ratify`/`em slice reratify` (→ `ready-to-implement`) and `em slice mark-implemented` (→ `implemented`); read by `em catalog`, `em render`/`em watch` header coloring; joined into `em export`'s `slice.doc.status` (schema `1.4`); paired with `implementedIn` to compute `slice.doc.driftSignal` (schema `1.5`, MIL-85) and `em validate`'s frontmatter-coherence check (MIL-85 — see [validation.md#frontmatter-coherence](https://github.com/milehimikey/em/blob/main/docs/validation.md#frontmatter-coherence)) |
59
+ | `status` | string, case-insensitive | `draft` \| `reviewed` \| `ready-to-implement` \| `implemented` | written by `em slice review` (→ `reviewed`, MIL-201), `em slice ratify`/`em slice reratify` (→ `ready-to-implement`) and `em slice mark-implemented` (→ `implemented`); read by `em catalog`, `em render`/`em watch` header coloring; joined into `em export`'s `slice.doc.status` (schema `1.4`); paired with `implementedIn` to compute `slice.doc.driftSignal` (schema `1.5`, MIL-85) and `em validate`'s frontmatter-coherence check (MIL-85 — see [validation.md#frontmatter-coherence](https://github.com/milehimikey/em/blob/main/docs/validation.md#frontmatter-coherence)); any other value is read as written but `em validate` warns `frontmatter-status-unrecognized` (GH #411) |
60
60
  | `version` | integer | positive integer, starts at `1` | joined into `em export`'s `slice.doc.version` (schema `1.4`, MIL-91) |
61
61
  | `implementedIn` | string | free text (PR/commit link) | joined into `em export`'s `slice.doc.implementedIn` (schema `1.4`, MIL-91); paired with `status` to compute `slice.doc.driftSignal` (schema `1.5`, MIL-85) and `em validate`'s frontmatter-coherence check (MIL-85 — see [validation.md#frontmatter-coherence](https://github.com/milehimikey/em/blob/main/docs/validation.md#frontmatter-coherence)) |
62
62
  | `split-from` | single ref | `<slice-key>@v<N>` | joined into `em export`'s `slice.doc.splitFrom` (schema `1.4`, MIL-91) and `em diff`'s `slice-added` entries (schema `1.6`, MIL-84); current-tree referential checks by `em validate` (MIL-84 — see [validation.md#lineage](https://github.com/milehimikey/em/blob/main/docs/validation.md#lineage)) |
@@ -472,6 +472,7 @@ re-run `em slice sync`.
472
472
 
473
473
  | Region | Section | Body |
474
474
  |---|---|---|
475
+ | `em-slice-story` | right under `![Diagram](…)` | one line: `Step N of M in **<Storyline>** · after <Prev> · before <Next> · <Pattern>` (Prev and Next link to the neighbouring slices' docs; a continuation neighbour is named, not linked) — where the slice sits in its story; the one region `em slice sync` inserts when missing on an unsigned doc (see `em slice sync` in the CLI reference) |
475
476
  | `em-slice-command` | `## Command / Input` | `**Command:** \`Name\`` + `\| Field \| Type \| Required \| Rules / Validation \|` (Required is `no` for a `name?: Type` field) |
476
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; failing that, the command from another slice that also records the event, written `not supplied by this command (from \`A\`, slice "Create")` (GH #344 / #362, the same union `fields-completeness/event-field-no-source` checks; worded as a statement about this slice, since the repeat's own command does not take the field); `assigned` for a system-assigned field, else `{{ }}`) |
477
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); `em slice sync` retires a stub once an authored scenario covers that field and event, by the stub's `<field> after <Event>` title or by a Given naming the event with the field named in the block (GH #368), and drops the `Scenario stubs for` line when none is left — else `{{ }}`) |
@@ -31,14 +31,22 @@ Change slice is paired with the State View slice that projects its event. A read
31
31
  along the timeline (`view X again`) shows the same projection at a later point; the instances are
32
32
  never connected to one another.
33
33
 
34
+ ## Storylines
35
+ <!-- Generated — `em slice index {{model-name}}.em` writes one row per storyline between the
36
+ markers (count, implemented, diagram); the canvas is `em render {{model-name}}.em --map`. -->
37
+ <!-- GENERATED:storylines:start -->
38
+ | Storyline | Slices | Implemented | Diagram |
39
+ |-----------|--------|-------------|---------|
40
+ <!-- GENERATED:storylines:end -->
41
+
34
42
  ## Slices
35
43
  <!-- The canonical slice index — the ONE place slices are enumerated (the state file
36
44
  points here rather than keeping its own copy). Generated — run
37
45
  `em slice index {{model-name}}.em` to (re)write the table below from the model and its
38
46
  slice docs; never hand-edit between the markers. -->
39
47
  <!-- GENERATED:slices:start -->
40
- | # | Slice | Pattern | Status | Reviewed by | Ratified by | Owner | Tracking | Implemented in | Design doc |
41
- |---|-------|---------|--------|-------------|-------------|-------|----------|----------------|------------|
48
+ | # | Slice | Pattern | Status | Design doc |
49
+ |---|-------|---------|--------|------------|
42
50
  <!-- GENERATED:slices:end -->
43
51
 
44
52
  ## Status
@@ -79,6 +79,10 @@ implementedIn: {{PR/commit link — fill in once status is `implemented`}}
79
79
 
80
80
  ![Diagram](./{{slice-name}}.svg)
81
81
 
82
+ <!-- GENERATED:em-slice-story:start — generated from the model by `em slice sync`; do not hand-edit -->
83
+ _Run `em slice sync` to place this slice in its story._
84
+ <!-- GENERATED:em-slice-story:end -->
85
+
82
86
  <!-- Only on re-ratification of an already-`implemented` slice — omit this whole section on a
83
87
  slice's first version (v1 has no delta yet). The heading is always the literal `## Delta`,
84
88
  never `## Delta: vX → vY` — hop metadata is a display line inside the section instead (see
@@ -164,7 +168,14 @@ an event directly.}}
164
168
  line and field table are a GENERATED region (one per read model; `em-slice-view-<view-slug>`
165
169
  when there are several): Source / Notes names the source event a field is copied from, or
166
170
  `Derived` for a field marked `derived` in the `.em` (docs/dsl.md#derived-fields) — say how
167
- it is derived in prose below the region. `{{ }}` marks a field the model cannot trace. -->
171
+ it is derived in prose below the region. `{{ }}` marks a field the model cannot trace.
172
+ If describing a field here needs a "Bookkeeping" paragraph — this view also keeps, beyond its
173
+ own rows, a lookup of some other entity's name/email/etc. so it can display it — that is the
174
+ views-as-lookups smell (methodology reference, "Views as lookups"): the field belongs on the
175
+ event that creates this view's row instead, copied from state the command/decider already
176
+ reads to enforce its own invariants. Fix the `.em`, not the prose. The built-from list names
177
+ every source, trigger and housekeeping alike; in the `.em` only the triggering event(s) sit in
178
+ `from`, the rest in `updates from` (methodology reference, "trigger and housekeeping"). -->
168
179
  <!-- GENERATED:em-slice-view:start — generated from the model by `em slice sync`; do not hand-edit -->
169
180
  - **View:** `{{View Name}}` built from events: {{"Event A", "Event B"}}
170
181
 
@@ -1,9 +1,9 @@
1
1
  {
2
- "name": "em-1-19-7",
2
+ "name": "em-1-21-0",
3
3
  "owner": {
4
4
  "name": "Mike Key"
5
5
  },
6
- "description": "em v1.19.7: the Event Modeling skill bundle and MCP server as a Claude Code plugin.",
6
+ "description": "em v1.21.0: the Event Modeling skill bundle and MCP server as a Claude Code plugin.",
7
7
  "plugins": [
8
8
  {
9
9
  "name": "em",
@@ -18,7 +18,8 @@ import { layoutDot, composeSvg, writeRendered } from "../render/render.js";
18
18
  import { buildSliceDiagram } from "../render/sliceDiagram.js";
19
19
  import { dedupe, kebabSlug } from "../util/slug.js";
20
20
  import { classifySlicePattern } from "./classify.js";
21
- import { readCoverageDocs } from "./coverage.js";
21
+ import { readCoverageDocsAll } from "./coverage.js";
22
+ import { docAbsPath, locateDoc } from "./docLocate.js";
22
23
  import { detectSliceDocCollisions } from "./modelCollisionValidate.js";
23
24
  import { parseSliceDoc } from "./sliceDoc.js";
24
25
  import { renderIndexPage, renderSlicePage } from "./pages.js";
@@ -81,7 +82,7 @@ export async function buildCatalog(inputs, opts) {
81
82
  let doc = ownDoc;
82
83
  let coveredBy = null;
83
84
  if (!ownDoc) {
84
- coverageDocs ??= readCoverageDocs(join(dirname(file), "slices"));
85
+ coverageDocs ??= readCoverageDocsAll(dirname(file)); // GH #421: every doc folder
85
86
  const covering = coverageDocs.get(sliceKey);
86
87
  if (covering) {
87
88
  doc = covering.doc;
@@ -95,8 +96,8 @@ export async function buildCatalog(inputs, opts) {
95
96
  // the own-file and MIL-121 covers: lookups have already come up empty.
96
97
  const continuation = continuationOf(model, refs, i);
97
98
  if (continuation) {
98
- const originatingPath = join(dirname(file), "slices", `${continuation.sliceKey}.md`);
99
- if (existsSync(originatingPath)) {
99
+ const originatingPath = locateDoc(dirname(file), `slices/${continuation.sliceKey}.md`); // GH #421
100
+ if (originatingPath) {
100
101
  doc = parseSliceDoc(readFileSync(originatingPath, "utf8"));
101
102
  const originatingSlice = model.slices[continuation.sliceIndex];
102
103
  coveredBy = { key: continuation.sliceKey, name: originatingSlice.name, kind: "continuation" };
@@ -109,7 +110,7 @@ export async function buildCatalog(inputs, opts) {
109
110
  // is copied in as-is so the catalog stays self-contained; otherwise built
110
111
  // fresh via buildSliceDiagram, written only into the catalog's own output
111
112
  // tree — `em catalog` never writes back into the source tree.
112
- const sliceSvgSrcPath = join(dirname(file), "slices", `${sliceKey}.svg`);
113
+ const sliceSvgSrcPath = docAbsPath(dirname(file), `slices/${sliceKey}.svg`, model.slices[i]); // GH #421
113
114
  const sliceDiagramFile = `${sliceKey}.svg`;
114
115
  if (existsSync(sliceSvgSrcPath)) {
115
116
  await copyFile(sliceSvgSrcPath, join(slicesDir, sliceDiagramFile));
@@ -1 +1 @@
1
- {"version":3,"file":"build.js","sourceRoot":"","sources":["../../src/catalog/build.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"build.js","sourceRoot":"","sources":["../../src/catalog/build.ts"],"names":[],"mappings":"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"}
@@ -13,3 +13,6 @@ export interface CoveringDoc {
13
13
  * uses — the one precedent for it is `em ledger`'s (src/cli/ledgerCheck.ts) own
14
14
  * `readdirSync(slicesDir)` sweep. */
15
15
  export declare function readCoverageDocs(slicesDir: string): Map<string, CoveringDoc>;
16
+ /** GH #421: the same sweep over every doc folder under the model's directory (the root's
17
+ * `slices/` and each storyline folder's), root first, first claim wins. */
18
+ export declare function readCoverageDocsAll(baseDir: string): Map<string, CoveringDoc>;
@@ -11,6 +11,7 @@
11
11
  import { existsSync, readdirSync, readFileSync } from "node:fs";
12
12
  import { join } from "node:path";
13
13
  import { parseSliceDoc } from "./sliceDoc.js";
14
+ import { docDirs } from "./docLocate.js";
14
15
  /** Scans every `slices/*.md` file (sorted, for deterministic first-wins on an overlapping
15
16
  * `covers:` claim) and maps each covered slice key to the doc — and that doc's own key — that
16
17
  * covers it. Directory-scan, not the single-file existsSync check every other doc lookup in em
@@ -31,4 +32,15 @@ export function readCoverageDocs(slicesDir) {
31
32
  }
32
33
  return coverage;
33
34
  }
35
+ /** GH #421: the same sweep over every doc folder under the model's directory (the root's
36
+ * `slices/` and each storyline folder's), root first, first claim wins. */
37
+ export function readCoverageDocsAll(baseDir) {
38
+ const coverage = new Map();
39
+ for (const dir of docDirs(baseDir)) {
40
+ for (const [k, v] of readCoverageDocs(dir))
41
+ if (!coverage.has(k))
42
+ coverage.set(k, v);
43
+ }
44
+ return coverage;
45
+ }
34
46
  //# sourceMappingURL=coverage.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"coverage.js","sourceRoot":"","sources":["../../src/catalog/coverage.ts"],"names":[],"mappings":"AAAA,+BAA+B;AAC/B,6FAA6F;AAC7F,6FAA6F;AAC7F,2FAA2F;AAC3F,gGAAgG;AAChG,4FAA4F;AAC5F,4FAA4F;AAC5F,gGAAgG;AAChG,0FAA0F;AAC1F,uDAAuD;AAEvD,OAAO,EAAE,UAAU,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAChE,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,aAAa,EAAY,MAAM,eAAe,CAAC;AAWxD;;;;sCAIsC;AACtC,MAAM,UAAU,gBAAgB,CAAC,SAAiB;IAChD,MAAM,QAAQ,GAAG,IAAI,GAAG,EAAuB,CAAC;IAChD,IAAI,CAAC,UAAU,CAAC,SAAS,CAAC;QAAE,OAAO,QAAQ,CAAC;IAC5C,MAAM,KAAK,GAAG,WAAW,CAAC,SAAS,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;IAC7E,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,MAAM,GAAG,GAAG,aAAa,CAAC,YAAY,CAAC,IAAI,CAAC,SAAS,EAAE,IAAI,CAAC,EAAE,MAAM,CAAC,CAAC,CAAC;QACvE,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;QACzC,KAAK,MAAM,UAAU,IAAI,GAAG,CAAC,MAAM,EAAE,CAAC;YACpC,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,UAAU,CAAC;gBAAE,QAAQ,CAAC,GAAG,CAAC,UAAU,EAAE,EAAE,GAAG,EAAE,GAAG,EAAE,CAAC,CAAC;QACxE,CAAC;IACH,CAAC;IACD,OAAO,QAAQ,CAAC;AAClB,CAAC"}
1
+ {"version":3,"file":"coverage.js","sourceRoot":"","sources":["../../src/catalog/coverage.ts"],"names":[],"mappings":"AAAA,+BAA+B;AAC/B,6FAA6F;AAC7F,6FAA6F;AAC7F,2FAA2F;AAC3F,gGAAgG;AAChG,4FAA4F;AAC5F,4FAA4F;AAC5F,gGAAgG;AAChG,0FAA0F;AAC1F,uDAAuD;AAEvD,OAAO,EAAE,UAAU,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAChE,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,aAAa,EAAY,MAAM,eAAe,CAAC;AACxD,OAAO,EAAE,OAAO,EAAE,MAAM,gBAAgB,CAAC;AAWzC;;;;sCAIsC;AACtC,MAAM,UAAU,gBAAgB,CAAC,SAAiB;IAChD,MAAM,QAAQ,GAAG,IAAI,GAAG,EAAuB,CAAC;IAChD,IAAI,CAAC,UAAU,CAAC,SAAS,CAAC;QAAE,OAAO,QAAQ,CAAC;IAC5C,MAAM,KAAK,GAAG,WAAW,CAAC,SAAS,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;IAC7E,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,MAAM,GAAG,GAAG,aAAa,CAAC,YAAY,CAAC,IAAI,CAAC,SAAS,EAAE,IAAI,CAAC,EAAE,MAAM,CAAC,CAAC,CAAC;QACvE,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;QACzC,KAAK,MAAM,UAAU,IAAI,GAAG,CAAC,MAAM,EAAE,CAAC;YACpC,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,UAAU,CAAC;gBAAE,QAAQ,CAAC,GAAG,CAAC,UAAU,EAAE,EAAE,GAAG,EAAE,GAAG,EAAE,CAAC,CAAC;QACxE,CAAC;IACH,CAAC;IACD,OAAO,QAAQ,CAAC;AAClB,CAAC;AAED;4EAC4E;AAC5E,MAAM,UAAU,mBAAmB,CAAC,OAAe;IACjD,MAAM,QAAQ,GAAG,IAAI,GAAG,EAAuB,CAAC;IAChD,KAAK,MAAM,GAAG,IAAI,OAAO,CAAC,OAAO,CAAC,EAAE,CAAC;QACnC,KAAK,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,gBAAgB,CAAC,GAAG,CAAC;YAAE,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC;gBAAE,QAAQ,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;IACvF,CAAC;IACD,OAAO,QAAQ,CAAC;AAClB,CAAC"}
@@ -0,0 +1,16 @@
1
+ export interface FrontmatterBadgeInput {
2
+ /** `SliceDoc.pattern` — kebab-case or null. */
3
+ pattern: string | null;
4
+ /** `SliceDoc.version` or null. */
5
+ version: number | null;
6
+ /** `SliceDoc.status`, lowercase, or null. */
7
+ status: string | null;
8
+ /** `classifyImplementationDrift(doc)`'s result, as its string value. */
9
+ driftSignal: string;
10
+ }
11
+ /** GH #402: a compact badge row — pattern, version, lifecycle status, implementation-drift
12
+ * signal — built from a slice doc's own frontmatter facts. Callers gate this on
13
+ * `doc.frontmatterPresent` themselves (catalog/pages.ts, render/sliceOverlay.ts): a legacy
14
+ * freeform doc with no frontmatter at all renders with no badge row, exactly as it did before
15
+ * this feature. */
16
+ export declare function renderFrontmatterBadgeRow(input: FrontmatterBadgeInput): string;
@@ -0,0 +1,50 @@
1
+ // SPDX-License-Identifier: MIT
2
+ // GH #402: the compact frontmatter badge row shown above a slice doc's body wherever
3
+ // `SliceDoc.html` is rendered — `em catalog`'s per-slice page (catalog/pages.ts) and the live
4
+ // viewer's doc flyout (render/sliceOverlay.ts). Both callers already hold a resolved `SliceDoc`
5
+ // and can compute its `classifyImplementationDrift()` (catalog/driftSignal.ts) themselves; this
6
+ // module takes the four already-resolved primitives rather than either type, so it carries no
7
+ // dependency on catalog/sliceDoc.ts or catalog/driftSignal.ts and can't become part of a cycle
8
+ // with either. Pure: primitives in, an HTML string out.
9
+ function escapeHtml(s) {
10
+ return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
11
+ }
12
+ /** `"state-change"` -> `"State Change"`, `"ready-to-implement"` -> `"Ready To Implement"` — the
13
+ * frontmatter's kebab-case machine values, in the display label shape the skill prose already
14
+ * uses for `pattern` (sliceDoc.ts's own comment: "the frontmatter value is a machine key, not a
15
+ * display label"). */
16
+ function titleCase(kebab) {
17
+ return kebab
18
+ .split("-")
19
+ .map((w) => (w.length > 0 ? w[0].toUpperCase() + w.slice(1) : w))
20
+ .join(" ");
21
+ }
22
+ /** Readable labels for `classifyImplementationDrift`'s `DriftSignalKind` — duplicated here as
23
+ * strings rather than importing the union type, so this module stays free of any dependency on
24
+ * catalog/driftSignal.ts (see file header). An unrecognized value (there shouldn't be one) falls
25
+ * back to showing itself verbatim rather than disappearing. */
26
+ const DRIFT_LABELS = {
27
+ "in-sync": "In sync",
28
+ "never-implemented": "Not implemented",
29
+ "unpropagated-delta": "Delta pending",
30
+ "implemented-without-link": "Implemented, no link",
31
+ uncertified: "Uncertified",
32
+ };
33
+ /** GH #402: a compact badge row — pattern, version, lifecycle status, implementation-drift
34
+ * signal — built from a slice doc's own frontmatter facts. Callers gate this on
35
+ * `doc.frontmatterPresent` themselves (catalog/pages.ts, render/sliceOverlay.ts): a legacy
36
+ * freeform doc with no frontmatter at all renders with no badge row, exactly as it did before
37
+ * this feature. */
38
+ export function renderFrontmatterBadgeRow(input) {
39
+ const badges = [];
40
+ if (input.pattern)
41
+ badges.push(`<span class="doc-badge doc-badge--pattern">${escapeHtml(titleCase(input.pattern))}</span>`);
42
+ if (input.version !== null)
43
+ badges.push(`<span class="doc-badge doc-badge--version">v${input.version}</span>`);
44
+ if (input.status)
45
+ badges.push(`<span class="doc-badge doc-badge--status">${escapeHtml(titleCase(input.status))}</span>`);
46
+ const driftLabel = DRIFT_LABELS[input.driftSignal] ?? input.driftSignal;
47
+ badges.push(`<span class="doc-badge doc-badge--drift doc-badge--drift-${escapeHtml(input.driftSignal)}">${escapeHtml(driftLabel)}</span>`);
48
+ return `<div class="doc-badge-row">${badges.join("")}</div>`;
49
+ }
50
+ //# sourceMappingURL=docBadge.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"docBadge.js","sourceRoot":"","sources":["../../src/catalog/docBadge.ts"],"names":[],"mappings":"AAAA,+BAA+B;AAC/B,qFAAqF;AACrF,8FAA8F;AAC9F,gGAAgG;AAChG,gGAAgG;AAChG,8FAA8F;AAC9F,+FAA+F;AAC/F,wDAAwD;AAExD,SAAS,UAAU,CAAC,CAAS;IAC3B,OAAO,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC;AACtG,CAAC;AAED;;;uBAGuB;AACvB,SAAS,SAAS,CAAC,KAAa;IAC9B,OAAO,KAAK;SACT,KAAK,CAAC,GAAG,CAAC;SACV,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,WAAW,EAAE,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;SAChE,IAAI,CAAC,GAAG,CAAC,CAAC;AACf,CAAC;AAED;;;gEAGgE;AAChE,MAAM,YAAY,GAA2B;IAC3C,SAAS,EAAE,SAAS;IACpB,mBAAmB,EAAE,iBAAiB;IACtC,oBAAoB,EAAE,eAAe;IACrC,0BAA0B,EAAE,sBAAsB;IAClD,WAAW,EAAE,aAAa;CAC3B,CAAC;AAaF;;;;oBAIoB;AACpB,MAAM,UAAU,yBAAyB,CAAC,KAA4B;IACpE,MAAM,MAAM,GAAa,EAAE,CAAC;IAC5B,IAAI,KAAK,CAAC,OAAO;QAAE,MAAM,CAAC,IAAI,CAAC,8CAA8C,UAAU,CAAC,SAAS,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,SAAS,CAAC,CAAC;IAC5H,IAAI,KAAK,CAAC,OAAO,KAAK,IAAI;QAAE,MAAM,CAAC,IAAI,CAAC,+CAA+C,KAAK,CAAC,OAAO,SAAS,CAAC,CAAC;IAC/G,IAAI,KAAK,CAAC,MAAM;QAAE,MAAM,CAAC,IAAI,CAAC,6CAA6C,UAAU,CAAC,SAAS,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,SAAS,CAAC,CAAC;IACzH,MAAM,UAAU,GAAG,YAAY,CAAC,KAAK,CAAC,WAAW,CAAC,IAAI,KAAK,CAAC,WAAW,CAAC;IACxE,MAAM,CAAC,IAAI,CACT,4DAA4D,UAAU,CAAC,KAAK,CAAC,WAAW,CAAC,KAAK,UAAU,CAAC,UAAU,CAAC,SAAS,CAC9H,CAAC;IACF,OAAO,8BAA8B,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC,QAAQ,CAAC;AAC/D,CAAC"}
@@ -0,0 +1,17 @@
1
+ /** Doc folder prefixes under `baseDir`: `""` (the root's `slices/`) first, then `"<sub>/"` for
2
+ * every immediate subdirectory that is a storyline model folder with a `slices/` directory,
3
+ * sorted. Never cached: a split or a new storyline folder mid-session must be seen on the next
4
+ * call. */
5
+ export declare function docFolders(baseDir: string): string[];
6
+ /** Absolute path of the existing file `relPath` (a `slices/<key>.md`-shaped path, or any path
7
+ * relative to the declaring file's directory), searched root first across the doc folders;
8
+ * `null` when no folder has it. A path that already starts with a folder prefix is honoured
9
+ * as written. */
10
+ export declare function locateDoc(baseDir: string, relPath: string): string | null;
11
+ /** Where `relPath` is — or, when it does not exist yet, where it belongs: the folder of the
12
+ * slice that declares it (`slice.file`'s directory), the root for a root slice. */
13
+ export declare function docAbsPath(baseDir: string, relPath: string, slice?: {
14
+ file?: string;
15
+ }): string;
16
+ /** Every existing `slices/` directory under `baseDir` (root first), absolute. */
17
+ export declare function docDirs(baseDir: string): string[];
@@ -0,0 +1,97 @@
1
+ // SPDX-License-Identifier: MIT
2
+ // GH #421 step 2 — finding a slice doc on disk when storylines live in their own folders.
3
+ //
4
+ // A doc path is `slices/<key>.md`, written and compared relative to the file that declares the
5
+ // slice (model/sliceDocPath.ts): for a root slice that is `<root>/slices/<key>.md`, for a slice
6
+ // of a storyline model `<root>/<dir>/slices/<key>.md`. Slice keys are unique per model, so a key
7
+ // names exactly one doc wherever it sits; this module looks it up across the root's `slices/`
8
+ // and every immediate subdirectory that has a `slices/` of its own (a storyline folder), root
9
+ // first. Reading needs no model in hand — `readSliceDoc(baseDir, key)` keeps its signature — and
10
+ // writing an existing doc rewrites it where it is; only a NEW doc needs its slice to pick the
11
+ // folder (`docAbsPath`'s third argument).
12
+ import { existsSync, readdirSync, readFileSync, statSync } from "node:fs";
13
+ import { join } from "node:path";
14
+ import { docDirOf } from "../model/sliceDocPath.js";
15
+ import { stripComment } from "../parser/lexer.js";
16
+ /** True when `dir` holds a storyline model file: a `.em` whose first significant line is a
17
+ * `storyline "…"` header with no `{` (parser `parseStorylineFile`'s shape). A sibling MODEL's
18
+ * directory (`examples/order-fulfillment/`, its `.em` starting with `model`/`slice`) is not a doc
19
+ * folder of this model, even though it has a `slices/` of its own. */
20
+ function isStorylineFolder(dir) {
21
+ let names;
22
+ try {
23
+ names = readdirSync(dir).filter((n) => n.endsWith(".em"));
24
+ }
25
+ catch {
26
+ return false;
27
+ }
28
+ for (const n of names) {
29
+ try {
30
+ const first = readFileSync(join(dir, n), "utf8").split(/\r?\n/).map((l) => stripComment(l).trim()).find((l) => l.length > 0);
31
+ if (first && /^storyline\b/.test(first) && !first.includes("{"))
32
+ return true;
33
+ }
34
+ catch {
35
+ // unreadable — not a storyline folder we can use
36
+ }
37
+ }
38
+ return false;
39
+ }
40
+ /** Doc folder prefixes under `baseDir`: `""` (the root's `slices/`) first, then `"<sub>/"` for
41
+ * every immediate subdirectory that is a storyline model folder with a `slices/` directory,
42
+ * sorted. Never cached: a split or a new storyline folder mid-session must be seen on the next
43
+ * call. */
44
+ export function docFolders(baseDir) {
45
+ const out = [""];
46
+ let entries;
47
+ try {
48
+ entries = readdirSync(baseDir).sort();
49
+ }
50
+ catch {
51
+ return out;
52
+ }
53
+ for (const name of entries) {
54
+ if (name.startsWith(".") || name === "slices" || name === "node_modules")
55
+ continue;
56
+ const sub = join(baseDir, name);
57
+ try {
58
+ if (statSync(sub).isDirectory() && statSync(join(sub, "slices")).isDirectory() && isStorylineFolder(sub))
59
+ out.push(`${name}/`);
60
+ }
61
+ catch {
62
+ // not a directory, or no slices/ inside — not a doc folder
63
+ }
64
+ }
65
+ return out;
66
+ }
67
+ /** Absolute path of the existing file `relPath` (a `slices/<key>.md`-shaped path, or any path
68
+ * relative to the declaring file's directory), searched root first across the doc folders;
69
+ * `null` when no folder has it. A path that already starts with a folder prefix is honoured
70
+ * as written. */
71
+ export function locateDoc(baseDir, relPath) {
72
+ const direct = join(baseDir, relPath);
73
+ if (existsSync(direct))
74
+ return direct;
75
+ if (!relPath.startsWith("slices/"))
76
+ return null;
77
+ for (const folder of docFolders(baseDir)) {
78
+ if (folder === "")
79
+ continue;
80
+ const candidate = join(baseDir, folder, relPath);
81
+ if (existsSync(candidate))
82
+ return candidate;
83
+ }
84
+ return null;
85
+ }
86
+ /** Where `relPath` is — or, when it does not exist yet, where it belongs: the folder of the
87
+ * slice that declares it (`slice.file`'s directory), the root for a root slice. */
88
+ export function docAbsPath(baseDir, relPath, slice) {
89
+ return locateDoc(baseDir, relPath) ?? join(baseDir, docDirOf(slice), relPath);
90
+ }
91
+ /** Every existing `slices/` directory under `baseDir` (root first), absolute. */
92
+ export function docDirs(baseDir) {
93
+ return docFolders(baseDir)
94
+ .map((f) => join(baseDir, f, "slices"))
95
+ .filter((d) => existsSync(d));
96
+ }
97
+ //# sourceMappingURL=docLocate.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"docLocate.js","sourceRoot":"","sources":["../../src/catalog/docLocate.ts"],"names":[],"mappings":"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"}
@@ -66,7 +66,7 @@ import { isSignedSliceDoc } from "./sliceDoc.js";
66
66
  import { checkScenarioQuality } from "./scenarioQualityValidate.js";
67
67
  import { authoredTextOf, findStructuredSectionProblems, generatedRegionPlaceholders, splitCells } from "./sliceSections.js";
68
68
  import { duplicateScenarioTitles, parseScenariosLenient } from "./scenarios.js";
69
- import { viewSourceNames } from "../model/continuation.js";
69
+ import { viewLoopedEventNames, viewSourceNames } from "../model/continuation.js";
70
70
  const COMMAND_MARKER = /^\*\*Command:\*\*\s*`([^`]+)`\s*$/;
71
71
  const EVENT_MARKER = /^\*\*Event:\*\*\s*`([^`]+)`(?:\s*→\s*context\s*`[^`]+`\s*)?$/;
72
72
  const VIEW_MARKER = /^-\s*\*\*View:\*\*\s*`([^`]+)`/;
@@ -405,6 +405,9 @@ function checkFields(diags, kind, docPath, docByName, modelByName, model, refs)
405
405
  // doc-names-unfed-event — the doc's field-table Source column (or, when the doc has exactly one
406
406
  // view, a scenario Given/Then item) names an event that exists in the model but is not among
407
407
  // the view's sources (the toolshed case: prose anticipated an edge the model never drew).
408
+ // An event reaching the view via a resolved `loops-to "<View>"` edge (MIL-199) counts as a
409
+ // source too (GH #397) — the DSL reference already says so, and `em export`'s `model.edges`
410
+ // carries the edge with `source: "loops-to"`.
408
411
  // doc-omits-fed-event — the model feeds the view an event the doc body never mentions.
409
412
  // Event names are found by scanning text for the model's own event names (longest first, word-
410
413
  // bounded, case-insensitive), so a name embedded in a longer event name is not double-counted.
@@ -505,7 +508,11 @@ function checkViewFeeds(diags, doc, docPath, markers, sliceIndices, ownerSlice,
505
508
  for (const marker of matched) {
506
509
  const view = modelViews.get(normalizeName(marker.name));
507
510
  const sources = viewSourceNames(model, view);
508
- const fed = new Set(sources.map(normalizeName));
511
+ // GH #397: a `loops-to "<View>"` edge re-feeds this view just like an ordinary `from` —
512
+ // an event naming only a loop-back is still read by the view, so the doc isn't wrong to
513
+ // list it under the view's sources.
514
+ const looped = viewLoopedEventNames(model, view);
515
+ const fed = new Set([...sources, ...looped].map(normalizeName));
509
516
  const refsOut = [refs.sliceKeys[view.sliceIndex], refs.refById.get(view.id)];
510
517
  let end = lines.length;
511
518
  for (let j = marker.bodyLine + 1; j < lines.length; j++) {