@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.
- package/.claude/skills/event-modeling/SKILL.md +1 -1
- package/.claude/skills/event-modeling-conform/SKILL.md +1 -1
- package/.claude/skills/event-modeling-conform/reference/conform.md +29 -1
- package/.claude/skills/event-modeling-design/SKILL.md +33 -7
- package/.claude/skills/event-modeling-discover/SKILL.md +2 -2
- package/.claude/skills/event-modeling-discover/reference/extract.md +12 -0
- package/.claude/skills/event-modeling-engagement/SKILL.md +1 -1
- package/.claude/skills/event-modeling-implement/SKILL.md +7 -2
- package/.claude/skills/event-modeling-implement/reference/implement.md +73 -6
- package/.claude/skills/event-modeling-review/SKILL.md +19 -7
- package/.claude/skills/event-modeling-shared/reference/em-dsl.md +57 -5
- package/.claude/skills/event-modeling-shared/reference/methodology.md +51 -2
- package/.claude/skills/event-modeling-shared/reference/slice-doc-schema.md +2 -1
- package/.claude/skills/event-modeling-shared/templates/model-readme.md +10 -2
- package/.claude/skills/event-modeling-shared/templates/slice.md +12 -1
- package/.claude-plugin/marketplace.json +2 -2
- package/dist/catalog/build.js +6 -5
- package/dist/catalog/build.js.map +1 -1
- package/dist/catalog/coverage.d.ts +3 -0
- package/dist/catalog/coverage.js +12 -0
- package/dist/catalog/coverage.js.map +1 -1
- package/dist/catalog/docBadge.d.ts +16 -0
- package/dist/catalog/docBadge.js +50 -0
- package/dist/catalog/docBadge.js.map +1 -0
- package/dist/catalog/docLocate.d.ts +17 -0
- package/dist/catalog/docLocate.js +97 -0
- package/dist/catalog/docLocate.js.map +1 -0
- package/dist/catalog/docModelConsistencyValidate.js +9 -2
- package/dist/catalog/docModelConsistencyValidate.js.map +1 -1
- package/dist/catalog/frontmatterCoherenceValidate.js +16 -1
- package/dist/catalog/frontmatterCoherenceValidate.js.map +1 -1
- package/dist/catalog/invTokens.d.ts +49 -0
- package/dist/catalog/invTokens.js +94 -0
- package/dist/catalog/invTokens.js.map +1 -0
- package/dist/catalog/orphanedSliceDocValidate.js +15 -10
- package/dist/catalog/orphanedSliceDocValidate.js.map +1 -1
- package/dist/catalog/pages.js +48 -1
- package/dist/catalog/pages.js.map +1 -1
- package/dist/catalog/readSliceDoc.js +5 -4
- package/dist/catalog/readSliceDoc.js.map +1 -1
- package/dist/catalog/scenarioQualityValidate.js +2 -2
- package/dist/catalog/scenarioQualityValidate.js.map +1 -1
- package/dist/catalog/scenarios.d.ts +4 -0
- package/dist/catalog/scenarios.js +4 -1
- package/dist/catalog/scenarios.js.map +1 -1
- package/dist/catalog/sliceDoc.d.ts +54 -1
- package/dist/catalog/sliceDoc.js +99 -2
- package/dist/catalog/sliceDoc.js.map +1 -1
- package/dist/catalog/sliceDocRender.d.ts +3 -0
- package/dist/catalog/sliceDocRender.js +295 -0
- package/dist/catalog/sliceDocRender.js.map +1 -0
- package/dist/catalog/sliceSections.d.ts +18 -1
- package/dist/catalog/sliceSections.js +38 -4
- package/dist/catalog/sliceSections.js.map +1 -1
- package/dist/cli/coverage.d.ts +13 -23
- package/dist/cli/coverage.js +51 -91
- package/dist/cli/coverage.js.map +1 -1
- package/dist/cli/defer.js +3 -2
- package/dist/cli/defer.js.map +1 -1
- package/dist/cli/ledgerCheck.js +11 -8
- package/dist/cli/ledgerCheck.js.map +1 -1
- package/dist/cli/markImplemented.js +2 -2
- package/dist/cli/markImplemented.js.map +1 -1
- package/dist/cli/ratify.js +2 -2
- package/dist/cli/ratify.js.map +1 -1
- package/dist/cli/refsCheck.d.ts +52 -0
- package/dist/cli/refsCheck.js +129 -2
- package/dist/cli/refsCheck.js.map +1 -1
- package/dist/cli/render-inputs.d.ts +1 -1
- package/dist/cli/render-inputs.js +3 -2
- package/dist/cli/render-inputs.js.map +1 -1
- package/dist/cli/review.js +2 -2
- package/dist/cli/review.js.map +1 -1
- package/dist/cli/revise.js +2 -2
- package/dist/cli/revise.js.map +1 -1
- package/dist/cli/skillDirs.d.ts +22 -0
- package/dist/cli/skillDirs.js +24 -0
- package/dist/cli/skillDirs.js.map +1 -1
- package/dist/cli/sliceConform.d.ts +57 -1
- package/dist/cli/sliceConform.js +111 -10
- package/dist/cli/sliceConform.js.map +1 -1
- package/dist/cli/sliceIndex.d.ts +19 -3
- package/dist/cli/sliceIndex.js +81 -17
- package/dist/cli/sliceIndex.js.map +1 -1
- package/dist/cli/sliceNew.js +2 -0
- package/dist/cli/sliceNew.js.map +1 -1
- package/dist/cli/sliceStubAll.js +7 -2
- package/dist/cli/sliceStubAll.js.map +1 -1
- package/dist/cli/sliceSync.d.ts +12 -0
- package/dist/cli/sliceSync.js +42 -4
- package/dist/cli/sliceSync.js.map +1 -1
- package/dist/cli/status.d.ts +3 -4
- package/dist/cli/status.js +5 -4
- package/dist/cli/status.js.map +1 -1
- package/dist/cli/storylineSplit.d.ts +18 -0
- package/dist/cli/storylineSplit.js +110 -0
- package/dist/cli/storylineSplit.js.map +1 -0
- package/dist/cli/upgrade.js +39 -14
- package/dist/cli/upgrade.js.map +1 -1
- package/dist/cli/watchStoryline.d.ts +33 -0
- package/dist/cli/watchStoryline.js +65 -0
- package/dist/cli/watchStoryline.js.map +1 -0
- package/dist/cli.js +423 -71
- package/dist/cli.js.map +1 -1
- package/dist/emit/docJson.d.ts +99 -0
- package/dist/emit/docJson.js +197 -0
- package/dist/emit/docJson.js.map +1 -0
- package/dist/emit/json.d.ts +22 -1
- package/dist/emit/json.js +12 -1
- package/dist/emit/json.js.map +1 -1
- package/dist/emit/refsJson.d.ts +5 -3
- package/dist/emit/refsJson.js +5 -2
- package/dist/emit/refsJson.js.map +1 -1
- package/dist/mcp/server.js +43 -9
- package/dist/mcp/server.js.map +1 -1
- package/dist/model/continuation.d.ts +9 -0
- package/dist/model/continuation.js +30 -0
- package/dist/model/continuation.js.map +1 -1
- package/dist/model/edges.d.ts +1 -1
- package/dist/model/edges.js +8 -1
- package/dist/model/edges.js.map +1 -1
- package/dist/model/loadModel.d.ts +38 -0
- package/dist/model/loadModel.js +122 -0
- package/dist/model/loadModel.js.map +1 -0
- package/dist/model/model.d.ts +9 -4
- package/dist/model/model.js +4 -0
- package/dist/model/model.js.map +1 -1
- package/dist/model/rules.d.ts +31 -0
- package/dist/model/rules.js +31 -0
- package/dist/model/rules.js.map +1 -1
- package/dist/model/shapeLint.js +143 -0
- package/dist/model/shapeLint.js.map +1 -1
- package/dist/model/sliceDocIndex.js +13 -5
- package/dist/model/sliceDocIndex.js.map +1 -1
- package/dist/model/sliceDocPath.d.ts +19 -0
- package/dist/model/sliceDocPath.js +49 -0
- package/dist/model/sliceDocPath.js.map +1 -0
- package/dist/model/storylineMap.d.ts +20 -0
- package/dist/model/storylineMap.js +55 -0
- package/dist/model/storylineMap.js.map +1 -0
- package/dist/model/validate.d.ts +7 -0
- package/dist/model/validate.js +127 -0
- package/dist/model/validate.js.map +1 -1
- package/dist/parser/ast.d.ts +27 -0
- package/dist/parser/parser.d.ts +17 -1
- package/dist/parser/parser.js +142 -3
- package/dist/parser/parser.js.map +1 -1
- package/dist/pipeline.d.ts +16 -0
- package/dist/pipeline.js +19 -2
- package/dist/pipeline.js.map +1 -1
- package/dist/render/drawEdges.d.ts +5 -1
- package/dist/render/drawEdges.js +57 -4
- package/dist/render/drawEdges.js.map +1 -1
- package/dist/render/drawNotes.d.ts +4 -1
- package/dist/render/drawNotes.js +40 -10
- package/dist/render/drawNotes.js.map +1 -1
- package/dist/render/render.d.ts +11 -0
- package/dist/render/render.js +93 -10
- package/dist/render/render.js.map +1 -1
- package/dist/render/serve.d.ts +5 -0
- package/dist/render/serve.js +18 -2
- package/dist/render/serve.js.map +1 -1
- package/dist/render/sliceDiagram.js +3 -0
- package/dist/render/sliceDiagram.js.map +1 -1
- package/dist/render/sliceOverlay.d.ts +5 -1
- package/dist/render/sliceOverlay.js +24 -2
- package/dist/render/sliceOverlay.js.map +1 -1
- package/dist/render/sliceStatus.d.ts +8 -0
- package/dist/render/sliceStatus.js +22 -15
- package/dist/render/sliceStatus.js.map +1 -1
- package/dist/render/storylineBands.d.ts +0 -3
- package/dist/render/storylineBands.js +2 -22
- package/dist/render/storylineBands.js.map +1 -1
- package/dist/render/storylineDiagram.js +17 -69
- package/dist/render/storylineDiagram.js.map +1 -1
- package/dist/render/storylineMap.d.ts +18 -0
- package/dist/render/storylineMap.js +263 -0
- package/dist/render/storylineMap.js.map +1 -0
- package/dist/render/storylinePicker.d.ts +9 -0
- package/dist/render/storylinePicker.js +17 -0
- package/dist/render/storylinePicker.js.map +1 -0
- package/dist/render/svgGeometry.d.ts +9 -0
- package/dist/render/svgGeometry.js +16 -0
- package/dist/render/svgGeometry.js.map +1 -1
- package/dist/render/viewerHtml.js +1 -1
- package/dist/render/viewerHtml.js.map +1 -1
- package/dist/templates.d.ts +1 -1
- package/dist/templates.js +10 -2
- package/dist/templates.js.map +1 -1
- package/package.json +1 -1
- package/plugin/.claude-plugin/plugin.json +2 -2
- package/plugin/shared/reference/em-dsl.md +57 -5
- package/plugin/shared/reference/methodology.md +51 -2
- package/plugin/shared/reference/slice-doc-schema.md +2 -1
- package/plugin/shared/templates/model-readme.md +10 -2
- package/plugin/shared/templates/slice.md +12 -1
- package/plugin/skills/conform/SKILL.md +1 -1
- package/plugin/skills/conform/reference/conform.md +29 -1
- package/plugin/skills/design/SKILL.md +33 -7
- package/plugin/skills/discover/SKILL.md +2 -2
- package/plugin/skills/discover/reference/extract.md +12 -0
- package/plugin/skills/engagement/SKILL.md +1 -1
- package/plugin/skills/event-modeling/SKILL.md +1 -1
- package/plugin/skills/implement/SKILL.md +7 -2
- package/plugin/skills/implement/reference/implement.md +73 -6
- 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 `` | 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 |
|
|
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
|

