@milehimikey/em 1.9.0 → 1.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/skills/event-modeling/SKILL.md +1 -1
- package/.claude/skills/event-modeling-conform/SKILL.md +1 -1
- package/.claude/skills/event-modeling-conform/reference/conform.md +1 -1
- package/.claude/skills/event-modeling-design/SKILL.md +3 -3
- package/.claude/skills/event-modeling-discover/SKILL.md +1 -1
- package/.claude/skills/event-modeling-implement/SKILL.md +1 -1
- package/.claude/skills/event-modeling-review/SKILL.md +1 -1
- package/.claude/skills/event-modeling-shared/reference/em-dsl.md +47 -2
- package/.claude/skills/event-modeling-shared/reference/operating-principles.md +4 -1
- package/.claude/skills/event-modeling-shared/reference/slice-doc-schema.md +381 -0
- package/.claude/skills/event-modeling-shared/templates/slice.md +7 -7
- package/README.md +8 -1
- package/dist/cli/ciInit.d.ts +10 -3
- package/dist/cli/ciInit.js +22 -14
- package/dist/cli/ciInit.js.map +1 -1
- package/dist/cli/systemInputs.d.ts +34 -0
- package/dist/cli/systemInputs.js +129 -0
- package/dist/cli/systemInputs.js.map +1 -0
- package/dist/cli.js +222 -7
- package/dist/cli.js.map +1 -1
- package/dist/emit/json.d.ts +16 -1
- package/dist/emit/json.js +36 -1
- package/dist/emit/json.js.map +1 -1
- package/dist/emit/queryJson.d.ts +6 -0
- package/dist/emit/queryJson.js +29 -0
- package/dist/emit/queryJson.js.map +1 -0
- package/dist/emit/systemJson.d.ts +8 -0
- package/dist/emit/systemJson.js +38 -0
- package/dist/emit/systemJson.js.map +1 -0
- package/dist/mcp/server.d.ts +1 -1
- package/dist/mcp/server.js +185 -6
- package/dist/mcp/server.js.map +1 -1
- package/dist/model/edges.d.ts +13 -1
- package/dist/model/edges.js +43 -15
- package/dist/model/edges.js.map +1 -1
- package/dist/model/model.d.ts +2 -0
- package/dist/model/model.js +1 -0
- package/dist/model/model.js.map +1 -1
- package/dist/model/qualifiedRef.d.ts +47 -0
- package/dist/model/qualifiedRef.js +100 -0
- package/dist/model/qualifiedRef.js.map +1 -0
- package/dist/model/queryIndex.d.ts +50 -0
- package/dist/model/queryIndex.js +111 -0
- package/dist/model/queryIndex.js.map +1 -0
- package/dist/model/rules.d.ts +69 -0
- package/dist/model/rules.js +82 -0
- package/dist/model/rules.js.map +1 -1
- package/dist/model/sliceDocIndex.d.ts +30 -0
- package/dist/model/sliceDocIndex.js +110 -0
- package/dist/model/sliceDocIndex.js.map +1 -0
- package/dist/model/validate.js +4 -11
- package/dist/model/validate.js.map +1 -1
- package/dist/parser/ast.d.ts +4 -0
- package/dist/parser/parser.d.ts +2 -0
- package/dist/parser/parser.js +8 -2
- package/dist/parser/parser.js.map +1 -1
- package/dist/query/format.d.ts +8 -0
- package/dist/query/format.js +80 -0
- package/dist/query/format.js.map +1 -0
- package/dist/query/pipeline.d.ts +17 -0
- package/dist/query/pipeline.js +38 -0
- package/dist/query/pipeline.js.map +1 -0
- package/dist/query/system.d.ts +60 -0
- package/dist/query/system.js +87 -0
- package/dist/query/system.js.map +1 -0
- package/dist/query/verbs.d.ts +100 -0
- package/dist/query/verbs.js +306 -0
- package/dist/query/verbs.js.map +1 -0
- package/dist/render/drawEdges.js +8 -4
- package/dist/render/drawEdges.js.map +1 -1
- package/dist/render/sliceDiagram.js +1 -0
- package/dist/render/sliceDiagram.js.map +1 -1
- package/dist/system/manifest.d.ts +45 -0
- package/dist/system/manifest.js +178 -0
- package/dist/system/manifest.js.map +1 -0
- package/dist/system/verify.d.ts +88 -0
- package/dist/system/verify.js +263 -0
- package/dist/system/verify.js.map +1 -0
- package/dist/util/slug.js +16 -10
- package/dist/util/slug.js.map +1 -1
- package/package.json +10 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: event-modeling
|
|
3
|
-
em-version: 1.
|
|
3
|
+
em-version: 1.10.0
|
|
4
4
|
description: >-
|
|
5
5
|
Use when the user runs `/event-modeling` (with or without a phase name), or wants to resume an
|
|
6
6
|
event-modeling session without specifying which part of the process applies. Reads the model's
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: event-modeling-conform
|
|
3
|
-
em-version: 1.
|
|
3
|
+
em-version: 1.10.0
|
|
4
4
|
description: >-
|
|
5
5
|
Use when checking a ratified event model (and its slice docs) for drift against the codebase
|
|
6
6
|
that implements it — advisory only, never a gate, never an unprompted edit — or when walking
|
|
@@ -145,7 +145,7 @@ For each slice in scope:
|
|
|
145
145
|
divergence** (step 4) and cite the annotation text as the evidence. This is the specific,
|
|
146
146
|
per-element, ratified counterpart to the idiom-level bullet above.
|
|
147
147
|
- **Unpropagated deltas (MIL-85):** before treating any slice-level finding as drift, check
|
|
148
|
-
that slice's `doc.driftSignal` in `em export --json` (see
|
|
148
|
+
that slice's `doc.driftSignal` in `em export --json` (see `../../event-modeling-shared/reference/slice-doc-schema.md`'s
|
|
149
149
|
"`status` under re-ratification" section for the mechanics). `"unpropagated-delta"` means
|
|
150
150
|
the slice was re-ratified (a new
|
|
151
151
|
`version`, `status` flipped back off `implemented`) but `implementedIn` still names the
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: event-modeling-design
|
|
3
|
-
em-version: 1.
|
|
3
|
+
em-version: 1.10.0
|
|
4
4
|
description: >-
|
|
5
5
|
Use when structuring a draft event model into swimlanes and the four patterns (State Change,
|
|
6
6
|
State View, Automation, Translation), evaluating a model's structural completeness, or writing
|
|
@@ -74,7 +74,7 @@ For each slice:
|
|
|
74
74
|
sections actually changed, then express the change as the `## Delta` section — fixed heading,
|
|
75
75
|
`### Added`/`Modified`/`Removed`/`Renamed` Requirement blocks each carrying its own scenarios
|
|
76
76
|
(see `../event-modeling-shared/templates/slice.md` and
|
|
77
|
-
|
|
77
|
+
`../event-modeling-shared/reference/slice-doc-schema.md#delta-section-grammar-and-lifecycle`)
|
|
78
78
|
— **overwriting** whatever the section held before, never appending to it. Then continue at
|
|
79
79
|
step 2 to bump `version` and flip `status` back to `ready-to-implement`, leaving
|
|
80
80
|
`implementedIn` naming the prior version's PR (intended drift signal, not a bug — see the
|
|
@@ -107,7 +107,7 @@ For each slice:
|
|
|
107
107
|
mechanized. Record the originating need (ticket/conversation link) in the Intent section when
|
|
108
108
|
one exists. When this doc exists because of a split, merge, or rename, add the matching
|
|
109
109
|
lineage key(s) by hand (`split-from`/`merged-from`/`superseded-by`, `<slice-key>@v<N>` grammar
|
|
110
|
-
— see
|
|
110
|
+
— see `../event-modeling-shared/reference/slice-doc-schema.md` for the full schema; `em validate` catches a malformed one
|
|
111
111
|
after the fact, see the `lineage-*` rules below).
|
|
112
112
|
**Re-ratification (step 0):** the doc already exists, so `em slice new` doesn't apply here
|
|
113
113
|
(it refuses to overwrite an existing file without `--force`, and forcing would blow away the
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: event-modeling-discover
|
|
3
|
-
em-version: 1.
|
|
3
|
+
em-version: 1.10.0
|
|
4
4
|
description: >-
|
|
5
5
|
Use when starting a brand-new event model from scratch — greenfield discovery: brainstorming
|
|
6
6
|
past-tense events, storyboarding the happy path, finding the commands and read models — or when
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: event-modeling-implement
|
|
3
|
-
em-version: 1.
|
|
3
|
+
em-version: 1.10.0
|
|
4
4
|
description: >-
|
|
5
5
|
Use when building a single ratified event-modeling slice (status: ready-to-implement) into
|
|
6
6
|
merged, tested code — the one phase where the agent builds rather than facilitates. Gates on
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: event-modeling-review
|
|
3
|
-
em-version: 1.
|
|
3
|
+
em-version: 1.10.0
|
|
4
4
|
description: >-
|
|
5
5
|
Use when starting em's live browser viewer (`em watch --serve`) so a team can watch a model
|
|
6
6
|
update in real time, or facilitating a SCHEDULED stakeholder walkthrough that steps through
|
|
@@ -44,7 +44,7 @@ em catalog <files> -o, --out <dir> # output directory
|
|
|
44
44
|
em catalog <files> -T, --format <fmt> # diagram format embedded in the catalog (svg or png)
|
|
45
45
|
em catalog <files> --title <text> # catalog site title
|
|
46
46
|
em catalog <files> --keep-empty-lanes # keep the API lane even when empty
|
|
47
|
-
em slice new <name> # scaffold a fresh slices/<key>.md doc — the 5 frontmatter keys required at `status: draft` plus the `# Slice:` heading and diagram-image stub; judgment sections (Intent, Scenarios, Open Questions, ...) stay hand-authored (see
|
|
47
|
+
em slice new <name> # scaffold a fresh slices/<key>.md doc — the 5 frontmatter keys required at `status: draft` plus the `# Slice:` heading and diagram-image stub; judgment sections (Intent, Scenarios, Open Questions, ...) stay hand-authored (see .claude/skills/event-modeling-shared/reference/slice-doc-schema.md, templates/slice.md)
|
|
48
48
|
em slice new <name> --pattern <pattern> # slice pattern: state-change | state-view | automation | translation
|
|
49
49
|
em slice new <name> --swimlane <swimlane> # swimlane, e.g. "Persona → Context"
|
|
50
50
|
em slice new <name> -f, --force # overwrite the file if it already exists
|
|
@@ -111,8 +111,43 @@ em status <files> -o, --out <path> # write output to
|
|
|
111
111
|
em freshness <file> # standalone freshness signal for one model's conformance record (MIL-164): "last conformed <rev> — N commits and M slice-PRs behind HEAD", computed from the same conform-scope machinery `em status`'s conformance clause uses — for when you want just this fact, no full state-of-the-system rollup (see docs/cli.md)
|
|
112
112
|
em freshness <file> --repo <path> # git repo to compute behind-HEAD in (default: the model's own directory)
|
|
113
113
|
em freshness <file> --json # print a JSON document instead of the text line
|
|
114
|
+
em query consumers <files> # views/reactions consuming an event, plus their slices
|
|
115
|
+
em query consumers <files> --event <ref-or-name> # the event's export ref or display name
|
|
116
|
+
em query consumers <files> --json # print a JSON document instead of the text report
|
|
117
|
+
em query producers <files> # commands producing an event, plus their slices and ui triggers
|
|
118
|
+
em query producers <files> --event <ref-or-name> # the event's export ref or display name
|
|
119
|
+
em query producers <files> --json # print a JSON document instead of the text report
|
|
120
|
+
em query downstream <files> # transitive closure along legal edges from an element — impact analysis
|
|
121
|
+
em query downstream <files> --of <ref-or-name> # the starting element's export ref or display name
|
|
122
|
+
em query downstream <files> --depth <n> # limit traversal to n hops (default: unlimited)
|
|
123
|
+
em query downstream <files> --json # print a JSON document instead of the text report
|
|
124
|
+
em query upstream <files> # transitive closure against legal-edge direction from an element
|
|
125
|
+
em query upstream <files> --of <ref-or-name> # the starting element's export ref or display name
|
|
126
|
+
em query upstream <files> --depth <n> # limit traversal to n hops (default: unlimited)
|
|
127
|
+
em query upstream <files> --json # print a JSON document instead of the text report
|
|
128
|
+
em query slices <files> # filtered slice list — pattern/status/context/persona/tag filters AND-combine
|
|
129
|
+
em query slices <files> --pattern <p> # state-change | state-view | automation | translation | unclassified
|
|
130
|
+
em query slices <files> --status <s> # the slice's joined doc status (draft, reviewed, ready-to-implement, implemented, ...)
|
|
131
|
+
em query slices <files> --context <c> # match a slice with an event in this @Context
|
|
132
|
+
em query slices <files> --persona <p> # match a slice with a ui in this @Persona
|
|
133
|
+
em query slices <files> --tag <t> # match a slice with an event carrying this tag key
|
|
134
|
+
em query slices <files> --json # print a JSON document instead of the text report
|
|
135
|
+
em query invariant <files> # declaring slice + doc facts for one INV-* id, and (with --tests) its test citations
|
|
136
|
+
em query invariant <files> --id <inv-id> # the INV-* id to look up
|
|
137
|
+
em query invariant <files> --tests <dir> # directory to scan for test files citing this id
|
|
138
|
+
em query invariant <files> --json # print a JSON document instead of the text report
|
|
139
|
+
em query field <files> # one field's facts on an element — type, tag/assigned markers, renamed-from chain
|
|
140
|
+
em query field <files> --of <element-ref> # the element's export ref or display name
|
|
141
|
+
em query field <files> --name <field> # the field's name
|
|
142
|
+
em query field <files> --json # print a JSON document instead of the text report
|
|
143
|
+
em query path <files> # shortest path between two elements through the six legal connection types
|
|
144
|
+
em query path <files> --from <ref-or-name> # the starting element's export ref or display name
|
|
145
|
+
em query path <files> --to <ref-or-name> # the ending element's export ref or display name
|
|
146
|
+
em query path <files> --json # print a JSON document instead of the text report
|
|
147
|
+
em system <manifest> # verify a seam manifest (system.yaml) against its models' exports — every `public` event/view bound to a reaction in another model, the cross-model half of "both ends of a flow" (MIL-194, see docs/cli.md)
|
|
148
|
+
em system <manifest> --json # print a JSON document instead of the text report (see docs/cli.md)
|
|
114
149
|
em contract # print the packaged implementation contract (reference/implement.md) to stdout — the agent-neutral discovery path for any agent that can run a shell, not just Claude Code (MIL-129); see docs/cli.md
|
|
115
|
-
em mcp # start an MCP (Model Context Protocol) server over stdio
|
|
150
|
+
em mcp # start an MCP (Model Context Protocol) server over stdio (MIL-21) — a structured, agent-facing alternative to shelling out to `em`; see docs/mcp.md for the full, current tool table (the list changes as commands gain MCP parity, so it's not repeated here — MIL-187). Equivalent to running the `em-mcp` bin directly
|
|
116
151
|
em skill install # copy the event-modeling skill bundle into .claude/skills/ (event-modeling, event-modeling-discover/-design/-implement/-conform/-review, event-modeling-shared)
|
|
117
152
|
em skill install -f, --force # overwrite an existing installation
|
|
118
153
|
em skill install --no-agents-md # skip writing/updating the AGENTS.md agent-contract section (on by default, MIL-129)
|
|
@@ -561,11 +596,13 @@ not the prose above has caught up yet. `--slice-ready <key>`-only codes are excl
|
|
|
561
596
|
| `both-ends-of-a-flow/view-unconsumed` | warning | Read model with no consumer | Add a `ui` or reaction that consumes it, or drop this instance. |
|
|
562
597
|
| `connection-legality/illegal-pair` | error | Illegal connection | Only ui→command→event→view→ui and view→reaction→command are legal — the message names the missing step. |
|
|
563
598
|
| `cross-model-slice-doc-collision` | warning | Colliding slice doc path across models | Give each model its own directory (see docs/cli.md, "Multi-model projects"). |
|
|
599
|
+
| `dangling-public-event` | warning | Public event/view no seam consumes | Declare the seam that reads it, or drop `public` if nothing outside the model does. |
|
|
564
600
|
| `doc-model-element-not-in-doc` | warning | Model element the doc doesn't mention | Add the matching marker to the doc, or remove the element from the model. |
|
|
565
601
|
| `doc-model-element-not-in-model` | warning | Doc names an element the model doesn't have | Add the element to the model, or fix/remove it in the doc. |
|
|
566
602
|
| `doc-model-field-mismatch` | warning | Doc/model field mismatch | Reconcile the field table with the model's fields — names and types. |
|
|
567
603
|
| `doc-model-pattern-mismatch` | warning | Doc/model pattern mismatch | Fix the doc's `pattern:` frontmatter to match the model, or restructure the slice to match the doc. |
|
|
568
604
|
| `duplicate-element-ref` | warning | Duplicate element ref | Rename the element so its export ref is unique. |
|
|
605
|
+
| `duplicate-model-key` | warning | Duplicate model key | Give each model a unique `model "Name"` so its key (and every cross-model ref) is stable. |
|
|
569
606
|
| `duplicate-name` | warning | Duplicate name | Rename one of the duplicates. |
|
|
570
607
|
| `duplicate-slice-name` | warning | Duplicate slice name | Rename the slice so its export key is unique. |
|
|
571
608
|
| `duplicate-type-name` | warning | Duplicate type name | Rename one of the duplicate `type` declarations. |
|
|
@@ -587,11 +624,19 @@ not the prose above has caught up yet. `--slice-ready <key>`-only codes are excl
|
|
|
587
624
|
| `orphaned-slice-doc` | warning | Orphaned slice doc | Rename it to a current slice's key, add `covers:` (plus a `note` binding) to attach it to a live slice, or delete it. |
|
|
588
625
|
| `reaction-from-future-view` | error | Backward timeline (reaction reads a future view) | Declare the view in or before the reaction's slice. |
|
|
589
626
|
| `reaction-from-unresolved` | error | Unknown read-model source | Project the event into a view first, or fix the `from` reference. |
|
|
627
|
+
| `seam-consumer-not-reaction` | error | Seam consumer is not a reaction | Point `to` at a translation/automation element (or a slice containing exactly one). |
|
|
628
|
+
| `seam-duplicate` | warning | Duplicate seam | Remove the repeated `from`/`to` pair. |
|
|
629
|
+
| `seam-endpoint-unresolved` | error | Seam endpoint does not resolve | Fix the ref to an element the named model actually exports (`em export` lists every ref), or re-declare the seam after a rename. |
|
|
630
|
+
| `seam-source-not-public` | error | Seam source is not `public` | Mark the event/view `public` in its model, or point the seam at the element that is. |
|
|
631
|
+
| `system-manifest-invalid` | error | Seam manifest invalid | Fix the manifest: required keys, `systemSchemaVersion: "1.0"`, a readable `source` per model, and only declared model keys in seam refs. |
|
|
632
|
+
| `system-model-key-mismatch` | error | Manifest model key differs from the export's `model.key` | Rename the manifest's `models:` key to the computed key the message prints. |
|
|
590
633
|
| `tag-composite-unknown-field` | error | Composite tag names an unknown field | Fix the field name, or add it to the event's fields. |
|
|
591
634
|
| `tag-duplicate-key` | error | Duplicate tag key | Rename one of the tags so every key on the event is unique. |
|
|
592
635
|
| `translation-name-collision` | warning | Translation name reused for different producers | Use a distinct name per producer to avoid confusion. |
|
|
593
636
|
| `type-cycle` | error | Cyclic type reference | Break the cycle, or route the self/mutual reference through an array. |
|
|
594
637
|
| `ui-shares-slice-with-automation` | warning | `ui` shares slice with a reaction | Move the `ui` to the slice that displays the read model, or drop it. |
|
|
638
|
+
| `unbound-translation` | warning | Externally-fed reaction no seam feeds | Declare the seam whose `to` is this reaction, or give it an in-model `from`. |
|
|
639
|
+
| `undeclared-seam-candidate` | warning | Looks connected across models, no seam declared | Declare the seam, or rename one side so the name match stops looking like a link. |
|
|
595
640
|
| `view-again-without-earlier` | error | `again` without an earlier declaration | Declare the view plainly the first time it appears. |
|
|
596
641
|
| `view-from-future-event` | error | Backward timeline (view reads a future event) | Move the source to a later `view X again` instance. |
|
|
597
642
|
| `view-from-unresolved` | error | Unknown event source | Fix the `from` reference to name an existing event. |
|
|
@@ -8,7 +8,10 @@ they, together with this doc, are the source of truth every phase skill builds o
|
|
|
8
8
|
live in `templates/`, a sibling of this `reference/` directory. References to `docs/*.md`
|
|
9
9
|
throughout these skills mean the
|
|
10
10
|
[em repository's docs](https://github.com/milehimikey/em/tree/main/docs) — they are not
|
|
11
|
-
vendored alongside these skills
|
|
11
|
+
vendored alongside these skills, with one exception: the frontmatter schema doc is vendored
|
|
12
|
+
right here as `reference/slice-doc-schema.md` (kept in sync with the em repo's
|
|
13
|
+
`docs/slice-doc-schema.md` by `em`'s own `docs:generate`/`docs:check`), so every
|
|
14
|
+
`slice-doc-schema.md` reference elsewhere in this bundle points there instead of at `docs/`.
|
|
12
15
|
|
|
13
16
|
## Operating principles
|
|
14
17
|
|
|
@@ -0,0 +1,381 @@
|
|
|
1
|
+
<!-- GENERATED FILE — do not hand-edit. Synced from docs/slice-doc-schema.md by
|
|
2
|
+
scripts/generate-skill-docs.ts (`npm run docs:generate`); `npm run docs:check` fails
|
|
3
|
+
on drift. A few links below are rewritten for this vendored location (see that
|
|
4
|
+
script's buildVendoredSliceDocSchema for exactly what changes and why) — everything
|
|
5
|
+
else is identical to the source doc. -->
|
|
6
|
+
|
|
7
|
+
# Slice-doc frontmatter schema
|
|
8
|
+
|
|
9
|
+
This documents the machine-read YAML frontmatter dialect `src/catalog/sliceDoc.ts` parses out
|
|
10
|
+
of `slices/<slice-key>.md` — the contract `em export`'s slice-frontmatter join reads. It covers
|
|
11
|
+
the **frontmatter block only**. Body/prose authoring conventions (Intent, Scenarios, Open
|
|
12
|
+
Questions, …) live in the event-modeling skill's
|
|
13
|
+
[`templates/slice.md`](../templates/slice.md), not here — with two
|
|
14
|
+
exceptions:
|
|
15
|
+
|
|
16
|
+
- The `## Open Questions` section's GFM checkboxes (`- [ ]` / `- [x]`) *are* now machine-parsed
|
|
17
|
+
by `sliceDoc.ts` too (`openQuestionsTotal`/`openQuestionsUnchecked`, MIL-87), feeding
|
|
18
|
+
`em validate --slice-ready <key>` (see [validation.md#slice-readiness](https://github.com/milehimikey/em/blob/main/docs/validation.md#slice-readiness)).
|
|
19
|
+
The section's cross-version lifecycle (MIL-156) is documented here too, alongside `## Delta`'s
|
|
20
|
+
— see [Open Questions section: lifecycle](#open-questions-section-lifecycle) below. Everything
|
|
21
|
+
else about the section's authoring — what a good open question looks like, when to resolve one
|
|
22
|
+
— stays owned by the skill template; this doc only covers the counting mechanics and lifecycle.
|
|
23
|
+
- The `## Delta` section's grammar and lifecycle (MIL-88) is documented here, in full, alongside
|
|
24
|
+
the lineage keys it complements — a 2026-08-14 ruling deliberately kept the slice-doc contract
|
|
25
|
+
as one document rather than splitting a structurally-related convention across two files, even
|
|
26
|
+
though (unlike Open Questions) `## Delta` is currently display-only prose that no `em` command
|
|
27
|
+
parses yet. See [Delta section: grammar and lifecycle](#delta-section-grammar-and-lifecycle)
|
|
28
|
+
below; authoring voice/tone for the section still lives in the skill template, same as every
|
|
29
|
+
other body section.
|
|
30
|
+
|
|
31
|
+
## How the parser reads frontmatter
|
|
32
|
+
|
|
33
|
+
A leading `---` / `---` fence, at the literal start of the file (like Jekyll/Hugo — a guidance
|
|
34
|
+
comment above the fence means the frontmatter doesn't count yet). Inside the fence: top-level
|
|
35
|
+
scalar `key: value` lines only — **no lists, no nesting, no real YAML**. Quoted values have
|
|
36
|
+
their outer quotes stripped; a value that's empty after trimming is dropped. Keys are matched
|
|
37
|
+
case-sensitively as `[A-Za-z][\w-]*` but folded to lowercase for lookup. Any line that doesn't
|
|
38
|
+
match that shape — including `#`-prefixed comment lines, blank lines, and YAML list
|
|
39
|
+
continuation lines (` - item`) — is silently skipped. An unterminated fence (no closing `---`)
|
|
40
|
+
means "no frontmatter at all": the whole file is treated as body.
|
|
41
|
+
|
|
42
|
+
No `yaml` dependency, by design — a real YAML parser was added and reverted (commit `6de0a05`,
|
|
43
|
+
"keep the Two Laws of the Timeline"). This is a deliberately shallow line-scan, and stays that
|
|
44
|
+
way: real YAML would also start accepting nesting/lists that this schema explicitly doesn't
|
|
45
|
+
define a meaning for.
|
|
46
|
+
|
|
47
|
+
`SliceDoc` (`src/catalog/sliceDoc.ts`) also exposes `frontmatterPresent` (was a well-formed
|
|
48
|
+
fence found and closed at all) and `missingRequiredFields` (which of the required-at-every-
|
|
49
|
+
status keys, above, were absent) — the mechanical basis `em export`'s doc join (MIL-91) uses
|
|
50
|
+
to decide `frontmatter-invalid` without re-deriving frontmatter-shape rules of its own.
|
|
51
|
+
|
|
52
|
+
## Canonical keys
|
|
53
|
+
|
|
54
|
+
| Key | Type | Grammar / enum | Read by |
|
|
55
|
+
|---|---|---|---|
|
|
56
|
+
| `schemaVersion` | integer | `1` (current dialect version) | its *value* is not read back by any `em` command today (reserved) — but its *presence* is required by `hasUsableFrontmatter()`: a doc omitting it is `frontmatter-invalid` (see below) |
|
|
57
|
+
| `pattern` | string | `state-change` \| `state-view` \| `automation` \| `translation` | authored/informational only — `em catalog` and `em export` both derive pattern from the `.em` AST instead (`em export`'s `slice.pattern`, schema `1.4`) |
|
|
58
|
+
| `swimlane` | string | free text, `<Persona> → <Context>` | display-only |
|
|
59
|
+
| `status` | string, case-insensitive | `draft` \| `reviewed` \| `ready-to-implement` \| `implemented` | `em catalog`, `em render`/`em watch` header coloring; joined into `em export`'s `slice.doc.status` (schema `1.4`); paired with `implementedIn` to compute `slice.doc.driftSignal` (schema `1.5`, MIL-85) and `em validate`'s frontmatter-coherence check (MIL-85 — see [validation.md#frontmatter-coherence](https://github.com/milehimikey/em/blob/main/docs/validation.md#frontmatter-coherence)) |
|
|
60
|
+
| `version` | integer | positive integer, starts at `1` | joined into `em export`'s `slice.doc.version` (schema `1.4`, MIL-91) |
|
|
61
|
+
| `implementedIn` | string | free text (PR/commit link) | joined into `em export`'s `slice.doc.implementedIn` (schema `1.4`, MIL-91); paired with `status` to compute `slice.doc.driftSignal` (schema `1.5`, MIL-85) and `em validate`'s frontmatter-coherence check (MIL-85 — see [validation.md#frontmatter-coherence](https://github.com/milehimikey/em/blob/main/docs/validation.md#frontmatter-coherence)) |
|
|
62
|
+
| `split-from` | single ref | `<slice-key>@v<N>` | joined into `em export`'s `slice.doc.splitFrom` (schema `1.4`, MIL-91) and `em diff`'s `slice-added` entries (schema `1.6`, MIL-84); current-tree referential checks by `em validate` (MIL-84 — see [validation.md#lineage](https://github.com/milehimikey/em/blob/main/docs/validation.md#lineage)) |
|
|
63
|
+
| `merged-from` | list of refs | comma-separated `<slice-key>@v<N>, ...` | joined into `em export`'s `slice.doc.mergedFrom` (schema `1.4`, MIL-91) and `em diff`'s `slice-added` entries (schema `1.6`, MIL-84); current-tree referential checks by `em validate` (MIL-84 — see [validation.md#lineage](https://github.com/milehimikey/em/blob/main/docs/validation.md#lineage)) |
|
|
64
|
+
| `superseded-by` | list of refs | comma-separated `<slice-key>@v<N>, ...` | joined into `em export`'s `slice.doc.supersededBy` (schema `1.4`, MIL-91) and `em diff`'s `slice-removed` entries (schema `1.6`, MIL-84); current-tree referential checks by `em validate` (MIL-84 — see [validation.md#lineage](https://github.com/milehimikey/em/blob/main/docs/validation.md#lineage)) |
|
|
65
|
+
| `covers` | list of slice keys | comma-separated `<slice-key>, ...` (plain keys — **not** the `<slice-key>@v<N>` ref grammar above) | ratifies a cross-slice binding (MIL-121 — see [Cross-slice coverage](#cross-slice-coverage-covers) below): read by `em export`'s doc join, `em validate --slice-ready`, and (best-effort) `em render`/`em watch`'s Slice Status legend |
|
|
66
|
+
| `ratifiedBy` | string | free text (typically a person's name) | written only by `em slice ratify` (MIL-165 — see [cli.md](https://github.com/milehimikey/em/blob/main/docs/cli.md#em-slice-ratify-file-slice-key---by-name)); joined into `em export`'s `slice.doc.ratifiedBy` (schema `1.8`) and `em slice index`'s Ratified by column |
|
|
67
|
+
| `ratifiedOn` | string | `YYYY-MM-DD` | written only by `em slice ratify` (MIL-165); joined into `em export`'s `slice.doc.ratifiedOn` (schema `1.8`) |
|
|
68
|
+
| `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[]` |
|
|
69
|
+
| `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 |
|
|
70
|
+
|
|
71
|
+
Five keys — `schemaVersion`/`pattern`/`swimlane`/`status`/`version` — are what
|
|
72
|
+
`hasUsableFrontmatter()` requires: a doc omitting any of them is `frontmatter-invalid` to
|
|
73
|
+
`em export`'s doc join and to `em validate --slice-ready` (the parser itself never *throws*
|
|
74
|
+
over an omission — it reports). `implementedIn` and the lineage keys are the genuinely
|
|
75
|
+
optional ones; `implementedIn`'s required-once-implemented rule in the table below is
|
|
76
|
+
convention, not parser-enforced.
|
|
77
|
+
|
|
78
|
+
## Required vs optional, by `status`
|
|
79
|
+
|
|
80
|
+
| Key | draft | reviewed | ready-to-implement | implemented |
|
|
81
|
+
|---|---|---|---|---|
|
|
82
|
+
| `schemaVersion`, `pattern`, `swimlane`, `status` | required | required | required | required |
|
|
83
|
+
| `version` | required (starts at `1`) | required | required | required |
|
|
84
|
+
| `implementedIn` | optional | optional | optional | required once the slice has *ever* reached `implemented` — may still name a **prior** version's PR during re-ratification, see below |
|
|
85
|
+
| `split-from`, `merged-from`, `superseded-by` | optional in every state — present only on docs created by a split/merge, or on a doc that has been retired |
|
|
86
|
+
| `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 |
|
|
87
|
+
| `ratifiedBy`, `ratifiedOn` | optional in every state — present once `em slice ratify` (MIL-165) has run at least once; a doc predating this feature, or ratified by hand, simply omits both |
|
|
88
|
+
| `owner`, `tracking` | optional in every state — hand-filled whenever a team wants a who-holds-this / external-tracker link; most docs never carry either (MIL-171) |
|
|
89
|
+
|
|
90
|
+
`em export`'s slice-doc join (MIL-91) is the first `em` command to mechanically check the
|
|
91
|
+
required row above: a bound doc missing any of `schemaVersion`/`pattern`/`swimlane`/`status`/
|
|
92
|
+
`version` (or missing a frontmatter block entirely) reports `slice.doc.reason:
|
|
93
|
+
"frontmatter-invalid"` plus a warning diagnostic — report, never fail, same as every other
|
|
94
|
+
export finding. `implementedIn`'s conditional requirement (once a slice has *ever* reached
|
|
95
|
+
`implemented`) is **not** checked — that needs git history no doc-only parse has access to.
|
|
96
|
+
No `em` command fails a build over this table. `em validate`'s frontmatter-coherence check
|
|
97
|
+
(MIL-85 — see [validation.md#frontmatter-coherence](https://github.com/milehimikey/em/blob/main/docs/validation.md#frontmatter-coherence)) warns
|
|
98
|
+
(never fails) on the one combination checkable without git history: `status: implemented` with
|
|
99
|
+
no `implementedIn` link at all.
|
|
100
|
+
|
|
101
|
+
## Lineage: grammar and cardinality
|
|
102
|
+
|
|
103
|
+
Grammar: `<slice-key>@v<N>` — `<slice-key>` is the referenced slice's kebab-case filename stem
|
|
104
|
+
(matches `kebabSlug()`, `src/util/slug.ts`); `<N>` is a positive integer, the referenced
|
|
105
|
+
slice's `version` at the point of reference.
|
|
106
|
+
|
|
107
|
+
| Key | Cardinality | Why |
|
|
108
|
+
|---|---|---|
|
|
109
|
+
| `split-from` | **single** ref | A slice splits *from* exactly one parent, even though a split can produce multiple successors. |
|
|
110
|
+
| `merged-from` | **list**, comma-separated | A merge combines two or more source slices into one surviving doc. |
|
|
111
|
+
| `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. |
|
|
112
|
+
|
|
113
|
+
**Worked example** (a split): `checkout` v4 splits into `checkout` v5 plus a new
|
|
114
|
+
`apply-discount` v1. The retired parent doc, `checkout.md`, gets:
|
|
115
|
+
|
|
116
|
+
```yaml
|
|
117
|
+
version: 4
|
|
118
|
+
status: implemented
|
|
119
|
+
superseded-by: checkout@v5, apply-discount@v1
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
Both surviving docs carry the back-link — `checkout.md` (now at v5) and `apply-discount.md`
|
|
123
|
+
each get:
|
|
124
|
+
|
|
125
|
+
```yaml
|
|
126
|
+
split-from: checkout@v4
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
**Scope note:** the parser (`sliceDoc.ts`) itself still never checks that a referenced
|
|
130
|
+
slice/version actually *exists* — that stays deliberately out of scope here, by design (fs-free,
|
|
131
|
+
pure). A malformed or dangling ref still parses (its `raw` text is preserved, `sliceKey`/
|
|
132
|
+
`version` come back `null` for a malformed value); referential checking is a separate, fs-aware
|
|
133
|
+
layer.
|
|
134
|
+
|
|
135
|
+
`em validate` (`src/catalog/lineageValidate.ts`, MIL-84) is that layer. It checks what the
|
|
136
|
+
*current tree* can prove wrong and stays silent about what it can't — see
|
|
137
|
+
[validation.md#lineage](https://github.com/milehimikey/em/blob/main/docs/validation.md#lineage) for exactly what's checked (grammar,
|
|
138
|
+
self-reference/cycles, dangling `superseded-by`, impossible version arithmetic) versus what's
|
|
139
|
+
deliberately never flagged (a `split-from`/`merged-from` naming a key legitimately absent from
|
|
140
|
+
the current tree — the normal state after a real split/merge). Deep historical verification —
|
|
141
|
+
did `slice@vN` really, once, exist? — stays out of core validate; the reason that's sufficient
|
|
142
|
+
is a same-commit authoring convention, not new plumbing (`em ledger`, below, is the opt-in,
|
|
143
|
+
two-revision command for the related-but-different version/content invariant):
|
|
144
|
+
|
|
145
|
+
> Lineage refs (`split-from:`/`merged-from:`/`superseded-by:`) are written in the same
|
|
146
|
+
> ratification commit that performs the operation they record, so the PR diff contains both
|
|
147
|
+
> sides of the claim (the predecessor at its final version, and the ref naming it). The review
|
|
148
|
+
> airlock is the history check.
|
|
149
|
+
|
|
150
|
+
## Cross-slice coverage: `covers`
|
|
151
|
+
|
|
152
|
+
Since `em` 1.7.1 (MIL-120), the canonical Automation/Translation shape is two raw slices: a
|
|
153
|
+
bare `view` slice, and the reaction+command+event slice that reads it. Each slice can still bind
|
|
154
|
+
its own `slices/<key>.md` doc the ordinary way, but a team documenting the whole design unit as
|
|
155
|
+
**one** doc needs a way to make the view-only slice count as doc-bound too, without splitting
|
|
156
|
+
the write-up back into two files. `covers` (MIL-121) is that escape hatch — a deliberately
|
|
157
|
+
two-ended handshake, not a one-sided claim:
|
|
158
|
+
|
|
159
|
+
1. In the `.em`, an element in the *covered* slice (the one with no doc of its own) carries
|
|
160
|
+
`note "slices/<other-key>.md"` — a path in the ordinary `slices/<key>.md` shape, just naming
|
|
161
|
+
a *different* slice's canonical doc instead of its own.
|
|
162
|
+
2. That other doc's own frontmatter ratifies the coverage with `covers`, naming the covered
|
|
163
|
+
slice's key back — same comma-separated, single scalar-line shape as `merged-from`/
|
|
164
|
+
`superseded-by` (this parser's frontmatter dialect has **no** real YAML list support, see
|
|
165
|
+
above):
|
|
166
|
+
```yaml
|
|
167
|
+
covers: detect-unpaid-orders
|
|
168
|
+
```
|
|
169
|
+
Multiple covered slices: `covers: detect-unpaid-orders, some-other-slice`.
|
|
170
|
+
|
|
171
|
+
Both sides are required. A `note` naming another slice's doc with no matching `covers` entry on
|
|
172
|
+
that doc — the file is missing, its frontmatter isn't usable, or `covers` doesn't list this
|
|
173
|
+
slice back — leaves the noting slice exactly as unbound as if it had no note at all
|
|
174
|
+
(`no-doc-bound`); em never guesses at a one-sided claim. When ratified, every consumer that
|
|
175
|
+
reads a bound doc's canonical fields (`em export`'s `slice.doc`, `em validate --slice-ready`,
|
|
176
|
+
`em render`/`em watch`'s Slice Status legend) reads the **covering** doc's `status`/`version`/
|
|
177
|
+
`implementedIn`/lineage/Open Questions, not the covered slice's own (nonexistent) file. A doc
|
|
178
|
+
covers its own canonical slice key implicitly — `covers` never needs to list it.
|
|
179
|
+
|
|
180
|
+
`covers` is a plain list of slice keys (`<slice-key>`, e.g. `detect-unpaid-orders`), **not** the
|
|
181
|
+
`<slice-key>@v<N>` ref grammar the lineage keys use above: coverage is a standing "this doc also
|
|
182
|
+
serves that slice" declaration, with no version component — unlike lineage, which records a
|
|
183
|
+
specific historical hop.
|
|
184
|
+
|
|
185
|
+
**Out of scope for THIS join:** `resolveSliceDocJoin` itself never warns on a *mismatched*
|
|
186
|
+
cross-note (a note naming a doc that doesn't ratify it back) — the slice's doc join still stays
|
|
187
|
+
silently `no-doc-bound`, the same as never having written the note; the join's own job is
|
|
188
|
+
resolving the *winning* binding, not auditing every note that didn't win. `em validate` does flag
|
|
189
|
+
the mismatch, as its own separate rule (MIL-126) — see
|
|
190
|
+
[validation.md#note-binding-mismatch](https://github.com/milehimikey/em/blob/main/docs/validation.md#note-binding-mismatch): a dangling cross-note
|
|
191
|
+
(missing file), one pointing at a doc with unusable frontmatter, one pointing at a usable doc
|
|
192
|
+
that just doesn't ratify (`covers` doesn't list this slice back), and an extra note doing nothing
|
|
193
|
+
in a slice that's already bound elsewhere, each get their own diagnostic there.
|
|
194
|
+
|
|
195
|
+
## Delta section: grammar and lifecycle
|
|
196
|
+
|
|
197
|
+
`## Delta` is the body section that pairs with a re-ratification (see "`status` under
|
|
198
|
+
re-ratification" below) — a structured record of what changed to produce the version currently
|
|
199
|
+
in the frontmatter, in typed operation blocks (MIL-88) rather than a one-line summary. Like the
|
|
200
|
+
lineage keys above, it's currently authored, not parsed — no `em` command reads *this section*
|
|
201
|
+
— but it's documented here rather than left purely to the skill template because its shape is a
|
|
202
|
+
structural contract other tooling may parse later, same reasoning as the lineage grammar.
|
|
203
|
+
`em diff --json` now emits the same four-word vocabulary on its own structural entries (`op:
|
|
204
|
+
"Added" | "Modified" | "Removed" | "Renamed"`, schema `1.7`, MIL-131 — see
|
|
205
|
+
[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
|
|
206
|
+
authored `## Delta` against the structural diff of the revisions it claims to describe is still
|
|
207
|
+
a separate, not-yet-built decision.
|
|
208
|
+
|
|
209
|
+
**The heading is always the literal string `## Delta` — never a variable heading** (e.g. never
|
|
210
|
+
`## Delta: v1 → v2`). Several consumers key on exact `##` text when locating sections in a slice
|
|
211
|
+
doc (the SDD-bridge's section splitter, the slice-to-spec mapping table, the redirect prompt
|
|
212
|
+
preambles); a heading that changes every ratification would break exact-match lookup and move
|
|
213
|
+
the anchor on every hop — the same problem a made-up per-doc key would cause anywhere else in
|
|
214
|
+
this schema. Version range, ratification date, and a one-line summary go on the **first line
|
|
215
|
+
inside** the section instead, as display text — not a frontmatter key, not parsed:
|
|
216
|
+
|
|
217
|
+
```markdown
|
|
218
|
+
## Delta
|
|
219
|
+
**v1 → v2, ratified 2026-08-12** — one-line summary of the ratified change.
|
|
220
|
+
|
|
221
|
+
### Added
|
|
222
|
+
#### Requirement: {{title}} ({{stable ID, e.g. an INV-<MNEMONIC>-n from ## Invariants}})
|
|
223
|
+
{{requirement text}}
|
|
224
|
+
##### Scenario: {{name}}
|
|
225
|
+
- **Given:** {{...}}
|
|
226
|
+
- **When:** {{...}}
|
|
227
|
+
- **Then:** {{...}}
|
|
228
|
+
|
|
229
|
+
### Modified
|
|
230
|
+
(same shape as Added — the ID names the requirement that changed)
|
|
231
|
+
|
|
232
|
+
### Removed
|
|
233
|
+
#### Requirement: {{title}} ({{stable ID}})
|
|
234
|
+
{{why it was removed}}
|
|
235
|
+
|
|
236
|
+
### Renamed
|
|
237
|
+
- {{old title}} ({{old ID}}) → {{new title}} ({{new ID}})
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
Stable IDs follow `INV-<MNEMONIC>-<n>` — a short (2-4 uppercase letters/digits) abbreviation of
|
|
241
|
+
the slice's key, unique among the model's slices, then a small integer, optionally letter-suffixed
|
|
242
|
+
for a closely-related sub-invariant (`INV-CHK-3a`) — see the Invariants section guidance in
|
|
243
|
+
[`templates/slice.md`](../templates/slice.md). Per-slice prefixes
|
|
244
|
+
make IDs globally unique across the model, so an `em coverage` citation is exact instead of
|
|
245
|
+
ambiguous across slices.
|
|
246
|
+
|
|
247
|
+
**Four `###` subsections, in that fixed order, Title Case singular words** — `Added`, `Modified`,
|
|
248
|
+
`Removed`, `Renamed` — not OpenSpec's shouting-case `ADDED Requirements` grouping. This is the
|
|
249
|
+
one deliberate divergence from the OpenSpec grammar the convention was validated against: the
|
|
250
|
+
four *operations* are retained (independent convergent design — OpenSpec arrived at the same
|
|
251
|
+
vocabulary from its own change-management-first angle), but OpenSpec's flat, file-per-delta
|
|
252
|
+
heading topology is not, because a slice doc's delta is a section inside a doc with its own
|
|
253
|
+
parsed heading contract, not a standalone file. Omit any of the four subsections with no entries
|
|
254
|
+
this hop — most re-ratifications touch one or two operations, not all four.
|
|
255
|
+
|
|
256
|
+
**`Renamed` is a requirement-level operation, not a slice-level one.** It records an invariant or
|
|
257
|
+
requirement *within this slice* being renamed or renumbered (e.g. `INV-CHK-3` → `INV-CHK-3a`) —
|
|
258
|
+
it is not a substitute for the `split-from`/`merged-from`/`superseded-by` lineage keys above,
|
|
259
|
+
which record the slice-doc-level operation of one doc splitting, merging, or being renamed into
|
|
260
|
+
another. `em diff`'s own `ChangeType` deliberately treats a slice/element rename as remove+add,
|
|
261
|
+
never a first-class rename (`src/model/diff.ts`: "a rename IS a model change") — `Renamed` here
|
|
262
|
+
is a finer grain, inside one otherwise-unchanged slice, and doesn't revisit that decision.
|
|
263
|
+
|
|
264
|
+
**Lifecycle: replace, never accumulate — the section is a window onto the latest hop, not a
|
|
265
|
+
scroll of every hop.** A slice's first version (`v1`) carries no `## Delta` section at all —
|
|
266
|
+
absence means no hop has happened yet, not an empty section. Ratifying `v2` writes the section
|
|
267
|
+
for the first time, in the same commit that bumps `version` and flips `status`. Ratifying `v3`
|
|
268
|
+
**overwrites** it with the `v2 → v3` hop; the `v1 → v2` content is not retained in the doc.
|
|
269
|
+
Full history is git's job — the same-commit ratification convention above makes `git log -p
|
|
270
|
+
slices/<name>.md` a precise, zero-maintenance delta archive — and a rolled-up multi-hop view, if
|
|
271
|
+
ever wanted, is a *rendering* (e.g. a future `em changelog`), never hand-maintained doc content.
|
|
272
|
+
An accumulating in-doc log would be a second history that diverges from git, grows unboundedly
|
|
273
|
+
over a slice's life, references requirements that have since moved (a *live* doc quietly going
|
|
274
|
+
stale reads as current, unlike git which is honestly historical), and forces every reader to
|
|
275
|
+
work out which delta is the live one — exactly the ambiguity `driftSignal` (MIL-85, below) is
|
|
276
|
+
built to avoid.
|
|
277
|
+
|
|
278
|
+
## Open Questions section: lifecycle
|
|
279
|
+
|
|
280
|
+
`## Open Questions` (MIL-87 for the counting mechanics; MIL-156 for this lifecycle) had no
|
|
281
|
+
cross-version story until now: `countOpenQuestions()` counts every GFM task item under the
|
|
282
|
+
heading, but nothing ever pruned a resolved (`- [x]`) one — left unaddressed, the section grows
|
|
283
|
+
by accretion across every re-ratification of a long-lived slice, the same problem `## Delta`
|
|
284
|
+
(above) solves for by replacing its content wholesale each hop.
|
|
285
|
+
|
|
286
|
+
**Decision: prune resolved items on the same commit that ratifies the next version — replace,
|
|
287
|
+
never accumulate, same stance as `## Delta`, applied by hand instead of by section-rewrite.**
|
|
288
|
+
Concretely:
|
|
289
|
+
|
|
290
|
+
- While the *current* version is still being drafted or reviewed, a checked item can stay in the
|
|
291
|
+
doc — it's a visible record of what this round already decided, not yet an accumulation
|
|
292
|
+
problem (a slice's first version, `v1`, commonly ships with every question already resolved;
|
|
293
|
+
see `examples/order-fulfillment/slices/browse-catalog.md`).
|
|
294
|
+
- On the commit that bumps `version` and rewrites `## Delta` for the next hop, prune every
|
|
295
|
+
already-checked item from `## Open Questions` — keep only what's still open (`- [ ]`), plus
|
|
296
|
+
anything new that hop's change surfaced.
|
|
297
|
+
- Full history isn't lost: it lives in git the same way `## Delta`'s superseded hops do —
|
|
298
|
+
`git log -p slices/<name>.md` shows exactly which questions were open, and how they were
|
|
299
|
+
answered, at each prior version.
|
|
300
|
+
|
|
301
|
+
**Why not keep resolved items as a permanent audit trail instead?** Same reasoning `## Delta`'s
|
|
302
|
+
lifecycle section gives: an ever-growing scroll of resolved questions is a second history that
|
|
303
|
+
diverges from git, grows unboundedly over a long-lived slice's life, and forces every reader to
|
|
304
|
+
work out which resolutions are still relevant to the *current* version versus leftover from a
|
|
305
|
+
prior one — exactly the ambiguity a live, current-version-only section avoids.
|
|
306
|
+
|
|
307
|
+
**Not a parsing/grammar change.** `countOpenQuestions()` (`sliceDoc.ts`) and
|
|
308
|
+
`slice-ready-open-questions-unchecked` (`sliceReadyValidate.ts`,
|
|
309
|
+
[validation.md#slice-readiness](https://github.com/milehimikey/em/blob/main/docs/validation.md#slice-readiness)) are unaffected: both already
|
|
310
|
+
only care about the CURRENT doc's checkbox state, never about how many hops of history the
|
|
311
|
+
section has accumulated. This is purely an authoring convention — same "documented, not
|
|
312
|
+
mechanically enforced" status `## Delta`'s own lifecycle has (no `em` command checks that pruning
|
|
313
|
+
actually happened on re-ratification).
|
|
314
|
+
|
|
315
|
+
## `status` under re-ratification
|
|
316
|
+
|
|
317
|
+
When a new version is ratified for a slice whose previous version already shipped, `status`
|
|
318
|
+
tracks the **current version's** implementation state, not a running "has this ever shipped"
|
|
319
|
+
flag. Ratifying v2 on an `implemented` slice flips `status` back to `ready-to-implement`, while
|
|
320
|
+
`implementedIn` keeps naming the v1 PR until v2 ships. That deliberate mismatch — version 2,
|
|
321
|
+
implemented-link still pointing at v1's work — is not staleness, it's the **drift signal**: a
|
|
322
|
+
reader, `em export`'s `slice.doc.driftSignal` (`"unpropagated-delta"`, schema `1.5`, MIL-85), and
|
|
323
|
+
the event-modeling skill's `conform` phase all read it the same way — a ratified delta hasn't
|
|
324
|
+
shipped yet, not a fresh finding against the still-live v1 code. `em validate`'s
|
|
325
|
+
frontmatter-coherence check (MIL-85) deliberately never flags this combination — only
|
|
326
|
+
`status: implemented` with no `implementedIn` link at all is checkable incoherence; see
|
|
327
|
+
[validation.md#frontmatter-coherence](https://github.com/milehimikey/em/blob/main/docs/validation.md#frontmatter-coherence).
|
|
328
|
+
|
|
329
|
+
Pair a re-ratification with a `## Delta` section (see
|
|
330
|
+
[Delta section: grammar and lifecycle](#delta-section-grammar-and-lifecycle) above) recording
|
|
331
|
+
the ratified change in typed operation blocks, so it's reviewable without opening git.
|
|
332
|
+
|
|
333
|
+
**Mechanically checking the invariant this section describes:** `em ledger` (MIL-89, opt-in —
|
|
334
|
+
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
|
|
335
|
+
content (body + lineage refs) always change together across two git revisions — a version bump
|
|
336
|
+
with no real content change, or a content change with no version bump, is a ledger bug.
|
|
337
|
+
It **deliberately excludes `status`/`implementedIn`** from what counts as "content" for exactly
|
|
338
|
+
the reason this section explains: a re-ratification legitimately flips `status` and leaves
|
|
339
|
+
`implementedIn` naming prior work, with no version bump of its own — including either field in
|
|
340
|
+
the comparison would flag every ordinary lifecycle transition as a false positive.
|
|
341
|
+
|
|
342
|
+
## Unknown keys
|
|
343
|
+
|
|
344
|
+
**Captured, never read, never a warning or error.** Real docs in the wild carry extra
|
|
345
|
+
non-canonical keys — `id`, `title`, `model`, `created`, `updated`, and YAML-list-valued keys
|
|
346
|
+
like `upstreamEvents`. Every top-level `key: value` scalar line is captured internally
|
|
347
|
+
regardless of whether it's one of the keys above; only the keys this document defines are ever
|
|
348
|
+
read back out onto `SliceDoc`. A list-valued key's own line (`upstreamEvents:`, value empty) is
|
|
349
|
+
captured with an empty value; its `- item` continuation lines don't match the `key: value` shape
|
|
350
|
+
at all and are simply skipped. Nothing throws, nothing warns — adding a new non-canonical key to
|
|
351
|
+
a doc is always safe.
|
|
352
|
+
|
|
353
|
+
## `schemaVersion` vs `version` — don't conflate these
|
|
354
|
+
|
|
355
|
+
| | `schemaVersion` | `version` |
|
|
356
|
+
|---|---|---|
|
|
357
|
+
| What it versions | The frontmatter dialect itself | This one slice's ratified definition |
|
|
358
|
+
| Who bumps it | `em` maintainers, when the dialect's canonical keys change | Whoever ratifies a delta on this slice |
|
|
359
|
+
| Cardinality | One value, same across every doc using this dialect | One per slice doc, incrementing per ratified delta |
|
|
360
|
+
| Current value | `1` (unchanged since MIL-86) | Starts at `1` |
|
|
361
|
+
| Read by `em` today | Value: no — captured, not exposed on `SliceDoc`. Presence: required for usable frontmatter | Yes — parsed onto `SliceDoc.version` |
|
|
362
|
+
|
|
363
|
+
## Legacy status bullet line
|
|
364
|
+
|
|
365
|
+
Docs written before the frontmatter dialect existed may use `- **Status:** ...` instead of
|
|
366
|
+
frontmatter `status:` — still accepted, status only. `version` and the lineage keys have no
|
|
367
|
+
legacy form; they're frontmatter-only from the day they were introduced.
|
|
368
|
+
|
|
369
|
+
## See also
|
|
370
|
+
|
|
371
|
+
- [cli.md](https://github.com/milehimikey/em/blob/main/docs/cli.md#em-render-file) — slice status colors
|
|
372
|
+
- [cli.md](https://github.com/milehimikey/em/blob/main/docs/cli.md#em-catalog-files) — pattern / doc lookup
|
|
373
|
+
- [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)
|
|
374
|
+
- [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)
|
|
375
|
+
- [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)
|
|
376
|
+
- [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)
|
|
377
|
+
- [validation.md#lineage](https://github.com/milehimikey/em/blob/main/docs/validation.md#lineage) — `em validate`'s lineage-ref resolution (MIL-84)
|
|
378
|
+
- [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)
|
|
379
|
+
- [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)
|
|
380
|
+
- [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)
|
|
381
|
+
- [`templates/slice.md`](../templates/slice.md) — the authored template
|