@milehimikey/em 1.21.0 → 1.22.1
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/agents/em-implementer.md +10 -0
- package/.claude/agents/em-validator.md +1 -0
- package/.claude/skills/event-modeling/SKILL.md +1 -1
- package/.claude/skills/event-modeling-conform/SKILL.md +7 -4
- package/.claude/skills/event-modeling-conform/reference/conform.md +82 -4
- package/.claude/skills/event-modeling-design/SKILL.md +5 -2
- package/.claude/skills/event-modeling-discover/SKILL.md +1 -1
- package/.claude/skills/event-modeling-engagement/SKILL.md +1 -1
- package/.claude/skills/event-modeling-engagement/reference/engagement.md +1 -0
- package/.claude/skills/event-modeling-implement/SKILL.md +5 -3
- package/.claude/skills/event-modeling-implement/reference/implement.md +14 -0
- package/.claude/skills/event-modeling-review/SKILL.md +1 -1
- package/.claude/skills/event-modeling-shared/reference/em-dsl.md +55 -6
- package/.claude/skills/event-modeling-shared/reference/operating-principles.md +26 -15
- package/.claude-plugin/marketplace.json +2 -2
- package/dist/cli/api.d.ts +136 -9
- package/dist/cli/api.js +278 -27
- package/dist/cli/api.js.map +1 -1
- package/dist/cli/bindings.d.ts +152 -0
- package/dist/cli/bindings.js +462 -0
- package/dist/cli/bindings.js.map +1 -0
- package/dist/cli/conformScope.d.ts +32 -0
- package/dist/cli/conformScope.js +68 -5
- package/dist/cli/conformScope.js.map +1 -1
- package/dist/cli/engagement.d.ts +24 -1
- package/dist/cli/engagement.js +50 -6
- package/dist/cli/engagement.js.map +1 -1
- package/dist/cli/engagementBrief.d.ts +127 -0
- package/dist/cli/engagementBrief.js +313 -0
- package/dist/cli/engagementBrief.js.map +1 -0
- package/dist/cli/engagementFile.d.ts +14 -0
- package/dist/cli/engagementFile.js +54 -0
- package/dist/cli/engagementFile.js.map +1 -1
- package/dist/cli/scopeInputs.js +2 -2
- package/dist/cli/scopeInputs.js.map +1 -1
- package/dist/cli/status.d.ts +5 -0
- package/dist/cli/status.js +1 -0
- package/dist/cli/status.js.map +1 -1
- package/dist/cli/upgrade.js +9 -10
- package/dist/cli/upgrade.js.map +1 -1
- package/dist/cli.js +143 -11
- package/dist/cli.js.map +1 -1
- package/dist/emit/api.d.ts +26 -1
- package/dist/emit/api.js +183 -25
- package/dist/emit/api.js.map +1 -1
- package/dist/emit/apiSurfaceJson.d.ts +3 -0
- package/dist/emit/apiSurfaceJson.js +43 -0
- package/dist/emit/apiSurfaceJson.js.map +1 -0
- package/dist/emit/diffJson.d.ts +1 -1
- package/dist/emit/diffJson.js +20 -1
- package/dist/emit/diffJson.js.map +1 -1
- package/dist/emit/engagementJson.d.ts +8 -3
- package/dist/emit/engagementJson.js +31 -4
- package/dist/emit/engagementJson.js.map +1 -1
- package/dist/emit/json.d.ts +42 -1
- package/dist/emit/json.js +65 -6
- package/dist/emit/json.js.map +1 -1
- package/dist/mcp/server.js +92 -3
- package/dist/mcp/server.js.map +1 -1
- package/dist/model/diff.d.ts +19 -1
- package/dist/model/diff.js +99 -0
- package/dist/model/diff.js.map +1 -1
- package/dist/model/model.d.ts +105 -9
- package/dist/model/model.js +116 -20
- package/dist/model/model.js.map +1 -1
- package/dist/model/refs.d.ts +4 -0
- package/dist/model/refs.js +35 -1
- package/dist/model/refs.js.map +1 -1
- package/dist/model/rules.d.ts +61 -0
- package/dist/model/rules.js +72 -0
- package/dist/model/rules.js.map +1 -1
- package/dist/model/validate.js +137 -4
- package/dist/model/validate.js.map +1 -1
- package/dist/parser/ast.d.ts +30 -0
- package/dist/parser/parser.js +67 -1
- package/dist/parser/parser.js.map +1 -1
- package/dist/render/sliceDiagram.js +6 -0
- package/dist/render/sliceDiagram.js.map +1 -1
- package/dist/render/storylineDiagram.js +6 -0
- package/dist/render/storylineDiagram.js.map +1 -1
- package/dist/system/adaptation.d.ts +5 -2
- package/dist/system/adaptation.js +5 -3
- package/dist/system/adaptation.js.map +1 -1
- package/dist/templates.d.ts +4 -0
- package/dist/templates.js +29 -0
- package/dist/templates.js.map +1 -1
- package/package.json +2 -1
- package/plugin/.claude-plugin/plugin.json +2 -2
- package/plugin/agents/em-implementer.md +10 -0
- package/plugin/agents/em-validator.md +1 -0
- package/plugin/shared/reference/em-dsl.md +55 -6
- package/plugin/shared/reference/operating-principles.md +26 -15
- package/plugin/skills/conform/SKILL.md +7 -4
- package/plugin/skills/conform/reference/conform.md +82 -4
- package/plugin/skills/design/SKILL.md +5 -2
- package/plugin/skills/discover/SKILL.md +1 -1
- package/plugin/skills/engagement/SKILL.md +1 -1
- package/plugin/skills/engagement/reference/engagement.md +1 -0
- package/plugin/skills/event-modeling/SKILL.md +1 -1
- package/plugin/skills/implement/SKILL.md +5 -3
- package/plugin/skills/implement/reference/implement.md +14 -0
- package/plugin/skills/review/SKILL.md +1 -1
|
@@ -16,7 +16,7 @@ Keep `.em` files focused on **structure**; put deep design in markdown linked vi
|
|
|
16
16
|
em --version # print the installed em version
|
|
17
17
|
em init [file] # scaffold a starter .em model
|
|
18
18
|
em init [file] -f, --force # overwrite if the file exists
|
|
19
|
-
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
|
+
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), plus the standard slices/, contracts/, model-versions/ and conformance/bindings/ directories (each seeded with a one-line README naming what lands there and which command writes it, instead of a .gitkeep) (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)
|
|
20
20
|
em scaffold <name> -f, --force # overwrite the directory's contents if it already exists
|
|
21
21
|
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
|
|
22
22
|
em render <file> # transpile a model and render it (or emit DOT)
|
|
@@ -38,6 +38,8 @@ em api generate <file> --stdout # print the
|
|
|
38
38
|
em api check <file> # is the committed contract current? With --base, annotate each public-surface change since that revision additive or breaking (annotation only — exits 1 only when stale)
|
|
39
39
|
em api check <file> --base <rev> # git revision to diff the public surface against
|
|
40
40
|
em api check <file> --json # print a JSON document instead of the text report (see docs/cli.md)
|
|
41
|
+
em api surface <file> # the model's public commands, events, views and reachable declared types as a versioned JSON document for companion tools (GH #448, see docs/cli.md)
|
|
42
|
+
em api surface <file> --json # print a JSON document instead of the text listing (see docs/cli.md)
|
|
41
43
|
em typespec <file> # DEPRECATED alias of `em api generate --stdout` (use em api generate)
|
|
42
44
|
em typespec <file> -o, --out <path> # write to a file instead of stdout
|
|
43
45
|
em diff <old> [new] # compare two models structurally (two files, or one file across git revisions)
|
|
@@ -143,6 +145,11 @@ em conform-supersede <file> <report-path> --locus <locus> # who/what t
|
|
|
143
145
|
em conform-supersede <file> <report-path> --by <name> # who ruled on the named finding(s) (requires --locus)
|
|
144
146
|
em conform-findings check <path> # shape-validate a conformance/<date>-findings.json file (findingsSchemaVersion, required fields, enum values, ids sorted+unique, a ruled finding carries resolvedBy+resolvedOn) — exit 1 on shape errors. The conform skill writes this file directly alongside its report; this is how a headless run verifies what it just wrote.
|
|
145
147
|
em conform-findings check <path> --json # print a JSON document instead of the text report
|
|
148
|
+
em conform bindings check <file> # verify every per-slice bindings manifest (conformance/bindings/<slice-key>.yaml, see docs/bindings.md): each bound element's `ref` resolves against the compiled model, and each bound `file` exists under --repo and contains its `identifier` as a plain substring (grep-level only, never code-parsed — language-agnostic). Distinct failure kinds: an unknown `--slice` key, a missing bindings file, malformed YAML, an unknown slice named inside a file, and an unknown element ref. Advisory only — exit code stays 0 unless --exit-code is given and a binding failed (the opt-in CI gate, same posture as `em refs --reachable-from`).
|
|
149
|
+
em conform bindings check <file> --repo <path> # path to the target codebase (bound `file`s resolve relative to it; never required to be a git repository)
|
|
150
|
+
em conform bindings check <file> --slice <key> # check only this slice's bindings file (default: every conformance/bindings/*.yaml)
|
|
151
|
+
em conform bindings check <file> --json # print a JSON document instead of the text report
|
|
152
|
+
em conform bindings check <file> --exit-code # exit 1 if any binding failed (git-diff convention) — the opt-in CI gate
|
|
146
153
|
em discover triage-check <file> # validate a discovery/prd-triage.json (prdTriageSchemaVersion 1.0): shape, the shipped rubric version, every statement/storyline quote appearing verbatim in the input document, and every statement carrying a category, an outcome and (modeled/rewritten) a group + storyline — exit 1 on errors. The discover skill writes this file directly; this is how a headless run verifies it.
|
|
147
154
|
em discover triage-check <file> --source <path> # the input PRD/spec to check quotes against (default: the file's own `source`, relative to it)
|
|
148
155
|
em discover triage-check <file> --markdown # after a passing check, print the Markdown view of the record instead of the summary
|
|
@@ -243,17 +250,23 @@ em engagement new <file> <slug> --parallel <n> # how many s
|
|
|
243
250
|
em engagement new <file> <slug> --by <name> # who created the engagement (createdBy)
|
|
244
251
|
em engagement new <file> <slug> --tracking <key> # an issue-tracker key or URL the slice PRs cite (a slice doc's own tracking: wins)
|
|
245
252
|
em engagement new <file> <slug> --pr-title <template> # PR title template for the slice PRs; {key} = tracking key, {slice} = slice name, {sliceKey} = slice key (default "{key}: {slice}", or "{slice}" without a key)
|
|
253
|
+
em engagement new <file> <slug> --context-doc <path> # repo-relative path to a document implementers need beyond the slice doc — the constitution, an NFR doc, an ADR (repeatable; GH #455)
|
|
246
254
|
em engagement new <file> <slug> --force # overwrite an existing engagement file with this slug
|
|
247
255
|
em engagement plan <file> <slug> # the engagement's build plan: dependency levels over model.edges (loops-to excluded), and per slice its readiness, branch impl/<key>, base (always main — MIL-280), and any hold
|
|
248
256
|
em engagement plan <file> <slug> --json # print a JSON document instead of text (same document as the MCP engagement_plan tool)
|
|
249
257
|
em engagement foundation <file> <slug> # GH #240: is the foundation PR (implement.md §8) actually in the code for the events this engagement's slices emit and consume? A text scan of --code: an event is present when a file carries an identifier derived from its name (OrderPlaced / order_placed / ORDER_PLACED / order-placed), its model fields and tag fields are then looked for in those files. Per event: present | incomplete (missing-field / missing-tag, named) | missing-event — so "foundation absent" and "foundation incomplete" are different reports. Exits 1 unless every event is present. Proves the contract was written down, not that it type-checks
|
|
250
258
|
em engagement foundation <file> <slug> --code <dir> # the target repository (or the directory the constitution's "where a slice's code lives" rule points at)
|
|
251
259
|
em engagement foundation <file> <slug> --json # print a JSON document instead of text (same document as the MCP engagement_foundation tool)
|
|
252
|
-
em engagement
|
|
253
|
-
em engagement
|
|
254
|
-
em engagement
|
|
255
|
-
em engagement
|
|
256
|
-
em engagement set <file> <slug>
|
|
260
|
+
em engagement brief <file> <slug> # GH #454: a deterministic, self-contained per-slice work brief for implementers em does not control (a ticket-driven cloud agent) — the bound slice doc verbatim (or the model's own elements when there is none), model context from the edge list, the public-contract surface, a definition-of-done (bindings manifest skeleton + conform check + isolation + PR title), and the engagement's context: documents. One brief per engagement slice unless --slice narrows it
|
|
261
|
+
em engagement brief <file> <slug> --slice <key> # only this slice's brief (export key, display name, or unique prefix)
|
|
262
|
+
em engagement brief <file> <slug> --inline-context # embed each context document's full text instead of just its path
|
|
263
|
+
em engagement brief <file> <slug> --json # print a JSON document instead of text (same document as the MCP engagement_brief tool)
|
|
264
|
+
em engagement set <file> <slug> [key] # the only write path to the Ledger: set one slice's state (and optionally branch/base/pr); idempotent; merged is terminal; --state held records a human hold. Or, on its own (no slice key), --context-doc replaces the engagement-level context: list (GH #455)
|
|
265
|
+
em engagement set <file> <slug> [key] --state <state> # one of: planned | building | validating | review | awaiting-merge | merged | held | gap
|
|
266
|
+
em engagement set <file> <slug> [key] --branch <name> # the slice's branch
|
|
267
|
+
em engagement set <file> <slug> [key] --base <name> # the branch the slice's PR targets
|
|
268
|
+
em engagement set <file> <slug> [key] --pr <url> # the slice's PR URL
|
|
269
|
+
em engagement set <file> <slug> [key] --context-doc <path> # repo-relative path to a document implementers need beyond the slice doc (repeatable; replaces the whole engagement-level context: list; GH #455)
|
|
257
270
|
em engagement status <file> <slug> # the Ledger joined with each slice doc's current status, plus whether the engagement is closable
|
|
258
271
|
em engagement status <file> <slug> --json # print a JSON document instead of text (same document as the MCP engagement_status tool)
|
|
259
272
|
em engagement close <file> <slug> # set status: closed — refuses unless every slice is merged (recorded or inferred) or gap
|
|
@@ -340,6 +353,8 @@ slice "Name" [source "url"] { # one vertical time step (a column); source is
|
|
|
340
353
|
arrow From Element -> To Element # explicit cross-slice edge (overrides inferred flow)
|
|
341
354
|
|
|
342
355
|
type Name { field: Type, ... } # named structured type, reusable from any field (see Named types)
|
|
356
|
+
scalar Name: base # identity alias over a public scalar table entry (see Named types)
|
|
357
|
+
enum Name { member, ... } # closed named value set, reusable from any field (see Named types)
|
|
343
358
|
```
|
|
344
359
|
|
|
345
360
|
### Element kinds (8 keywords, nothing else)
|
|
@@ -608,6 +623,31 @@ how tree/recursive data (categories, org charts, comment threads, BOMs) gets exp
|
|
|
608
623
|
fields and ordinary element fields alike) — `{ name, ref, array }` when resolved, `null`
|
|
609
624
|
otherwise. `em diff` tracks types added/removed and field changes on surviving types.
|
|
610
625
|
|
|
626
|
+
**Identity scalars** (GH #477): `scalar Name: base` declares an identity alias over one public
|
|
627
|
+
scalar table entry (`base`, case-insensitive) — so a `public` field can carry a domain name on
|
|
628
|
+
the wire (`orderId: OrderId`) instead of degrading to the bare table type (`uuid`) to satisfy
|
|
629
|
+
[Strict public types](#strict-public-types). Reference it bare or `Name[]`, same as a declared
|
|
630
|
+
`type`; on a `public` element it satisfies the strict table directly, on an internal element it
|
|
631
|
+
resolves opportunistically. `em validate` errors on an unknown `base`, a redeclared name, or a
|
|
632
|
+
name colliding with a declared `type` (either direction). `em api generate` declares a used
|
|
633
|
+
scalar once, after the table scalars, extending the formatted table scalar
|
|
634
|
+
(`scalar OrderId extends Uuid;`) or the core type (`scalar Sku extends string;`) — a model with
|
|
635
|
+
no `scalar` emits unchanged. `em export` adds `model.scalars[]` and an additive `scalarRef` field
|
|
636
|
+
key, mutually exclusive with `typeRef`.
|
|
637
|
+
|
|
638
|
+
**Enums** (GH #478): `enum Name { member, … }` declares a closed, named value set — members are
|
|
639
|
+
bare identifiers or quoted strings, in declaration order. Reference it bare or `Name[]`, same
|
|
640
|
+
as a declared `type`/`scalar`; on a `public` element it satisfies the strict table directly, on
|
|
641
|
+
an internal element it resolves opportunistically. `em validate` errors on an empty enum, a
|
|
642
|
+
case-insensitive duplicate member, a redeclared name, or a name colliding with a declared
|
|
643
|
+
`type`/`scalar` (either direction). `em api generate` declares a used enum once, members in
|
|
644
|
+
declaration order (`enum OrderStatus { placed, shipped }`), after the scalar declarations and
|
|
645
|
+
before type blocks — a member that isn't a bare TypeSpec identifier emits as a quoted string
|
|
646
|
+
literal; a model with no `enum` emits unchanged. `em export` adds `model.enums[]` and an
|
|
647
|
+
additive `enumRef` field key, mutually exclusive with both `typeRef` and `scalarRef`. `em api
|
|
648
|
+
check --base` judges a surviving enum's member changes by role: removed/renamed is breaking on
|
|
649
|
+
an output, added is breaking on an input, additive on a pure output.
|
|
650
|
+
|
|
611
651
|
### Swimlane band order (top → bottom)
|
|
612
652
|
Header row → **Automation** (only if used) → **persona** rows (in declared order) → **API**
|
|
613
653
|
(commands + views share this lane) → **context** rows (in declared order).
|
|
@@ -938,11 +978,17 @@ not the prose above has caught up yet. `--slice-ready <key>`-only codes are excl
|
|
|
938
978
|
| `doc-names-unfed-event` | warning | Doc names an event its view does not read | Add the event to the view's `from` in the model (if the edge is real), or fix the doc's Source column / scenario. |
|
|
939
979
|
| `doc-omits-fed-event` | warning | Doc omits an event its view reads | Mention the event in the view's Source column (run `em slice sync`), or drop it from the view's `from`. |
|
|
940
980
|
| `duplicate-element-ref` | warning | Duplicate element ref | Rename the element so its export ref is unique. |
|
|
981
|
+
| `duplicate-enum-ref` | warning | Duplicate enum ref | Rename the enum so its export ref is unique. |
|
|
941
982
|
| `duplicate-model-key` | warning | Duplicate model key | Give each model a unique `model "Name"` so its key (and every cross-model ref) is stable. |
|
|
942
983
|
| `duplicate-name` | warning | Duplicate name | Rename one of the duplicates. |
|
|
984
|
+
| `duplicate-scalar-ref` | warning | Duplicate scalar ref | Rename the scalar so its export ref is unique. |
|
|
943
985
|
| `duplicate-slice-name` | warning | Duplicate slice name | Rename the slice so its export key is unique. |
|
|
944
986
|
| `duplicate-type-name` | warning | Duplicate type name | Rename one of the duplicate `type` declarations. |
|
|
945
987
|
| `duplicate-type-ref` | warning | Duplicate type ref | Rename the type so its export ref is unique. |
|
|
988
|
+
| `enum-duplicate-member` | error | Enum duplicate member | Remove or rename the duplicate member — members are compared case-insensitively. |
|
|
989
|
+
| `enum-duplicate-name` | error | Enum duplicate name | Declare the enum once; rename or remove the duplicate `enum` block. |
|
|
990
|
+
| `enum-empty` | error | Empty enum | Give the enum at least one member, or remove the declaration. |
|
|
991
|
+
| `enum-name-collision` | error | Enum/type/scalar name collision | Rename the `enum` or the colliding `type`/`scalar` so the names no longer collide — a name can't mean both at once. |
|
|
946
992
|
| `fields-completeness/event-field-no-source` | warning | Event field not from a command | Add the field to a command that records the event (in any slice), mark it assigned if the handler sets it, or remove it from the event. |
|
|
947
993
|
| `fields-completeness/view-field-no-source` | warning | View field with no source | Add the field to a source event, or remove it from the view. |
|
|
948
994
|
| `frontmatter-coherence-implemented-without-link` | warning | Implemented without a link | Add `implementedIn` once the slice ships. |
|
|
@@ -975,6 +1021,9 @@ not the prose above has caught up yet. `--slice-ready <key>`-only codes are excl
|
|
|
975
1021
|
| `reaction-from-future-view` | error | Backward timeline (reaction reads a future view) | Declare the view in or before the reaction's slice. |
|
|
976
1022
|
| `reaction-from-unresolved` | error | Unknown read-model source | Project the event into a view first, or fix the `from` reference. |
|
|
977
1023
|
| `retry-slice-of` | warning | Slice only retries another command | Re-fire the same reaction via `view … again from` the retry-scheduled event; a different actor's override is its own slice with its own command (docs/patterns.md#automation). |
|
|
1024
|
+
| `scalar-duplicate-name` | error | Scalar duplicate name | Declare the scalar once; rename or remove the duplicate `scalar` line. |
|
|
1025
|
+
| `scalar-type-name-collision` | error | Scalar/type name collision | Rename the `scalar` or the `type` so the two names no longer collide — a name can't mean both at once. |
|
|
1026
|
+
| `scalar-unknown-base` | error | Scalar unknown base | Point `base` at one of the public scalar table's names (string, text, int, long, decimal, boolean, uuid, date, datetime, duration, bytes), case-insensitive. |
|
|
978
1027
|
| `scenario-then-not-outcome` | warning | Scenario's Then names no event or rejection | Make the Then bullets name the event(s) the command records, or the rejection/error it returns. |
|
|
979
1028
|
| `scenario-when-not-command` | warning | Scenario's When does not name the slice's command | Make the When bullet name the slice's command (for a reaction slice: the command, the reaction element, or a view of the slice) — each scenario exercises exactly one command. For a Translation, naming the translation element satisfies it. |
|
|
980
1029
|
| `seam-consumer-not-reaction` | error | Seam consumer is not a reaction | Point `to` at a translation/automation element (or a slice containing exactly one). |
|
|
@@ -174,25 +174,36 @@ including sessions started from bare `/em:event-modeling`.
|
|
|
174
174
|
<model-name>.svg # render target for em watch
|
|
175
175
|
README.md # overview + slice index (from templates/model-readme.md)
|
|
176
176
|
.event-modeling.md # resumable state (from templates/state.md)
|
|
177
|
+
constitution.md # implementation constitution (skipped, with a note, in a spec-kit project)
|
|
177
178
|
slices/<slice-name>.md # one rich slice doc per slice (from templates/slice.md)
|
|
178
|
-
|
|
179
|
-
#
|
|
179
|
+
contracts/<model-name>.tsp
|
|
180
|
+
# `em api generate`'s generated TypeSpec contract — never hand-edit
|
|
181
|
+
model-versions/v<N>.json # `em model version bump`'s design-version snapshots
|
|
182
|
+
conformance/
|
|
183
|
+
<date>-report.md # conform-phase reports (from templates/conformance-report.md)
|
|
184
|
+
bindings/<slice-key>.yaml
|
|
185
|
+
# per-slice bindings manifests (`em conform bindings check`, GH #449)
|
|
180
186
|
<model-name>-asis.em # conform-phase scratch model, regenerated per run — git-ignore this
|
|
181
187
|
```
|
|
182
188
|
|
|
183
|
-
When creating a new model, run `em scaffold <model-name>` — it creates `<model-name
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
mechanical fields (model path, `Current phase: discover`, `Current step: 1`, dates) for you,
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
model
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
`
|
|
189
|
+
When creating a new model, run `em scaffold <model-name>` — it creates `<model-name>/`, writes
|
|
190
|
+
the four starter files in one step (`<model-name>.em`, `README.md`, `.event-modeling.md`,
|
|
191
|
+
`constitution.md`), filling the Model Name/model-name placeholders and the state file's
|
|
192
|
+
mechanical fields (model path, `Current phase: discover`, `Current step: 1`, dates) for you, and
|
|
193
|
+
also creates the standard `slices/`, `contracts/`, `model-versions/` and `conformance/bindings/`
|
|
194
|
+
directories — each seeded with a one-line README (never a `.gitkeep`) naming what lands there and
|
|
195
|
+
which command writes it. There's nothing left to hand-copy or fill in. Refuses if
|
|
196
|
+
`<model-name>/` already exists; pass `--force` to overwrite. You'll usually replace the
|
|
197
|
+
scaffolded `.em`'s content as the discovery conversation builds up the real model — it starts out
|
|
198
|
+
as the same starter model `em init` writes, titled from `<model-name>`.
|
|
199
|
+
Those directories exist from the start, but their real content doesn't — `slices/*.md` slice
|
|
200
|
+
docs come from the `slice` phase (`em:design`), `conformance/<date>-report.md`
|
|
201
|
+
reports from the `conform` phase (`em:conform`), and `contracts/*.tsp` /
|
|
202
|
+
`model-versions/*.json` / `conformance/bindings/*.yaml` only once `em api generate` / `em model
|
|
203
|
+
version bump` / an implementing agent's bindings manifest actually run. `<model-name>-asis.em` is
|
|
204
|
+
separate conform-phase scratch, not part of the scaffold at all; `em conform-scope --seed-asis`
|
|
205
|
+
creates it and gitignores `*-asis.em` for you (see `em:conform`'s
|
|
206
|
+
`reference/conform.md`) — it's scratch, never committed.
|
|
196
207
|
|
|
197
208
|
### Multi-model projects
|
|
198
209
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: conform
|
|
3
|
-
em-version: 1.
|
|
3
|
+
em-version: 1.22.1
|
|
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
|
|
@@ -29,9 +29,12 @@ duplicating them.
|
|
|
29
29
|
In short: run `em conform-scope <model-name>.em --repo <target-repo-path> --seed-asis` to
|
|
30
30
|
compute the in-scope slice set (diff-scoped by default, via `Last conformance:` + the target
|
|
31
31
|
repo's changed paths mapped through each slice doc's `implementedIn:`) and seed the scratch
|
|
32
|
-
model in one step; for each in-scope slice,
|
|
33
|
-
|
|
34
|
-
|
|
32
|
+
model in one step; for each in-scope slice, check for a `conformance/bindings/<key>.yaml` first
|
|
33
|
+
(GH #450) — verify it with `em conform bindings check` and scope the walk through it when it
|
|
34
|
+
exists, backfill it from this walk's own evidence when it doesn't — then gather code evidence
|
|
35
|
+
*before* comparing to the model or doc; write the as-is picture into that scratch model
|
|
36
|
+
(`<model-name>-asis.em`, reusing the canonical model's names wherever the code matches them);
|
|
37
|
+
run `em diff <model-name>.em
|
|
35
38
|
<model-name>-asis.em --json` and let `em` decide the structural deltas; classify every finding
|
|
36
39
|
(real drift / model gap / internal inconsistency / uncertainty) with cited evidence; write
|
|
37
40
|
`conformance/<date>-report.md` **and its structured twin `conformance/<date>-findings.json`**
|
|
@@ -95,7 +95,7 @@ e.g. a PR link, deterministically matches nothing). It prints one JSON document
|
|
|
95
95
|
```json
|
|
96
96
|
{ "lastConformance": { "date": "...", "revision": "..." } | null,
|
|
97
97
|
"changedPaths": ["..."],
|
|
98
|
-
"candidateSlices": [ { "key": "checkout", "matchedBy": "implementedIn", "paths": ["..."] } ],
|
|
98
|
+
"candidateSlices": [ { "key": "checkout", "matchedBy": "implementedIn", "paths": ["..."], "file": null } ],
|
|
99
99
|
"unmappedPaths": ["..."] }
|
|
100
100
|
```
|
|
101
101
|
|
|
@@ -105,6 +105,20 @@ your in-scope set for step 2. If `lastConformance` is `null` (first run) or you
|
|
|
105
105
|
`changedPaths`/`unmappedPaths` both empty) — the tool makes this call for you, no need to
|
|
106
106
|
special-case it yourself.
|
|
107
107
|
|
|
108
|
+
**Storyline models (GH #451).** A model may split its storylines into their own files — a root
|
|
109
|
+
declares `storyline "Name" [after "Other"]` with no body, and the storyline's slices live in
|
|
110
|
+
`<slug>/<slug>.em` (`docs/dsl.md`'s "Storyline models" section). `em conform-scope` already
|
|
111
|
+
compiles the root plus every storyline file it declares as ONE model
|
|
112
|
+
(the same way every other `em` command does — a storyline file never compiles on its own), so
|
|
113
|
+
scoping via `implementedIn` needs no special handling. What DOES change: each `candidateSlice`'s
|
|
114
|
+
`file` names the storyline file that slice's body lives in — posix, relative to the model's
|
|
115
|
+
directory — or `null` for a slice in the root file. This is which physical file you anchor on in
|
|
116
|
+
step 2 and which scratch file you write the as-is picture into in step 3 (`--seed-asis` seeds one
|
|
117
|
+
scratch file per physical file, not just the root — see there). Don't confuse a slice's `file`
|
|
118
|
+
with its doc's path: slice docs for a storyline model live under that storyline's own
|
|
119
|
+
`slices/` folder, but `em` finds them by key across every folder regardless, so doc resolution
|
|
120
|
+
itself needs no extra care.
|
|
121
|
+
|
|
108
122
|
**`unmappedPaths` is your judgment queue, not a dead end.** A changed path the tool couldn't
|
|
109
123
|
place mechanically (`implementedIn` stale, a URL-only link, code that moved) still might belong
|
|
110
124
|
to an in-scope slice — a quick Grep for the path's obvious symbols against the slice docs/model
|
|
@@ -120,6 +134,31 @@ keeps a recurring conform run cheap; don't invent a smarter trigger.
|
|
|
120
134
|
|
|
121
135
|
For each slice in scope:
|
|
122
136
|
|
|
137
|
+
**Check for an existing bindings file first (GH #450).** `conformance/bindings/<slice-key>.yaml`,
|
|
138
|
+
beside the model, records this slice's own prior evidence — file and identifier per model
|
|
139
|
+
element, and per `INV-*` invariant's enforcement/test sites (`docs/bindings.md`'s schema). Two
|
|
140
|
+
cases, mutually exclusive:
|
|
141
|
+
|
|
142
|
+
- **It exists:** verify it before walking anything by hand —
|
|
143
|
+
`em conform bindings check <model-name>.em --repo <target-repo-path> --slice <slice-key>`.
|
|
144
|
+
A binding that still checks out is evidence you don't need to re-derive; **scope the rest of
|
|
145
|
+
this slice's walk through it** — re-read only what the check reports failing (`file not
|
|
146
|
+
found`/`identifier not found`, an unknown `ref`) plus whatever `em conform-scope`'s
|
|
147
|
+
`changedPaths` (step 1) says moved since `implementedIn`. This is what turns a recurring
|
|
148
|
+
conform walk incremental instead of a full re-derivation every time — don't throw that away by
|
|
149
|
+
re-walking evidence the file already gives you clean.
|
|
150
|
+
- **It doesn't exist:** the common shape for a slice an external or ticket-driven implementer
|
|
151
|
+
shipped without ever running `em` (`docs/bindings.md` is written so that implementer can still
|
|
152
|
+
author one by hand from its own ticket spec; this backfill is the safety net for the ones that
|
|
153
|
+
didn't, or for any slice that shipped before GH #450). Walk the slice normally (the numbered
|
|
154
|
+
steps below) and, once you've gathered that evidence, **write
|
|
155
|
+
`conformance/bindings/<slice-key>.yaml` from it** — the same file an implementer would have
|
|
156
|
+
written, now backfilled from your conform evidence instead of from the implementer's own
|
|
157
|
+
knowledge of the code. This is advisory tooling catching up a gap, not a model or doc edit: it
|
|
158
|
+
needs no ratification and doesn't change this phase's advisory-only stance (see "Stance"
|
|
159
|
+
above) — you're recording where code already lives, not deciding what it should do. From the
|
|
160
|
+
next conform run on, this slice follows the "it exists" branch above.
|
|
161
|
+
|
|
123
162
|
1. Read the slice's **doc** (`slices/<slice-name>.md`) alongside its elements in the `.em`.
|
|
124
163
|
This tells you the vocabulary to anchor on and the claims to check — not the answer.
|
|
125
164
|
2. Walk the code and **record evidence before comparing**:
|
|
@@ -140,6 +179,20 @@ For each slice in scope:
|
|
|
140
179
|
as missing. This is the *general*, idiom-level version of the next bullet — prefer citing
|
|
141
180
|
an actual `divergence` annotation when the element carries one; fall back to this idiom
|
|
142
181
|
note only when it doesn't.
|
|
182
|
+
- **`updates from` sources (GH #451) — housekeeping evidence, not trigger evidence.** A view
|
|
183
|
+
declared `view X from "A" updates from "B", "C"` (`docs/dsl.md`'s `updates from` section)
|
|
184
|
+
splits its sources into what put a row on the list (`from`) and what keeps an existing row
|
|
185
|
+
current or removes it (`updates from`). The two need different evidence: a `from` source is
|
|
186
|
+
what an automation/processor reacting to this view should fire on, or what a reader expects
|
|
187
|
+
to see a NEW row appear for — missing evidence there is the usual structural/spec drift. A
|
|
188
|
+
`updates from` source is expected to have an update/delete handler for the SAME row, never a
|
|
189
|
+
"new item created" or "automation triggered" site — don't report the absence of trigger-type
|
|
190
|
+
evidence for an `updates from` source as drift; that is literally what the clause says it
|
|
191
|
+
isn't. `em export`'s `from` list is the union of both (so `em diff`'s structural comparison
|
|
192
|
+
sees every source regardless of which list it's in — folding `from`/`updates from` into each
|
|
193
|
+
other is NOT itself a structural change `em diff` would catch), but the *diagram* draws no
|
|
194
|
+
arrow for an `updates from` source and the *evidence walk* above should read the same way:
|
|
195
|
+
check it as "does this row get kept current/removed", not "does this cause a new row".
|
|
143
196
|
- **Accepted divergences:** before treating any finding as drift, check whether the
|
|
144
197
|
relevant canonical element already carries a `divergence "..."` annotation (visible in
|
|
145
198
|
the `.em`, in `em export --json`'s `divergence` field, or — for a structural finding — as
|
|
@@ -162,8 +215,15 @@ For each slice in scope:
|
|
|
162
215
|
3. Write what you found into the scratch model (`<model-name>-asis.em`, see below) — start it
|
|
163
216
|
with `em conform-scope <model-name>.em --repo <target-repo-path> --seed-asis`, which
|
|
164
217
|
byte-copies the canonical `.em` to `<model-name>-asis.em` and ensures `*-asis.em` is
|
|
165
|
-
gitignored (idempotent — safe to pass on every run). **
|
|
166
|
-
|
|
218
|
+
gitignored (idempotent — safe to pass on every run). **Storyline models (GH #451):** when the
|
|
219
|
+
slice you're rewriting has a non-null `file` in step 1's `candidateSlices` (its body lives in
|
|
220
|
+
a storyline file, not the root), `--seed-asis` also wrote a scratch copy of THAT file — the
|
|
221
|
+
`seeded.storylineFiles` entry naming it — and the copied root's declaration already points at
|
|
222
|
+
it. Edit that storyline file's own `-asis` sibling, never `<model-name>-asis.em` (it has no
|
|
223
|
+
body for that slice to rewrite) and never the canonical storyline file (that corrupts the
|
|
224
|
+
real model, not a scratch copy). **Then replace only the in-scope slices** with the as-is
|
|
225
|
+
picture, leaving out-of-scope slices byte-identical, in whichever scratch file(s) hold them.
|
|
226
|
+
This is not an optimization:
|
|
167
227
|
`em diff` matches slices by name, so a scratch model containing *only* the in-scope slices
|
|
168
228
|
reports every other canonical slice as `slice-removed` plus one `element-removed` per
|
|
169
229
|
element — a phantom-removal flood on every diff-scoped run, which is the exact false drift
|
|
@@ -406,7 +466,12 @@ each call adds its own stamp rather than overwriting the last.
|
|
|
406
466
|
directory as `.event-modeling.md`). Always seeded as a copy of the canonical model with only
|
|
407
467
|
the in-scope slices rewritten (step 2) — `em conform-scope --seed-asis` does the seeding and
|
|
408
468
|
the gitignoring (`*-asis.em`, idempotent) mechanically, never built up from nothing and never
|
|
409
|
-
hand-maintained. It's regenerated every conform run.
|
|
469
|
+
hand-maintained. It's regenerated every conform run. **Storyline models (GH #451):** a split
|
|
470
|
+
model gets ONE scratch file per physical file — `<model-name>-asis.em` for the root, plus a
|
|
471
|
+
`-asis` sibling for every storyline file the root declares (`seeded.storylineFiles` names
|
|
472
|
+
them) — never just the root; the copied root's `in` clause already points at each sibling, so
|
|
473
|
+
`em diff <model-name>.em <model-name>-asis.em` reads the whole scratch set as one model, same
|
|
474
|
+
as the canonical root reads the whole canonical set.
|
|
410
475
|
- **Report location:** `conformance/<YYYY-MM-DD>-report.md` in the model directory. One file
|
|
411
476
|
per run; don't overwrite a prior date's report. Its findings go in the same-stem `-findings.json`; gates read every same-revision findings file, so per-slice runs may write `<date>-<name>-findings.json`, and a ruling is written to the file the report names. The same-revision lookup uses the report's OWN revision (its `Target repo: … @ <rev>` header line; the caller's `<rev>` / `--as-of` only when the report has none).
|
|
412
477
|
- **Red note wording:** `issue "conformance: <what's wrong, plainly>"` — plain enough that
|
|
@@ -448,6 +513,13 @@ whatever cadence the user wants (see `docs/ci.md` for a scheduled-run recipe).
|
|
|
448
513
|
- **A scratch model holding only the in-scope slices** — `em diff` reads every omitted
|
|
449
514
|
canonical slice as removed, so a diff-scoped run drowns in phantom removals. Seed from the
|
|
450
515
|
canonical model and rewrite only the slices you walked.
|
|
516
|
+
- **Editing a storyline file's canonical copy as if it were the scratch model (GH #451)** — for
|
|
517
|
+
a slice whose `file` isn't the root, the scratch copy is that storyline file's OWN `-asis`
|
|
518
|
+
sibling (`seeded.storylineFiles`), never `<model-name>-asis.em` (no body there to rewrite) and
|
|
519
|
+
never the canonical storyline file itself (that's the model, not a scratch copy of it).
|
|
520
|
+
- **Treating an `updates from` source like a `from` source during the evidence walk** — a
|
|
521
|
+
missing "new row appears" or "automation fires" site is not drift for a source the view only
|
|
522
|
+
lists under `updates from`; check it for row-update/removal evidence instead (step 2).
|
|
451
523
|
- **Reporting uncertainty as drift** — "I couldn't find the enforcement site" is an
|
|
452
524
|
uncertainty, never a finding against the model.
|
|
453
525
|
- **Editing the model or a slice doc without ratification** — conform proposes; the user
|
|
@@ -458,6 +530,12 @@ whatever cadence the user wants (see `docs/ci.md` for a scheduled-run recipe).
|
|
|
458
530
|
- **Re-deriving what `em diff` already decided** — once you have the JSON, use it; don't
|
|
459
531
|
eyeball the two `.em` files yourself looking for structural differences the tool already
|
|
460
532
|
computed for you.
|
|
533
|
+
- **Re-deriving evidence a verified bindings file already gives you (GH #450)** — when
|
|
534
|
+
`em conform bindings check` passes for a slice's `conformance/bindings/<key>.yaml`, walking
|
|
535
|
+
every element and invariant by hand anyway defeats the point of the file; scope through it
|
|
536
|
+
(step 2) instead.
|
|
537
|
+
- **Skipping the backfill because a slice has no bindings file** — the absence is the signal to
|
|
538
|
+
write one from this walk's own evidence, not a reason to leave the gap for next time.
|
|
461
539
|
- **Re-litigating an accepted divergence** — a non-null `acceptedDivergence` on a diff entry
|
|
462
540
|
(or a `divergence` annotation on the element behind a spec/internal finding) means this exact
|
|
463
541
|
question was already reasoned through and ratified. Classify it as Accepted divergence and
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: design
|
|
3
|
-
em-version: 1.
|
|
3
|
+
em-version: 1.22.1
|
|
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
|
|
@@ -235,7 +235,10 @@ For each slice:
|
|
|
235
235
|
1. Hold a Socratic deep-dive to fill every section of `${CLAUDE_PLUGIN_ROOT}/shared/templates/slice.md`:
|
|
236
236
|
intent, trigger/actor,
|
|
237
237
|
command fields (types & rules), event(s) + payload (mark immutable facts) — field names and
|
|
238
|
-
types go into the `.em`, the doc's tables are generated from it
|
|
238
|
+
types go into the `.em`, the doc's tables are generated from it (on a `public` element, a
|
|
239
|
+
field's type must be the fixed scalar table, a declared `type`, a declared identity `scalar`,
|
|
240
|
+
or a declared `enum` — see `em-dsl.md`'s Strict public types/Identity scalars/Enums
|
|
241
|
+
sections) — invariants
|
|
239
242
|
(give each a stable ID — see **Invariants live in the model** below), Given/When/Then scenarios (happy path + rule boundaries + edge cases),
|
|
240
243
|
alternate/error flows (retries, idempotency, compensations), non-functional requirements
|
|
241
244
|
(security/authz, PII/compliance, performance/SLA), read models affected, open questions. Park
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: discover
|
|
3
|
-
em-version: 1.
|
|
3
|
+
em-version: 1.22.1
|
|
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
|
|
@@ -48,6 +48,7 @@ Validator (em-validator, <model>):
|
|
|
48
48
|
| diff footprint inside the slice package | PASS |
|
|
49
49
|
| no diff under `slices/**`, `*.em`, `constitution.md`, `engagements/` | PASS |
|
|
50
50
|
| PR body cites the slice doc | PASS |
|
|
51
|
+
| `conformance/bindings/<key>.yaml` exists + `em conform bindings check --slice <key> --exit-code` | PASS |
|
|
51
52
|
|
|
52
53
|
Reviewer (em-reviewer, <model>): <findings list verbatim, or NO FINDINGS>
|
|
53
54
|
Critic (em-critic, <model>): <findings list verbatim, or NO FINDINGS>
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: event-modeling
|
|
3
|
-
em-version: 1.
|
|
3
|
+
em-version: 1.22.1
|
|
4
4
|
description: >-
|
|
5
5
|
Use when the user runs `/em: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: implement
|
|
3
|
-
em-version: 1.
|
|
3
|
+
em-version: 1.22.1
|
|
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
|
|
@@ -29,8 +29,10 @@ ships in the originating doc's PR); ship the endpoint a
|
|
|
29
29
|
model doesn't show; surface every gap to the user instead of deciding it silently; cover every
|
|
30
30
|
`INV-<MNEMONIC>-n` and every scenario with tests, proving the citations before merge with
|
|
31
31
|
`em coverage <model>.em --slice <key> --tests <dir> --strict` (the default form skips a
|
|
32
|
-
`ready-to-implement` slice);
|
|
33
|
-
|
|
32
|
+
`ready-to-implement` slice); write or update the slice's `conformance/bindings/<key>.yaml`
|
|
33
|
+
(GH #450, `docs/bindings.md`'s schema) and verify it with `em conform bindings check <model>.em
|
|
34
|
+
--repo <path> --slice <key> --exit-code` before calling the slice done; in spec-kit projects,
|
|
35
|
+
allocate via em-sdd-bridge (redirect mode) and never run `/speckit.specify`.
|
|
34
36
|
|
|
35
37
|
**Gate: no constitution, no first slice.** The constitution is the project's ratified house rules
|
|
36
38
|
— stack and architectural shape (including which implementation skill each slice pattern routes
|
|
@@ -301,6 +301,20 @@ or seem small — see §10.
|
|
|
301
301
|
you didn't touch the `.em`, so this only fails if something else broke).
|
|
302
302
|
- Nothing between the slice doc and the code was committed as a source of truth (see §9 —
|
|
303
303
|
work containers are ephemeral; generated or symlinked specs are renderings).
|
|
304
|
+
- **The slice's bindings file is written or updated** (GH #450): `conformance/bindings/<slice-
|
|
305
|
+
key>.yaml`, beside the model, per [`docs/bindings.md`](https://github.com/milehimikey/em/blob/main/docs/bindings.md)'s
|
|
306
|
+
schema. You just wrote the code — you know which file and identifier anchors each element
|
|
307
|
+
this slice's doc declares (`ref: <kind>.<slug>`, bare, no `<slice-key>/` prefix) and each
|
|
308
|
+
`INV-<MNEMONIC>-n` invariant's enforcement and test sites. This is a file contract, not an
|
|
309
|
+
`em`-run write: author or hand-edit the YAML directly, the same way you'd write any other file
|
|
310
|
+
in your PR. Verify it before calling the slice done:
|
|
311
|
+
```bash
|
|
312
|
+
em conform bindings check <model>.em --repo <path> --slice <slice-key> --exit-code
|
|
313
|
+
```
|
|
314
|
+
Exit 0 is required — fix the file (a wrong `ref`, a stale `file`/`identifier` pair) rather than
|
|
315
|
+
shipping a binding that doesn't check out. A continuation key (§5, an `again` view instance)
|
|
316
|
+
has no bindings file of its own; its elements and invariants are bound inside the originating
|
|
317
|
+
slice's file alongside the rest of that doc's bindings.
|
|
304
318
|
|
|
305
319
|
## 6. At merge: the lifecycle flip
|
|
306
320
|
|