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