|
|
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-
|
|
2
|
+
"name": "em-1-21-0",
|
|
3
3
|
"owner": {
|
|
4
4
|
"name": "Mike Key"
|
|
5
5
|
},
|
|
6
|
-
"description": "em v1.
|
|
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",
|
package/dist/catalog/build.js
CHANGED
|
@@ -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 {
|
|
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 ??=
|
|
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 =
|
|
99
|
-
if (
|
|
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 =
|
|
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":"AAAA,+BAA+B;AAC/B,kEAAkE;AAClE,0EAA0E;AAC1E,4EAA4E;AAC5E,EAAE;AACF,cAAc;AACd,iBAAiB;AACjB,mBAAmB;AACnB,8BAA8B;AAC9B,gBAAgB;AAChB,2BAA2B;AAC3B,2FAA2F;AAE3F,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACnD,OAAO,EAAE,QAAQ,EAAE,KAAK,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAC9D,OAAO,EAAE,QAAQ,EAAE,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAE7D,OAAO,EAAE,cAAc,EAAE,MAAM,0BAA0B,CAAC;AAE1D,OAAO,EAAE,SAAS,EAAE,UAAU,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAC3E,OAAO,EAAE,iBAAiB,EAAE,MAAM,2BAA2B,CAAC;AAG9D,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AACpD,OAAO,EAAE,oBAAoB,EAAE,MAAM,eAAe,CAAC;AACrD,OAAO,EAAe,
|
|
1
|
+
{"version":3,"file":"build.js","sourceRoot":"","sources":["../../src/catalog/build.ts"],"names":[],"mappings":"AAAA,+BAA+B;AAC/B,kEAAkE;AAClE,0EAA0E;AAC1E,4EAA4E;AAC5E,EAAE;AACF,cAAc;AACd,iBAAiB;AACjB,mBAAmB;AACnB,8BAA8B;AAC9B,gBAAgB;AAChB,2BAA2B;AAC3B,2FAA2F;AAE3F,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACnD,OAAO,EAAE,QAAQ,EAAE,KAAK,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAC9D,OAAO,EAAE,QAAQ,EAAE,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAE7D,OAAO,EAAE,cAAc,EAAE,MAAM,0BAA0B,CAAC;AAE1D,OAAO,EAAE,SAAS,EAAE,UAAU,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAC3E,OAAO,EAAE,iBAAiB,EAAE,MAAM,2BAA2B,CAAC;AAG9D,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AACpD,OAAO,EAAE,oBAAoB,EAAE,MAAM,eAAe,CAAC;AACrD,OAAO,EAAe,mBAAmB,EAAE,MAAM,eAAe,CAAC;AACjE,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AACvD,OAAO,EAAE,wBAAwB,EAAE,MAAM,6BAA6B,CAAC;AACvE,OAAO,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAC9C,OAAO,EAAE,eAAe,EAAE,eAAe,EAA4C,MAAM,YAAY,CAAC;AAmCxG,MAAM,CAAC,KAAK,UAAU,YAAY,CAChC,MAA2B,EAC3B,IAAoB;IAEpB,MAAM,MAAM,GAAG,IAAI,CAAC,MAAM,IAAI,KAAK,CAAC;IACpC,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,IAAI,qBAAqB,CAAC;IAClD,MAAM,MAAM,GAAG,IAAI,CAAC,MAAM,CAAC;IAE3B,MAAM,KAAK,CAAC,MAAM,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAEzC,MAAM,aAAa,GAAG,IAAI,GAAG,EAAU,CAAC;IACxC,MAAM,cAAc,GAA0B,EAAE,CAAC;IACjD,MAAM,WAAW,GAAkD,EAAE,CAAC;IACtE,IAAI,UAAU,GAAG,CAAC,CAAC;IAEnB,yFAAyF;IACzF,sFAAsF;IACtF,6FAA6F;IAC7F,sFAAsF;IACtF,yFAAyF;IACzF,MAAM,UAAU,GAAG,wBAAwB,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,EAAE,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,IAAI,CAAC,SAAS,EAAE,CAAC,CAAC,CAAC,CAAC;IACnH,KAAK,MAAM,CAAC,IAAI,UAAU;QAAE,WAAW,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,EAAE,WAAW,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;IAE5F,KAAK,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,GAAG,EAAE,IAAI,EAAE,IAAI,MAAM,EAAE,CAAC;QACtD,MAAM,QAAQ,GAAG,MAAM,CAAC,SAAS,CAAC,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,aAAa,EAAE,GAAG,CAAC,CAAC;QACtF,MAAM,QAAQ,GAAG,IAAI,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC;QACxC,MAAM,SAAS,GAAG,IAAI,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC;QAC3C,MAAM,KAAK,CAAC,SAAS,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;QAE5C,MAAM,WAAW,GAAG,WAAW,MAAM,EAAE,CAAC;QACxC,mFAAmF;QACnF,sFAAsF;QACtF,MAAM,GAAG,GAAG,MAAM,SAAS,CAAC,GAAG,CAAC,CAAC;QACjC,MAAM,OAAO,GAAG,UAAU,CAAC,GAAG,EAAE,KAAK,EAAE,IAAI,EAAE,OAAO,CAAC,IAAI,CAAC,EAAE,QAAQ,CAAC,CAAC;QACtE,MAAM,aAAa,CAAC,OAAO,EAAE,IAAI,CAAC,QAAQ,EAAE,WAAW,CAAC,EAAE,MAAM,CAAC,CAAC;QAElE,IAAI,IAAI,CAAC,WAAW,CAAC,MAAM,GAAG,CAAC;YAAE,WAAW,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,WAAW,EAAE,IAAI,CAAC,WAAW,EAAE,CAAC,CAAC;QAC3F,MAAM,cAAc,GAA0B,EAAE,CAAC;QACjD,oFAAoF;QACpF,uFAAuF;QACvF,oFAAoF;QACpF,oCAAoC;QACpC,IAAI,YAAY,GAAoC,IAAI,CAAC;QAEzD,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,KAAK,CAAC,MAAM,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;YAC7C,MAAM,KAAK,GAAG,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;YAC9B,MAAM,QAAQ,GAAG,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC;YACnC,MAAM,OAAO,GAAG,oBAAoB,CAAC,KAAK,CAAC,CAAC;YAE5C,mFAAmF;YACnF,oFAAoF;YACpF,mFAAmF;YACnF,sFAAsF;YACtF,qFAAqF;YACrF,6EAA6E;YAC7E,MAAM,UAAU,GAAG,IAAI,CAAC,QAAQ,EAAE,GAAG,QAAQ,KAAK,CAAC,CAAC;YACpD,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,UAAU,CAAC,CAAC;YAChD,MAAM,MAAM,GAAG,UAAU,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,aAAa,CAAC,YAAY,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;YAEzF,yFAAyF;YACzF,yFAAyF;YACzF,wFAAwF;YACxF,0FAA0F;YAC1F,yFAAyF;YACzF,wFAAwF;YACxF,QAAQ;YACR,IAAI,GAAG,GAAG,MAAM,CAAC;YACjB,IAAI,SAAS,GAA0E,IAAI,CAAC;YAC5F,IAAI,CAAC,MAAM,EAAE,CAAC;gBACZ,YAAY,KAAK,mBAAmB,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,4BAA4B;gBACjF,MAAM,QAAQ,GAAG,YAAY,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;gBAC5C,IAAI,QAAQ,EAAE,CAAC;oBACb,GAAG,GAAG,QAAQ,CAAC,GAAG,CAAC;oBACnB,MAAM,aAAa,GAAG,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC;oBAC3D,SAAS,GAAG,EAAE,GAAG,EAAE,QAAQ,CAAC,GAAG,EAAE,IAAI,EAAE,aAAa,IAAI,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,MAAM,CAAC,aAAa,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,QAAQ,CAAC,GAAG,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAC;gBAChI,CAAC;qBAAM,CAAC;oBACN,qFAAqF;oBACrF,gFAAgF;oBAChF,qFAAqF;oBACrF,uEAAuE;oBACvE,MAAM,YAAY,GAAG,cAAc,CAAC,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC;oBACpD,IAAI,YAAY,EAAE,CAAC;wBACjB,MAAM,eAAe,GAAG,SAAS,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,UAAU,YAAY,CAAC,QAAQ,KAAK,CAAC,CAAC,CAAC,UAAU;wBAClG,IAAI,eAAe,EAAE,CAAC;4BACpB,GAAG,GAAG,aAAa,CAAC,YAAY,CAAC,eAAe,EAAE,MAAM,CAAC,CAAC,CAAC;4BAC3D,MAAM,gBAAgB,GAAG,KAAK,CAAC,MAAM,CAAC,YAAY,CAAC,UAAU,CAAC,CAAC;4BAC/D,SAAS,GAAG,EAAE,GAAG,EAAE,YAAY,CAAC,QAAQ,EAAE,IAAI,EAAE,gBAAgB,CAAC,IAAI,EAAE,IAAI,EAAE,cAAc,EAAE,CAAC;wBAChG,CAAC;oBACH,CAAC;gBACH,CAAC;YACH,CAAC;YAED,8EAA8E;YAC9E,+EAA+E;YAC/E,0EAA0E;YAC1E,0EAA0E;YAC1E,8DAA8D;YAC9D,MAAM,eAAe,GAAG,UAAU,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,UAAU,QAAQ,MAAM,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,UAAU;YACxG,MAAM,gBAAgB,GAAG,GAAG,QAAQ,MAAM,CAAC;YAC3C,IAAI,UAAU,CAAC,eAAe,CAAC,EAAE,CAAC;gBAChC,MAAM,QAAQ,CAAC,eAAe,EAAE,IAAI,CAAC,SAAS,EAAE,gBAAgB,CAAC,CAAC,CAAC;YACrE,CAAC;iBAAM,CAAC;gBACN,+EAA+E;gBAC/E,8EAA8E;gBAC9E,6EAA6E;gBAC7E,8EAA8E;gBAC9E,4EAA4E;gBAC5E,sEAAsE;gBACtE,MAAM,EAAE,KAAK,EAAE,UAAU,EAAE,IAAI,EAAE,SAAS,EAAE,GAAG,EAAE,QAAQ,EAAE,GAAG,iBAAiB,CAAC,KAAK,EAAE,CAAC,EAAE;oBACxF,cAAc,EAAE,IAAI,CAAC,cAAc;iBACpC,CAAC,CAAC;gBACH,MAAM,QAAQ,GAAG,MAAM,SAAS,CAAC,QAAQ,CAAC,CAAC;gBAC3C,MAAM,QAAQ,GAAG,UAAU,CAAC,QAAQ,EAAE,UAAU,EAAE,SAAS,EAAE,OAAO,CAAC,IAAI,CAAC,EAAE,SAAS,CAAC,CAAC;gBACvF,MAAM,SAAS,CAAC,IAAI,CAAC,SAAS,EAAE,gBAAgB,CAAC,EAAE,QAAQ,EAAE,MAAM,CAAC,CAAC;YACvE,CAAC;YAED,MAAM,WAAW,GAAG,IAAI,GAAG,EAAkB,CAAC;YAC9C,KAAK,MAAM,EAAE,IAAI,KAAK,CAAC,QAAQ,EAAE,CAAC;gBAChC,MAAM,GAAG,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC;gBACpC,IAAI,GAAG;oBAAE,WAAW,CAAC,GAAG,CAAC,EAAE,CAAC,EAAE,EAAE,GAAG,CAAC,CAAC;YACvC,CAAC;YAED,MAAM,IAAI,GAAG,eAAe,CAAC;gBAC3B,SAAS,EAAE,KAAK,CAAC,IAAI;gBACrB,WAAW,EAAE,MAAM,WAAW,EAAE;gBAChC,gBAAgB;gBAChB,KAAK;gBACL,QAAQ;gBACR,OAAO;gBACP,WAAW;gBACX,GAAG;gBACH,SAAS;gBACT,eAAe,EAAE,UAAU;aAC5B,CAAC,CAAC;YACH,MAAM,SAAS,CAAC,IAAI,CAAC,SAAS,EAAE,GAAG,QAAQ,OAAO,CAAC,EAAE,IAAI,EAAE,MAAM,CAAC,CAAC;YAEnE,cAAc,CAAC,IAAI,CAAC;gBAClB,GAAG,EAAE,QAAQ;gBACb,IAAI,EAAE,KAAK,CAAC,IAAI;gBAChB,OAAO;gBACP,MAAM,EAAE,GAAG,KAAK,IAAI;gBACpB,MAAM,EAAE,GAAG,EAAE,MAAM,IAAI,IAAI;aAC5B,CAAC,CAAC;YACH,UAAU,EAAE,CAAC;QACf,CAAC;QAED,cAAc,CAAC,IAAI,CAAC,EAAE,GAAG,EAAE,QAAQ,EAAE,IAAI,EAAE,KAAK,CAAC,IAAI,EAAE,IAAI,EAAE,WAAW,EAAE,MAAM,EAAE,cAAc,EAAE,CAAC,CAAC;IACtG,CAAC;IAED,MAAM,SAAS,GAAG,eAAe,CAAC,cAAc,EAAE,KAAK,EAAE,MAAM,CAAC,CAAC;IACjE,MAAM,SAAS,CAAC,IAAI,CAAC,MAAM,EAAE,YAAY,CAAC,EAAE,SAAS,EAAE,MAAM,CAAC,CAAC;IAE/D,OAAO,EAAE,MAAM,EAAE,cAAc,CAAC,MAAM,EAAE,MAAM,EAAE,UAAU,EAAE,WAAW,EAAE,CAAC;AAC5E,CAAC"}
|
|
@@ -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>;
|
package/dist/catalog/coverage.js
CHANGED
|
@@ -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;
|
|
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, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
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
|
-
|
|
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++) {
|