@milehimikey/em 1.13.1 → 1.14.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/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 +126 -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 +1 -1
- package/.claude/skills/event-modeling-shared/reference/em-dsl.md +274 -189
- package/.claude/skills/event-modeling-shared/reference/operating-principles.md +61 -0
- package/.claude/skills/event-modeling-shared/reference/slice-doc-schema.md +108 -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/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 +3 -2
- package/dist/cli/reratify.js +36 -14
- 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 +554 -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 +866 -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 +566 -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 +297 -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
|
@@ -0,0 +1,566 @@
|
|
|
1
|
+
<!-- GENERATED FILE from .claude/skills/event-modeling-shared/reference/slice-doc-schema.md by scripts/build-plugin.ts — do not hand-edit; run npm run docs:generate -->
|
|
2
|
+
<!-- GENERATED FILE — do not hand-edit. Synced from docs/slice-doc-schema.md by
|
|
3
|
+
scripts/generate-skill-docs.ts (`npm run docs:generate`); `npm run docs:check` fails
|
|
4
|
+
on drift. A few links below are rewritten for this vendored location (see that
|
|
5
|
+
script's buildVendoredSliceDocSchema for exactly what changes and why) — everything
|
|
6
|
+
else is identical to the source doc. -->
|
|
7
|
+
|
|
8
|
+
# Slice-doc frontmatter schema
|
|
9
|
+
|
|
10
|
+
This documents the machine-read YAML frontmatter dialect `src/catalog/sliceDoc.ts` parses out
|
|
11
|
+
of `slices/<slice-key>.md` — the contract `em export`'s slice-frontmatter join reads. It covers
|
|
12
|
+
the **frontmatter block only**. Body/prose authoring conventions (Intent, Scenarios, Open
|
|
13
|
+
Questions, …) live in the event-modeling skill's
|
|
14
|
+
[`templates/slice.md`](../templates/slice.md), not here — with two
|
|
15
|
+
exceptions:
|
|
16
|
+
|
|
17
|
+
- The `## Open Questions` section's GFM checkboxes (`- [ ]` / `- [x]`) *are* now machine-parsed
|
|
18
|
+
by `sliceDoc.ts` too (`openQuestionsTotal`/`openQuestionsUnchecked`, MIL-87), feeding
|
|
19
|
+
`em validate --slice-ready <key>` (see [validation.md#slice-readiness](https://github.com/milehimikey/em/blob/main/docs/validation.md#slice-readiness)).
|
|
20
|
+
The section's cross-version lifecycle (MIL-156) is documented here too, alongside `## Delta`'s
|
|
21
|
+
— see [Open Questions section: lifecycle](#open-questions-section-lifecycle) below. Everything
|
|
22
|
+
else about the section's authoring — what a good open question looks like, when to resolve one
|
|
23
|
+
— stays owned by the skill template; this doc only covers the counting mechanics and lifecycle.
|
|
24
|
+
- The `## Delta` section's grammar and lifecycle (MIL-88) is documented here, in full, alongside
|
|
25
|
+
the lineage keys it complements — a 2026-08-14 ruling deliberately kept the slice-doc contract
|
|
26
|
+
as one document rather than splitting a structurally-related convention across two files, even
|
|
27
|
+
though (unlike Open Questions) `## Delta` is currently display-only prose that no `em` command
|
|
28
|
+
parses yet. See [Delta section: grammar and lifecycle](#delta-section-grammar-and-lifecycle)
|
|
29
|
+
below; authoring voice/tone for the section still lives in the skill template, same as every
|
|
30
|
+
other body section.
|
|
31
|
+
|
|
32
|
+
## How the parser reads frontmatter
|
|
33
|
+
|
|
34
|
+
A leading `---` / `---` fence, at the literal start of the file (like Jekyll/Hugo — a guidance
|
|
35
|
+
comment above the fence means the frontmatter doesn't count yet). Inside the fence: top-level
|
|
36
|
+
scalar `key: value` lines only — **no lists, no nesting, no real YAML**. Quoted values have
|
|
37
|
+
their outer quotes stripped; a value that's empty after trimming is dropped. Keys are matched
|
|
38
|
+
case-sensitively as `[A-Za-z][\w-]*` but folded to lowercase for lookup. Any line that doesn't
|
|
39
|
+
match that shape — including `#`-prefixed comment lines, blank lines, and YAML list
|
|
40
|
+
continuation lines (` - item`) — is silently skipped. An unterminated fence (no closing `---`)
|
|
41
|
+
means "no frontmatter at all": the whole file is treated as body.
|
|
42
|
+
|
|
43
|
+
No `yaml` dependency, by design — a real YAML parser was added and reverted (commit `6de0a05`,
|
|
44
|
+
"keep the Two Laws of the Timeline"). This is a deliberately shallow line-scan, and stays that
|
|
45
|
+
way: real YAML would also start accepting nesting/lists that this schema explicitly doesn't
|
|
46
|
+
define a meaning for.
|
|
47
|
+
|
|
48
|
+
`SliceDoc` (`src/catalog/sliceDoc.ts`) also exposes `frontmatterPresent` (was a well-formed
|
|
49
|
+
fence found and closed at all) and `missingRequiredFields` (which of the required-at-every-
|
|
50
|
+
status keys, above, were absent) — the mechanical basis `em export`'s doc join (MIL-91) uses
|
|
51
|
+
to decide `frontmatter-invalid` without re-deriving frontmatter-shape rules of its own.
|
|
52
|
+
|
|
53
|
+
## Canonical keys
|
|
54
|
+
|
|
55
|
+
| Key | Type | Grammar / enum | Read by |
|
|
56
|
+
|---|---|---|---|
|
|
57
|
+
| `schemaVersion` | integer | `1` (current dialect version) | its *value* is not read back by any `em` command today (reserved) — but its *presence* is required by `hasUsableFrontmatter()`: a doc omitting it is `frontmatter-invalid` (see below) |
|
|
58
|
+
| `pattern` | string | `state-change` \| `state-view` \| `automation` \| `translation` | authored/informational only — `em catalog` and `em export` both derive pattern from the `.em` AST instead (`em export`'s `slice.pattern`, schema `1.4`) |
|
|
59
|
+
| `swimlane` | string | free text, `<Persona> → <Context>` | display-only |
|
|
60
|
+
| `status` | string, case-insensitive | `draft` \| `reviewed` \| `ready-to-implement` \| `implemented` | written by `em slice review` (→ `reviewed`, MIL-201), `em slice ratify`/`em slice reratify` (→ `ready-to-implement`) and `em slice mark-implemented` (→ `implemented`); read by `em catalog`, `em render`/`em watch` header coloring; joined into `em export`'s `slice.doc.status` (schema `1.4`); paired with `implementedIn` to compute `slice.doc.driftSignal` (schema `1.5`, MIL-85) and `em validate`'s frontmatter-coherence check (MIL-85 — see [validation.md#frontmatter-coherence](https://github.com/milehimikey/em/blob/main/docs/validation.md#frontmatter-coherence)) |
|
|
61
|
+
| `version` | integer | positive integer, starts at `1` | joined into `em export`'s `slice.doc.version` (schema `1.4`, MIL-91) |
|
|
62
|
+
| `implementedIn` | string | free text (PR/commit link) | joined into `em export`'s `slice.doc.implementedIn` (schema `1.4`, MIL-91); paired with `status` to compute `slice.doc.driftSignal` (schema `1.5`, MIL-85) and `em validate`'s frontmatter-coherence check (MIL-85 — see [validation.md#frontmatter-coherence](https://github.com/milehimikey/em/blob/main/docs/validation.md#frontmatter-coherence)) |
|
|
63
|
+
| `split-from` | single ref | `<slice-key>@v<N>` | joined into `em export`'s `slice.doc.splitFrom` (schema `1.4`, MIL-91) and `em diff`'s `slice-added` entries (schema `1.6`, MIL-84); current-tree referential checks by `em validate` (MIL-84 — see [validation.md#lineage](https://github.com/milehimikey/em/blob/main/docs/validation.md#lineage)) |
|
|
64
|
+
| `merged-from` | list of refs | comma-separated `<slice-key>@v<N>, ...` | joined into `em export`'s `slice.doc.mergedFrom` (schema `1.4`, MIL-91) and `em diff`'s `slice-added` entries (schema `1.6`, MIL-84); current-tree referential checks by `em validate` (MIL-84 — see [validation.md#lineage](https://github.com/milehimikey/em/blob/main/docs/validation.md#lineage)) |
|
|
65
|
+
| `superseded-by` | list of refs | comma-separated `<slice-key>@v<N>, ...` | joined into `em export`'s `slice.doc.supersededBy` (schema `1.4`, MIL-91) and `em diff`'s `slice-removed` entries (schema `1.6`, MIL-84); current-tree referential checks by `em validate` (MIL-84 — see [validation.md#lineage](https://github.com/milehimikey/em/blob/main/docs/validation.md#lineage)) |
|
|
66
|
+
| `covers` | list of slice keys | comma-separated `<slice-key>, ...` (plain keys — **not** the `<slice-key>@v<N>` ref grammar above) | ratifies a cross-slice binding (MIL-121 — see [Cross-slice coverage](#cross-slice-coverage-covers) below): read by `em export`'s doc join, `em validate --slice-ready`, and (best-effort) `em render`/`em watch`'s Slice Status legend |
|
|
67
|
+
| `reviewedBy` | string | free text (typically the facilitator's or reviewer's name) | written only by `em slice review` (MIL-201 — see [cli.md](https://github.com/milehimikey/em/blob/main/docs/cli.md#em-slice-review-file-slice-key---by-name)); joined into `em export`'s `slice.doc.reviewedBy` (schema `1.11`) and `em slice index`'s Reviewed by column |
|
|
68
|
+
| `reviewedOn` | string | `YYYY-MM-DD` | written only by `em slice review` (MIL-201); joined into `em export`'s `slice.doc.reviewedOn` (schema `1.11`) |
|
|
69
|
+
| `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 |
|
|
70
|
+
| `ratifiedOn` | string | `YYYY-MM-DD` | written only by `em slice ratify` (MIL-165); joined into `em export`'s `slice.doc.ratifiedOn` (schema `1.8`) |
|
|
71
|
+
| `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) |
|
|
72
|
+
| `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) |
|
|
73
|
+
| `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[]` |
|
|
74
|
+
| `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 |
|
|
75
|
+
| `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 |
|
|
76
|
+
| `conformedAt` | string | free text (a target-repo revision, NOT a `<slice-key>@v<N>` lineage ref) | written only by `em slice conform` (MIL-214): the revision that certification sweep diffed against. Joined into `em export`'s `slice.doc.conformedAt` (schema `1.13`) and `em slice index`'s Conformed column |
|
|
77
|
+
| `conformedOn` | string | `YYYY-MM-DD` | written only by `em slice conform` (MIL-214). Joined into `em export`'s `slice.doc.conformedOn` (schema `1.13`) |
|
|
78
|
+
|
|
79
|
+
Five keys — `schemaVersion`/`pattern`/`swimlane`/`status`/`version` — are what
|
|
80
|
+
`hasUsableFrontmatter()` requires: a doc omitting any of them is `frontmatter-invalid` to
|
|
81
|
+
`em export`'s doc join and to `em validate --slice-ready` (the parser itself never *throws*
|
|
82
|
+
over an omission — it reports). `implementedIn` and the lineage keys are the genuinely
|
|
83
|
+
optional ones; `implementedIn`'s required-once-implemented rule in the table below is
|
|
84
|
+
convention, not parser-enforced.
|
|
85
|
+
|
|
86
|
+
## Required vs optional, by `status`
|
|
87
|
+
|
|
88
|
+
| Key | draft | reviewed | ready-to-implement | implemented |
|
|
89
|
+
|---|---|---|---|---|
|
|
90
|
+
| `schemaVersion`, `pattern`, `swimlane`, `status` | required | required | required | required |
|
|
91
|
+
| `version` | required (starts at `1`) | required | required | required |
|
|
92
|
+
| `implementedIn` | optional | optional | optional | required once the slice has *ever* reached `implemented` — may still name a **prior** version's PR during re-ratification, see below |
|
|
93
|
+
| `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 |
|
|
94
|
+
| `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 |
|
|
95
|
+
| `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) |
|
|
96
|
+
| `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 |
|
|
97
|
+
| `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`) |
|
|
98
|
+
| `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) |
|
|
99
|
+
| `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) |
|
|
100
|
+
|
|
101
|
+
`em export`'s slice-doc join (MIL-91) is the first `em` command to mechanically check the
|
|
102
|
+
required row above: a bound doc missing any of `schemaVersion`/`pattern`/`swimlane`/`status`/
|
|
103
|
+
`version` (or missing a frontmatter block entirely) reports `slice.doc.reason:
|
|
104
|
+
"frontmatter-invalid"` plus a warning diagnostic — report, never fail, same as every other
|
|
105
|
+
export finding. `implementedIn`'s conditional requirement (once a slice has *ever* reached
|
|
106
|
+
`implemented`) is **not** checked — that needs git history no doc-only parse has access to.
|
|
107
|
+
No `em` command fails a build over this table. `em validate`'s frontmatter-coherence check
|
|
108
|
+
(MIL-85 — see [validation.md#frontmatter-coherence](https://github.com/milehimikey/em/blob/main/docs/validation.md#frontmatter-coherence)) warns
|
|
109
|
+
(never fails) on the one combination checkable without git history: `status: implemented` with
|
|
110
|
+
no `implementedIn` link at all.
|
|
111
|
+
|
|
112
|
+
## Stub docs
|
|
113
|
+
|
|
114
|
+
`em slice new --stub` and `em slice stub-all` (MIL-184, ruled 2026-09-08 on GH #128 — see that
|
|
115
|
+
issue for the fuller design rationale) write a **stub**: the exact same 5 required-at-`status:
|
|
116
|
+
draft` frontmatter keys above, but a one-line placeholder body (`_Stub — deepen with the slice
|
|
117
|
+
phase (see slice-doc-schema.md)._`) instead of the diagram-image stub and every judgment section
|
|
118
|
+
(Intent, Command, Scenarios, Open Questions, …) a full slice doc eventually carries.
|
|
119
|
+
|
|
120
|
+
A stub is not a lesser doc, frontmatter-wise — it's a real doc with a real `status`, parsed and
|
|
121
|
+
joined by `em export`/`em catalog`/`em status`/`em slice index` exactly like a fully-written one,
|
|
122
|
+
because it's the same frontmatter dialect. The doc stays the single source of truth for
|
|
123
|
+
`status`; a stub is just the cheapest possible way to give a slice one before anyone's ready to
|
|
124
|
+
write the real spec.
|
|
125
|
+
|
|
126
|
+
**A stub CAN pass `em validate --slice-ready`.** That gate checks six things (`computeSliceReadyGates`,
|
|
127
|
+
[validation.md#slice-readiness](https://github.com/milehimikey/em/blob/main/docs/validation.md#slice-readiness)): a doc is bound, its frontmatter
|
|
128
|
+
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
|
|
129
|
+
stub with no `## Open Questions` section at all has `openQuestionsTotal: 0` (see
|
|
130
|
+
[Open Questions section: lifecycle](#open-questions-section-lifecycle) below) — zero unchecked is
|
|
131
|
+
vacuously true — so a stub escalated to `ready-to-implement` (`em slice stub-all --status
|
|
132
|
+
ready-to-implement --by <name>`, which records the ratifier) passes cleanly. The readiness gate is frontmatter-plus-checkbox-
|
|
133
|
+
count only; it has no opinion about whether Intent/Command/Scenarios/Invariants were ever
|
|
134
|
+
written. Demanding that content is the `slice` phase's (and, for a live walkthrough, the review
|
|
135
|
+
skill's) job, not a machine gate's — see `../../skills/design/SKILL.md` and
|
|
136
|
+
`../../skills/review/SKILL.md`.
|
|
137
|
+
|
|
138
|
+
## Lineage: grammar and cardinality
|
|
139
|
+
|
|
140
|
+
Grammar: `<slice-key>@v<N>` — `<slice-key>` is the referenced slice's kebab-case filename stem
|
|
141
|
+
(matches `kebabSlug()`, `src/util/slug.ts`); `<N>` is a positive integer, the referenced
|
|
142
|
+
slice's `version` at the point of reference.
|
|
143
|
+
|
|
144
|
+
| Key | Cardinality | Why |
|
|
145
|
+
|---|---|---|
|
|
146
|
+
| `split-from` | **single** ref | A slice splits *from* exactly one parent, even though a split can produce multiple successors. |
|
|
147
|
+
| `merged-from` | **list**, comma-separated | A merge combines two or more source slices into one surviving doc. |
|
|
148
|
+
| `superseded-by` | **list**, comma-separated | The retired side of a split names potentially multiple successors; the retired side of a merge or a simple rename usually names exactly one, but the grammar stays list-shaped either way. |
|
|
149
|
+
|
|
150
|
+
**Worked example** (a split): `checkout` v4 splits into `checkout` v5 plus a new
|
|
151
|
+
`apply-discount` v1. The retired parent doc, `checkout.md`, gets:
|
|
152
|
+
|
|
153
|
+
```yaml
|
|
154
|
+
version: 4
|
|
155
|
+
status: implemented
|
|
156
|
+
superseded-by: checkout@v5, apply-discount@v1
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
Both surviving docs carry the back-link — `checkout.md` (now at v5) and `apply-discount.md`
|
|
160
|
+
each get:
|
|
161
|
+
|
|
162
|
+
```yaml
|
|
163
|
+
split-from: checkout@v4
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
**Scope note:** the parser (`sliceDoc.ts`) itself still never checks that a referenced
|
|
167
|
+
slice/version actually *exists* — that stays deliberately out of scope here, by design (fs-free,
|
|
168
|
+
pure). A malformed or dangling ref still parses (its `raw` text is preserved, `sliceKey`/
|
|
169
|
+
`version` come back `null` for a malformed value); referential checking is a separate, fs-aware
|
|
170
|
+
layer.
|
|
171
|
+
|
|
172
|
+
`em validate` (`src/catalog/lineageValidate.ts`, MIL-84) is that layer. It checks what the
|
|
173
|
+
*current tree* can prove wrong and stays silent about what it can't — see
|
|
174
|
+
[validation.md#lineage](https://github.com/milehimikey/em/blob/main/docs/validation.md#lineage) for exactly what's checked (grammar,
|
|
175
|
+
self-reference/cycles, dangling `superseded-by`, impossible version arithmetic) versus what's
|
|
176
|
+
deliberately never flagged (a `split-from`/`merged-from` naming a key legitimately absent from
|
|
177
|
+
the current tree — the normal state after a real split/merge). Deep historical verification —
|
|
178
|
+
did `slice@vN` really, once, exist? — stays out of core validate; the reason that's sufficient
|
|
179
|
+
is a same-commit authoring convention, not new plumbing (`em ledger`, below, is the opt-in,
|
|
180
|
+
two-revision command for the related-but-different version/content invariant):
|
|
181
|
+
|
|
182
|
+
> Lineage refs (`split-from:`/`merged-from:`/`superseded-by:`) are written in the same
|
|
183
|
+
> ratification commit that performs the operation they record, so the PR diff contains both
|
|
184
|
+
> sides of the claim (the predecessor at its final version, and the ref naming it). The review
|
|
185
|
+
> airlock is the history check.
|
|
186
|
+
|
|
187
|
+
## Cross-slice coverage: `covers`
|
|
188
|
+
|
|
189
|
+
Since `em` 1.7.1 (MIL-120), the canonical Automation/Translation shape is two raw slices: a
|
|
190
|
+
bare `view` slice, and the reaction+command+event slice that reads it. Each slice can still bind
|
|
191
|
+
its own `slices/<key>.md` doc the ordinary way, but a team documenting the whole design unit as
|
|
192
|
+
**one** doc needs a way to make the view-only slice count as doc-bound too, without splitting
|
|
193
|
+
the write-up back into two files. `covers` (MIL-121) is that escape hatch — a deliberately
|
|
194
|
+
two-ended handshake, not a one-sided claim:
|
|
195
|
+
|
|
196
|
+
1. In the `.em`, an element in the *covered* slice (the one with no doc of its own) carries
|
|
197
|
+
`note "slices/<other-key>.md"` — a path in the ordinary `slices/<key>.md` shape, just naming
|
|
198
|
+
a *different* slice's canonical doc instead of its own.
|
|
199
|
+
2. That other doc's own frontmatter ratifies the coverage with `covers`, naming the covered
|
|
200
|
+
slice's key back — same comma-separated, single scalar-line shape as `merged-from`/
|
|
201
|
+
`superseded-by` (this parser's frontmatter dialect has **no** real YAML list support, see
|
|
202
|
+
above):
|
|
203
|
+
```yaml
|
|
204
|
+
covers: detect-unpaid-orders
|
|
205
|
+
```
|
|
206
|
+
Multiple covered slices: `covers: detect-unpaid-orders, some-other-slice`.
|
|
207
|
+
|
|
208
|
+
Both sides are required. A `note` naming another slice's doc with no matching `covers` entry on
|
|
209
|
+
that doc — the file is missing, its frontmatter isn't usable, or `covers` doesn't list this
|
|
210
|
+
slice back — leaves the noting slice exactly as unbound as if it had no note at all
|
|
211
|
+
(`no-doc-bound`); em never guesses at a one-sided claim. When ratified, every consumer that
|
|
212
|
+
reads a bound doc's canonical fields (`em export`'s `slice.doc`, `em validate --slice-ready`,
|
|
213
|
+
`em render`/`em watch`'s Slice Status legend) reads the **covering** doc's `status`/`version`/
|
|
214
|
+
`implementedIn`/lineage/Open Questions, not the covered slice's own (nonexistent) file. A doc
|
|
215
|
+
covers its own canonical slice key implicitly — `covers` never needs to list it.
|
|
216
|
+
|
|
217
|
+
`em catalog` (MIL-137) is one such consumer, via the same filename+frontmatter `covers:` scan
|
|
218
|
+
(`src/catalog/coverage.ts`), never the `note` clause itself: a covered slice's detail page shows
|
|
219
|
+
the covering doc's Status, but a "Documented as part of <covering slice>" banner instead
|
|
220
|
+
of the covering doc's body — linking to the covering slice's own catalog page rather than
|
|
221
|
+
inlining content authored under a different slice's name.
|
|
222
|
+
|
|
223
|
+
`covers` is a plain list of slice keys (`<slice-key>`, e.g. `detect-unpaid-orders`), **not** the
|
|
224
|
+
`<slice-key>@v<N>` ref grammar the lineage keys use above: coverage is a standing "this doc also
|
|
225
|
+
serves that slice" declaration, with no version component — unlike lineage, which records a
|
|
226
|
+
specific historical hop.
|
|
227
|
+
|
|
228
|
+
**Out of scope for THIS join:** `resolveSliceDocJoin` itself never warns on a *mismatched*
|
|
229
|
+
cross-note (a note naming a doc that doesn't ratify it back) — the slice's doc join still stays
|
|
230
|
+
silently `no-doc-bound`, the same as never having written the note; the join's own job is
|
|
231
|
+
resolving the *winning* binding, not auditing every note that didn't win. `em validate` does flag
|
|
232
|
+
the mismatch, as its own separate rule (MIL-126) — see
|
|
233
|
+
[validation.md#note-binding-mismatch](https://github.com/milehimikey/em/blob/main/docs/validation.md#note-binding-mismatch): a dangling cross-note
|
|
234
|
+
(missing file), one pointing at a doc with unusable frontmatter, one pointing at a usable doc
|
|
235
|
+
that just doesn't ratify (`covers` doesn't list this slice back), and an extra note doing nothing
|
|
236
|
+
in a slice that's already bound elsewhere, each get their own diagnostic there.
|
|
237
|
+
|
|
238
|
+
## Continuations
|
|
239
|
+
|
|
240
|
+
Since `em` 1.12.0 (MIL-208): a `view X again` instance is a **continuation** of the slice
|
|
241
|
+
holding `X`'s first (originating) declaration — one read model, fed by more than one event over
|
|
242
|
+
the timeline — not a spec unit of its own. Unlike `covers` above, this is fully automatic and
|
|
243
|
+
structural: no frontmatter, no `note`, nothing to author. A slice is a continuation iff every
|
|
244
|
+
non-`ui` element it declares is a `view` with `again === true`; the doc join resolves it
|
|
245
|
+
straight through to the originating slice's own doc, exactly the way a ratified `covers:`
|
|
246
|
+
binding resolves a covered slice — same `doc.found`/`status`/`driftSignal`, plus a
|
|
247
|
+
`continuationOf: <originating-key>` field naming why. `em export`, `em status`, `em coverage`,
|
|
248
|
+
`em slice index`, `em catalog`, and the render pipeline's Slice Status legend all resolve a
|
|
249
|
+
continuation slice this way; `em slice ratify`/`review`/`reratify`/`mark-implemented` and
|
|
250
|
+
`em slice new --wire` refuse on one outright, naming the originating slice to act on instead —
|
|
251
|
+
a continuation slice has no status/ratification of its own to flip.
|
|
252
|
+
|
|
253
|
+
The originating slice's own `em export` object also gains `alsoReads: [{ event, atSlice }]` —
|
|
254
|
+
the union of events every later `again` instance of that view reads via `from`, in timeline
|
|
255
|
+
order — so implementing the read model from one export never means walking every `again`
|
|
256
|
+
slice's doc by hand to assemble the full event list.
|
|
257
|
+
|
|
258
|
+
**Authoring convention:** a continuation's behavior belongs in the originating doc's own
|
|
259
|
+
`## Scenarios (Given / When / Then)` section — one scenario per event a later `again` instance
|
|
260
|
+
feeds — rather than in a doc of its own; no new frontmatter is needed to record it.
|
|
261
|
+
|
|
262
|
+
**Legacy own doc wins.** A slice that would otherwise be a continuation but still carries its
|
|
263
|
+
own doc — a `note "slices/<again-key>.md"` binding it, or a stray `slices/<again-key>.md` file
|
|
264
|
+
left over from before this rule existed — keeps today's per-instance behavior unchanged: its own
|
|
265
|
+
doc, its own status, its own ratification. `em validate`'s `continuation-has-own-doc` warning
|
|
266
|
+
(see [validation.md](https://github.com/milehimikey/em/blob/main/docs/validation.md)) flags it either way, so an author can fold that doc's
|
|
267
|
+
scenarios into `slices/<originating-key>.md` and delete it. This is the migration path — an
|
|
268
|
+
existing model with per-`again`-instance docs keeps working exactly as before until each one is
|
|
269
|
+
folded in.
|
|
270
|
+
|
|
271
|
+
## Delta section: grammar and lifecycle
|
|
272
|
+
|
|
273
|
+
`## Delta` is the body section that pairs with a re-ratification (see "`status` under
|
|
274
|
+
re-ratification" below) — a structured record of what changed to produce the version currently
|
|
275
|
+
in the frontmatter, in typed operation blocks (MIL-88) rather than a one-line summary. Like the
|
|
276
|
+
lineage keys above, it's currently authored, not parsed — no `em` command reads *this section*
|
|
277
|
+
— but it's documented here rather than left purely to the skill template because its shape is a
|
|
278
|
+
structural contract other tooling may parse later, same reasoning as the lineage grammar.
|
|
279
|
+
`em diff --json` now emits the same four-word vocabulary on its own structural entries (`op:
|
|
280
|
+
"Added" | "Modified" | "Removed" | "Renamed"`, schema `1.7`, MIL-131 — see
|
|
281
|
+
[cli.md](https://github.com/milehimikey/em/blob/main/docs/cli.md#em-diff-old-new)), so the two sides speak the same language; comparing an
|
|
282
|
+
authored `## Delta` against the structural diff of the revisions it claims to describe is still
|
|
283
|
+
a separate, not-yet-built decision.
|
|
284
|
+
|
|
285
|
+
**The heading is always the literal string `## Delta` — never a variable heading** (e.g. never
|
|
286
|
+
`## Delta: v1 → v2`). Several consumers key on exact `##` text when locating sections in a slice
|
|
287
|
+
doc (the SDD-bridge's section splitter, the slice-to-spec mapping table, the redirect prompt
|
|
288
|
+
preambles); a heading that changes every ratification would break exact-match lookup and move
|
|
289
|
+
the anchor on every hop — the same problem a made-up per-doc key would cause anywhere else in
|
|
290
|
+
this schema. Version range, ratification date, and a one-line summary go on the **first line
|
|
291
|
+
inside** the section instead, as display text — not a frontmatter key, not parsed:
|
|
292
|
+
|
|
293
|
+
```markdown
|
|
294
|
+
## Delta
|
|
295
|
+
**v1 → v2, ratified 2026-08-12** — one-line summary of the ratified change.
|
|
296
|
+
|
|
297
|
+
### Added
|
|
298
|
+
#### Requirement: {{title}} ({{stable ID, e.g. an INV-<MNEMONIC>-n from ## Invariants}})
|
|
299
|
+
{{requirement text}}
|
|
300
|
+
##### Scenario: {{name}}
|
|
301
|
+
- **Given:** {{...}}
|
|
302
|
+
- **When:** {{...}}
|
|
303
|
+
- **Then:** {{...}}
|
|
304
|
+
|
|
305
|
+
### Modified
|
|
306
|
+
(same shape as Added — the ID names the requirement that changed)
|
|
307
|
+
|
|
308
|
+
### Removed
|
|
309
|
+
#### Requirement: {{title}} ({{stable ID}})
|
|
310
|
+
{{why it was removed}}
|
|
311
|
+
|
|
312
|
+
### Renamed
|
|
313
|
+
- {{old title}} ({{old ID}}) → {{new title}} ({{new ID}})
|
|
314
|
+
```
|
|
315
|
+
|
|
316
|
+
Stable IDs follow `INV-<MNEMONIC>-<n>` — a short (2-4 uppercase letters/digits) abbreviation of
|
|
317
|
+
the slice's key, unique among the model's slices, then a small integer, optionally letter-suffixed
|
|
318
|
+
for a closely-related sub-invariant (`INV-CHK-3a`) — see the Invariants section guidance in
|
|
319
|
+
[`templates/slice.md`](../templates/slice.md). Per-slice prefixes
|
|
320
|
+
make IDs globally unique across the model, so an `em coverage` citation is exact instead of
|
|
321
|
+
ambiguous across slices.
|
|
322
|
+
|
|
323
|
+
**Four `###` subsections, in that fixed order, Title Case singular words** — `Added`, `Modified`,
|
|
324
|
+
`Removed`, `Renamed` — not OpenSpec's shouting-case `ADDED Requirements` grouping. This is the
|
|
325
|
+
one deliberate divergence from the OpenSpec grammar the convention was validated against: the
|
|
326
|
+
four *operations* are retained (independent convergent design — OpenSpec arrived at the same
|
|
327
|
+
vocabulary from its own change-management-first angle), but OpenSpec's flat, file-per-delta
|
|
328
|
+
heading topology is not, because a slice doc's delta is a section inside a doc with its own
|
|
329
|
+
parsed heading contract, not a standalone file. Omit any of the four subsections with no entries
|
|
330
|
+
this hop — most re-ratifications touch one or two operations, not all four.
|
|
331
|
+
|
|
332
|
+
**`Renamed` is a requirement-level operation, not a slice-level one.** It records an invariant or
|
|
333
|
+
requirement *within this slice* being renamed or renumbered (e.g. `INV-CHK-3` → `INV-CHK-3a`) —
|
|
334
|
+
it is not a substitute for the `split-from`/`merged-from`/`superseded-by` lineage keys above,
|
|
335
|
+
which record the slice-doc-level operation of one doc splitting, merging, or being renamed into
|
|
336
|
+
another. `em diff`'s own `ChangeType` deliberately treats a slice/element rename as remove+add,
|
|
337
|
+
never a first-class rename (`src/model/diff.ts`: "a rename IS a model change") — `Renamed` here
|
|
338
|
+
is a finer grain, inside one otherwise-unchanged slice, and doesn't revisit that decision.
|
|
339
|
+
|
|
340
|
+
**Lifecycle: replace, never accumulate — the section is a window onto the latest hop, not a
|
|
341
|
+
scroll of every hop.** A slice's first version (`v1`) carries no `## Delta` section at all —
|
|
342
|
+
absence means no hop has happened yet, not an empty section. Ratifying `v2` writes the section
|
|
343
|
+
for the first time, in the same commit that bumps `version` and flips `status`. Ratifying `v3`
|
|
344
|
+
**overwrites** it with the `v2 → v3` hop; the `v1 → v2` content is not retained in the doc.
|
|
345
|
+
Full history is git's job — the same-commit ratification convention above makes `git log -p
|
|
346
|
+
slices/<name>.md` a precise, zero-maintenance delta archive — and a rolled-up multi-hop view, if
|
|
347
|
+
ever wanted, is a *rendering* (e.g. a future `em changelog`), never hand-maintained doc content.
|
|
348
|
+
An accumulating in-doc log would be a second history that diverges from git, grows unboundedly
|
|
349
|
+
over a slice's life, references requirements that have since moved (a *live* doc quietly going
|
|
350
|
+
stale reads as current, unlike git which is honestly historical), and forces every reader to
|
|
351
|
+
work out which delta is the live one — exactly the ambiguity `driftSignal` (MIL-85, below) is
|
|
352
|
+
built to avoid.
|
|
353
|
+
|
|
354
|
+
## Invariants
|
|
355
|
+
|
|
356
|
+
Since MIL-265 the **model is the preferred place to declare an invariant**: an `invariant
|
|
357
|
+
INV-<MNEMONIC>-<n> "rule"` line after the command or event it guards (see
|
|
358
|
+
[dsl.md](dsl.md#invariants)) carries the ID and the one-line rule sentence. The doc's
|
|
359
|
+
`## Invariants / Business Rules` section then **cites** each model-declared ID and elaborates —
|
|
360
|
+
why the rule exists, what a violation looks like, edge cases, the error returned:
|
|
361
|
+
|
|
362
|
+
```markdown
|
|
363
|
+
## Invariants / Business Rules
|
|
364
|
+
- INV-ORD-1 — a zero-total cart is rejected before payment is attempted
|
|
365
|
+
- the check runs on the server; the UI's own check is a convenience only
|
|
366
|
+
```
|
|
367
|
+
|
|
368
|
+
Write the citation as a plain mention (`- INV-ORD-1 — …`), not with the declaring label
|
|
369
|
+
`**INV-ORD-1:**` — restating a model-declared ID with that label is
|
|
370
|
+
`invariants/declared-in-both` (a warning pointing at the migration). Ownership follows the model:
|
|
371
|
+
a model-declared ID belongs to the slice of the element that declares it, for `em coverage` and
|
|
372
|
+
`em query invariant`, whatever any doc says.
|
|
373
|
+
|
|
374
|
+
**Doc declaration is still honoured.** A doc that declares its own invariants
|
|
375
|
+
(`- **INV-CHK-1:** rule`, or a 1.13 doc's bare `- **INV-1:** rule`) keeps working exactly as
|
|
376
|
+
before: for every ID the model does not declare, the doc-body reader (`## Invariants` and
|
|
377
|
+
`## Delta` sections, top-level bullet/subheading lines only — MIL-149/155) is the fallback.
|
|
378
|
+
A doc mentioning an `INV-…` ID that neither the model nor any doc declares gets
|
|
379
|
+
`invariants/doc-cites-undeclared`. See [validation.md](https://github.com/milehimikey/em/blob/main/docs/validation.md#invariants).
|
|
380
|
+
|
|
381
|
+
## Generated regions and authored sections
|
|
382
|
+
|
|
383
|
+
Since MIL-266 a slice doc has two kinds of section.
|
|
384
|
+
|
|
385
|
+
**Generated regions** restate the model, and `em` writes them. Each is wrapped in a pair of html
|
|
386
|
+
markers; `em slice new --wire` fills them from the `.em`, and `em slice sync <model>.em` refreshes
|
|
387
|
+
them in place after any model edit. Never hand-edit between the markers — change the model and
|
|
388
|
+
re-run `em slice sync`.
|
|
389
|
+
|
|
390
|
+
| Region | Section | Body |
|
|
391
|
+
|---|---|---|
|
|
392
|
+
| `em-slice-command` | `## Command / Input` | `**Command:** \`Name\`` + `\| Field \| Type \| Required \| Rules / Validation \|` (Required is `no` for a `name?: Type` field) |
|
|
393
|
+
| `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 `{{ }}`) |
|
|
394
|
+
| `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 `{{ }}`) |
|
|
395
|
+
| `em-slice-invariants` | `## Invariants / Business Rules` | one `- **INV-X** — rule` line per model-declared invariant of the slice's commands and events |
|
|
396
|
+
|
|
397
|
+
A slice with several elements of one kind gets one region per element, the name suffixed with
|
|
398
|
+
the element's slug (`em-slice-event-order-placed`). A kind the slice lacks keeps its one region
|
|
399
|
+
with a "has no …" line, so adding the first element later is a plain `em slice sync`. The
|
|
400
|
+
`**Command:**`/`**Event:**`/`- **View:**` marker lines sit inside the regions, so the
|
|
401
|
+
[doc-model consistency](https://github.com/milehimikey/em/blob/main/docs/validation.md#doc-model-consistency) check reads them as before (and,
|
|
402
|
+
inside a generated `em-slice-view` region, now compares the view's field table too).
|
|
403
|
+
|
|
404
|
+
```markdown
|
|
405
|
+
## Command / Input
|
|
406
|
+
<!-- GENERATED:em-slice-command:start — generated from the model by `em slice sync`; do not hand-edit -->
|
|
407
|
+
**Command:** `Submit Order`
|
|
408
|
+
|
|
409
|
+
| Field | Type | Required | Rules / Validation |
|
|
410
|
+
|-------|------|----------|--------------------|
|
|
411
|
+
| total | decimal | yes | — |
|
|
412
|
+
| note | text | no | — |
|
|
413
|
+
<!-- GENERATED:em-slice-command:end -->
|
|
414
|
+
```
|
|
415
|
+
|
|
416
|
+
The invariants list uses `**INV-X** — rule`, not the declaring label `**INV-X:**`, so a
|
|
417
|
+
generated list never trips `invariants/declared-in-both`. Elaborate each ID below the region
|
|
418
|
+
(after the `<!-- elaborate below this list; IDs are declared in the model -->` comment).
|
|
419
|
+
|
|
420
|
+
**Authored sections** are everything else — Intent, Trigger & Actor, Trigger, `**Read by:**`,
|
|
421
|
+
`Consumed by`/freshness, the invariant elaborations, Scenarios, Alternate & Error Flows, NFRs,
|
|
422
|
+
Dependencies, Open Questions. `em` never writes them; `em slice sync` leaves every byte outside
|
|
423
|
+
the region markers untouched (CRLF included).
|
|
424
|
+
|
|
425
|
+
**Scenario grammar (authored, constrained).** Inside `## Scenarios (Given / When / Then)`, each
|
|
426
|
+
case is a block:
|
|
427
|
+
|
|
428
|
+
```markdown
|
|
429
|
+
### Scenario: Non-positive total is rejected (INV-CHK-1)
|
|
430
|
+
- **Given:**
|
|
431
|
+
- a basket whose total is 0
|
|
432
|
+
- **When:**
|
|
433
|
+
- the customer submits the order
|
|
434
|
+
- **Then:**
|
|
435
|
+
- the command is rejected with a validation error
|
|
436
|
+
- no event is recorded
|
|
437
|
+
```
|
|
438
|
+
|
|
439
|
+
A `### Scenario: <title>` heading, then the three column-0 bullets `- **Given:**`, `- **When:**`,
|
|
440
|
+
`- **Then:**`, each with nested ` - ` items (text after the label on the same line counts as a
|
|
441
|
+
first item). `em export --json` publishes the parsed blocks as `slice.doc.scenarios`
|
|
442
|
+
(`{ title, given, when, then }[]`, `null` when the doc has none). A block missing any of the
|
|
443
|
+
three is `slice-doc/structured-section-malformed`, as is a region whose markers don't balance or
|
|
444
|
+
whose table header differs from the template's — a warning in `em validate`, and a blocker
|
|
445
|
+
(`slice-ready-structured-section-malformed`) under `--slice-ready`. See
|
|
446
|
+
[validation.md](https://github.com/milehimikey/em/blob/main/docs/validation.md#structured-sections).
|
|
447
|
+
|
|
448
|
+
**Migration.** Nothing is required: a doc with no region markers and no `### Scenario:` heading
|
|
449
|
+
(every doc written before 1.14) stays valid, is never flagged, and `em slice sync` skips it with a
|
|
450
|
+
note. To adopt regions, re-create the doc with `em slice new "<name>" … --wire <model>.em --force`
|
|
451
|
+
and carry the authored prose across, or paste the marker pairs in by hand and run
|
|
452
|
+
`em slice sync`.
|
|
453
|
+
|
|
454
|
+
## Open Questions section: lifecycle
|
|
455
|
+
|
|
456
|
+
`## Open Questions` (MIL-87 for the counting mechanics; MIL-156 for this lifecycle) had no
|
|
457
|
+
cross-version story until now: `countOpenQuestions()` counts every GFM task item under the
|
|
458
|
+
heading, but nothing ever pruned a resolved (`- [x]`) one — left unaddressed, the section grows
|
|
459
|
+
by accretion across every re-ratification of a long-lived slice, the same problem `## Delta`
|
|
460
|
+
(above) solves for by replacing its content wholesale each hop.
|
|
461
|
+
|
|
462
|
+
**Decision: prune resolved items on the same commit that ratifies the next version — replace,
|
|
463
|
+
never accumulate, same stance as `## Delta`, applied by hand instead of by section-rewrite.**
|
|
464
|
+
Concretely:
|
|
465
|
+
|
|
466
|
+
- While the *current* version is still being drafted or reviewed, a checked item can stay in the
|
|
467
|
+
doc — it's a visible record of what this round already decided, not yet an accumulation
|
|
468
|
+
problem (a slice's first version, `v1`, commonly ships with every question already resolved;
|
|
469
|
+
see `examples/order-fulfillment/slices/browse-catalog.md`).
|
|
470
|
+
- On the commit that bumps `version` and rewrites `## Delta` for the next hop, prune every
|
|
471
|
+
already-checked item from `## Open Questions` — keep only what's still open (`- [ ]`), plus
|
|
472
|
+
anything new that hop's change surfaced.
|
|
473
|
+
- Full history isn't lost: it lives in git the same way `## Delta`'s superseded hops do —
|
|
474
|
+
`git log -p slices/<name>.md` shows exactly which questions were open, and how they were
|
|
475
|
+
answered, at each prior version.
|
|
476
|
+
|
|
477
|
+
**Why not keep resolved items as a permanent audit trail instead?** Same reasoning `## Delta`'s
|
|
478
|
+
lifecycle section gives: an ever-growing scroll of resolved questions is a second history that
|
|
479
|
+
diverges from git, grows unboundedly over a long-lived slice's life, and forces every reader to
|
|
480
|
+
work out which resolutions are still relevant to the *current* version versus leftover from a
|
|
481
|
+
prior one — exactly the ambiguity a live, current-version-only section avoids.
|
|
482
|
+
|
|
483
|
+
**Not a parsing/grammar change.** `countOpenQuestions()` (`sliceDoc.ts`) and
|
|
484
|
+
`slice-ready-open-questions-unchecked` (`sliceReadyValidate.ts`,
|
|
485
|
+
[validation.md#slice-readiness](https://github.com/milehimikey/em/blob/main/docs/validation.md#slice-readiness)) are unaffected: both already
|
|
486
|
+
only care about the CURRENT doc's checkbox state, never about how many hops of history the
|
|
487
|
+
section has accumulated. This is purely an authoring convention — same "documented, not
|
|
488
|
+
mechanically enforced" status `## Delta`'s own lifecycle has (no `em` command checks that pruning
|
|
489
|
+
actually happened on re-ratification).
|
|
490
|
+
|
|
491
|
+
## `status` under re-ratification
|
|
492
|
+
|
|
493
|
+
When a new version is ratified for a slice whose previous version already shipped, `status`
|
|
494
|
+
tracks the **current version's** implementation state, not a running "has this ever shipped"
|
|
495
|
+
flag. Ratifying v2 on an `implemented` slice flips `status` back to `ready-to-implement`, while
|
|
496
|
+
`implementedIn` keeps naming the v1 PR until v2 ships. That deliberate mismatch — version 2,
|
|
497
|
+
implemented-link still pointing at v1's work — is not staleness, it's the **drift signal**: a
|
|
498
|
+
reader, `em export`'s `slice.doc.driftSignal` (`"unpropagated-delta"`, schema `1.5`, MIL-85), and
|
|
499
|
+
the event-modeling skill's `conform` phase all read it the same way — a ratified delta hasn't
|
|
500
|
+
shipped yet, not a fresh finding against the still-live v1 code. `em validate`'s
|
|
501
|
+
frontmatter-coherence check (MIL-85) deliberately never flags this combination — only
|
|
502
|
+
`status: implemented` with no `implementedIn` link at all is checkable incoherence; see
|
|
503
|
+
[validation.md#frontmatter-coherence](https://github.com/milehimikey/em/blob/main/docs/validation.md#frontmatter-coherence).
|
|
504
|
+
|
|
505
|
+
**An unshipped version has nothing to flip back** (MIL-258). A ratified doc that has not shipped
|
|
506
|
+
yet (`status: ready-to-implement` with `ratifiedBy:` set) changes the same way: `em slice
|
|
507
|
+
reratify` bumps `version:` and clears `ratifiedBy`/`ratifiedOn`/`reviewedBy`/`reviewedOn` (and `meaningConfirmed`/`contractChange`, MIL-238), but
|
|
508
|
+
leaves `status` at `ready-to-implement` (and any `implementedIn` as it was — an absent one stays
|
|
509
|
+
absent). This is the path for an answered gap mid-build. In both cases the new version is not
|
|
510
|
+
ratified until `em slice ratify --by <name>` records the sign-off. A `ready-to-implement` doc with
|
|
511
|
+
no `ratifiedBy` is awaiting that sign-off, so a second `reratify` refuses rather than
|
|
512
|
+
double-bump; `draft`/`reviewed` docs are not ratified and are simply edited.
|
|
513
|
+
|
|
514
|
+
Pair a re-ratification with a `## Delta` section (see
|
|
515
|
+
[Delta section: grammar and lifecycle](#delta-section-grammar-and-lifecycle) above) recording
|
|
516
|
+
the ratified change in typed operation blocks, so it's reviewable without opening git.
|
|
517
|
+
|
|
518
|
+
**Mechanically checking the invariant this section describes:** `em ledger` (MIL-89, opt-in —
|
|
519
|
+
see [cli.md#em-ledger-file](https://github.com/milehimikey/em/blob/main/docs/cli.md#em-ledger-file)) checks that `version:` and a slice doc's
|
|
520
|
+
content (body + lineage refs) always change together across two git revisions — a version bump
|
|
521
|
+
with no real content change, or a content change with no version bump, is a ledger bug.
|
|
522
|
+
It **deliberately excludes `status`/`implementedIn`** from what counts as "content" for exactly
|
|
523
|
+
the reason this section explains: a re-ratification legitimately flips `status` and leaves
|
|
524
|
+
`implementedIn` naming prior work, with no version bump of its own — including either field in
|
|
525
|
+
the comparison would flag every ordinary lifecycle transition as a false positive.
|
|
526
|
+
|
|
527
|
+
## Unknown keys
|
|
528
|
+
|
|
529
|
+
**Captured, never read, never a warning or error.** Real docs in the wild carry extra
|
|
530
|
+
non-canonical keys — `id`, `title`, `model`, `created`, `updated`, and YAML-list-valued keys
|
|
531
|
+
like `upstreamEvents`. Every top-level `key: value` scalar line is captured internally
|
|
532
|
+
regardless of whether it's one of the keys above; only the keys this document defines are ever
|
|
533
|
+
read back out onto `SliceDoc`. A list-valued key's own line (`upstreamEvents:`, value empty) is
|
|
534
|
+
captured with an empty value; its `- item` continuation lines don't match the `key: value` shape
|
|
535
|
+
at all and are simply skipped. Nothing throws, nothing warns — adding a new non-canonical key to
|
|
536
|
+
a doc is always safe.
|
|
537
|
+
|
|
538
|
+
## `schemaVersion` vs `version` — don't conflate these
|
|
539
|
+
|
|
540
|
+
| | `schemaVersion` | `version` |
|
|
541
|
+
|---|---|---|
|
|
542
|
+
| What it versions | The frontmatter dialect itself | This one slice's ratified definition |
|
|
543
|
+
| Who bumps it | `em` maintainers, when the dialect's canonical keys change | Whoever ratifies a delta on this slice |
|
|
544
|
+
| Cardinality | One value, same across every doc using this dialect | One per slice doc, incrementing per ratified delta |
|
|
545
|
+
| Current value | `1` (unchanged since MIL-86) | Starts at `1` |
|
|
546
|
+
| Read by `em` today | Value: no — captured, not exposed on `SliceDoc`. Presence: required for usable frontmatter | Yes — parsed onto `SliceDoc.version` |
|
|
547
|
+
|
|
548
|
+
## Legacy status bullet line
|
|
549
|
+
|
|
550
|
+
Docs written before the frontmatter dialect existed may use `- **Status:** ...` instead of
|
|
551
|
+
frontmatter `status:` — still accepted, status only. `version` and the lineage keys have no
|
|
552
|
+
legacy form; they're frontmatter-only from the day they were introduced.
|
|
553
|
+
|
|
554
|
+
## See also
|
|
555
|
+
|
|
556
|
+
- [cli.md](https://github.com/milehimikey/em/blob/main/docs/cli.md#em-render-file) — slice status colors
|
|
557
|
+
- [cli.md](https://github.com/milehimikey/em/blob/main/docs/cli.md#em-catalog-files) — pattern / doc lookup
|
|
558
|
+
- [cli.md](https://github.com/milehimikey/em/blob/main/docs/cli.md#em-slice-ratify-file-slice-key---by-name) — `em slice ratify`, the mechanized ratification act (`ratifiedBy`/`ratifiedOn`, schema `1.8`, MIL-165)
|
|
559
|
+
- [cli.md](https://github.com/milehimikey/em/blob/main/docs/cli.md#em-slice-reratify-file-slice-key) — `em slice reratify`, the mechanized re-ratification version bump/status flip (MIL-161)
|
|
560
|
+
- [cli.md](https://github.com/milehimikey/em/blob/main/docs/cli.md#em-export-file) — the `em export` join (`slice.pattern`/`slice.doc`, schema `1.4`, MIL-91), including `owner`/`tracking` (schema `1.9`, MIL-171)
|
|
561
|
+
- [cli.md](https://github.com/milehimikey/em/blob/main/docs/cli.md#em-diff-old-new) — the `em diff` lineage annotation (schema `1.6`, MIL-84)
|
|
562
|
+
- [validation.md#lineage](https://github.com/milehimikey/em/blob/main/docs/validation.md#lineage) — `em validate`'s lineage-ref resolution (MIL-84)
|
|
563
|
+
- [validation.md#slice-readiness](https://github.com/milehimikey/em/blob/main/docs/validation.md#slice-readiness) — `em validate --slice-ready`'s note-binding gate, including the `covers` cross-binding (MIL-121)
|
|
564
|
+
- [cli.md#em-coverage-file---tests-dir](https://github.com/milehimikey/em/blob/main/docs/cli.md#em-coverage-file---tests-dir) — `em coverage`'s INV-ID extraction, scoped to the Invariants/Delta sections' structural (non-nested) bullet lines (MIL-149/MIL-155/MIL-156)
|
|
565
|
+
- [validation.md#note-binding-mismatch](https://github.com/milehimikey/em/blob/main/docs/validation.md#note-binding-mismatch) — `em validate`'s check for a doc-shaped note that doesn't participate in its slice's binding (MIL-126)
|
|
566
|
+
- [`templates/slice.md`](../templates/slice.md) — the authored template
|