@milehimikey/em 1.13.1 → 1.14.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-critic.md +26 -0
- package/.claude/agents/em-implementer.md +34 -0
- package/.claude/agents/em-reviewer.md +31 -0
- package/.claude/agents/em-validator.md +27 -0
- package/.claude/skills/event-modeling/SKILL.md +50 -11
- package/.claude/skills/event-modeling-conform/SKILL.md +1 -1
- package/.claude/skills/event-modeling-design/SKILL.md +127 -18
- package/.claude/skills/event-modeling-discover/SKILL.md +1 -1
- package/.claude/skills/event-modeling-engagement/SKILL.md +192 -0
- package/.claude/skills/event-modeling-engagement/reference/engagement.md +143 -0
- package/.claude/skills/event-modeling-implement/SKILL.md +1 -1
- package/.claude/skills/event-modeling-implement/reference/implement.md +38 -11
- package/.claude/skills/event-modeling-review/SKILL.md +2 -2
- package/.claude/skills/event-modeling-shared/reference/em-dsl.md +279 -189
- package/.claude/skills/event-modeling-shared/reference/operating-principles.md +61 -0
- package/.claude/skills/event-modeling-shared/reference/slice-doc-schema.md +116 -5
- package/.claude/skills/event-modeling-shared/templates/constitution.md +18 -1
- package/.claude/skills/event-modeling-shared/templates/slice.md +64 -39
- package/.claude/skills/event-modeling-shared/templates/state.md +5 -1
- package/.claude-plugin/marketplace.json +14 -0
- package/README.md +25 -11
- package/dist/catalog/apiFirst.d.ts +28 -0
- package/dist/catalog/apiFirst.js +49 -0
- package/dist/catalog/apiFirst.js.map +1 -0
- package/dist/catalog/docJoin.d.ts +13 -0
- package/dist/catalog/docJoin.js +11 -0
- package/dist/catalog/docJoin.js.map +1 -1
- package/dist/catalog/docModelConsistencyValidate.js +44 -4
- package/dist/catalog/docModelConsistencyValidate.js.map +1 -1
- package/dist/catalog/invariantsValidate.d.ts +4 -0
- package/dist/catalog/invariantsValidate.js +102 -0
- package/dist/catalog/invariantsValidate.js.map +1 -0
- package/dist/catalog/scenarios.d.ts +15 -0
- package/dist/catalog/scenarios.js +97 -0
- package/dist/catalog/scenarios.js.map +1 -0
- package/dist/catalog/sliceDoc.d.ts +8 -0
- package/dist/catalog/sliceDoc.js +9 -0
- package/dist/catalog/sliceDoc.js.map +1 -1
- package/dist/catalog/sliceReadyValidate.d.ts +31 -4
- package/dist/catalog/sliceReadyValidate.js +66 -8
- package/dist/catalog/sliceReadyValidate.js.map +1 -1
- package/dist/catalog/sliceSections.d.ts +55 -0
- package/dist/catalog/sliceSections.js +317 -0
- package/dist/catalog/sliceSections.js.map +1 -0
- package/dist/cli/agentsMd.js +7 -1
- package/dist/cli/agentsMd.js.map +1 -1
- package/dist/cli/api.d.ts +90 -0
- package/dist/cli/api.js +318 -0
- package/dist/cli/api.js.map +1 -0
- package/dist/cli/ciInit.d.ts +82 -21
- package/dist/cli/ciInit.js +468 -64
- package/dist/cli/ciInit.js.map +1 -1
- package/dist/cli/conformScope.d.ts +28 -0
- package/dist/cli/conformScope.js +67 -0
- package/dist/cli/conformScope.js.map +1 -1
- package/dist/cli/coverage.d.ts +9 -0
- package/dist/cli/coverage.js +60 -8
- package/dist/cli/coverage.js.map +1 -1
- package/dist/cli/defer.d.ts +55 -0
- package/dist/cli/defer.js +244 -0
- package/dist/cli/defer.js.map +1 -0
- package/dist/cli/engagement.d.ts +210 -0
- package/dist/cli/engagement.js +444 -0
- package/dist/cli/engagement.js.map +1 -0
- package/dist/cli/engagementFile.d.ts +86 -0
- package/dist/cli/engagementFile.js +255 -0
- package/dist/cli/engagementFile.js.map +1 -0
- package/dist/cli/metrics.d.ts +10 -0
- package/dist/cli/metrics.js +49 -1
- package/dist/cli/metrics.js.map +1 -1
- package/dist/cli/pluginPin.d.ts +55 -0
- package/dist/cli/pluginPin.js +188 -0
- package/dist/cli/pluginPin.js.map +1 -0
- package/dist/cli/ratify.d.ts +27 -2
- package/dist/cli/ratify.js +112 -13
- package/dist/cli/ratify.js.map +1 -1
- package/dist/cli/reratify.d.ts +4 -2
- package/dist/cli/reratify.js +41 -16
- package/dist/cli/reratify.js.map +1 -1
- package/dist/cli/scopeInputs.d.ts +48 -0
- package/dist/cli/scopeInputs.js +176 -0
- package/dist/cli/scopeInputs.js.map +1 -0
- package/dist/cli/skillCheck.d.ts +15 -1
- package/dist/cli/skillCheck.js +22 -1
- package/dist/cli/skillCheck.js.map +1 -1
- package/dist/cli/skillDirs.d.ts +5 -0
- package/dist/cli/skillDirs.js +13 -1
- package/dist/cli/skillDirs.js.map +1 -1
- package/dist/cli/skillSync.d.ts +4 -0
- package/dist/cli/skillSync.js +23 -0
- package/dist/cli/skillSync.js.map +1 -1
- package/dist/cli/sliceNew.d.ts +11 -7
- package/dist/cli/sliceNew.js +91 -18
- package/dist/cli/sliceNew.js.map +1 -1
- package/dist/cli/sliceSync.d.ts +50 -0
- package/dist/cli/sliceSync.js +170 -0
- package/dist/cli/sliceSync.js.map +1 -0
- package/dist/cli/status.d.ts +24 -1
- package/dist/cli/status.js +20 -1
- package/dist/cli/status.js.map +1 -1
- package/dist/cli/systemInputs.d.ts +53 -10
- package/dist/cli/systemInputs.js +186 -11
- package/dist/cli/systemInputs.js.map +1 -1
- package/dist/cli/upgrade.d.ts +16 -3
- package/dist/cli/upgrade.js +366 -30
- package/dist/cli/upgrade.js.map +1 -1
- package/dist/cli/usageLog.d.ts +4 -2
- package/dist/cli/usageLog.js +4 -2
- package/dist/cli/usageLog.js.map +1 -1
- package/dist/cli.js +599 -94
- package/dist/cli.js.map +1 -1
- package/dist/emit/api.d.ts +29 -0
- package/dist/emit/api.js +220 -0
- package/dist/emit/api.js.map +1 -0
- package/dist/emit/apiCheckJson.d.ts +3 -0
- package/dist/emit/apiCheckJson.js +21 -0
- package/dist/emit/apiCheckJson.js.map +1 -0
- package/dist/emit/codeownersJson.d.ts +3 -0
- package/dist/emit/codeownersJson.js +19 -0
- package/dist/emit/codeownersJson.js.map +1 -0
- package/dist/emit/diffJson.d.ts +1 -1
- package/dist/emit/diffJson.js +8 -1
- package/dist/emit/diffJson.js.map +1 -1
- package/dist/emit/engagementJson.d.ts +8 -0
- package/dist/emit/engagementJson.js +46 -0
- package/dist/emit/engagementJson.js.map +1 -0
- package/dist/emit/glossaryJson.d.ts +1 -1
- package/dist/emit/glossaryJson.js +4 -1
- package/dist/emit/glossaryJson.js.map +1 -1
- package/dist/emit/json.d.ts +17 -1
- package/dist/emit/json.js +30 -1
- package/dist/emit/json.js.map +1 -1
- package/dist/emit/metricsJson.d.ts +1 -1
- package/dist/emit/metricsJson.js +4 -1
- package/dist/emit/metricsJson.js.map +1 -1
- package/dist/emit/queryJson.d.ts +1 -1
- package/dist/emit/queryJson.js +7 -1
- package/dist/emit/queryJson.js.map +1 -1
- package/dist/emit/scopeJson.d.ts +12 -0
- package/dist/emit/scopeJson.js +26 -0
- package/dist/emit/scopeJson.js.map +1 -0
- package/dist/emit/skillCheckJson.d.ts +3 -2
- package/dist/emit/skillCheckJson.js +10 -2
- package/dist/emit/skillCheckJson.js.map +1 -1
- package/dist/emit/statusJson.d.ts +1 -1
- package/dist/emit/statusJson.js +15 -1
- package/dist/emit/statusJson.js.map +1 -1
- package/dist/emit/systemJson.d.ts +15 -8
- package/dist/emit/systemJson.js +28 -13
- package/dist/emit/systemJson.js.map +1 -1
- package/dist/emit/validateJson.d.ts +3 -3
- package/dist/emit/validateJson.js +10 -4
- package/dist/emit/validateJson.js.map +1 -1
- package/dist/mcp/server.d.ts +1 -1
- package/dist/mcp/server.js +163 -31
- package/dist/mcp/server.js.map +1 -1
- package/dist/model/diff.d.ts +4 -1
- package/dist/model/diff.js +44 -8
- package/dist/model/diff.js.map +1 -1
- package/dist/model/glossary.d.ts +2 -0
- package/dist/model/glossary.js +1 -0
- package/dist/model/glossary.js.map +1 -1
- package/dist/model/model.d.ts +55 -2
- package/dist/model/model.js +57 -0
- package/dist/model/model.js.map +1 -1
- package/dist/model/qualifiedRef.d.ts +18 -0
- package/dist/model/qualifiedRef.js +26 -0
- package/dist/model/qualifiedRef.js.map +1 -1
- package/dist/model/queryIndex.d.ts +9 -1
- package/dist/model/queryIndex.js +18 -2
- package/dist/model/queryIndex.js.map +1 -1
- package/dist/model/rules.d.ts +143 -2
- package/dist/model/rules.js +162 -3
- package/dist/model/rules.js.map +1 -1
- package/dist/model/validate.d.ts +40 -0
- package/dist/model/validate.js +147 -1
- package/dist/model/validate.js.map +1 -1
- package/dist/parser/ast.d.ts +31 -3
- package/dist/parser/parser.js +148 -14
- package/dist/parser/parser.js.map +1 -1
- package/dist/query/format.js +1 -1
- package/dist/query/format.js.map +1 -1
- package/dist/query/verbs.d.ts +6 -0
- package/dist/query/verbs.js +6 -2
- package/dist/query/verbs.js.map +1 -1
- package/dist/system/adaptation.d.ts +14 -0
- package/dist/system/adaptation.js +49 -0
- package/dist/system/adaptation.js.map +1 -0
- package/dist/system/codeowners.d.ts +77 -0
- package/dist/system/codeowners.js +242 -0
- package/dist/system/codeowners.js.map +1 -0
- package/dist/system/manifest.d.ts +10 -5
- package/dist/system/manifest.js +54 -26
- package/dist/system/manifest.js.map +1 -1
- package/dist/system/migrateManifest.d.ts +28 -0
- package/dist/system/migrateManifest.js +155 -0
- package/dist/system/migrateManifest.js.map +1 -0
- package/dist/system/scope.d.ts +46 -0
- package/dist/system/scope.js +132 -0
- package/dist/system/scope.js.map +1 -0
- package/dist/system/verify.d.ts +50 -13
- package/dist/system/verify.js +179 -58
- package/dist/system/verify.js.map +1 -1
- package/dist/templates.d.ts +2 -2
- package/dist/templates.js +23 -2
- package/dist/templates.js.map +1 -1
- package/package.json +5 -2
- package/plugin/.claude-plugin/plugin.json +18 -0
- package/plugin/agents/em-critic.md +27 -0
- package/plugin/agents/em-implementer.md +35 -0
- package/plugin/agents/em-reviewer.md +32 -0
- package/plugin/agents/em-validator.md +28 -0
- package/plugin/shared/reference/em-dsl.md +871 -0
- package/plugin/shared/reference/methodology.md +329 -0
- package/plugin/shared/reference/operating-principles.md +222 -0
- package/plugin/shared/reference/slice-doc-schema.md +574 -0
- package/plugin/shared/templates/conformance-report.md +85 -0
- package/plugin/shared/templates/constitution.md +136 -0
- package/plugin/shared/templates/model-readme.md +46 -0
- package/plugin/shared/templates/slice.md +251 -0
- package/plugin/shared/templates/state.md +77 -0
- package/plugin/skills/conform/SKILL.md +93 -0
- package/plugin/skills/conform/reference/conform.md +428 -0
- package/plugin/skills/design/SKILL.md +298 -0
- package/plugin/skills/discover/SKILL.md +90 -0
- package/plugin/skills/discover/reference/extract.md +147 -0
- package/plugin/skills/engagement/SKILL.md +193 -0
- package/plugin/skills/engagement/reference/engagement.md +144 -0
- package/plugin/skills/event-modeling/SKILL.md +132 -0
- package/plugin/skills/implement/SKILL.md +61 -0
- package/plugin/skills/implement/reference/implement.md +439 -0
- package/plugin/skills/review/SKILL.md +74 -0
- package/dist/emit/typespec.d.ts +0 -26
- package/dist/emit/typespec.js +0 -356
- package/dist/emit/typespec.js.map +0 -1
|
@@ -73,6 +73,67 @@ right here as `reference/slice-doc-schema.md` (kept in sync with the em repo's
|
|
|
73
73
|
hand-run `em validate --json` and format the line yourself. This is the team's only usage
|
|
74
74
|
signal today (`docs/usage-data.md`) — keep it cheap and habitual, not a task to skip.
|
|
75
75
|
|
|
76
|
+
## Write scope and evidence (MIL-236)
|
|
77
|
+
|
|
78
|
+
These bind every design session — `discover`, `model`, `slice` — whichever skill runs it,
|
|
79
|
+
including sessions started from bare `/event-modeling`.
|
|
80
|
+
|
|
81
|
+
- **Write scope: one model directory.** The session is bound to the model directory it started
|
|
82
|
+
in: `dirname(<model>.em)`, which holds `slices/`, `model-versions/`, `conformance/`,
|
|
83
|
+
`contracts/`, `.event-modeling.md` and `README.md`. Never write under another model's
|
|
84
|
+
directory or to another model's contract file (`<model dir>/contracts/<model key>.tsp`). When
|
|
85
|
+
you need a neighbour, read only its public surface, read-only: the elements with
|
|
86
|
+
`public: true` in `em export <neighbour>.em`, or its committed contract file. Never open the
|
|
87
|
+
neighbour's slice docs or state file.
|
|
88
|
+
- **Seam change request instead of a cross-model edit.** When resolving an open question needs
|
|
89
|
+
another model's `public` element changed (a field added, renamed, retyped; an element made
|
|
90
|
+
`public`), record it in THIS model's `.event-modeling.md` `## Decisions log` as one dated
|
|
91
|
+
bullet, exactly this shape (`em changelog` reads only dated bullets):
|
|
92
|
+
|
|
93
|
+
```
|
|
94
|
+
- YYYY-MM-DD: seam change request → <producerKey>:<kind>.<slug> — <what> — <why>
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
`<producerKey>:<kind>.<slug>` is the same ref a `consumes` clause uses (`kind` is `event` or
|
|
98
|
+
`view`; write `command` for a public command). Leave the open question open with
|
|
99
|
+
`blocked on: <producerKey> owners (seam change request YYYY-MM-DD)`, and tell the user to take
|
|
100
|
+
the request to the producer's owners. Never edit the other side.
|
|
101
|
+
- **Out of scope during design — never write these:**
|
|
102
|
+
- generated contract artifacts: `contracts/*.tsp`, any OpenAPI or JSON-schema output;
|
|
103
|
+
- SDD/spec artifacts: `specs/`, `.specify/` (read `.specify/memory/constitution.md` when a
|
|
104
|
+
slice needs the house rules; never write it);
|
|
105
|
+
- implementation source: `src/`, and any path under the model's `Code roots:` bullet;
|
|
106
|
+
- `plugin/` (the generated skill plugin);
|
|
107
|
+
- a draft slice doc's `version:` (see the version rule below).
|
|
108
|
+
|
|
109
|
+
One exception, and only this one: when the session marks an element `public` (or changes a
|
|
110
|
+
field of a `public` element), run `em api generate <model>.em` before handing slices to
|
|
111
|
+
ratification. The API-first gate (`em validate --slice-ready`) refuses a public-touching
|
|
112
|
+
slice whose contract is missing or stale, so the regenerated `contracts/<model key>.tsp` is
|
|
113
|
+
committed with the model edit. Never hand-edit it.
|
|
114
|
+
- **Evidence rule.** The `.em` model and the slice docs are the evidence for every modeling
|
|
115
|
+
decision. Never open implementation source, generated contracts, or `specs/` to settle a
|
|
116
|
+
modeling question; ask the user. Code is consulted only in `extract`
|
|
117
|
+
(`event-modeling-discover`) and `conform` (`event-modeling-conform`), where reading it is the
|
|
118
|
+
point.
|
|
119
|
+
- **Version rule: never bump a draft's `version:` on edit.** A `draft`/`reviewed` doc is edited
|
|
120
|
+
in place. Check the release state with `em status <model>.em --json`: the model is
|
|
121
|
+
**pre-release** when its `modelVersion` entry has `design: null` and
|
|
122
|
+
`slices.byStatus.implemented === 0`. Pre-release: never run `em slice reratify`; edit
|
|
123
|
+
`draft`/`reviewed` docs in place and leave `version:` as it is. A pre-release doc that is
|
|
124
|
+
already ratified (`ready-to-implement` with `ratifiedBy`) carries a human sign-off: STOP,
|
|
125
|
+
record the needed change as an open question, and let the ratifier decide. Released (either
|
|
126
|
+
condition false): changing an `implemented` or ratified doc goes through `em slice reratify`
|
|
127
|
+
(see `event-modeling-design`).
|
|
128
|
+
- **Pre-commit check (STOP on any finding).** Stage the session's files, then run both:
|
|
129
|
+
1. `em system scope --staged` from the repository root. Any `seam-crossing` error → STOP:
|
|
130
|
+
unstage the other model's files and record a seam change request instead.
|
|
131
|
+
2. `git diff --cached --name-only`. Any path outside the bound model directory, or any
|
|
132
|
+
out-of-scope path above (`contracts/*.tsp` except the regenerated exception, OpenAPI /
|
|
133
|
+
JSON-schema output, `specs/`, `.specify/`, `src/`, code roots, `plugin/`), or a draft
|
|
134
|
+
doc whose `version:` line changed (`git diff --cached -- <doc>`) → STOP: unstage it and
|
|
135
|
+
tell the user what was touched and why it is out of scope.
|
|
136
|
+
|
|
76
137
|
## Preconditions (run first)
|
|
77
138
|
|
|
78
139
|
1. Check the tool: `em --version`. If missing, tell the user to run `npm i -g @milehimikey/em`
|
|
@@ -67,6 +67,8 @@ to decide `frontmatter-invalid` without re-deriving frontmatter-shape rules of i
|
|
|
67
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`) |
|
|
68
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 |
|
|
69
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`) |
|
|
70
|
+
| `meaningConfirmed` | boolean | `true` | written only by `em slice ratify`/`reratify --meaning-unchanged` (MIL-238 — see [cli.md](https://github.com/milehimikey/em/blob/main/docs/cli.md#em-slice-ratify-file-slice-key---by-name)): the API-first sign-off on a public-touching slice — this version does not change what the public contract means. Joined into `em export`'s `slice.doc.meaningConfirmed` (schema `1.15`; `false` when absent) |
|
|
71
|
+
| `contractChange` | string | quoted free text (`contractChange: "<why>"`) | written only by `em slice ratify`/`reratify --contract-change "<why>"` (MIL-238): the API-first sign-off on a public-touching slice — why a consumer must read this change differently. Joined into `em export`'s `slice.doc.contractChange` (schema `1.15`, quotes stripped; `null` when absent) |
|
|
70
72
|
| `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[]` |
|
|
71
73
|
| `tracking` | string | free text (typically an external ticket/issue URL) | hand-filled — no `em` command writes it; joined into `em export`'s `slice.doc.tracking` (schema `1.9`, MIL-171) and `em slice index`'s Tracking column. This is the exact field `em-tracker-bridge` reads to find the ticket mirroring this slice — `em` only stores and displays it, it never talks to a tracker itself |
|
|
72
74
|
| `conformedVersion` | integer | positive integer | written only by `em slice conform` (MIL-214 — see [cli.md](https://github.com/milehimikey/em/blob/main/docs/cli.md#em-slice-conform-file-slice-key---at-rev)): this slice's `version:` at the time a conform sweep last certified it. Joined into `em export`'s `slice.doc.conformedVersion` (schema `1.13`); paired with `version` to compute `slice.doc.driftSignal`'s `"uncertified"` case |
|
|
@@ -90,7 +92,8 @@ convention, not parser-enforced.
|
|
|
90
92
|
| `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 |
|
|
91
93
|
| `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 |
|
|
92
94
|
| `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) |
|
|
93
|
-
| `ratifiedBy`, `ratifiedOn` | optional in every state — present once `em slice ratify` (MIL-165) has run
|
|
95
|
+
| `ratifiedBy`, `ratifiedOn` | optional in every state, but `ready-to-implement` needs `ratifiedBy` to pass `em validate --slice-ready` (`slice-ready-not-ratified`, MIL-259) — present once `em slice ratify` (MIL-165) has run; `em slice reratify` clears both. `em upgrade` (`ratified-signoff`, 1.14) grandfathers a `version: 1` `ready-to-implement` doc that never had one by writing `ratifiedBy: "grandfathered (unsigned before em 1.14)"` and `ratifiedOn:` the upgrade date |
|
|
96
|
+
| `meaningConfirmed`, `contractChange` | optional in every state, at most one present — written right after `ratifiedOn:` by `em slice ratify` and right after `version:` by `em slice reratify` (MIL-238). On a **public-touching** slice (one owning a `public` command, event or view) both commands refuse without `--meaning-unchanged` or `--contract-change "<why>"`, so a doc ratified on 1.14+ carries one; elsewhere either is optional. `em slice reratify` clears both before writing its own (they describe the prior version, same as `reviewedBy`/`ratifiedBy`). `em status` counts public-touching `ready-to-implement`/`implemented` docs with neither (`publicSlicesUnconfirmed`) |
|
|
94
97
|
| `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) |
|
|
95
98
|
| `conformedVersion`, `conformedAt`, `conformedOn` | optional in every state — present once `em slice conform` (MIL-214) has run at least once for the CURRENT version; a doc that's never been through a conform sweep, or whose `version` has since bumped past what was certified, simply has none of the three (or a stale triple — see `driftSignal`'s `uncertified` case below) |
|
|
96
99
|
|
|
@@ -119,13 +122,13 @@ because it's the same frontmatter dialect. The doc stays the single source of tr
|
|
|
119
122
|
`status`; a stub is just the cheapest possible way to give a slice one before anyone's ready to
|
|
120
123
|
write the real spec.
|
|
121
124
|
|
|
122
|
-
**A stub CAN pass `em validate --slice-ready`.** That gate checks
|
|
125
|
+
**A stub CAN pass `em validate --slice-ready`.** That gate checks six things (`computeSliceReadyGates`,
|
|
123
126
|
[validation.md#slice-readiness](https://github.com/milehimikey/em/blob/main/docs/validation.md#slice-readiness)): a doc is bound, its frontmatter
|
|
124
|
-
is usable, `status` is `ready-to-implement`,
|
|
127
|
+
is usable, `status` is `ready-to-implement`, it carries a `ratifiedBy` (MIL-259), its Open Questions count has none unchecked, and — for a slice owning a `public` element — the model's contract is current (MIL-238; nothing in the doc). A
|
|
125
128
|
stub with no `## Open Questions` section at all has `openQuestionsTotal: 0` (see
|
|
126
129
|
[Open Questions section: lifecycle](#open-questions-section-lifecycle) below) — zero unchecked is
|
|
127
130
|
vacuously true — so a stub escalated to `ready-to-implement` (`em slice stub-all --status
|
|
128
|
-
ready-to-implement --by <name
|
|
131
|
+
ready-to-implement --by <name>`, which records the ratifier) passes cleanly. The readiness gate is frontmatter-plus-checkbox-
|
|
129
132
|
count only; it has no opinion about whether Intent/Command/Scenarios/Invariants were ever
|
|
130
133
|
written. Demanding that content is the `slice` phase's (and, for a live walkthrough, the review
|
|
131
134
|
skill's) job, not a machine gate's — see `../.claude/skills/event-modeling-design/SKILL.md` and
|
|
@@ -347,6 +350,106 @@ stale reads as current, unlike git which is honestly historical), and forces eve
|
|
|
347
350
|
work out which delta is the live one — exactly the ambiguity `driftSignal` (MIL-85, below) is
|
|
348
351
|
built to avoid.
|
|
349
352
|
|
|
353
|
+
## Invariants
|
|
354
|
+
|
|
355
|
+
Since MIL-265 the **model is the preferred place to declare an invariant**: an `invariant
|
|
356
|
+
INV-<MNEMONIC>-<n> "rule"` line after the command or event it guards (see
|
|
357
|
+
[dsl.md](dsl.md#invariants)) carries the ID and the one-line rule sentence. The doc's
|
|
358
|
+
`## Invariants / Business Rules` section then **cites** each model-declared ID and elaborates —
|
|
359
|
+
why the rule exists, what a violation looks like, edge cases, the error returned:
|
|
360
|
+
|
|
361
|
+
```markdown
|
|
362
|
+
## Invariants / Business Rules
|
|
363
|
+
- INV-ORD-1 — a zero-total cart is rejected before payment is attempted
|
|
364
|
+
- the check runs on the server; the UI's own check is a convenience only
|
|
365
|
+
```
|
|
366
|
+
|
|
367
|
+
Write the citation as a plain mention (`- INV-ORD-1 — …`), not with the declaring label
|
|
368
|
+
`**INV-ORD-1:**` — restating a model-declared ID with that label is
|
|
369
|
+
`invariants/declared-in-both` (a warning pointing at the migration). Ownership follows the model:
|
|
370
|
+
a model-declared ID belongs to the slice of the element that declares it, for `em coverage` and
|
|
371
|
+
`em query invariant`, whatever any doc says.
|
|
372
|
+
|
|
373
|
+
**Doc declaration is still honoured.** A doc that declares its own invariants
|
|
374
|
+
(`- **INV-CHK-1:** rule`, or a 1.13 doc's bare `- **INV-1:** rule`) keeps working exactly as
|
|
375
|
+
before: for every ID the model does not declare, the doc-body reader (`## Invariants` and
|
|
376
|
+
`## Delta` sections, top-level bullet/subheading lines only — MIL-149/155) is the fallback.
|
|
377
|
+
A doc mentioning an `INV-…` ID that neither the model nor any doc declares gets
|
|
378
|
+
`invariants/doc-cites-undeclared`. See [validation.md](https://github.com/milehimikey/em/blob/main/docs/validation.md#invariants).
|
|
379
|
+
|
|
380
|
+
## Generated regions and authored sections
|
|
381
|
+
|
|
382
|
+
Since MIL-266 a slice doc has two kinds of section.
|
|
383
|
+
|
|
384
|
+
**Generated regions** restate the model, and `em` writes them. Each is wrapped in a pair of html
|
|
385
|
+
markers; `em slice new --wire` fills them from the `.em`, and `em slice sync <model>.em` refreshes
|
|
386
|
+
them in place after any model edit. Never hand-edit between the markers — change the model and
|
|
387
|
+
re-run `em slice sync`.
|
|
388
|
+
|
|
389
|
+
| Region | Section | Body |
|
|
390
|
+
|---|---|---|
|
|
391
|
+
| `em-slice-command` | `## Command / Input` | `**Command:** \`Name\`` + `\| Field \| Type \| Required \| Rules / Validation \|` (Required is `no` for a `name?: Type` field) |
|
|
392
|
+
| `em-slice-event` | `## Event(s) Emitted` | `**Event:** \`Name\` → context \`Ctx\`` + `\| Field \| Type \| Immutable Fact? \| Source / Notes \|` (Source: the same-slice command field it is copied from, `assigned` for a system-assigned field, else `{{ }}`) |
|
|
393
|
+
| `em-slice-view` | `## Read Model / View` | `- **View:** \`Name\` built from events: …` + `\| Field \| Type \| Source / Notes \|` (Source: the source event carrying the field, `Derived` for a `derived` field, else `{{ }}`) |
|
|
394
|
+
| `em-slice-invariants` | `## Invariants / Business Rules` | one `- **INV-X** — rule` line per model-declared invariant of the slice's commands and events |
|
|
395
|
+
|
|
396
|
+
A slice with several elements of one kind gets one region per element, the name suffixed with
|
|
397
|
+
the element's slug (`em-slice-event-order-placed`). A kind the slice lacks keeps its one region
|
|
398
|
+
with a "has no …" line, so adding the first element later is a plain `em slice sync`. The
|
|
399
|
+
`**Command:**`/`**Event:**`/`- **View:**` marker lines sit inside the regions, so the
|
|
400
|
+
[doc-model consistency](https://github.com/milehimikey/em/blob/main/docs/validation.md#doc-model-consistency) check reads them as before (and,
|
|
401
|
+
inside a generated `em-slice-view` region, now compares the view's field table too).
|
|
402
|
+
|
|
403
|
+
```markdown
|
|
404
|
+
## Command / Input
|
|
405
|
+
<!-- GENERATED:em-slice-command:start — generated from the model by `em slice sync`; do not hand-edit -->
|
|
406
|
+
**Command:** `Submit Order`
|
|
407
|
+
|
|
408
|
+
| Field | Type | Required | Rules / Validation |
|
|
409
|
+
|-------|------|----------|--------------------|
|
|
410
|
+
| total | decimal | yes | — |
|
|
411
|
+
| note | text | no | — |
|
|
412
|
+
<!-- GENERATED:em-slice-command:end -->
|
|
413
|
+
```
|
|
414
|
+
|
|
415
|
+
The invariants list uses `**INV-X** — rule`, not the declaring label `**INV-X:**`, so a
|
|
416
|
+
generated list never trips `invariants/declared-in-both`. Elaborate each ID below the region
|
|
417
|
+
(after the `<!-- elaborate below this list; IDs are declared in the model -->` comment).
|
|
418
|
+
|
|
419
|
+
**Authored sections** are everything else — Intent, Trigger & Actor, Trigger, `**Read by:**`,
|
|
420
|
+
`Consumed by`/freshness, the invariant elaborations, Scenarios, Alternate & Error Flows, NFRs,
|
|
421
|
+
Dependencies, Open Questions. `em` never writes them; `em slice sync` leaves every byte outside
|
|
422
|
+
the region markers untouched (CRLF included).
|
|
423
|
+
|
|
424
|
+
**Scenario grammar (authored, constrained).** Inside `## Scenarios (Given / When / Then)`, each
|
|
425
|
+
case is a block:
|
|
426
|
+
|
|
427
|
+
```markdown
|
|
428
|
+
### Scenario: Non-positive total is rejected (INV-CHK-1)
|
|
429
|
+
- **Given:**
|
|
430
|
+
- a basket whose total is 0
|
|
431
|
+
- **When:**
|
|
432
|
+
- the customer submits the order
|
|
433
|
+
- **Then:**
|
|
434
|
+
- the command is rejected with a validation error
|
|
435
|
+
- no event is recorded
|
|
436
|
+
```
|
|
437
|
+
|
|
438
|
+
A `### Scenario: <title>` heading, then the three column-0 bullets `- **Given:**`, `- **When:**`,
|
|
439
|
+
`- **Then:**`, each with nested ` - ` items (text after the label on the same line counts as a
|
|
440
|
+
first item). `em export --json` publishes the parsed blocks as `slice.doc.scenarios`
|
|
441
|
+
(`{ title, given, when, then }[]`, `null` when the doc has none). A block missing any of the
|
|
442
|
+
three is `slice-doc/structured-section-malformed`, as is a region whose markers don't balance or
|
|
443
|
+
whose table header differs from the template's — a warning in `em validate`, and a blocker
|
|
444
|
+
(`slice-ready-structured-section-malformed`) under `--slice-ready`. See
|
|
445
|
+
[validation.md](https://github.com/milehimikey/em/blob/main/docs/validation.md#structured-sections).
|
|
446
|
+
|
|
447
|
+
**Migration.** Nothing is required: a doc with no region markers and no `### Scenario:` heading
|
|
448
|
+
(every doc written before 1.14) stays valid, is never flagged, and `em slice sync` skips it with a
|
|
449
|
+
note. To adopt regions, re-create the doc with `em slice new "<name>" … --wire <model>.em --force`
|
|
450
|
+
and carry the authored prose across, or paste the marker pairs in by hand and run
|
|
451
|
+
`em slice sync`.
|
|
452
|
+
|
|
350
453
|
## Open Questions section: lifecycle
|
|
351
454
|
|
|
352
455
|
`## Open Questions` (MIL-87 for the counting mechanics; MIL-156 for this lifecycle) had no
|
|
@@ -376,6 +479,14 @@ diverges from git, grows unboundedly over a long-lived slice's life, and forces
|
|
|
376
479
|
work out which resolutions are still relevant to the *current* version versus leftover from a
|
|
377
480
|
prior one — exactly the ambiguity a live, current-version-only section avoids.
|
|
378
481
|
|
|
482
|
+
**Deferral marker (MIL-275).** A question this version will not answer is not left `- [ ]` and
|
|
483
|
+
not deleted: `em slice defer` checks it and rewrites it as `- [x] <original> — v<current>:
|
|
484
|
+
<decision>; deferred to v<n> (<date>[, <by>])`. The `deferred to v<n>` marker is what
|
|
485
|
+
`em slice reratify` reads: when a bump reaches v<n> it rewrites each such item back to
|
|
486
|
+
`- [ ] <original>`, so the question must be resolved or re-deferred before `--slice-ready` passes
|
|
487
|
+
again. The prune rule above does not apply to a deferred item before v<n> (it stays as the
|
|
488
|
+
record); once reopened it is an ordinary open question.
|
|
489
|
+
|
|
379
490
|
**Not a parsing/grammar change.** `countOpenQuestions()` (`sliceDoc.ts`) and
|
|
380
491
|
`slice-ready-open-questions-unchecked` (`sliceReadyValidate.ts`,
|
|
381
492
|
[validation.md#slice-readiness](https://github.com/milehimikey/em/blob/main/docs/validation.md#slice-readiness)) are unaffected: both already
|
|
@@ -400,7 +511,7 @@ frontmatter-coherence check (MIL-85) deliberately never flags this combination
|
|
|
400
511
|
|
|
401
512
|
**An unshipped version has nothing to flip back** (MIL-258). A ratified doc that has not shipped
|
|
402
513
|
yet (`status: ready-to-implement` with `ratifiedBy:` set) changes the same way: `em slice
|
|
403
|
-
reratify` bumps `version:` and clears `ratifiedBy`/`ratifiedOn`/`reviewedBy`/`reviewedOn`, but
|
|
514
|
+
reratify` bumps `version:` and clears `ratifiedBy`/`ratifiedOn`/`reviewedBy`/`reviewedOn` (and `meaningConfirmed`/`contractChange`, MIL-238), but
|
|
404
515
|
leaves `status` at `ready-to-implement` (and any `implementedIn` as it was — an absent one stays
|
|
405
516
|
absent). This is the path for an answered gap mid-build. In both cases the new version is not
|
|
406
517
|
ratified until `em slice ratify --by <name>` records the sign-off. A `ready-to-implement` doc with
|
|
@@ -23,7 +23,10 @@ NAMED human and an ISO date, hand-filled at sign-off (no `em` command writes thi
|
|
|
23
23
|
unratified, says so, and does not proceed on unilateral style or stack decisions for a first slice.
|
|
24
24
|
|
|
25
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
|
|
26
|
+
or validates its content, and the CLI parses nothing here. Three lines are read as plain text by
|
|
27
|
+
the engagement skill and its sub-agents, so keep their exact shape: `- **Test command:**` (the
|
|
28
|
+
validator), `- **Merge strategy:**` and `- **Agent models:**` (the engagement skill). In a spec-kit
|
|
29
|
+
project they merge under the same headings in `.specify/memory/constitution.md`.
|
|
27
30
|
-->
|
|
28
31
|
|
|
29
32
|
---
|
|
@@ -86,6 +89,10 @@ table is the only routing rule.
|
|
|
86
89
|
with rejection scenarios asserting the doc's named rejection reason.
|
|
87
90
|
- **Test naming convention:** {{the exact pattern, e.g. `INV-ORD-3: rejects a second submit`}}
|
|
88
91
|
- **What "green" means:** {{the command CI runs}}
|
|
92
|
+
- **Test command:** `{{the one command that runs the tests, e.g. ./gradlew test}}`
|
|
93
|
+
What the `em-validator` sub-agent runs. Keep exactly this shape: one line, the command in
|
|
94
|
+
backticks. Absent, the validator reports "no test command in the constitution" and runs only the
|
|
95
|
+
em checks.
|
|
89
96
|
|
|
90
97
|
## NFR baselines
|
|
91
98
|
|
|
@@ -113,6 +120,16 @@ it is silent, these apply.
|
|
|
113
120
|
amend** — a `covers:` doc ships as one PR for both keys; a read model is one doc, so a later
|
|
114
121
|
`again` instance is a continuation of the doc that first declared it, not a PR of its own. See
|
|
115
122
|
the agent guide's §5/§10.
|
|
123
|
+
- **Merge strategy:** merge-commits-when-stacked
|
|
124
|
+
{{**How do PRs merge, and what happens when one PR depends on another?** Merge commits whenever
|
|
125
|
+
a dependent PR is open; squash only a PR with no open dependent (implement contract §8 rule 2a).
|
|
126
|
+
Keep this exact line: it is the only value the engagement skill accepts, and it refuses to run
|
|
127
|
+
without it.}}
|
|
128
|
+
- **Agent models:** implementer=sonnet, validator=sonnet, reviewer=sonnet, critic=opus
|
|
129
|
+
{{**Which model runs each em sub-agent?** Keep this exact line shape, with the defaults shown or
|
|
130
|
+
your own choices. The critic must run on a different model from the implementer; `critic=codex`
|
|
131
|
+
selects the Codex critic. Absent, the engagement skill states these defaults once and
|
|
132
|
+
proceeds.}}
|
|
116
133
|
- **At merge:** run `em slice mark-implemented <model>.em <slice-key> <pr-url>` — the one edit an
|
|
117
134
|
implementing agent makes to a ratified slice doc, run by {{the PR author | CI}}. Never bump
|
|
118
135
|
`version:`; that moves only when a delta is ratified.
|
|
@@ -36,9 +36,10 @@ to write up. Delete it otherwise; most docs never carry it. Requires a matching
|
|
|
36
36
|
bind anything. Plain slice keys, comma-separated — not the `<slice-key>@v<N>` ref grammar above.
|
|
37
37
|
|
|
38
38
|
`ratifiedBy`/`ratifiedOn` record who ratified this doc's current `status`/`version`, and when —
|
|
39
|
-
don't hand-fill these; run `em slice ratify <model
|
|
39
|
+
don't hand-fill these; run `em slice ratify <model> <key> --by <name>` at the
|
|
40
40
|
handoff gate instead (docs/process.md#what-ratified-means), which flips `status` to
|
|
41
41
|
`ready-to-implement` and writes both in one edit.
|
|
42
|
+
public-touching slice: add --meaning-unchanged, or --contract-change "<why>"; run em api generate first.
|
|
42
43
|
|
|
43
44
|
`owner`/`tracking` are optional and, unlike `ratifiedBy`/`ratifiedOn`, hand-filled — no `em`
|
|
44
45
|
command writes either. `owner` is free text naming who (a person or team) holds this slice;
|
|
@@ -121,13 +122,18 @@ state we react to). Either way, name the command this reaction triggers — reac
|
|
|
121
122
|
an event directly.}}
|
|
122
123
|
|
|
123
124
|
## Command / Input
|
|
124
|
-
<!--
|
|
125
|
-
|
|
125
|
+
<!-- GENERATED region (MIL-266): the table between the markers is written from the model —
|
|
126
|
+
`em slice new` fills it, `em slice sync <model>.em` refreshes it after any model edit. Never
|
|
127
|
+
hand-edit inside the markers: change the `.em` and re-run `em slice sync`. Required = `no`
|
|
128
|
+
for a field written `name?: Type`. A slice with several commands gets one region per command
|
|
129
|
+
(`em-slice-command-<command-slug>`); a pure State View slice's region says it has none. -->
|
|
130
|
+
<!-- GENERATED:em-slice-command:start — generated from the model by `em slice sync`; do not hand-edit -->
|
|
126
131
|
**Command:** `{{Command Name}}`
|
|
127
132
|
|
|
128
133
|
| Field | Type | Required | Rules / Validation |
|
|
129
134
|
|-------|------|----------|--------------------|
|
|
130
135
|
| {{field}} | {{Type}} | {{yes/no}} | {{constraints, formats, ranges}} |
|
|
136
|
+
<!-- GENERATED:em-slice-command:end -->
|
|
131
137
|
|
|
132
138
|
## Trigger
|
|
133
139
|
<!-- What issues this slice's command. Required: a command nothing points at is a write nobody
|
|
@@ -136,63 +142,82 @@ an event directly.}}
|
|
|
136
142
|
**Triggered by:** {{screen `X` @Persona | processor `Y`, also in this slice}}
|
|
137
143
|
|
|
138
144
|
## Event(s) Emitted
|
|
139
|
-
<!-- The immutable facts recorded.
|
|
145
|
+
<!-- The immutable facts recorded. The marker line and payload table are a GENERATED region (one
|
|
146
|
+
per event; `em-slice-event-<event-slug>` when the slice records several): Source / Notes
|
|
147
|
+
names the command field a value is copied from, `assigned` for a system-assigned field, or
|
|
148
|
+
`{{ }}` where the model cannot tell. `**Read by:**` below the region is authored. -->
|
|
149
|
+
<!-- GENERATED:em-slice-event:start — generated from the model by `em slice sync`; do not hand-edit -->
|
|
140
150
|
**Event:** `{{Event Name}}` → context `{{Context}}`
|
|
151
|
+
|
|
152
|
+
| Field | Type | Immutable Fact? | Source / Notes |
|
|
153
|
+
|-------|------|-----------------|----------------|
|
|
154
|
+
| {{field}} | {{Type}} | {{yes/no}} | {{where the value comes from}} |
|
|
155
|
+
<!-- GENERATED:em-slice-event:end -->
|
|
141
156
|
**Read by:** {{which read model projects this event, and in which slice}}
|
|
142
157
|
<!-- Required, not optional. Every event must be read by a read model — an event nothing
|
|
143
158
|
projects is a write nobody can see, and `em validate` warns on it. A reaction consuming
|
|
144
159
|
it does NOT count: reactions read views, not events. If the honest answer is "nothing
|
|
145
160
|
reads it", that's a question for the business, not a field to leave blank. -->
|
|
146
161
|
|
|
147
|
-
| Field | Type | Immutable Fact? | Source / Notes |
|
|
148
|
-
|-------|------|-----------------|----------------|
|
|
149
|
-
| {{field}} | {{Type}} | {{yes/no}} | {{where the value comes from}} |
|
|
150
|
-
|
|
151
162
|
## Read Model / View
|
|
152
|
-
<!-- For State View slices, and any read model this slice produces or feeds.
|
|
163
|
+
<!-- For State View slices, and any read model this slice produces or feeds. The `- **View:**`
|
|
164
|
+
line and field table are a GENERATED region (one per read model; `em-slice-view-<view-slug>`
|
|
165
|
+
when there are several): Source / Notes names the source event a field is copied from, or
|
|
166
|
+
`Derived` for a field marked `derived` in the `.em` (docs/dsl.md#derived-fields) — say how
|
|
167
|
+
it is derived in prose below the region. `{{ }}` marks a field the model cannot trace. -->
|
|
168
|
+
<!-- GENERATED:em-slice-view:start — generated from the model by `em slice sync`; do not hand-edit -->
|
|
153
169
|
- **View:** `{{View Name}}` built from events: {{"Event A", "Event B"}}
|
|
170
|
+
|
|
171
|
+
| Field | Type | Source / Notes |
|
|
172
|
+
|-------|------|----------------|
|
|
173
|
+
| {{field}} | {{Type}} | {{which event field it's copied from, or `Derived: <rule>`}} |
|
|
174
|
+
<!-- GENERATED:em-slice-view:end -->
|
|
154
175
|
- **Consumed by:** {{which UI screen (or API-caller persona), or reaction}}
|
|
155
176
|
<!-- "Consumed by" is required, not optional. A read model nothing displays or watches is
|
|
156
177
|
information projected out of the system and then dropped, and `em validate` warns. Every
|
|
157
178
|
instance of a repeated view needs its own consumer, not just the last one. -->
|
|
158
179
|
- **Freshness / consistency expectation:** {{real-time | eventual | on-demand}}
|
|
159
180
|
|
|
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
|
-
|
|
164
181
|
## Invariants / Business Rules
|
|
182
|
+
<!-- IDs and rule sentences are declared in the MODEL (MIL-265): an `invariant INV-<MNEMONIC>-<n>
|
|
183
|
+
"rule"` line after the command or event it guards (docs/dsl.md#invariants). The list between
|
|
184
|
+
the markers is GENERATED from those lines (`em slice sync`); never edit it. Below it, cite
|
|
185
|
+
each ID as a plain mention and elaborate — `- INV-CHK-1 — why it holds, edge cases, the error
|
|
186
|
+
a violation returns` — never re-declare it with the `**INV-CHK-1:**` label
|
|
187
|
+
(`invariants/declared-in-both`). A rule not (yet) in the model may still be declared below
|
|
188
|
+
with that label; em reads it as the fallback. -->
|
|
165
189
|
<!-- What must ALWAYS hold. Give each a stable ID so tests and code can reference it:
|
|
166
190
|
`INV-<MNEMONIC>-<n>`, where `<MNEMONIC>` is a short (2-4 letter/digit), slice-unique
|
|
167
191
|
abbreviation of this slice's key (e.g. slice `checkout` -> `INV-CHK-1`) — see
|
|
168
|
-
../reference/slice-doc-schema.md.
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
- **INV-{{MNEMONIC}}-1
|
|
177
|
-
|
|
178
|
-
|
|
192
|
+
../reference/slice-doc-schema.md. Keep the rule statement itself to one line. If it needs
|
|
193
|
+
more — why the rule exists, what "violation" looks like in practice, an edge case worth
|
|
194
|
+
calling out — add that as a nested bullet under the elaboration line instead of one long
|
|
195
|
+
run-on sentence: this section renders as HTML (`em catalog`/`em render`/`em watch`), and a
|
|
196
|
+
nested bullet stays visually distinct where a wrapped sentence collapses into a wall of
|
|
197
|
+
text. The ID must stay on the top-level bullet line — `em coverage` extracts INV IDs from
|
|
198
|
+
that line only, never from a nested elaboration bullet underneath it. -->
|
|
199
|
+
<!-- GENERATED:em-slice-invariants:start — generated from the model by `em slice sync`; do not hand-edit -->
|
|
200
|
+
- **INV-{{MNEMONIC}}-1** — {{rule, as declared by `invariant INV-{{MNEMONIC}}-1 "rule"` in the model}}
|
|
201
|
+
<!-- GENERATED:em-slice-invariants:end -->
|
|
202
|
+
<!-- elaborate below this list; IDs are declared in the model -->
|
|
203
|
+
- INV-{{MNEMONIC}}-1 — {{elaboration of the model-declared rule: why it holds, what a violation looks like, the error returned}}
|
|
204
|
+
- {{optional: edge-case detail — its own bullet}}
|
|
205
|
+
- **INV-{{MNEMONIC}}-2:** {{a rule not yet in the model, declared here as the fallback — move it into the model when you can}}
|
|
179
206
|
|
|
180
207
|
## Scenarios (Given / When / Then)
|
|
181
|
-
<!-- The executable specification
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
-
|
|
189
|
-
|
|
190
|
-
-
|
|
191
|
-
|
|
192
|
-
-
|
|
193
|
-
-
|
|
194
|
-
- **When:** {{...}}
|
|
195
|
-
- **Then:** {{...}}
|
|
208
|
+
<!-- The executable specification — authored, in a constrained shape `em` reads back (`em export
|
|
209
|
+
--json` → slice.doc.scenarios). Cover the happy path AND the key rule boundaries, one block
|
|
210
|
+
per case: a `### Scenario: <title>` heading, then exactly the three column-0 bullets
|
|
211
|
+
`- **Given:**`, `- **When:**`, `- **Then:**`, each with its beats as nested ` - ` bullets.
|
|
212
|
+
A block missing any of the three is flagged `slice-doc/structured-section-malformed`. -->
|
|
213
|
+
### Scenario: Happy path
|
|
214
|
+
- **Given:**
|
|
215
|
+
- {{starting state / prior events}}
|
|
216
|
+
- **When:**
|
|
217
|
+
- {{command/trigger}}
|
|
218
|
+
- **Then:**
|
|
219
|
+
- {{event(s) recorded}}
|
|
220
|
+
- {{resulting read-model change}}
|
|
196
221
|
|
|
197
222
|
## Alternate & Error Flows
|
|
198
223
|
<!-- Failure paths, retries, compensations, timeouts, idempotency. -->
|
|
@@ -48,7 +48,11 @@ Keep it current at the end of every working session.
|
|
|
48
48
|
|
|
49
49
|
## Decisions log
|
|
50
50
|
<!-- Resolved choices, with the reasoning, so they aren't re-litigated. In a live workshop,
|
|
51
|
-
attribute each entry to the participant who made the call (see Participants above).
|
|
51
|
+
attribute each entry to the participant who made the call (see Participants above).
|
|
52
|
+
Every entry is a dated bullet ("- YYYY-MM-DD: ..."); em changelog skips any other line.
|
|
53
|
+
Seam change request (a needed change to another model's public element; never edit that
|
|
54
|
+
model, keep the open question blocked on its owners):
|
|
55
|
+
- YYYY-MM-DD: seam change request → <producerKey>:<kind>.<slug> — <what> — <why> -->
|
|
52
56
|
- {{YYYY-MM-DD}}: {{decision}} — {{why}} — by {{participant, if a live workshop}}
|
|
53
57
|
|
|
54
58
|
## Usage log
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "em-1-14-1",
|
|
3
|
+
"owner": {
|
|
4
|
+
"name": "Mike Key"
|
|
5
|
+
},
|
|
6
|
+
"description": "em v1.14.1: the Event Modeling skill bundle and MCP server as a Claude Code plugin.",
|
|
7
|
+
"plugins": [
|
|
8
|
+
{
|
|
9
|
+
"name": "em",
|
|
10
|
+
"source": "./plugin",
|
|
11
|
+
"description": "Event Modeling with em: discover, design, implement, conform, review and engagement skills over a slice-first text DSL, plus the em MCP server."
|
|
12
|
+
}
|
|
13
|
+
]
|
|
14
|
+
}
|
package/README.md
CHANGED
|
@@ -38,12 +38,19 @@ em diff model.em --from HEAD~1 # what this change did to the model
|
|
|
38
38
|
em changelog model.em # the model's git history as a business ledger
|
|
39
39
|
```
|
|
40
40
|
|
|
41
|
-
A system of several models — one per team or
|
|
42
|
-
the same treatment across model boundaries
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
41
|
+
A system of several models — one per team or bounded context, each in its own directory —
|
|
42
|
+
gets the same treatment across model boundaries. A producer marks the commands, events and
|
|
43
|
+
views other teams depend on `public`, and `em api generate` writes them as the model's own
|
|
44
|
+
TypeSpec contract (`contracts/<model>.tsp`). A consumer declares what it reads on its own side,
|
|
45
|
+
`translation Order Intake consumes checkout:event.order-submitted`. `em system` finds the
|
|
46
|
+
models (a `system.yaml` listing them, or discovery of every `.em` in the repo), resolves every
|
|
47
|
+
`consumes` against the producers' public surface, fails when a consumer still reads a field its
|
|
48
|
+
producer dropped, and emits the org-level context map. `em system codeowners` puts every
|
|
49
|
+
consuming team on the producer's contract file in CODEOWNERS, so a contract change cannot merge
|
|
50
|
+
without them, and two gates hold the line: `--slice-ready` will not hand off a slice whose
|
|
51
|
+
contract is stale, and `em system scope` fails a PR that changes a contract and its consumer
|
|
52
|
+
together ([docs/process.md](docs/process.md#seams-between-models-who-does-what); worked example
|
|
53
|
+
in [examples/multi-model/](examples/multi-model/)).
|
|
47
54
|
|
|
48
55
|
A model is a list of slices — vertical time steps, read left to right — whose elements land
|
|
49
56
|
in swimlane rows:
|
|
@@ -90,16 +97,23 @@ flowchart LR
|
|
|
90
97
|
|
|
91
98
|
## Model with AI
|
|
92
99
|
|
|
93
|
-
`em` ships a Claude Code skill bundle — one router skill plus
|
|
100
|
+
`em` ships a Claude Code skill bundle — one router skill plus six focused, SDLC-stage skills
|
|
94
101
|
(discover/extract, model/slice, implement, conform/validate, watch/review) — that runs a
|
|
95
102
|
facilitated Event Modeling session: the AI asks the questions, you supply the domain, and the
|
|
96
103
|
model renders live as it grows.
|
|
97
104
|
|
|
105
|
+
Install it as the `em` Claude Code plugin, pinned to the release you run (two commands, once per
|
|
106
|
+
machine; replace `1.14.0` with your `em --version`):
|
|
107
|
+
|
|
98
108
|
```bash
|
|
99
|
-
|
|
109
|
+
claude plugin marketplace add milehimikey/em@v1.14.0 --scope project
|
|
110
|
+
claude plugin install em@em-1-14-0 --scope project
|
|
100
111
|
```
|
|
101
112
|
|
|
102
|
-
Then run `/event-modeling` in Claude Code. The
|
|
113
|
+
Then run `/em:event-modeling` in Claude Code. The older vendored route (`em skill install`,
|
|
114
|
+
which copies the bundle into `.claude/skills/`) still works but is deprecated since 1.14;
|
|
115
|
+
`em upgrade --apply` moves a vendored repo to the pinned plugin in one commit. The same bundle
|
|
116
|
+
also runs the reverse direction:
|
|
103
117
|
`extract` derives a model from a system that already exists, and `conform` checks a model
|
|
104
118
|
against the code implementing it and reports where they've drifted. See
|
|
105
119
|
[docs/ai-workflow.md](docs/ai-workflow.md) for the phases and what a session produces, and the
|
|
@@ -112,12 +126,12 @@ built this way.
|
|
|
112
126
|
|---|---|
|
|
113
127
|
| [docs/tutorial.md](docs/tutorial.md) | Learn the tool by building a model from scratch |
|
|
114
128
|
| [docs/workflow.md](docs/workflow.md) | The model lifecycle: specify, gate, hand off, track change, detect drift |
|
|
115
|
-
| [docs/process.md](docs/process.md) | Who does what: where humans are required, where agents work with review |
|
|
129
|
+
| [docs/process.md](docs/process.md) | Who does what: where humans are required, where agents work with review, and the seam lifecycle between models |
|
|
116
130
|
| [docs/patterns.md](docs/patterns.md) | The four Event Modeling patterns and their DSL shapes |
|
|
117
131
|
| [docs/dsl.md](docs/dsl.md) | Full DSL reference: keywords, `from`, `again`, fields, notes |
|
|
118
132
|
| [docs/cli.md](docs/cli.md) | Every command and flag |
|
|
119
133
|
| [docs/validation.md](docs/validation.md) | Every rule `em validate` checks, and the fixes |
|
|
120
|
-
| [docs/ci.md](docs/ci.md) | Copy-paste CI recipes: validate `.em` changes, run conformance on a schedule |
|
|
134
|
+
| [docs/ci.md](docs/ci.md) | Copy-paste CI recipes: validate `.em` changes, the multi-model gates, run conformance on a schedule |
|
|
121
135
|
| [docs/upgrading.md](docs/upgrading.md) | Bring a model repo forward across em releases: `em upgrade`, release by release |
|
|
122
136
|
| [docs/timeline.md](docs/timeline.md) | The Two Laws of the Timeline |
|
|
123
137
|
| [docs/ai-workflow.md](docs/ai-workflow.md) | The Claude Code skill: install, phases, artifacts |
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { NormalizedModel, Slice } from "../model/model.js";
|
|
2
|
+
import { RefsResult } from "../model/refs.js";
|
|
3
|
+
import { Diagnostic } from "../model/validate.js";
|
|
4
|
+
/** True when `slice` owns at least one `public` command, event or view. */
|
|
5
|
+
export declare function slicePublicTouching(slice: Slice): boolean;
|
|
6
|
+
/** `slicePublicTouching` by export key; false for an unknown key. */
|
|
7
|
+
export declare function isPublicTouchingSlice(model: NormalizedModel, refs: RefsResult, sliceKey: string): boolean;
|
|
8
|
+
/** What the contract-currency check needs beyond the compiled model: the `.em` path as the
|
|
9
|
+
* caller was given it (the contract path is built from it, as `em api generate` does), its
|
|
10
|
+
* source text, and the compile diagnostics. */
|
|
11
|
+
export interface ContractCheckInput {
|
|
12
|
+
file: string;
|
|
13
|
+
source: string;
|
|
14
|
+
diagnostics: Diagnostic[];
|
|
15
|
+
}
|
|
16
|
+
export type ContractState = "current" | "missing" | "stale";
|
|
17
|
+
export interface ContractStatus {
|
|
18
|
+
state: ContractState;
|
|
19
|
+
/** The contract path, built from `file` exactly as `em api generate` builds it. */
|
|
20
|
+
contractPath: string;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Is the committed contract current? `missing` when the file does not exist; `stale` when its
|
|
24
|
+
* text differs from the freshly generated text — or when the model cannot be generated from at
|
|
25
|
+
* all (a public field with no resolvable type raises `public-field-type-unresolved`, which
|
|
26
|
+
* `em api generate` refuses on; the contract cannot be current then either).
|
|
27
|
+
*/
|
|
28
|
+
export declare function contractStatus(model: NormalizedModel, refs: RefsResult, input: ContractCheckInput): ContractStatus;
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
// The API-first design exit gate (MIL-238, briefing R10): the two facts `em validate
|
|
3
|
+
// --slice-ready`, `em slice ratify`/`reratify` and `em status` all share.
|
|
4
|
+
//
|
|
5
|
+
// - "public-touching slice": a slice owning at least one `public` command, event or view —
|
|
6
|
+
// the elements `em api generate` puts on the model's contract (emit/api.ts).
|
|
7
|
+
// - "contract current": `<modelDir>/contracts/<modelKey>.tsp` exists and its text equals what
|
|
8
|
+
// `em api generate` would write from the current model. A plain text comparison (R12): the
|
|
9
|
+
// generated header carries no source hash, so an internal-only `.em` edit never makes the
|
|
10
|
+
// contract stale. Generation reuses `generateContract` (src/cli/api.ts) in-process — the same
|
|
11
|
+
// builder `em api generate`/`em api check` call, never a shell-out.
|
|
12
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
13
|
+
import { computeModelKey } from "../model/qualifiedRef.js";
|
|
14
|
+
import { contractPathFor, generateContract } from "../cli/api.js";
|
|
15
|
+
const CONTRACT_KINDS = new Set(["command", "event", "view"]);
|
|
16
|
+
/** True when `slice` owns at least one `public` command, event or view. */
|
|
17
|
+
export function slicePublicTouching(slice) {
|
|
18
|
+
return slice.elements.some((el) => el.public === true && CONTRACT_KINDS.has(el.kind));
|
|
19
|
+
}
|
|
20
|
+
/** `slicePublicTouching` by export key; false for an unknown key. */
|
|
21
|
+
export function isPublicTouchingSlice(model, refs, sliceKey) {
|
|
22
|
+
const i = refs.sliceKeys.indexOf(sliceKey);
|
|
23
|
+
return i !== -1 && slicePublicTouching(model.slices[i]);
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Is the committed contract current? `missing` when the file does not exist; `stale` when its
|
|
27
|
+
* text differs from the freshly generated text — or when the model cannot be generated from at
|
|
28
|
+
* all (a public field with no resolvable type raises `public-field-type-unresolved`, which
|
|
29
|
+
* `em api generate` refuses on; the contract cannot be current then either).
|
|
30
|
+
*/
|
|
31
|
+
export function contractStatus(model, refs, input) {
|
|
32
|
+
// generateContract reads only model/refs/diagnostics from its compile argument.
|
|
33
|
+
const compiled = { model, refs, diagnostics: input.diagnostics };
|
|
34
|
+
let generated;
|
|
35
|
+
try {
|
|
36
|
+
generated = generateContract(input.file, input.source, compiled);
|
|
37
|
+
}
|
|
38
|
+
catch {
|
|
39
|
+
// Generation needs a resolvable public type per field; without one the contract can't be
|
|
40
|
+
// regenerated, so it can't be current. Path is still the conventional one.
|
|
41
|
+
const fallback = contractPathFor(input.file, computeModelKey(model, input.file));
|
|
42
|
+
return { state: existsSync(fallback) ? "stale" : "missing", contractPath: fallback };
|
|
43
|
+
}
|
|
44
|
+
if (!existsSync(generated.contractPath))
|
|
45
|
+
return { state: "missing", contractPath: generated.contractPath };
|
|
46
|
+
const committed = readFileSync(generated.contractPath, "utf8");
|
|
47
|
+
return { state: committed === generated.text ? "current" : "stale", contractPath: generated.contractPath };
|
|
48
|
+
}
|
|
49
|
+
//# sourceMappingURL=apiFirst.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"apiFirst.js","sourceRoot":"","sources":["../../src/catalog/apiFirst.ts"],"names":[],"mappings":"AAAA,+BAA+B;AAC/B,qFAAqF;AACrF,0EAA0E;AAC1E,EAAE;AACF,4FAA4F;AAC5F,gFAAgF;AAChF,+FAA+F;AAC/F,8FAA8F;AAC9F,6FAA6F;AAC7F,iGAAiG;AACjG,uEAAuE;AAEvE,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAInD,OAAO,EAAE,eAAe,EAAE,MAAM,0BAA0B,CAAC;AAC3D,OAAO,EAAE,eAAe,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;AAElE,MAAM,cAAc,GAAwB,IAAI,GAAG,CAAC,CAAC,SAAS,EAAE,OAAO,EAAE,MAAM,CAAC,CAAC,CAAC;AAElF,2EAA2E;AAC3E,MAAM,UAAU,mBAAmB,CAAC,KAAY;IAC9C,OAAO,KAAK,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,CAAC,MAAM,KAAK,IAAI,IAAI,cAAc,CAAC,GAAG,CAAC,EAAE,CAAC,IAAI,CAAC,CAAC,CAAC;AACxF,CAAC;AAED,qEAAqE;AACrE,MAAM,UAAU,qBAAqB,CAAC,KAAsB,EAAE,IAAgB,EAAE,QAAgB;IAC9F,MAAM,CAAC,GAAG,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;IAC3C,OAAO,CAAC,KAAK,CAAC,CAAC,IAAI,mBAAmB,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC;AAC1D,CAAC;AAmBD;;;;;GAKG;AACH,MAAM,UAAU,cAAc,CAAC,KAAsB,EAAE,IAAgB,EAAE,KAAyB;IAChG,gFAAgF;IAChF,MAAM,QAAQ,GAAG,EAAE,KAAK,EAAE,IAAI,EAAE,WAAW,EAAE,KAAK,CAAC,WAAW,EAAuD,CAAC;IACtH,IAAI,SAA8C,CAAC;IACnD,IAAI,CAAC;QACH,SAAS,GAAG,gBAAgB,CAAC,KAAK,CAAC,IAAI,EAAE,KAAK,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC;IACnE,CAAC;IAAC,MAAM,CAAC;QACP,yFAAyF;QACzF,2EAA2E;QAC3E,MAAM,QAAQ,GAAG,eAAe,CAAC,KAAK,CAAC,IAAI,EAAE,eAAe,CAAC,KAAK,EAAE,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC;QACjF,OAAO,EAAE,KAAK,EAAE,UAAU,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,SAAS,EAAE,YAAY,EAAE,QAAQ,EAAE,CAAC;IACvF,CAAC;IACD,IAAI,CAAC,UAAU,CAAC,SAAS,CAAC,YAAY,CAAC;QAAE,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE,YAAY,EAAE,SAAS,CAAC,YAAY,EAAE,CAAC;IAC3G,MAAM,SAAS,GAAG,YAAY,CAAC,SAAS,CAAC,YAAY,EAAE,MAAM,CAAC,CAAC;IAC/D,OAAO,EAAE,KAAK,EAAE,SAAS,KAAK,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,OAAO,EAAE,YAAY,EAAE,SAAS,CAAC,YAAY,EAAE,CAAC;AAC7G,CAAC"}
|