@milehimikey/em 1.10.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 +5 -1
- package/.claude/skills/event-modeling-design/SKILL.md +41 -14
- package/.claude/skills/event-modeling-discover/SKILL.md +1 -1
- package/.claude/skills/event-modeling-implement/SKILL.md +24 -7
- package/.claude/skills/event-modeling-implement/reference/implement.md +227 -19
- package/.claude/skills/event-modeling-review/SKILL.md +14 -3
- package/.claude/skills/event-modeling-shared/reference/em-dsl.md +80 -15
- package/.claude/skills/event-modeling-shared/reference/slice-doc-schema.md +43 -1
- package/.claude/skills/event-modeling-shared/templates/constitution.md +118 -0
- package/.claude/skills/event-modeling-shared/templates/model-readme.md +2 -2
- package/.claude/skills/event-modeling-shared/templates/slice.md +4 -0
- 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 +19 -2
- package/dist/catalog/docJoin.js +30 -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/sliceDoc.d.ts +8 -0
- package/dist/catalog/sliceDoc.js +2 -0
- package/dist/catalog/sliceDoc.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/ledgerCheck.d.ts +72 -0
- package/dist/cli/ledgerCheck.js +122 -1
- package/dist/cli/ledgerCheck.js.map +1 -1
- package/dist/cli/markImplemented.js +12 -1
- package/dist/cli/markImplemented.js.map +1 -1
- package/dist/cli/ratify.d.ts +37 -5
- package/dist/cli/ratify.js +123 -10
- package/dist/cli/ratify.js.map +1 -1
- package/dist/cli/reratify.js +26 -6
- package/dist/cli/reratify.js.map +1 -1
- package/dist/cli/review.d.ts +38 -0
- package/dist/cli/review.js +171 -0
- package/dist/cli/review.js.map +1 -0
- package/dist/cli/sliceIndex.d.ts +3 -0
- package/dist/cli/sliceIndex.js +14 -8
- 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/stateFile.d.ts +10 -0
- package/dist/cli/stateFile.js +14 -0
- package/dist/cli/stateFile.js.map +1 -1
- package/dist/cli/status.d.ts +55 -3
- package/dist/cli/status.js +92 -19
- package/dist/cli/status.js.map +1 -1
- package/dist/cli.js +203 -47
- 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/freshnessJson.d.ts +1 -1
- package/dist/emit/freshnessJson.js +5 -1
- package/dist/emit/freshnessJson.js.map +1 -1
- package/dist/emit/json.d.ts +45 -1
- package/dist/emit/json.js +82 -3
- package/dist/emit/json.js.map +1 -1
- package/dist/emit/ledgerJson.d.ts +1 -1
- package/dist/emit/ledgerJson.js +6 -1
- package/dist/emit/ledgerJson.js.map +1 -1
- package/dist/emit/queryJson.d.ts +1 -1
- package/dist/emit/queryJson.js +5 -1
- package/dist/emit/queryJson.js.map +1 -1
- package/dist/emit/statusJson.d.ts +1 -1
- package/dist/emit/statusJson.js +10 -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 +47 -22
- 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/edges.d.ts +18 -3
- package/dist/model/edges.js +34 -0
- package/dist/model/edges.js.map +1 -1
- package/dist/model/model.d.ts +4 -0
- package/dist/model/model.js +1 -0
- package/dist/model/model.js.map +1 -1
- package/dist/model/queryIndex.d.ts +5 -2
- package/dist/model/queryIndex.js +5 -2
- package/dist/model/queryIndex.js.map +1 -1
- package/dist/model/rules.d.ts +25 -0
- package/dist/model/rules.js +25 -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/model/validate.js +82 -1
- package/dist/model/validate.js.map +1 -1
- package/dist/parser/ast.d.ts +21 -0
- package/dist/parser/parser.js +96 -32
- package/dist/parser/parser.js.map +1 -1
- package/dist/render/drawEdges.js +7 -0
- package/dist/render/drawEdges.js.map +1 -1
- package/dist/render/drawNotes.d.ts +13 -3
- package/dist/render/drawNotes.js +96 -20
- package/dist/render/drawNotes.js.map +1 -1
- package/dist/render/render.js +6 -5
- package/dist/render/render.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 +14 -1
- package/dist/templates.js +141 -5
- 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
|
@@ -15,7 +15,7 @@ Keep `.em` files focused on **structure**; put deep design in markdown linked vi
|
|
|
15
15
|
em --version # print the installed em version
|
|
16
16
|
em init [file] # scaffold a starter .em model
|
|
17
17
|
em init [file] -f, --force # overwrite if the file exists
|
|
18
|
-
em scaffold <name> # scaffold a full project: <slug>/<slug>.em, README.md, .event-modeling.md (see docs/cli.md — for just a starter .em, use `em init`; for a multi-model project, pass --under to nest it under a shared parent directory)
|
|
18
|
+
em scaffold <name> # scaffold a full project: <slug>/<slug>.em, README.md, .event-modeling.md, and constitution.md (the implementation constitution — skipped, with a note, in a spec-kit project, where `.specify/memory/constitution.md` IS that document) (see docs/cli.md — for just a starter .em, use `em init`; for a multi-model project, pass --under to nest it under a shared parent directory)
|
|
19
19
|
em scaffold <name> -f, --force # overwrite the directory's contents if it already exists
|
|
20
20
|
em scaffold <name> --under <dir> # parent directory to scaffold into — writes <dir>/<slug>/ instead of ./<slug>/, the supported multi-model layout (docs/cli.md, "Multi-model projects"): one directory per model, so each model's slices/ never collides with a sibling model's
|
|
21
21
|
em render <file> # transpile a model and render it (or emit DOT)
|
|
@@ -52,10 +52,14 @@ em slice new <name> --wire <model-file> # also insert the
|
|
|
52
52
|
em slice index <file> # rewrite the model's sibling README.md's GENERATED Slices table from `em export`'s slice facts (key, pattern, doc status/implementedIn) — the hand-maintained table is deprecated
|
|
53
53
|
em slice index <file> --check # verify the table is current; exit non-zero on drift without writing (CI)
|
|
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
|
-
em slice
|
|
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
|
+
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, local date)
|
|
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
|
|
56
59
|
em slice ratify <file> <slice-key> --by <name> # the ratifier's name
|
|
57
|
-
em slice ratify <file> <slice-key> --on <date> # ratification date, YYYY-MM-DD (default: today)
|
|
58
|
-
em slice
|
|
60
|
+
em slice ratify <file> <slice-key> --on <date> # ratification date, YYYY-MM-DD (default: today, local date)
|
|
61
|
+
em slice ratify <file> <slice-key> --skip-review # ratify without a recorded review — prints a loud notice on stderr
|
|
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
|
|
59
63
|
em changelog <file> # render a model's git history as a business-readable ledger (see docs/cli.md)
|
|
60
64
|
em changelog <file> --from <rev> # start the walk at this revision (inclusive)
|
|
61
65
|
em changelog <file> --to <rev> # end the walk at this revision (inclusive; default HEAD)
|
|
@@ -77,7 +81,7 @@ em conform-scope <file> --seed-asis # write <model>-as
|
|
|
77
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
|
|
78
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`
|
|
79
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"
|
|
80
|
-
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)
|
|
81
85
|
em watch <file> # re-render on every save
|
|
82
86
|
em watch <file> -o, --out <path> # output path (extension picks the format)
|
|
83
87
|
em watch <file> -T, --format <fmt> # output format (svg, png, pdf, ...)
|
|
@@ -96,10 +100,12 @@ em migrate <file> --write # apply the rewrit
|
|
|
96
100
|
em ledger <file> # check slice docs' version: field agrees with their content across two git revisions (opt-in CI check, MIL-89 — never part of `em validate`, see docs/ci.md)
|
|
97
101
|
em ledger <file> --from <rev> # baseline revision
|
|
98
102
|
em ledger <file> --to <rev> # compare revision (default: current working tree)
|
|
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
|
|
99
104
|
em ledger <file> --json # print a JSON document instead of the text report (see docs/cli.md)
|
|
100
|
-
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
|
|
101
106
|
em coverage <file> --tests <dir> # directory to scan recursively for test files citing invariant IDs
|
|
102
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)
|
|
103
109
|
em coverage <file> --json # print a JSON document instead of the text report (see docs/cli.md)
|
|
104
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)
|
|
105
111
|
em status <files> --tests <dir> # directory to scan for INV-* test citations — enables invariant coverage totals
|
|
@@ -147,9 +153,9 @@ em query path <files> --json # print a JSON doc
|
|
|
147
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)
|
|
148
154
|
em system <manifest> --json # print a JSON document instead of the text report (see docs/cli.md)
|
|
149
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
|
|
150
|
-
em mcp # start an MCP (Model Context Protocol) server over stdio (MIL-21) — a structured, agent-facing alternative to shelling out to `em`;
|
|
151
|
-
em skill install # copy the event-modeling skill bundle into .claude/skills/ (event-modeling, event-modeling-discover/-design/-implement/-conform/-review, event-modeling-shared)
|
|
152
|
-
em skill install -f, --force # overwrite an existing installation
|
|
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
|
|
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)
|
|
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)
|
|
153
159
|
em skill install --no-agents-md # skip writing/updating the AGENTS.md agent-contract section (on by default, MIL-129)
|
|
154
160
|
em skill sync [path] # update the vendored .claude/skills/ event-modeling skill bundle in [path] to match the installed em package (overwrites unconditionally; local edits are never merged, MIL-93)
|
|
155
161
|
em skill sync [path] --no-agents-md # skip writing/updating the AGENTS.md agent-contract section (on by default, MIL-129)
|
|
@@ -195,7 +201,7 @@ type Name { field: Type, ... } # named structured type, reusable from any f
|
|
|
195
201
|
| `ui` | persona | screen / interface | `@Persona` | `note`, `issue`, `divergence`, `{ fields }` |
|
|
196
202
|
| `command` | API | state-changing request | — | `note`, `issue`, `divergence`, `renamed from`, `{ fields }` |
|
|
197
203
|
| `view` | API | read model / projection | — | `from "Event"…`, `note`, `issue`, `divergence`, `public`, `{ fields }` |
|
|
198
|
-
| `event` | context | recorded fact (past tense) | `@Context` | `note`, `issue`, `divergence`, `public`, `tag`, `renamed from`, field-level `assigned`, `{ fields }` |
|
|
204
|
+
| `event` | context | recorded fact (past tense) | `@Context` | `note`, `issue`, `divergence`, `public`, `tag`, `renamed from`, `loops-to`, field-level `assigned`, `{ fields }` |
|
|
199
205
|
| `processor` / `automation` / `saga` / `translation` | automation | system reaction / adapter | — | `from "…"`, `note`, `issue`, `divergence`, `{ fields }` |
|
|
200
206
|
|
|
201
207
|
### Clauses
|
|
@@ -219,7 +225,27 @@ type Name { field: Type, ... } # named structured type, reusable from any f
|
|
|
219
225
|
connected to one another — continuity is implied by the shared name, and the events reaching each
|
|
220
226
|
instance are what show the view changing. `again` with no earlier declaration is a validation error.
|
|
221
227
|
Use `again` (not a plain repeated `view` name) whenever the view is referenced by a
|
|
222
|
-
`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.
|
|
233
|
+
- **`loops-to "View"`** (events only): marks an event as re-feeding an earlier read model — a
|
|
234
|
+
poll/retry loop where a later fact must re-trigger an automation that already ran (a lapsed
|
|
235
|
+
reservation re-notifying the next person waiting in line, and similar re-triggers). Written
|
|
236
|
+
after the event's name/`@Context`, either on the header line or trailing an
|
|
237
|
+
inline `{ fields }` block; repeatable (`loops-to "A" loops-to "B"`, or comma-joined). The
|
|
238
|
+
target must resolve to a `view` instance STRICTLY EARLIER on the timeline — the opposite of
|
|
239
|
+
`from`'s "nearest at-or-before" rule; pointing at the same slice or later is a validation
|
|
240
|
+
error naming `from` on a later `view … again` instance as the forward-flowing alternative,
|
|
241
|
+
and naming a view that doesn't exist is a separate unresolved-target error. A resolved
|
|
242
|
+
`loops-to` counts as the event being read for both-ends-of-a-flow completeness. **No arrow is
|
|
243
|
+
ever drawn** for it — drawing one backward would violate the timeline laws — instead the
|
|
244
|
+
event gets an indigo ↩ corner marker (bottom-left) and the legend gains a "Loops" section:
|
|
245
|
+
`↩ 1 <Event> re-feeds "<View>"` (the number identifies the event, not the target). `em
|
|
246
|
+
export` carries it as `loopsTo: { name, ref }[] | null` (`from`'s shape); `model.edges` gains
|
|
247
|
+
`{ from: <event ref>, to: <view ref>, source: "loops-to" }`; `em query`'s adjacency labels the
|
|
248
|
+
connection `"loops-to"` too, so `downstream`/`upstream` follow it (cycle-safely).
|
|
223
249
|
- **`note "path.md"`** on ANY element links a markdown doc. Relative to the `.em` file. Renders as
|
|
224
250
|
a clickable marker in SVG and a legend entry in PNG/PDF. **This is how slice docs attach.**
|
|
225
251
|
- **`issue "text"`** on ANY element flags an open question inline — the diagram-visible red
|
|
@@ -280,6 +306,25 @@ type Name { field: Type, ... } # named structured type, reusable from any f
|
|
|
280
306
|
`=== true` convention as `tag`. **Reach for this before echoing a server-set field onto the
|
|
281
307
|
command just to silence a fields-completeness warning** — that misrepresents what the client
|
|
282
308
|
actually sends.
|
|
309
|
+
- **`derived`** (view fields only, MIL-200): marks a field as computed from which events have
|
|
310
|
+
landed, rather than copied from a single source event's payload (a status stepping through
|
|
311
|
+
states as events arrive, a computed ordinal, an availability flag two events can flip).
|
|
312
|
+
Trails a field's type, or the bare name of a typeless field — bare `derived` (no traced
|
|
313
|
+
source) or traced `derived from "Event A", "Event B"` (the two-word form parsed before the
|
|
314
|
+
bare check, same discipline as `renamed from` vs. plain `from`). A field whose ENTIRE text is
|
|
315
|
+
just `derived` is a field named `derived`, not a clause. `derived`/`derived from` on a
|
|
316
|
+
command/event/`ui`/`type` field is a parse error. Excludes the field from
|
|
317
|
+
`fields-completeness/view-field-no-source` entirely (and thus from `--slice-ready`). The
|
|
318
|
+
traced form still checks something: every named event must resolve among the view's ACTUAL
|
|
319
|
+
sources (its own `from` list, or the same-slice events for a `from`-less view) — an
|
|
320
|
+
unresolved name is a validation error, `derived-from-unresolved`. `em export` carries it as
|
|
321
|
+
`derived: boolean` on the field (always present, same `=== true` convention as `tag`/
|
|
322
|
+
`assigned`) and `derivedFrom: string[] | null` (the traced event name(s), `null` on a bare
|
|
323
|
+
`derived` field). **Reach for this instead of dropping a computed field out of the `.em` and
|
|
324
|
+
into doc-only prose** — the marker keeps `em export`/the model aware the field exists (the
|
|
325
|
+
`doc-model-field-mismatch` doc↔model check does not compare view field tables at all today —
|
|
326
|
+
see the slice template's field-table guidance — so this is an export-side win specifically,
|
|
327
|
+
not yet a doc-drift check).
|
|
283
328
|
- **Fields:** `command Place Order { orderId: UUID, items: LineItem[], customerId }` — inline or
|
|
284
329
|
one-per-line. Types are free text (no semantic checking) UNLESS the type string names a
|
|
285
330
|
declared `type` (see Named types below), in which case it resolves to a structured
|
|
@@ -476,6 +521,11 @@ slice "Read Quote — created" {
|
|
|
476
521
|
Keep the read model adjacent to its event, and keep a sub-flow that detours into another context
|
|
477
522
|
together rather than parking it at the end of the model.
|
|
478
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
|
+
|
|
479
529
|
### Reaction wiring — no positional gotcha
|
|
480
530
|
|
|
481
531
|
A reaction's command is inferred from **same-slice presence**, exactly like `ui -> command`: put
|
|
@@ -521,6 +571,14 @@ it to the slice that displays the read model, or drop it.
|
|
|
521
571
|
a ref naming a version higher than the target's own `version:`. A `split-from`/`merged-from`
|
|
522
572
|
naming a key absent from the tree is deliberately silent — that's the normal state after a
|
|
523
573
|
real split/merge (see the four `lineage-*` codes in the table below).
|
|
574
|
+
10. **`loops-to` not earlier, or unresolved** — an event's `loops-to "View"` naming a view at the
|
|
575
|
+
same slice or later (`loops-to-forward`; that's what `from` on a later `view … again`
|
|
576
|
+
instance is for), or naming a view that doesn't exist at all, or a name that resolves to a
|
|
577
|
+
non-view (`loops-to-unresolved`).
|
|
578
|
+
11. **`derived from` names an unresolved event** — a view field's traced `derived from "Event
|
|
579
|
+
A", "Event B"` clause naming an event that isn't among the view's actual sources (its own
|
|
580
|
+
`from` list, or the same-slice events for a `from`-less view) — `derived-from-unresolved`.
|
|
581
|
+
Name one of the view's real sources, or drop the traced event.
|
|
524
582
|
|
|
525
583
|
**Warnings (should fix):**
|
|
526
584
|
1. **Reaction with no command** — a `processor`/`automation`/`saga`/`translation` that triggers
|
|
@@ -537,9 +595,11 @@ it to the slice that displays the read model, or drop it.
|
|
|
537
595
|
5. **Event nobody reads** — the mirror of (4): recording an event no read model projects is a
|
|
538
596
|
write with no reader. Follow every write slice with the read slice that consumes its event.
|
|
539
597
|
Counts as read when a `view` names it in `from` (any `again` instance will do), when a view
|
|
540
|
-
with no `from` of its own sits in its slice,
|
|
541
|
-
|
|
542
|
-
`
|
|
598
|
+
with no `from` of its own sits in its slice, via an explicit `event -> view` arrow, or via a
|
|
599
|
+
resolved `loops-to "View"` clause (a loop-back is a real consumer too — an UNRESOLVED or
|
|
600
|
+
forward `loops-to` does not count, so the event still warns as unread alongside its own
|
|
601
|
+
`loops-to-forward`/`loops-to-unresolved` error). (Events marked `public` are exempt — their
|
|
602
|
+
reader is outside the model; same for a `public` view and the no-consumer warning.)
|
|
543
603
|
6. **Read model without source** — add `from "Event"` or place the view in a slice with an event.
|
|
544
604
|
7. **Duplicate name** — the same name defined N times; references resolve to the first. Rename.
|
|
545
605
|
(A duplicate `type` name always warns, unlike a duplicate element name — there's no
|
|
@@ -549,7 +609,8 @@ it to the slice that displays the read model, or drop it.
|
|
|
549
609
|
fail while any remain open.
|
|
550
610
|
9. **View field with no source** — a `view` field whose name matches no field on any instance
|
|
551
611
|
of its source events. Only checked once BOTH the view and at least one source event declare
|
|
552
|
-
`{ fields }
|
|
612
|
+
`{ fields }` — unless the field is marked `derived` (optionally `derived from "Event A",
|
|
613
|
+
"Event B"`, whose names must be among the view's sources), which exempts it entirely.
|
|
553
614
|
10. **Event field not from a command** — an `event` field whose name matches no field on any
|
|
554
615
|
command in the same slice. Only checked once BOTH the event and at least one same-slice
|
|
555
616
|
command declare `{ fields }`. This is the payoff of the fields feature for slicing rigor:
|
|
@@ -595,8 +656,10 @@ not the prose above has caught up yet. `--slice-ready <key>`-only codes are excl
|
|
|
595
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. |
|
|
596
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. |
|
|
597
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. |
|
|
598
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"). |
|
|
599
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. |
|
|
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`. |
|
|
600
663
|
| `doc-model-element-not-in-doc` | warning | Model element the doc doesn't mention | Add the matching marker to the doc, or remove the element from the model. |
|
|
601
664
|
| `doc-model-element-not-in-model` | warning | Doc names an element the model doesn't have | Add the element to the model, or fix/remove it in the doc. |
|
|
602
665
|
| `doc-model-field-mismatch` | warning | Doc/model field mismatch | Reconcile the field table with the model's fields — names and types. |
|
|
@@ -616,6 +679,8 @@ not the prose above has caught up yet. `--slice-ready <key>`-only codes are excl
|
|
|
616
679
|
| `lineage-ref-cycle` | error | Lineage cycle | Break the cycle — a slice can't be its own ancestor. |
|
|
617
680
|
| `lineage-ref-malformed` | error | Malformed lineage ref | Fix the value to `<slice-key>@v<N>`, or remove it. |
|
|
618
681
|
| `lineage-version-impossible` | error | Impossible lineage version | Fix the referenced version, or ratify the target slice first. |
|
|
682
|
+
| `loops-to-forward` | error | `loops-to` target not earlier on the timeline | Point `loops-to` at an earlier view, or use `from` on a later `view … again` instance instead. |
|
|
683
|
+
| `loops-to-unresolved` | error | `loops-to` target unresolved | Name a view that exists, or declare it before this event. |
|
|
619
684
|
| `note-binding-dangling` | warning | Dangling cross-slice note | Create the doc at that path, or fix/remove the note. |
|
|
620
685
|
| `note-binding-extra` | warning | Extra doc-binding note, ignored | Remove the note, or point it at the slice's actual bound doc. |
|
|
621
686
|
| `note-binding-unratified` | warning | Unratified cross-slice note | Add `covers: <this-slice-key>` to that doc's frontmatter, or correct the note's path. |
|
|
@@ -56,13 +56,15 @@ 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` | `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)) |
|
|
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)) |
|
|
63
63
|
| `merged-from` | list of refs | comma-separated `<slice-key>@v<N>, ...` | joined into `em export`'s `slice.doc.mergedFrom` (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)) |
|
|
64
64
|
| `superseded-by` | list of refs | comma-separated `<slice-key>@v<N>, ...` | joined into `em export`'s `slice.doc.supersededBy` (schema `1.4`, MIL-91) and `em diff`'s `slice-removed` 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)) |
|
|
65
65
|
| `covers` | list of slice keys | comma-separated `<slice-key>, ...` (plain keys — **not** the `<slice-key>@v<N>` ref grammar above) | ratifies a cross-slice binding (MIL-121 — see [Cross-slice coverage](#cross-slice-coverage-covers) below): read by `em export`'s doc join, `em validate --slice-ready`, and (best-effort) `em render`/`em watch`'s Slice Status legend |
|
|
66
|
+
| `reviewedBy` | string | free text (typically the facilitator's or reviewer's name) | written only by `em slice review` (MIL-201 — see [cli.md](https://github.com/milehimikey/em/blob/main/docs/cli.md#em-slice-review-file-slice-key---by-name)); joined into `em export`'s `slice.doc.reviewedBy` (schema `1.11`) and `em slice index`'s Reviewed by column |
|
|
67
|
+
| `reviewedOn` | string | `YYYY-MM-DD` | written only by `em slice review` (MIL-201); joined into `em export`'s `slice.doc.reviewedOn` (schema `1.11`) |
|
|
66
68
|
| `ratifiedBy` | string | free text (typically a person's name) | written only by `em slice ratify` (MIL-165 — see [cli.md](https://github.com/milehimikey/em/blob/main/docs/cli.md#em-slice-ratify-file-slice-key---by-name)); joined into `em export`'s `slice.doc.ratifiedBy` (schema `1.8`) and `em slice index`'s Ratified by column |
|
|
67
69
|
| `ratifiedOn` | string | `YYYY-MM-DD` | written only by `em slice ratify` (MIL-165); joined into `em export`'s `slice.doc.ratifiedOn` (schema `1.8`) |
|
|
68
70
|
| `owner` | string | free text (typically a person or team name) | hand-filled — no `em` command writes it; joined into `em export`'s `slice.doc.owner` (schema `1.9`, MIL-171), `em slice index`'s Owner column, and `em status`'s per-slice `owners[]` |
|
|
@@ -84,6 +86,7 @@ convention, not parser-enforced.
|
|
|
84
86
|
| `implementedIn` | optional | optional | optional | required once the slice has *ever* reached `implemented` — may still name a **prior** version's PR during re-ratification, see below |
|
|
85
87
|
| `split-from`, `merged-from`, `superseded-by` | optional in every state — present only on docs created by a split/merge, or on a doc that has been retired |
|
|
86
88
|
| `covers` | optional in every state — present only on a doc that deliberately also serves another slice (MIL-121, see below); most docs never carry it |
|
|
89
|
+
| `reviewedBy`, `reviewedOn` | optional in every state — present once `em slice review` (MIL-201) has run at least once; a doc that never went through the review gate, or was hand-flipped to `reviewed`, simply omits both. `em slice ratify` never clears them; `em slice reratify` does (they describe the version that shipped) |
|
|
87
90
|
| `ratifiedBy`, `ratifiedOn` | optional in every state — present once `em slice ratify` (MIL-165) has run at least once; a doc predating this feature, or ratified by hand, simply omits both |
|
|
88
91
|
| `owner`, `tracking` | optional in every state — hand-filled whenever a team wants a who-holds-this / external-tracker link; most docs never carry either (MIL-171) |
|
|
89
92
|
|
|
@@ -177,6 +180,12 @@ reads a bound doc's canonical fields (`em export`'s `slice.doc`, `em validate --
|
|
|
177
180
|
`implementedIn`/lineage/Open Questions, not the covered slice's own (nonexistent) file. A doc
|
|
178
181
|
covers its own canonical slice key implicitly — `covers` never needs to list it.
|
|
179
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
|
+
|
|
180
189
|
`covers` is a plain list of slice keys (`<slice-key>`, e.g. `detect-unpaid-orders`), **not** the
|
|
181
190
|
`<slice-key>@v<N>` ref grammar the lineage keys use above: coverage is a standing "this doc also
|
|
182
191
|
serves that slice" declaration, with no version component — unlike lineage, which records a
|
|
@@ -192,6 +201,39 @@ the mismatch, as its own separate rule (MIL-126) — see
|
|
|
192
201
|
that just doesn't ratify (`covers` doesn't list this slice back), and an extra note doing nothing
|
|
193
202
|
in a slice that's already bound elsewhere, each get their own diagnostic there.
|
|
194
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
|
+
|
|
195
237
|
## Delta section: grammar and lifecycle
|
|
196
238
|
|
|
197
239
|
`## Delta` is the body section that pairs with a re-ratification (see "`status` under
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
The implementation constitution: the house rules an implementing agent follows on EVERY slice —
|
|
3
|
+
stack, code style, testing norms, NFR baselines, review norms. It answers "how do we build things
|
|
4
|
+
here" once, so no slice doc has to repeat it and no agent has to invent it. Slice docs say WHAT to
|
|
5
|
+
build; this says HOW it gets built here.
|
|
6
|
+
|
|
7
|
+
Where this document lives:
|
|
8
|
+
- No SDD tool: `constitution.md` beside the model — `em scaffold` writes it there.
|
|
9
|
+
- spec-kit project (a `.specify/` directory exists anywhere above the model): the document IS
|
|
10
|
+
`.specify/memory/constitution.md`. em writes no second copy; the sections below merge into
|
|
11
|
+
that file under these same headings, never overwriting text already there, and ratification is
|
|
12
|
+
recorded in spec-kit's own `**Ratified**:` footer line instead of the frontmatter below.
|
|
13
|
+
|
|
14
|
+
How it gets filled: not by an agent guessing. Each section opens with the question a short
|
|
15
|
+
facilitated conversation asks before the first slice is implemented — the procedure is "Before the
|
|
16
|
+
first slice: the constitution" in the `event-modeling-implement` skill's `reference/implement.md`
|
|
17
|
+
(`em contract` prints it). Replace each `{{...}}` block with the real answers; delete the guidance
|
|
18
|
+
comments and any row that doesn't apply.
|
|
19
|
+
|
|
20
|
+
Ratification: `ratifiedBy:`/`ratifiedOn:` are the same identity discipline slice docs use — a
|
|
21
|
+
NAMED human and an ISO date, hand-filled at sign-off (no `em` command writes this file).
|
|
22
|
+
**A constitution with an empty `ratifiedBy:` is a draft**: an implementing agent treats it as
|
|
23
|
+
unratified, says so, and does not proceed on unilateral style or stack decisions for a first slice.
|
|
24
|
+
|
|
25
|
+
`em status` reports this document as present or absent per model — existence only. em never reads
|
|
26
|
+
or validates its content, and nothing here is machine-parsed.
|
|
27
|
+
-->
|
|
28
|
+
|
|
29
|
+
---
|
|
30
|
+
schemaVersion: 1
|
|
31
|
+
ratifiedBy:
|
|
32
|
+
ratifiedOn:
|
|
33
|
+
---
|
|
34
|
+
# {{Project Name}} — implementation constitution
|
|
35
|
+
|
|
36
|
+
The house rules for how implementation happens in this project. They bound every "purely
|
|
37
|
+
technical choice" an implementing agent would otherwise make alone.
|
|
38
|
+
|
|
39
|
+
## Stack and architectural shape
|
|
40
|
+
|
|
41
|
+
> **What do we build on, what shape does the code take, and which implementation skill or
|
|
42
|
+
> approach does each slice pattern route to?**
|
|
43
|
+
|
|
44
|
+
- **Languages / runtimes:** {{e.g. Kotlin 2.x on JVM 21; TypeScript 5 on Node 24}}
|
|
45
|
+
- **Frameworks / platform:** {{web framework, persistence, messaging, build tool}}
|
|
46
|
+
- **Architectural pattern:** {{e.g. event-sourced CQRS with vertical slices; layered service over
|
|
47
|
+
a relational store; functional core / imperative shell}}
|
|
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}}
|
|
52
|
+
|
|
53
|
+
Routing — which implementation skill or approach each of the four slice patterns uses:
|
|
54
|
+
|
|
55
|
+
| Pattern | Skill / approach |
|
|
56
|
+
|---|---|
|
|
57
|
+
| State Change | {{e.g. the `axon-*` skill set — axon-entity-patterns, then axon-entity-testing}} |
|
|
58
|
+
| State View | {{e.g. the `axon-*` skill set — axon-projection-patterns, then axon-projection-testing}} |
|
|
59
|
+
| Automation | {{e.g. the `axon-*` skill set — axon-policy-patterns, then axon-policy-testing}} |
|
|
60
|
+
| Translation | {{e.g. the `axon-*` skill set — axon-translation-patterns}} |
|
|
61
|
+
|
|
62
|
+
An agent implementing a slice reads the doc's `pattern:` frontmatter and follows that row — this
|
|
63
|
+
table is the only routing rule.
|
|
64
|
+
|
|
65
|
+
## Code style
|
|
66
|
+
|
|
67
|
+
> **What does "idiomatic" mean in this codebase — formatting, naming, module layout?**
|
|
68
|
+
|
|
69
|
+
- **Formatter / linter:** {{the tool and the command that runs it — never hand-format against it}}
|
|
70
|
+
- **Naming:** {{commands, events, handlers, files, test names}}
|
|
71
|
+
- **Module layout:** {{what lives in which file/package, and what may not import what}}
|
|
72
|
+
- **Idiomatic here means:** {{the two or three habits a new contributor most often gets wrong}}
|
|
73
|
+
|
|
74
|
+
## Testing norms
|
|
75
|
+
|
|
76
|
+
> **What must be tested for each slice pattern, where do those tests live, and how are they named
|
|
77
|
+
> so `em coverage` can trace invariant IDs?**
|
|
78
|
+
|
|
79
|
+
- **Levels required per pattern:** {{e.g. State Change — decider unit tests plus a
|
|
80
|
+
given/when/then fixture test; State View — projection tests; Automation / Translation —
|
|
81
|
+
reaction tests, including a negative case}}
|
|
82
|
+
- **Invariant tests:** {{where they live}} — every `INV-<MNEMONIC>-n` in a slice doc needs at least
|
|
83
|
+
one test **citing that exact ID** in its name or a comment, so
|
|
84
|
+
`em coverage <model>.em --tests <dir>` can trace it.
|
|
85
|
+
- **Scenario tests:** {{where they live}} — one per `## Scenarios (Given / When / Then)` entry,
|
|
86
|
+
with rejection scenarios asserting the doc's named rejection reason.
|
|
87
|
+
- **Test naming convention:** {{the exact pattern, e.g. `INV-ORD-3: rejects a second submit`}}
|
|
88
|
+
- **What "green" means:** {{the command CI runs}}
|
|
89
|
+
|
|
90
|
+
## NFR baselines
|
|
91
|
+
|
|
92
|
+
> **What standing non-functional defaults does every slice inherit — authz, PII, performance,
|
|
93
|
+
> observability?**
|
|
94
|
+
|
|
95
|
+
- **Authorization model:** {{who may issue which commands, how it's enforced, and where}}
|
|
96
|
+
- **PII / data handling:** {{what is sensitive, what may be logged, retention}}
|
|
97
|
+
- **Performance defaults:** {{latency / throughput budgets that apply unless a slice says otherwise}}
|
|
98
|
+
- **Observability:** {{logging, metrics, tracing every slice is expected to emit}}
|
|
99
|
+
|
|
100
|
+
A slice doc's own `## Non-Functional Requirements` section overrides these for that slice; where
|
|
101
|
+
it is silent, these apply.
|
|
102
|
+
|
|
103
|
+
## Review and merge norms
|
|
104
|
+
|
|
105
|
+
> **Who reviews an implementation PR, what blocks a merge, and how does the slice doc's status
|
|
106
|
+
> flip fit in?**
|
|
107
|
+
|
|
108
|
+
- **Reviewers:** {{who — a team, a CODEOWNERS rule, the slice's ratifier}}
|
|
109
|
+
- **What blocks a merge:** {{e.g. a failing suite, an uncovered invariant, a gap that belongs back
|
|
110
|
+
at the model}}
|
|
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.
|
|
116
|
+
- **At merge:** run `em slice mark-implemented <model>.em <slice-key> <pr-url>` — the one edit an
|
|
117
|
+
implementing agent makes to a ratified slice doc, run by {{the PR author | CI}}. Never bump
|
|
118
|
+
`version:`; that moves only when a delta is ratified.
|
|
@@ -37,8 +37,8 @@ never connected to one another.
|
|
|
37
37
|
`em slice index {{model-name}}.em` to (re)write the table below from the model and its
|
|
38
38
|
slice docs; never hand-edit between the markers. -->
|
|
39
39
|
<!-- GENERATED:slices:start -->
|
|
40
|
-
| # | Slice | Pattern | Status | Ratified by | Owner | Tracking | Implemented in | Design doc |
|
|
41
|
-
|
|
40
|
+
| # | Slice | Pattern | Status | Reviewed by | Ratified by | Owner | Tracking | Implemented in | Design doc |
|
|
41
|
+
|---|-------|---------|--------|-------------|-------------|-------|----------|----------------|------------|
|
|
42
42
|
<!-- GENERATED:slices:end -->
|
|
43
43
|
|
|
44
44
|
## Status
|
|
@@ -157,6 +157,10 @@ an event directly.}}
|
|
|
157
157
|
instance of a repeated view needs its own consumer, not just the last one. -->
|
|
158
158
|
- **Freshness / consistency expectation:** {{real-time | eventual | on-demand}}
|
|
159
159
|
|
|
160
|
+
| Field | Type | Source / Notes |
|
|
161
|
+
|-------|------|-----------------|
|
|
162
|
+
| {{field}} | {{Type}} | {{which event field it's copied from, or `Derived: <plain-language rule>` for a value computed from which events have landed (a status stepping through states, a computed rank, a flag two events can flip) — a field written as `Derived:` here should ALSO carry the `derived` marker (bare `derived`, or `derived from "Event A", "Event B"` naming the events the rule depends on) on the view's field in the `.em`, so `em export`/the model know the field exists too, not just this doc (docs/dsl.md#derived-fields)}} |
|
|
163
|
+
|
|
160
164
|
## Invariants / Business Rules
|
|
161
165
|
<!-- What must ALWAYS hold. Give each a stable ID so tests and code can reference it:
|
|
162
166
|
`INV-<MNEMONIC>-<n>`, where `<MNEMONIC>` is a short (2-4 letter/digit), slice-unique
|
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");
|
|
@@ -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;
|
|
1
|
+
{"version":3,"file":"build.js","sourceRoot":"","sources":["../../src/catalog/build.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { SliceDoc } from "./sliceDoc.js";
|
|
2
|
+
/** A covering doc, plus the covering slice's OWN key (its `slices/<key>.md` filename stem,
|
|
3
|
+
* stripped of the extension). Callers that need to link to or name the covering slice itself
|
|
4
|
+
* (`em catalog`'s "documented as part of" banner) get it here rather than re-deriving "which
|
|
5
|
+
* slice does this doc actually belong to" from `doc`, which has no notion of its own key. */
|
|
6
|
+
export interface CoveringDoc {
|
|
7
|
+
doc: SliceDoc;
|
|
8
|
+
key: string;
|
|
9
|
+
}
|
|
10
|
+
/** Scans every `slices/*.md` file (sorted, for deterministic first-wins on an overlapping
|
|
11
|
+
* `covers:` claim) and maps each covered slice key to the doc — and that doc's own key — that
|
|
12
|
+
* covers it. Directory-scan, not the single-file existsSync check every other doc lookup in em
|
|
13
|
+
* uses — the one precedent for it is `em ledger`'s (src/cli/ledgerCheck.ts) own
|
|
14
|
+
* `readdirSync(slicesDir)` sweep. */
|
|
15
|
+
export declare function readCoverageDocs(slicesDir: string): Map<string, CoveringDoc>;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
// Shared MIL-121 "covered by" doc-discovery: scans `slices/*.md` for a frontmatter `covers:`
|
|
3
|
+
// entry naming a slice that has no doc of its own. Every consumer that needs this fallback —
|
|
4
|
+
// the render pipeline's Slice Status legend (src/render/sliceStatus.ts) and `em catalog`'s
|
|
5
|
+
// detail pages (src/catalog/build.ts, MIL-137) — shares this one scan, so a covered slice never
|
|
6
|
+
// resolves to a different doc (or a different notion of which slice covers it) depending on
|
|
7
|
+
// which command asked. Filename+frontmatter discovery only: never reads Element.note — that
|
|
8
|
+
// cross-note is docJoin.ts's concern (`em export`'s doc join, `em validate --slice-ready`), not
|
|
9
|
+
// this module's; test/catalog.e2e.test.ts pins "never reads note" as an invariant for `em
|
|
10
|
+
// catalog`, and this module is exactly as blind to it.
|
|
11
|
+
import { existsSync, readdirSync, readFileSync } from "node:fs";
|
|
12
|
+
import { join } from "node:path";
|
|
13
|
+
import { parseSliceDoc } from "./sliceDoc.js";
|
|
14
|
+
/** Scans every `slices/*.md` file (sorted, for deterministic first-wins on an overlapping
|
|
15
|
+
* `covers:` claim) and maps each covered slice key to the doc — and that doc's own key — that
|
|
16
|
+
* covers it. Directory-scan, not the single-file existsSync check every other doc lookup in em
|
|
17
|
+
* uses — the one precedent for it is `em ledger`'s (src/cli/ledgerCheck.ts) own
|
|
18
|
+
* `readdirSync(slicesDir)` sweep. */
|
|
19
|
+
export function readCoverageDocs(slicesDir) {
|
|
20
|
+
const coverage = new Map();
|
|
21
|
+
if (!existsSync(slicesDir))
|
|
22
|
+
return coverage;
|
|
23
|
+
const files = readdirSync(slicesDir).filter((f) => f.endsWith(".md")).sort();
|
|
24
|
+
for (const file of files) {
|
|
25
|
+
const doc = parseSliceDoc(readFileSync(join(slicesDir, file), "utf8"));
|
|
26
|
+
const key = file.slice(0, -".md".length);
|
|
27
|
+
for (const coveredKey of doc.covers) {
|
|
28
|
+
if (!coverage.has(coveredKey))
|
|
29
|
+
coverage.set(coveredKey, { doc, key });
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
return coverage;
|
|
33
|
+
}
|
|
34
|
+
//# sourceMappingURL=coverage.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"coverage.js","sourceRoot":"","sources":["../../src/catalog/coverage.ts"],"names":[],"mappings":"AAAA,+BAA+B;AAC/B,6FAA6F;AAC7F,6FAA6F;AAC7F,2FAA2F;AAC3F,gGAAgG;AAChG,4FAA4F;AAC5F,4FAA4F;AAC5F,gGAAgG;AAChG,0FAA0F;AAC1F,uDAAuD;AAEvD,OAAO,EAAE,UAAU,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAChE,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,aAAa,EAAY,MAAM,eAAe,CAAC;AAWxD;;;;sCAIsC;AACtC,MAAM,UAAU,gBAAgB,CAAC,SAAiB;IAChD,MAAM,QAAQ,GAAG,IAAI,GAAG,EAAuB,CAAC;IAChD,IAAI,CAAC,UAAU,CAAC,SAAS,CAAC;QAAE,OAAO,QAAQ,CAAC;IAC5C,MAAM,KAAK,GAAG,WAAW,CAAC,SAAS,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;IAC7E,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,MAAM,GAAG,GAAG,aAAa,CAAC,YAAY,CAAC,IAAI,CAAC,SAAS,EAAE,IAAI,CAAC,EAAE,MAAM,CAAC,CAAC,CAAC;QACvE,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;QACzC,KAAK,MAAM,UAAU,IAAI,GAAG,CAAC,MAAM,EAAE,CAAC;YACpC,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,UAAU,CAAC;gBAAE,QAAQ,CAAC,GAAG,CAAC,UAAU,EAAE,EAAE,GAAG,EAAE,GAAG,EAAE,CAAC,CAAC;QACxE,CAAC;IACH,CAAC;IACD,OAAO,QAAQ,CAAC;AAClB,CAAC"}
|