@milehimikey/em 1.6.0 → 1.7.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 +60 -12
- package/.claude/skills/event-modeling/reference/conform.md +30 -8
- package/.claude/skills/event-modeling/reference/em-dsl.md +145 -23
- package/.claude/skills/event-modeling/reference/extract.md +2 -1
- package/.claude/skills/event-modeling/reference/implement.md +172 -0
- package/.claude/skills/event-modeling/reference/methodology.md +5 -0
- package/.claude/skills/event-modeling/templates/conformance-report.md +15 -9
- package/.claude/skills/event-modeling/templates/model-readme.md +3 -3
- package/.claude/skills/event-modeling/templates/slice.md +70 -4
- package/.claude/skills/event-modeling/templates/state.md +6 -5
- package/README.md +1 -0
- package/dist/catalog/build.d.ts +5 -0
- package/dist/catalog/build.js +1 -3
- package/dist/catalog/build.js.map +1 -1
- package/dist/catalog/docJoin.d.ts +39 -0
- package/dist/catalog/docJoin.js +86 -0
- package/dist/catalog/docJoin.js.map +1 -0
- package/dist/catalog/driftSignal.d.ts +16 -0
- package/dist/catalog/driftSignal.js +24 -0
- package/dist/catalog/driftSignal.js.map +1 -0
- package/dist/catalog/frontmatterCoherenceValidate.d.ts +8 -0
- package/dist/catalog/frontmatterCoherenceValidate.js +52 -0
- package/dist/catalog/frontmatterCoherenceValidate.js.map +1 -0
- package/dist/catalog/lineageValidate.d.ts +9 -0
- package/dist/catalog/lineageValidate.js +161 -0
- package/dist/catalog/lineageValidate.js.map +1 -0
- package/dist/catalog/readSliceDoc.d.ts +2 -0
- package/dist/catalog/readSliceDoc.js +21 -0
- package/dist/catalog/readSliceDoc.js.map +1 -0
- package/dist/catalog/sliceDoc.d.ts +94 -3
- package/dist/catalog/sliceDoc.js +157 -7
- package/dist/catalog/sliceDoc.js.map +1 -1
- package/dist/catalog/sliceReadyValidate.d.ts +11 -0
- package/dist/catalog/sliceReadyValidate.js +85 -0
- package/dist/catalog/sliceReadyValidate.js.map +1 -0
- package/dist/cli/diff-inputs.d.ts +20 -0
- package/dist/cli/diff-inputs.js +75 -18
- package/dist/cli/diff-inputs.js.map +1 -1
- package/dist/cli/ledgerCheck.d.ts +38 -0
- package/dist/cli/ledgerCheck.js +116 -0
- package/dist/cli/ledgerCheck.js.map +1 -0
- package/dist/cli/skillCheck.d.ts +18 -0
- package/dist/cli/skillCheck.js +63 -0
- package/dist/cli/skillCheck.js.map +1 -0
- package/dist/cli/skillSync.d.ts +22 -0
- package/dist/cli/skillSync.js +69 -0
- package/dist/cli/skillSync.js.map +1 -0
- package/dist/cli/skillVersionCheck.d.ts +32 -0
- package/dist/cli/skillVersionCheck.js +145 -0
- package/dist/cli/skillVersionCheck.js.map +1 -0
- package/dist/cli.d.ts +3 -1
- package/dist/cli.js +199 -32
- package/dist/cli.js.map +1 -1
- package/dist/emit/diffJson.d.ts +1 -1
- package/dist/emit/diffJson.js +18 -14
- package/dist/emit/diffJson.js.map +1 -1
- package/dist/emit/json.d.ts +10 -25
- package/dist/emit/json.js +70 -103
- package/dist/emit/json.js.map +1 -1
- package/dist/emit/ledgerJson.d.ts +5 -0
- package/dist/emit/ledgerJson.js +25 -0
- package/dist/emit/ledgerJson.js.map +1 -0
- package/dist/emit/skillCheckJson.d.ts +5 -0
- package/dist/emit/skillCheckJson.js +32 -0
- package/dist/emit/skillCheckJson.js.map +1 -0
- package/dist/model/diff.d.ts +44 -1
- package/dist/model/diff.js +51 -9
- package/dist/model/diff.js.map +1 -1
- package/dist/model/model.d.ts +1 -1
- package/dist/model/refs.d.ts +23 -0
- package/dist/model/refs.js +75 -0
- package/dist/model/refs.js.map +1 -0
- package/dist/model/rules.d.ts +252 -0
- package/dist/model/rules.js +253 -0
- package/dist/model/rules.js.map +1 -0
- package/dist/model/validate.d.ts +23 -1
- package/dist/model/validate.js +184 -49
- package/dist/model/validate.js.map +1 -1
- package/dist/parser/ast.d.ts +3 -2
- package/dist/parser/parser.js +36 -14
- package/dist/parser/parser.js.map +1 -1
- package/dist/pipeline.d.ts +9 -0
- package/dist/pipeline.js +4 -2
- package/dist/pipeline.js.map +1 -1
- package/dist/render/sliceStatus.js +1 -1
- package/dist/render/sliceStatus.js.map +1 -1
- package/dist/util/isMainModule.d.ts +14 -0
- package/dist/util/isMainModule.js +33 -0
- package/dist/util/isMainModule.js.map +1 -0
- package/dist/util/walkDir.d.ts +4 -0
- package/dist/util/walkDir.js +28 -0
- package/dist/util/walkDir.js.map +1 -0
- package/package.json +5 -2
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
# Implementing a ratified slice
|
|
2
|
+
|
|
3
|
+
The contract for the implementation half of the workflow: an agent (or engineer) turning **one
|
|
4
|
+
ratified slice** into merged, verified code. The facilitation phases (`discover`/`extract` →
|
|
5
|
+
`model` → `slice`) produce the inputs; this document governs consuming them. It applies to any
|
|
6
|
+
implementing agent, whether or not the session started from `/event-modeling`.
|
|
7
|
+
|
|
8
|
+
**The contract in one paragraph:** the slice doc is the spec — nothing between it and the code
|
|
9
|
+
is a source of truth. Verify readiness mechanically before writing any code; implement exactly
|
|
10
|
+
what the doc says; put every gap in front of a human instead of deciding it silently; never
|
|
11
|
+
edit the ratified doc except the two lifecycle fields at merge; prove the work with tests
|
|
12
|
+
traceable to the doc's invariants and scenarios.
|
|
13
|
+
|
|
14
|
+
Why the rules are shaped this way: **agents propose, humans ratify.** Everything in the model
|
|
15
|
+
and its slice docs is there because a human decided it on purpose — that is what makes the
|
|
16
|
+
model authoritative. An implementing agent that silently patches a gap, or edits a ratified
|
|
17
|
+
doc outside a session, converts the model into just another generated artifact and voids the
|
|
18
|
+
whole methodology. (The full human/agent partition is `docs/process.md` in the
|
|
19
|
+
[em repository](https://github.com/milehimikey/em/blob/main/docs/process.md) — not vendored
|
|
20
|
+
with this skill.)
|
|
21
|
+
|
|
22
|
+
## 1. Gate: verify readiness before starting
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
em validate <model>.em --slice-ready <slice-key>
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
`<slice-key>` is the slice's export key — the kebab-case slug of its name (`"Place Order"` →
|
|
29
|
+
`place-order`). Exit 0 means: the slice has a doc bound via `note "slices/<key>.md"`, its
|
|
30
|
+
frontmatter is usable, `status: ready-to-implement`, every `## Open Questions` checkbox is
|
|
31
|
+
checked, and no status/version/link incoherence is flagged.
|
|
32
|
+
|
|
33
|
+
**Non-zero exit means stop.** Report which checks failed and hand the slice back to the humans.
|
|
34
|
+
Never make the gate pass yourself — checking an open-question box, flipping `status`, or
|
|
35
|
+
editing frontmatter are ratification decisions, and ratification happens in a facilitated
|
|
36
|
+
session, not in an implementation branch.
|
|
37
|
+
|
|
38
|
+
## 2. Read the spec
|
|
39
|
+
|
|
40
|
+
Read `slices/<slice-key>.md` end to end — every section is load-bearing:
|
|
41
|
+
|
|
42
|
+
| Section | What it is to the implementer |
|
|
43
|
+
|---|---|
|
|
44
|
+
| frontmatter `pattern:` | Which of the four patterns' implementation shapes to build (State Change / State View / Automation / Translation) |
|
|
45
|
+
| Command / Event field tables | The contracts — names, types, validation rules, immutable-fact markers |
|
|
46
|
+
| `## Invariants / Business Rules` (INV-n IDs) | The non-negotiable rules; every one needs a test citing its ID |
|
|
47
|
+
| `## Scenarios (Given / When / Then)` | The acceptance tests, already written — compile them, don't reinvent them |
|
|
48
|
+
| `## Alternate & Error Flows` | Retries, idempotency, compensations — real requirements, not appendix |
|
|
49
|
+
| `## Non-Functional Requirements` | Authz, PII, performance — implement or consciously surface, never skip |
|
|
50
|
+
| `## Dependencies & Read Models Affected` | The blast radius — what else to check before and after |
|
|
51
|
+
|
|
52
|
+
For timeline context, read the slice's surroundings in the `.em` (what triggers it, what
|
|
53
|
+
consumes its output) — the slice's own diagram (`slices/<slice-key>.svg`) shows its canonical
|
|
54
|
+
pattern shape. For machine-readable facts, use `em export <model>.em` rather than parsing the
|
|
55
|
+
DSL or the doc's frontmatter yourself: it carries the slice's `pattern`, element refs, fields,
|
|
56
|
+
and the joined doc metadata (`slice.doc.status`, `version`, `driftSignal`).
|
|
57
|
+
|
|
58
|
+
If the project ships pattern-specific implementation skills (for example an Axon/DCB skill
|
|
59
|
+
set keyed on a slice doc's `pattern:` frontmatter), route by the slice's pattern and follow
|
|
60
|
+
those.
|
|
61
|
+
|
|
62
|
+
## 3. Two modes: first version vs. ratified delta
|
|
63
|
+
|
|
64
|
+
Check the doc's `version:` and `## Delta` section (or `em export`'s `slice.doc.driftSignal`):
|
|
65
|
+
|
|
66
|
+
- **First implementation** (`version: 1`, no `## Delta`, `driftSignal: never-implemented`):
|
|
67
|
+
green-field slice implementation. Build the whole pattern shape the doc specifies.
|
|
68
|
+
- **Ratified delta** (`version` > 1, `## Delta` present, `status: ready-to-implement` while
|
|
69
|
+
`implementedIn` still names the prior version's PR — `driftSignal: unpropagated-delta`):
|
|
70
|
+
this is a **change to existing, owned code**. The `## Delta` section's typed operations
|
|
71
|
+
(Added/Modified/Removed/Renamed, each with scenarios) scope the work precisely — which
|
|
72
|
+
invariants, which scenarios, which tests change. Touch what the delta names; leave the rest.
|
|
73
|
+
**Never regenerate merged code wholesale from the model** — after merge, code is owned by
|
|
74
|
+
the people who've edited it (generated-then-owned); regeneration is legal only for slices
|
|
75
|
+
whose code never merged.
|
|
76
|
+
|
|
77
|
+
## 4. Gaps: propose, never decide
|
|
78
|
+
|
|
79
|
+
Mid-implementation you will hit things the doc doesn't answer — a field's edge case, an
|
|
80
|
+
undocumented ordering, a contradiction with adjacent code. The discipline:
|
|
81
|
+
|
|
82
|
+
- **Stop that thread and surface it.** State precisely what's unspecified, the options, and
|
|
83
|
+
your recommendation. The human resolves it — at the model, where the resolution lands as an
|
|
84
|
+
answered open question or a small ratified delta alongside your fix, so the decision is
|
|
85
|
+
recorded instead of buried in an implementation diff.
|
|
86
|
+
- **Never edit the ratified doc to record your own answer**, and never quietly pick a behavior
|
|
87
|
+
a business person could have an opinion on. Silent divergence is the failure mode the whole
|
|
88
|
+
conformance loop exists to catch — don't manufacture it.
|
|
89
|
+
- Purely technical choices with no behavioral surface (private naming, idiomatic structure,
|
|
90
|
+
which assertion library) are yours; anything observable in behavior, data, or contract is not.
|
|
91
|
+
|
|
92
|
+
## 5. Definition of done
|
|
93
|
+
|
|
94
|
+
- Every `INV-n` invariant has at least one test that cites its ID.
|
|
95
|
+
- Every scenario in `## Scenarios (Given / When / Then)` exists as a passing test; rejection
|
|
96
|
+
scenarios assert the doc's named rejection reason.
|
|
97
|
+
- Alternate/error flows (idempotency included) are covered by tests.
|
|
98
|
+
- The build and full test suite are green; the model still validates (`em validate` in CI —
|
|
99
|
+
you didn't touch the `.em`, so this only fails if something else broke).
|
|
100
|
+
- Nothing between the slice doc and the code was committed as a source of truth (see §7 —
|
|
101
|
+
work containers are ephemeral; generated or symlinked specs are renderings).
|
|
102
|
+
|
|
103
|
+
## 6. At merge: the lifecycle flip
|
|
104
|
+
|
|
105
|
+
When the PR merges, exactly **two** frontmatter fields change on the slice doc:
|
|
106
|
+
|
|
107
|
+
```yaml
|
|
108
|
+
status: implemented
|
|
109
|
+
implementedIn: <PR or commit URL>
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
This is supply-loop mechanics, not ratification — it's the one edit an implementing agent makes
|
|
113
|
+
to a slice doc, and it's why `em ledger` deliberately excludes these two fields from what
|
|
114
|
+
counts as content. **Do not bump `version:`** — versions bump only when a delta is ratified;
|
|
115
|
+
a bump here is a ledger defect. In bridge-equipped projects,
|
|
116
|
+
`npx -p em-sdd-bridge em-sdd-mark-implemented <slice-key> <pr-url>` does the flip for you.
|
|
117
|
+
|
|
118
|
+
Then update the model's `README.md` slice index if the project keeps one, so the one canonical
|
|
119
|
+
slice table reflects the new status and link. The `implementedIn` link is what the `conform`
|
|
120
|
+
phase later uses to anchor drift-checking — leaving it empty blinds the loop.
|
|
121
|
+
|
|
122
|
+
## 7. Spec-kit projects: the SDD adapter
|
|
123
|
+
|
|
124
|
+
If the repository uses spec-kit (a `.specify/` directory exists), do not hand-author spec-kit
|
|
125
|
+
artifacts — allocate through **em-sdd-bridge**, redirect mode preferred:
|
|
126
|
+
|
|
127
|
+
```bash
|
|
128
|
+
npx em-sdd-bridge@<pinned-version> <slice-key> --symlink
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
That allocates the feature branch and `specs/NNN-slug/` dir, runs the same readiness gates,
|
|
132
|
+
and drops `spec.md` as a **symlink to the slice doc** — the slice doc itself travels through
|
|
133
|
+
plan/tasks as FEATURE_SPEC. The rules that keep redirect mode safe:
|
|
134
|
+
|
|
135
|
+
- **Never run `/speckit.specify`.** The ratified slice *is* the spec; an interactive specify
|
|
136
|
+
session against a defined slice creates a second, unratified source of truth.
|
|
137
|
+
- **`/speckit.clarify` must never write into a slice doc.** Open questions resolve at the
|
|
138
|
+
model (that's what the readiness gate enforced); a ratified slice has nothing to clarify.
|
|
139
|
+
Projects using em-sdd-preset's overlays carry a mechanical guard for this — respect the
|
|
140
|
+
rule even where the guard isn't installed.
|
|
141
|
+
- `/speckit.plan` and `/speckit.tasks` may run against the slice doc directly; the preset's
|
|
142
|
+
redirect preambles map spec-kit's section vocabulary onto the slice doc's sections.
|
|
143
|
+
- **Emission fallback**: where redirect can't work (Windows checkouts without symlink
|
|
144
|
+
privileges, two-doc pattern-pair bundles, a toolchain that mutates its spec files), run the
|
|
145
|
+
bridge without `--symlink` to render `spec.md` from the slice doc. The generated file is a
|
|
146
|
+
rendering — never hand-edit it; regenerate it from the slice doc instead.
|
|
147
|
+
- Everything under `specs/NNN-*/` is an **ephemeral work container** — one per change effort,
|
|
148
|
+
discardable; the PR is the durable record of the effort, the slice doc the durable
|
|
149
|
+
definition of the behavior.
|
|
150
|
+
|
|
151
|
+
No spec-kit (or any SDD tool)? Implement straight from the slice doc — it already contains
|
|
152
|
+
everything a spec holds. Don't introduce an intermediate spec document of your own.
|
|
153
|
+
|
|
154
|
+
## 8. Never do
|
|
155
|
+
|
|
156
|
+
| Rule | Because |
|
|
157
|
+
|---|---|
|
|
158
|
+
| Never implement from a slice that fails `--slice-ready` | The gate is the handoff contract; bypassing it hands you unratified decisions |
|
|
159
|
+
| Never edit a ratified slice doc, except the two §6 fields at merge | Doc content is ratified; edits outside a session are unratified decisions |
|
|
160
|
+
| Never edit the `.em` model | Model edits are ratified decisions — propose, don't write |
|
|
161
|
+
| Never bump `version:` | Versions move only with ratified deltas; see `em ledger` |
|
|
162
|
+
| Never silently decide unspecified behavior | Silent divergence is the disease the conformance loop exists to catch |
|
|
163
|
+
| Never regenerate merged code from the model | Generated-then-owned: post-merge code belongs to its owners |
|
|
164
|
+
| Never commit an authored intermediate spec | The slice is the spec; anything between it and the code is a rendering |
|
|
165
|
+
|
|
166
|
+
## 9. Afterward: the loop closes
|
|
167
|
+
|
|
168
|
+
Implementation isn't the end of the slice's story. On a cadence — or whenever someone asks —
|
|
169
|
+
the `conform` phase checks implemented slices against the code, using the `implementedIn` link
|
|
170
|
+
you recorded, and reports drift for humans to rule on (see `reference/conform.md`). Your two
|
|
171
|
+
duties to that future loop are already behind you if you followed this doc: an accurate
|
|
172
|
+
lifecycle flip, and zero silent decisions.
|
|
@@ -135,6 +135,11 @@ records an event directly.
|
|
|
135
135
|
The processor band holds no event; the command in the next slice produces it. Keeping the
|
|
136
136
|
triggered command in the same slice as the processor is a validation warning — always split it.
|
|
137
137
|
Forgetting the third slice leaves `E` unread, which is also a warning.
|
|
138
|
+
- **Naming:** name the read model after the pending work — `Payments To Process`,
|
|
139
|
+
`Orders To Fulfill`, `Pending Approvals` — never after the triggering event. The event is what
|
|
140
|
+
happened; the view is what's left to do. Reusing the event's name also collides in the shared
|
|
141
|
+
element namespace: `from` references then resolve by element kind, and `em validate` flags the
|
|
142
|
+
duplicated name.
|
|
138
143
|
- Socratic prompts: *"What should happen without a human? What condition triggers it? What work
|
|
139
144
|
list does the processor watch? What command does it fire? What if it fails or retries?"*
|
|
140
145
|
|
|
@@ -16,11 +16,15 @@ Replace bracketed placeholders; delete guidance comments before finishing.
|
|
|
16
16
|
|
|
17
17
|
## Summary
|
|
18
18
|
|
|
19
|
-
| Surface | Real drift | Model gap | Internal inconsistency | Accepted divergence | Uncertainty |
|
|
20
|
-
|
|
21
|
-
| Structural | {{n}} | {{n}} | — | {{n}} | {{n}} |
|
|
22
|
-
| Spec | {{n}} | {{n}} | — | {{n}} | {{n}} |
|
|
23
|
-
| Internal | — | — | {{n}} | {{n}} | {{n}} |
|
|
19
|
+
| Surface | Real drift | Model gap | Internal inconsistency | Accepted divergence | Unpropagated delta | Uncertainty |
|
|
20
|
+
|---|---|---|---|---|---|---|
|
|
21
|
+
| Structural | {{n}} | {{n}} | — | {{n}} | {{n}} | {{n}} |
|
|
22
|
+
| Spec | {{n}} | {{n}} | — | {{n}} | {{n}} | {{n}} |
|
|
23
|
+
| Internal | — | — | {{n}} | {{n}} | — | {{n}} |
|
|
24
|
+
|
|
25
|
+
**Unpropagated delta** — a slice re-ratified past its shipped version (`slice.doc.driftSignal:
|
|
26
|
+
"unpropagated-delta"` in `em export --json`): a known state, not a code defect. See
|
|
27
|
+
`reference/conform.md` step 4.
|
|
24
28
|
|
|
25
29
|
{{One or two sentences: overall read — clean, mostly clean with N items, or notably drifted.}}
|
|
26
30
|
|
|
@@ -32,15 +36,17 @@ Replace bracketed placeholders; delete guidance comments before finishing.
|
|
|
32
36
|
### {{n}}. {{Short finding title}}
|
|
33
37
|
|
|
34
38
|
- **Surface:** {{structural | spec | internal}}
|
|
35
|
-
- **Classification:** {{real drift | model gap | internal inconsistency | accepted divergence}}
|
|
39
|
+
- **Classification:** {{real drift | model gap | internal inconsistency | accepted divergence | unpropagated delta}}
|
|
36
40
|
- **Slice:** {{slice name}}
|
|
37
41
|
- **Evidence:** {{file path(s) and line/symbol, or the `em diff --json` entry (type + name) —
|
|
38
|
-
for accepted divergence, the `divergence "..."` annotation text itself
|
|
42
|
+
for accepted divergence, the `divergence "..."` annotation text itself; for unpropagated
|
|
43
|
+
delta, `slice.doc.driftSignal` and `slice.doc.version` from `em export --json`}}
|
|
39
44
|
- **Claim:** {{what the model/doc says vs. what the code/doc shows — the actual disagreement}}
|
|
40
45
|
- **Proposed red note:** `issue "conformance: {{text}}"` on {{element}} (slice "{{slice}}")
|
|
41
46
|
<!-- Omit this line entirely for internal-inconsistency findings with no clear side to flag,
|
|
42
|
-
for any accepted-divergence finding (already ratified
|
|
43
|
-
|
|
47
|
+
for any accepted-divergence or unpropagated-delta finding (already ratified, or already
|
|
48
|
+
a known pending-ship state — don't propose re-ratifying either), or when the finding
|
|
49
|
+
doesn't warrant a red note (say why instead). -->
|
|
44
50
|
|
|
45
51
|
## Scenario / invariant coverage
|
|
46
52
|
|
|
@@ -34,9 +34,9 @@ never connected to one another.
|
|
|
34
34
|
<!-- The canonical slice index — the ONE place slices are enumerated (the state file
|
|
35
35
|
points here rather than keeping its own copy). Keep it in sync as slice docs are
|
|
36
36
|
written and as statuses move. -->
|
|
37
|
-
| # | Slice | Pattern | Status | Design doc |
|
|
38
|
-
|
|
39
|
-
| 1 | {{Slice Name}} | {{Pattern}} | {{
|
|
37
|
+
| # | Slice | Pattern | Status | Implemented in | Design doc |
|
|
38
|
+
|---|-------|---------|--------|----------------|------------|
|
|
39
|
+
| 1 | {{Slice Name}} | {{Pattern}} | {{no doc yet / draft / reviewed / ready-to-implement / implemented}} | {{PR/commit link once implemented, else —}} | [slices/{{slice-name}}.md](slices/{{slice-name}}.md) |
|
|
40
40
|
|
|
41
41
|
## Status
|
|
42
42
|
See [`.event-modeling.md`](.event-modeling.md) for current phase, decisions, and open questions.
|
|
@@ -4,18 +4,84 @@ linked from the .em model with: note "slices/<slice-name>.md" on the slice's d
|
|
|
4
4
|
Fill every section through Socratic questioning. Leave "Open Questions" rather than guessing.
|
|
5
5
|
Replace the bracketed placeholders; delete guidance comments before finishing.
|
|
6
6
|
|
|
7
|
+
The frontmatter below is the canonical, machine-read metadata dialect — `em`'s own parser
|
|
8
|
+
(src/catalog/sliceDoc.ts) reads `status:`, `version:`, `pattern:`, and the lineage keys
|
|
9
|
+
(`split-from:`/`merged-from:`/`superseded-by:`) from it. `pattern` is kebab-case
|
|
10
|
+
(state-change/state-view/automation/translation) even though the skill's prose always says
|
|
11
|
+
"State Change"/"State View"/etc. — the frontmatter value is a machine key, not a display label.
|
|
12
|
+
Older docs using a `- **Status:** ...` bullet line instead of frontmatter still parse (legacy/
|
|
13
|
+
accepted input), but new docs should always use this frontmatter form; `version` and lineage
|
|
14
|
+
have no legacy form — frontmatter-only from day one.
|
|
15
|
+
|
|
16
|
+
`version` is this slice's own ratified-content version — starts at `1`, bumps when a delta is
|
|
17
|
+
ratified — distinct from `schemaVersion`, which versions the frontmatter dialect itself, not
|
|
18
|
+
this slice. When a ratified change lands on an already-`implemented` slice: bump `version`,
|
|
19
|
+
flip `status` back to `ready-to-implement` (it tracks the CURRENT version's implementation
|
|
20
|
+
state — `implementedIn` legitimately keeps naming the PRIOR version's PR until the new version
|
|
21
|
+
ships; that mismatch is an intended drift signal, not a bug), and fill in the `## Delta` section
|
|
22
|
+
below with this hop's change, replacing whatever it held before (it always shows only the latest
|
|
23
|
+
hop — full history lives in git, same-commit-ratification convention). Full grammar — the fixed
|
|
24
|
+
heading, the four operation subsections, why the heading never varies, the replace-not-accumulate
|
|
25
|
+
lifecycle — is documented in docs/slice-doc-schema.md#delta-section-grammar-and-lifecycle; treat
|
|
26
|
+
it the same Socratic way as every other section here, don't generate it mechanically.
|
|
27
|
+
|
|
28
|
+
The three lineage keys only apply when this doc was produced by a split, merge, or rename —
|
|
29
|
+
delete them otherwise; most slices never carry them. Grammar: `<slice-key>@v<N>`, where
|
|
30
|
+
`<slice-key>` is the referenced slice's kebab-case filename stem.
|
|
31
|
+
|
|
32
|
+
Full machine schema — required-vs-optional keys per `status`, value types/enums, the
|
|
33
|
+
unknown-key policy — is documented in docs/slice-doc-schema.md.
|
|
34
|
+
|
|
7
35
|
The diagram below is generated, not hand-drawn: `em render <model>.em --slice "{{Slice Name}}"
|
|
8
36
|
-o slices/{{slice-name}}.svg` (kebab-case the slice name to match this doc's own filename).
|
|
9
37
|
-->
|
|
10
38
|
|
|
39
|
+
---
|
|
40
|
+
schemaVersion: 1
|
|
41
|
+
pattern: {{state-change | state-view | automation | translation}}
|
|
42
|
+
swimlane: {{Persona/Actor}} → {{Context/Aggregate}}
|
|
43
|
+
status: {{draft | reviewed | ready-to-implement | implemented}}
|
|
44
|
+
version: 1
|
|
45
|
+
implementedIn: {{PR/commit link — fill in once status is `implemented`}}
|
|
46
|
+
# Lineage — only when this doc exists because of a split, merge, or rename (delete these three
|
|
47
|
+
# lines otherwise). Grammar: <slice-key>@v<N>. See
|
|
48
|
+
# docs/slice-doc-schema.md#lineage-grammar-and-cardinality.
|
|
49
|
+
# split-from: <slice-key>@v<N>
|
|
50
|
+
# merged-from: <slice-key>@v<N>, <slice-key>@v<N>
|
|
51
|
+
# superseded-by: <slice-key>@v<N>, <slice-key>@v<N>
|
|
52
|
+
---
|
|
11
53
|
# Slice: {{Slice Name}}
|
|
12
54
|
|
|
13
55
|

|
|
14
56
|
|
|
15
|
-
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
-
|
|
57
|
+
<!-- Only on re-ratification of an already-`implemented` slice — omit this whole section on a
|
|
58
|
+
slice's first version (v1 has no delta yet). The heading is always the literal `## Delta`,
|
|
59
|
+
never `## Delta: vX → vY` — hop metadata is a display line inside the section instead (see
|
|
60
|
+
docs/slice-doc-schema.md#delta-section-grammar-and-lifecycle for why). Replace this
|
|
61
|
+
section's content wholesale on the next re-ratification — it shows only the latest hop, not
|
|
62
|
+
an accumulating log. Omit any of the four subsections below with no entries this hop; keep
|
|
63
|
+
the remaining ones in this order (Added/Modified/Removed/Renamed). `Renamed` is for a
|
|
64
|
+
requirement/invariant renamed within this slice, not a substitute for the frontmatter
|
|
65
|
+
`split-from`/`merged-from`/`superseded-by` lineage keys above, which record the slice-doc
|
|
66
|
+
itself splitting, merging, or being renamed. -->
|
|
67
|
+
## Delta
|
|
68
|
+
**v{{X}} → v{{Y}}, ratified {{date}}** — {{one-line summary of the ratified change}}
|
|
69
|
+
|
|
70
|
+
### Added
|
|
71
|
+
#### Requirement: {{title}} ({{stable ID, e.g. an INV-n from Invariants below}})
|
|
72
|
+
{{requirement text — same voice as an Invariants entry}}
|
|
73
|
+
##### Scenario: {{name}}
|
|
74
|
+
- **GIVEN** {{...}} **WHEN** {{...}} **THEN** {{...}}
|
|
75
|
+
|
|
76
|
+
### Modified
|
|
77
|
+
<!-- Same shape as Added — the stable ID names the requirement that changed. -->
|
|
78
|
+
|
|
79
|
+
### Removed
|
|
80
|
+
#### Requirement: {{title}} ({{stable ID}})
|
|
81
|
+
{{why it was removed}}
|
|
82
|
+
|
|
83
|
+
### Renamed
|
|
84
|
+
- {{old title}} ({{old ID}}) → {{new title}} ({{new ID}})
|
|
19
85
|
|
|
20
86
|
## Intent
|
|
21
87
|
{{Why this slice exists — the user or business goal it serves, in one or two sentences. Note the
|
|
@@ -7,7 +7,7 @@ Keep it current at the end of every working session.
|
|
|
7
7
|
# Event Modeling Progress — {{Model Name}}
|
|
8
8
|
|
|
9
9
|
- **Model file:** `{{model-name}}.em`
|
|
10
|
-
- **Current phase:** {{
|
|
10
|
+
- **Current phase:** {{discover | extract | model | slice | implement | conform | review | validate}}
|
|
11
11
|
- **Current step:** {{1–7, see methodology; or extraction round R1–R7}}
|
|
12
12
|
- **Last updated:** {{YYYY-MM-DD}}
|
|
13
13
|
- **Last conformance:** {{YYYY-MM-DD @ <target-repo revision> — report: conformance/<date>-report.md | never}}
|
|
@@ -50,10 +50,11 @@ Keep it current at the end of every working session.
|
|
|
50
50
|
|
|
51
51
|
## Usage log
|
|
52
52
|
<!-- The team's only usage signal today (see docs/usage-data.md) — cheap and coarse on purpose.
|
|
53
|
-
One line per session: phase(s) touched, and validate diagnostic *categories* hit (
|
|
54
|
-
|
|
55
|
-
instance message or any domain content). Append, never edit
|
|
56
|
-
|
|
53
|
+
One line per session: phase(s) touched, and validate diagnostic *categories* hit (one of
|
|
54
|
+
the exact fixed strings in docs/usage-data.md#categories, e.g. "read model has no
|
|
55
|
+
consumer" — never the full instance message or any domain content). Append, never edit
|
|
56
|
+
past entries. -->
|
|
57
|
+
- {{YYYY-MM-DD}}: phases: {{discover | extract | model | slice | implement | conform | review | validate | watch, ...}} — validate: {{diagnostic category (docs/usage-data.md#categories), ... | none}}
|
|
57
58
|
|
|
58
59
|
## Open questions / parking lot
|
|
59
60
|
<!-- Unresolved items to bring back to the user. Never guess these. Metadata is optional and
|
package/README.md
CHANGED
|
@@ -84,6 +84,7 @@ built this way.
|
|
|
84
84
|
|---|---|
|
|
85
85
|
| [docs/tutorial.md](docs/tutorial.md) | Learn the tool by building a model from scratch |
|
|
86
86
|
| [docs/workflow.md](docs/workflow.md) | The model lifecycle: specify, gate, hand off, track change, detect drift |
|
|
87
|
+
| [docs/process.md](docs/process.md) | Who does what: where humans are required, where agents work with review |
|
|
87
88
|
| [docs/patterns.md](docs/patterns.md) | The four Event Modeling patterns and their DSL shapes |
|
|
88
89
|
| [docs/dsl.md](docs/dsl.md) | Full DSL reference: keywords, `from`, `again`, fields, notes |
|
|
89
90
|
| [docs/cli.md](docs/cli.md) | Every command and flag |
|
package/dist/catalog/build.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { NormalizedModel } from "../model/model.js";
|
|
2
2
|
import { Grid } from "../layout/grid.js";
|
|
3
|
+
import { RefsResult } from "../model/refs.js";
|
|
3
4
|
import { Diagnostic } from "../model/validate.js";
|
|
4
5
|
export interface CatalogModelInput {
|
|
5
6
|
/** The .em file as given on the command line — used for the doc-discovery path and display. */
|
|
@@ -7,6 +8,10 @@ export interface CatalogModelInput {
|
|
|
7
8
|
model: NormalizedModel;
|
|
8
9
|
grid: Grid;
|
|
9
10
|
dot: string;
|
|
11
|
+
/** Already-computed by the same `compile()` call that produced `model`/`grid` (MIL-91) —
|
|
12
|
+
* never recomputed here, so its ref-collision diagnostics (already in `compile()`'s own
|
|
13
|
+
* `diagnostics`) aren't emitted a second time. */
|
|
14
|
+
refs: RefsResult;
|
|
10
15
|
}
|
|
11
16
|
export interface CatalogOptions {
|
|
12
17
|
outDir: string;
|
package/dist/catalog/build.js
CHANGED
|
@@ -15,7 +15,6 @@ import { copyFile, mkdir, writeFile } from "node:fs/promises";
|
|
|
15
15
|
import { basename, dirname, extname, join } from "node:path";
|
|
16
16
|
import { layoutDot, composeSvg, writeRendered } from "../render/render.js";
|
|
17
17
|
import { buildSliceDiagram } from "../render/sliceDiagram.js";
|
|
18
|
-
import { computeRefs } from "../emit/json.js";
|
|
19
18
|
import { dedupe, kebabSlug } from "../util/slug.js";
|
|
20
19
|
import { classifySlicePattern } from "./classify.js";
|
|
21
20
|
import { parseSliceDoc } from "./sliceDoc.js";
|
|
@@ -29,7 +28,7 @@ export async function buildCatalog(inputs, opts) {
|
|
|
29
28
|
const modelSummaries = [];
|
|
30
29
|
const diagnostics = [];
|
|
31
30
|
let sliceCount = 0;
|
|
32
|
-
for (const { file, model, grid, dot } of inputs) {
|
|
31
|
+
for (const { file, model, grid, dot, refs } of inputs) {
|
|
33
32
|
const modelKey = dedupe(kebabSlug(basename(file, extname(file))), usedModelKeys, "~");
|
|
34
33
|
const modelDir = join(outDir, modelKey);
|
|
35
34
|
const slicesDir = join(modelDir, "slices");
|
|
@@ -40,7 +39,6 @@ export async function buildCatalog(inputs, opts) {
|
|
|
40
39
|
const raw = await layoutDot(dot);
|
|
41
40
|
const mainSvg = composeSvg(raw, model, grid, dirname(file), modelDir);
|
|
42
41
|
await writeRendered(mainSvg, join(modelDir, diagramFile), format);
|
|
43
|
-
const refs = computeRefs(model);
|
|
44
42
|
if (refs.diagnostics.length > 0)
|
|
45
43
|
diagnostics.push({ file, diagnostics: refs.diagnostics });
|
|
46
44
|
const sliceSummaries = [];
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"build.js","sourceRoot":"","sources":["../../src/catalog/build.ts"],"names":[],"mappings":"AAAA,+BAA+B;AAC/B,kEAAkE;AAClE,0EAA0E;AAC1E,4EAA4E;AAC5E,EAAE;AACF,cAAc;AACd,iBAAiB;AACjB,mBAAmB;AACnB,8BAA8B;AAC9B,gBAAgB;AAChB,2BAA2B;AAC3B,2FAA2F;AAE3F,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACnD,OAAO,EAAE,QAAQ,EAAE,KAAK,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAC9D,OAAO,EAAE,QAAQ,EAAE,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAG7D,OAAO,EAAE,SAAS,EAAE,UAAU,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAC3E,OAAO,EAAE,iBAAiB,EAAE,MAAM,2BAA2B,CAAC;
|
|
1
|
+
{"version":3,"file":"build.js","sourceRoot":"","sources":["../../src/catalog/build.ts"],"names":[],"mappings":"AAAA,+BAA+B;AAC/B,kEAAkE;AAClE,0EAA0E;AAC1E,4EAA4E;AAC5E,EAAE;AACF,cAAc;AACd,iBAAiB;AACjB,mBAAmB;AACnB,8BAA8B;AAC9B,gBAAgB;AAChB,2BAA2B;AAC3B,2FAA2F;AAE3F,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACnD,OAAO,EAAE,QAAQ,EAAE,KAAK,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAC9D,OAAO,EAAE,QAAQ,EAAE,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAG7D,OAAO,EAAE,SAAS,EAAE,UAAU,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAC3E,OAAO,EAAE,iBAAiB,EAAE,MAAM,2BAA2B,CAAC;AAG9D,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AACpD,OAAO,EAAE,oBAAoB,EAAE,MAAM,eAAe,CAAC;AACrD,OAAO,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAC9C,OAAO,EAAE,eAAe,EAAE,eAAe,EAA4C,MAAM,YAAY,CAAC;AAmCxG,MAAM,CAAC,KAAK,UAAU,YAAY,CAChC,MAA2B,EAC3B,IAAoB;IAEpB,MAAM,MAAM,GAAG,IAAI,CAAC,MAAM,IAAI,KAAK,CAAC;IACpC,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,IAAI,qBAAqB,CAAC;IAClD,MAAM,MAAM,GAAG,IAAI,CAAC,MAAM,CAAC;IAE3B,MAAM,KAAK,CAAC,MAAM,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAEzC,MAAM,aAAa,GAAG,IAAI,GAAG,EAAU,CAAC;IACxC,MAAM,cAAc,GAA0B,EAAE,CAAC;IACjD,MAAM,WAAW,GAAkD,EAAE,CAAC;IACtE,IAAI,UAAU,GAAG,CAAC,CAAC;IAEnB,KAAK,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,GAAG,EAAE,IAAI,EAAE,IAAI,MAAM,EAAE,CAAC;QACtD,MAAM,QAAQ,GAAG,MAAM,CAAC,SAAS,CAAC,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,aAAa,EAAE,GAAG,CAAC,CAAC;QACtF,MAAM,QAAQ,GAAG,IAAI,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC;QACxC,MAAM,SAAS,GAAG,IAAI,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC;QAC3C,MAAM,KAAK,CAAC,SAAS,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;QAE5C,MAAM,WAAW,GAAG,WAAW,MAAM,EAAE,CAAC;QACxC,mFAAmF;QACnF,sFAAsF;QACtF,MAAM,GAAG,GAAG,MAAM,SAAS,CAAC,GAAG,CAAC,CAAC;QACjC,MAAM,OAAO,GAAG,UAAU,CAAC,GAAG,EAAE,KAAK,EAAE,IAAI,EAAE,OAAO,CAAC,IAAI,CAAC,EAAE,QAAQ,CAAC,CAAC;QACtE,MAAM,aAAa,CAAC,OAAO,EAAE,IAAI,CAAC,QAAQ,EAAE,WAAW,CAAC,EAAE,MAAM,CAAC,CAAC;QAElE,IAAI,IAAI,CAAC,WAAW,CAAC,MAAM,GAAG,CAAC;YAAE,WAAW,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,WAAW,EAAE,IAAI,CAAC,WAAW,EAAE,CAAC,CAAC;QAC3F,MAAM,cAAc,GAA0B,EAAE,CAAC;QAEjD,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,KAAK,CAAC,MAAM,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;YAC7C,MAAM,KAAK,GAAG,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;YAC9B,MAAM,QAAQ,GAAG,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC;YACnC,MAAM,OAAO,GAAG,oBAAoB,CAAC,KAAK,CAAC,CAAC;YAE5C,mFAAmF;YACnF,oFAAoF;YACpF,mFAAmF;YACnF,sFAAsF;YACtF,qFAAqF;YACrF,6EAA6E;YAC7E,MAAM,UAAU,GAAG,IAAI,CAAC,QAAQ,EAAE,GAAG,QAAQ,KAAK,CAAC,CAAC;YACpD,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,UAAU,CAAC,CAAC;YAChD,MAAM,GAAG,GAAG,UAAU,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,aAAa,CAAC,YAAY,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;YAEtF,8EAA8E;YAC9E,+EAA+E;YAC/E,0EAA0E;YAC1E,0EAA0E;YAC1E,8DAA8D;YAC9D,MAAM,eAAe,GAAG,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,QAAQ,EAAE,GAAG,QAAQ,MAAM,CAAC,CAAC;YACzE,MAAM,gBAAgB,GAAG,GAAG,QAAQ,MAAM,CAAC;YAC3C,IAAI,UAAU,CAAC,eAAe,CAAC,EAAE,CAAC;gBAChC,MAAM,QAAQ,CAAC,eAAe,EAAE,IAAI,CAAC,SAAS,EAAE,gBAAgB,CAAC,CAAC,CAAC;YACrE,CAAC;iBAAM,CAAC;gBACN,+EAA+E;gBAC/E,8EAA8E;gBAC9E,6EAA6E;gBAC7E,8EAA8E;gBAC9E,4EAA4E;gBAC5E,sEAAsE;gBACtE,MAAM,EAAE,KAAK,EAAE,UAAU,EAAE,IAAI,EAAE,SAAS,EAAE,GAAG,EAAE,QAAQ,EAAE,GAAG,iBAAiB,CAAC,KAAK,EAAE,CAAC,EAAE;oBACxF,cAAc,EAAE,IAAI,CAAC,cAAc;iBACpC,CAAC,CAAC;gBACH,MAAM,QAAQ,GAAG,MAAM,SAAS,CAAC,QAAQ,CAAC,CAAC;gBAC3C,MAAM,QAAQ,GAAG,UAAU,CAAC,QAAQ,EAAE,UAAU,EAAE,SAAS,EAAE,OAAO,CAAC,IAAI,CAAC,EAAE,SAAS,CAAC,CAAC;gBACvF,MAAM,SAAS,CAAC,IAAI,CAAC,SAAS,EAAE,gBAAgB,CAAC,EAAE,QAAQ,EAAE,MAAM,CAAC,CAAC;YACvE,CAAC;YAED,MAAM,WAAW,GAAG,IAAI,GAAG,EAAkB,CAAC;YAC9C,KAAK,MAAM,EAAE,IAAI,KAAK,CAAC,QAAQ,EAAE,CAAC;gBAChC,MAAM,GAAG,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC;gBACpC,IAAI,GAAG;oBAAE,WAAW,CAAC,GAAG,CAAC,EAAE,CAAC,EAAE,EAAE,GAAG,CAAC,CAAC;YACvC,CAAC;YAED,MAAM,IAAI,GAAG,eAAe,CAAC;gBAC3B,SAAS,EAAE,KAAK,CAAC,IAAI;gBACrB,WAAW,EAAE,MAAM,WAAW,EAAE;gBAChC,gBAAgB;gBAChB,KAAK;gBACL,QAAQ;gBACR,OAAO;gBACP,WAAW;gBACX,GAAG;gBACH,eAAe,EAAE,UAAU;aAC5B,CAAC,CAAC;YACH,MAAM,SAAS,CAAC,IAAI,CAAC,SAAS,EAAE,GAAG,QAAQ,OAAO,CAAC,EAAE,IAAI,EAAE,MAAM,CAAC,CAAC;YAEnE,cAAc,CAAC,IAAI,CAAC;gBAClB,GAAG,EAAE,QAAQ;gBACb,IAAI,EAAE,KAAK,CAAC,IAAI;gBAChB,OAAO;gBACP,MAAM,EAAE,GAAG,KAAK,IAAI;gBACpB,MAAM,EAAE,GAAG,EAAE,MAAM,IAAI,IAAI;aAC5B,CAAC,CAAC;YACH,UAAU,EAAE,CAAC;QACf,CAAC;QAED,cAAc,CAAC,IAAI,CAAC,EAAE,GAAG,EAAE,QAAQ,EAAE,IAAI,EAAE,KAAK,CAAC,IAAI,EAAE,IAAI,EAAE,WAAW,EAAE,MAAM,EAAE,cAAc,EAAE,CAAC,CAAC;IACtG,CAAC;IAED,MAAM,SAAS,GAAG,eAAe,CAAC,cAAc,EAAE,KAAK,EAAE,MAAM,CAAC,CAAC;IACjE,MAAM,SAAS,CAAC,IAAI,CAAC,MAAM,EAAE,YAAY,CAAC,EAAE,SAAS,EAAE,MAAM,CAAC,CAAC;IAE/D,OAAO,EAAE,MAAM,EAAE,cAAc,CAAC,MAAM,EAAE,MAAM,EAAE,UAAU,EAAE,WAAW,EAAE,CAAC;AAC5E,CAAC"}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { Slice } from "../model/model.js";
|
|
2
|
+
import { Diagnostic } from "../model/validate.js";
|
|
3
|
+
import { DriftSignalKind } from "./driftSignal.js";
|
|
4
|
+
import { SliceRef } from "./sliceDoc.js";
|
|
5
|
+
export type DocReason = "no-doc-bound" | "binding-missing-file" | "frontmatter-invalid" | null;
|
|
6
|
+
/** A slice's export-facing doc join — always a non-null object (never JSON `null`), matching
|
|
7
|
+
* every other optional field in `em export`'s style: explicit-null-on-a-stable-key, never a
|
|
8
|
+
* nullable wrapper. `reason` is null exactly when `found` is true and the frontmatter parsed
|
|
9
|
+
* cleanly; otherwise it names which of the three broken states applies. */
|
|
10
|
+
export interface SliceDocExport {
|
|
11
|
+
found: boolean;
|
|
12
|
+
/** The conventional path (`slices/<sliceKey>.md`), always present regardless of `found` —
|
|
13
|
+
* the path a doc would need to exist at, and (when `found`) the path it was read from. */
|
|
14
|
+
path: string;
|
|
15
|
+
reason: DocReason;
|
|
16
|
+
status: string | null;
|
|
17
|
+
version: number | null;
|
|
18
|
+
implementedIn: string | null;
|
|
19
|
+
splitFrom: SliceRef | null;
|
|
20
|
+
mergedFrom: SliceRef[];
|
|
21
|
+
supersededBy: SliceRef[];
|
|
22
|
+
/** Implementation-drift classification (MIL-85), from `status`+`implementedIn` alone — see
|
|
23
|
+
* catalog/driftSignal.ts. Null only when `found` is false (nothing to classify); paired with
|
|
24
|
+
* `version` above from the same doc parse — a finding built from `driftSignal` should always
|
|
25
|
+
* cite `version` alongside it, never cache one without the other. */
|
|
26
|
+
driftSignal: DriftSignalKind | null;
|
|
27
|
+
}
|
|
28
|
+
export interface SliceDocJoinResult {
|
|
29
|
+
doc: SliceDocExport;
|
|
30
|
+
diagnostics: Diagnostic[];
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Resolve one slice's doc join. `baseDir` is the `.em` file's directory (doc paths, like note
|
|
34
|
+
* paths everywhere else in em, are relative to it). `sliceRef`/`elementRefOf` supply the
|
|
35
|
+
* export-stable refs (from the same `computeRefs()` result `em export`'s per-element/slice
|
|
36
|
+
* entries use) so the returned diagnostics point at the same identities every other export
|
|
37
|
+
* diagnostic does — never a second identifier scheme.
|
|
38
|
+
*/
|
|
39
|
+
export declare function resolveSliceDocJoin(slice: Slice, sliceKey: string, baseDir: string, elementRefOf: (id: string) => string): SliceDocJoinResult;
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
// The `em export` slice-doc join (MIL-91): for each slice, resolve whether a design doc was
|
|
3
|
+
// declared, found, and well-formed, and if so pull its canonical frontmatter fields into the
|
|
4
|
+
// export JSON. Used ONLY by `em export` (src/emit/json.ts) — `em catalog`'s own doc lookup
|
|
5
|
+
// (catalog/build.ts) deliberately keeps its simpler existsSync+parseSliceDoc convention: it
|
|
6
|
+
// has a standing tested invariant that catalog's doc discovery ignores `note` entirely
|
|
7
|
+
// (test/catalog.e2e.test.ts), and doesn't need this module's 3-state reason granularity. The
|
|
8
|
+
// underlying read (`readSliceDoc.ts`) is shared with `em diff`'s lineage annotation
|
|
9
|
+
// (model/diff.ts) and `em validate`'s lineage-ref resolution (catalog/lineageValidate.ts,
|
|
10
|
+
// MIL-84) — this module's `note`-binding gate and 3-state `DocReason` stay export-specific.
|
|
11
|
+
//
|
|
12
|
+
// Three states (MIL-91): the `note "slices/<name>.md"` binding on an element is the
|
|
13
|
+
// load-bearing fact — binding a note IS the ratified declaration that a doc should exist. No
|
|
14
|
+
// note binding is a normal, unremarkable state (a slice that hasn't reached specify yet); a
|
|
15
|
+
// binding pointing at a missing file, or a doc whose frontmatter is missing or malformed, are
|
|
16
|
+
// both broken-contract states and warn.
|
|
17
|
+
import { makeDiag } from "../model/rules.js";
|
|
18
|
+
import { classifyImplementationDrift } from "./driftSignal.js";
|
|
19
|
+
import { readSliceDoc } from "./readSliceDoc.js";
|
|
20
|
+
import { hasUsableFrontmatter } from "./sliceDoc.js";
|
|
21
|
+
const EMPTY_CONTENT = {
|
|
22
|
+
status: null,
|
|
23
|
+
version: null,
|
|
24
|
+
implementedIn: null,
|
|
25
|
+
splitFrom: null,
|
|
26
|
+
mergedFrom: [],
|
|
27
|
+
supersededBy: [],
|
|
28
|
+
driftSignal: null,
|
|
29
|
+
};
|
|
30
|
+
/**
|
|
31
|
+
* Resolve one slice's doc join. `baseDir` is the `.em` file's directory (doc paths, like note
|
|
32
|
+
* paths everywhere else in em, are relative to it). `sliceRef`/`elementRefOf` supply the
|
|
33
|
+
* export-stable refs (from the same `computeRefs()` result `em export`'s per-element/slice
|
|
34
|
+
* entries use) so the returned diagnostics point at the same identities every other export
|
|
35
|
+
* diagnostic does — never a second identifier scheme.
|
|
36
|
+
*/
|
|
37
|
+
export function resolveSliceDocJoin(slice, sliceKey, baseDir, elementRefOf) {
|
|
38
|
+
const path = `slices/${sliceKey}.md`;
|
|
39
|
+
const boundEls = slice.elements.filter((el) => el.note === path);
|
|
40
|
+
if (boundEls.length === 0) {
|
|
41
|
+
return { doc: { found: false, path, reason: "no-doc-bound", ...EMPTY_CONTENT }, diagnostics: [] };
|
|
42
|
+
}
|
|
43
|
+
const parsed = readSliceDoc(baseDir, sliceKey);
|
|
44
|
+
if (!parsed) {
|
|
45
|
+
return {
|
|
46
|
+
doc: { found: false, path, reason: "binding-missing-file", ...EMPTY_CONTENT },
|
|
47
|
+
diagnostics: [
|
|
48
|
+
makeDiag("binding-missing-file", {
|
|
49
|
+
message: `slice "${slice.name}" notes "${path}" but no such file exists`,
|
|
50
|
+
line: boundEls[0].line,
|
|
51
|
+
refs: [sliceKey, ...boundEls.map((el) => elementRefOf(el.id))],
|
|
52
|
+
}),
|
|
53
|
+
],
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
if (!hasUsableFrontmatter(parsed)) {
|
|
57
|
+
return {
|
|
58
|
+
doc: { found: true, path, reason: "frontmatter-invalid", ...EMPTY_CONTENT },
|
|
59
|
+
diagnostics: [
|
|
60
|
+
makeDiag("frontmatter-invalid", {
|
|
61
|
+
message: parsed.frontmatterPresent
|
|
62
|
+
? `slice doc "${path}" is missing required frontmatter keys: ${parsed.missingRequiredFields.join(", ")}`
|
|
63
|
+
: `slice doc "${path}" has no frontmatter block`,
|
|
64
|
+
line: slice.line,
|
|
65
|
+
refs: [sliceKey],
|
|
66
|
+
}),
|
|
67
|
+
],
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
return {
|
|
71
|
+
doc: {
|
|
72
|
+
found: true,
|
|
73
|
+
path,
|
|
74
|
+
reason: null,
|
|
75
|
+
status: parsed.status,
|
|
76
|
+
version: parsed.version,
|
|
77
|
+
implementedIn: parsed.implementedIn,
|
|
78
|
+
splitFrom: parsed.splitFrom,
|
|
79
|
+
mergedFrom: parsed.mergedFrom,
|
|
80
|
+
supersededBy: parsed.supersededBy,
|
|
81
|
+
driftSignal: classifyImplementationDrift(parsed),
|
|
82
|
+
},
|
|
83
|
+
diagnostics: [],
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
//# sourceMappingURL=docJoin.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"docJoin.js","sourceRoot":"","sources":["../../src/catalog/docJoin.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { SliceDoc } from "./sliceDoc.js";
|
|
2
|
+
export type DriftSignalKind =
|
|
3
|
+
/** status: implemented, implementedIn set — normal shipped state. */
|
|
4
|
+
"in-sync"
|
|
5
|
+
/** status not implemented, implementedIn absent — normal pre-ship state. */
|
|
6
|
+
| "never-implemented"
|
|
7
|
+
/** status not implemented, implementedIn still set — a ratified delta hasn't shipped yet.
|
|
8
|
+
* Expected, not a defect: never surface this as fresh drift or a validate diagnostic. */
|
|
9
|
+
| "unpropagated-delta"
|
|
10
|
+
/** status: implemented, implementedIn absent — genuine incoherence (em validate warns). */
|
|
11
|
+
| "implemented-without-link";
|
|
12
|
+
/**
|
|
13
|
+
* Classify a doc's status/implementedIn pair. Takes a `Pick` rather than the full `SliceDoc` so
|
|
14
|
+
* callers building a partial/synthetic doc (tests, future callers) don't need every field.
|
|
15
|
+
*/
|
|
16
|
+
export declare function classifyImplementationDrift(doc: Pick<SliceDoc, "status" | "implementedIn">): DriftSignalKind;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
// Classifies a slice doc's implementation-drift state from `status` + `implementedIn` alone
|
|
3
|
+
// (MIL-85). Pure — same spirit as sliceDoc.ts itself: no fs, no model, no baseDir — so
|
|
4
|
+
// `em export`'s doc join (catalog/docJoin.ts), `em validate`'s frontmatter-coherence check
|
|
5
|
+
// (catalog/frontmatterCoherenceValidate.ts), and the conform skill (reading `em export --json`'s
|
|
6
|
+
// `slice.doc.driftSignal`) all consume the exact same answer instead of re-deriving this
|
|
7
|
+
// predicate three different ways.
|
|
8
|
+
//
|
|
9
|
+
// The load-bearing distinction (docs/slice-doc-schema.md, "`status` under re-ratification"):
|
|
10
|
+
// re-ratifying a shipped slice flips `status` back off `implemented` while `implementedIn` keeps
|
|
11
|
+
// naming the prior version's PR. That combination — `unpropagated-delta` — is the EXPECTED drift
|
|
12
|
+
// signal, not staleness, and must never be flagged as incoherence. Only `implemented-without-link`
|
|
13
|
+
// (status: implemented, no link at all) is a genuine coherence problem worth a diagnostic.
|
|
14
|
+
/**
|
|
15
|
+
* Classify a doc's status/implementedIn pair. Takes a `Pick` rather than the full `SliceDoc` so
|
|
16
|
+
* callers building a partial/synthetic doc (tests, future callers) don't need every field.
|
|
17
|
+
*/
|
|
18
|
+
export function classifyImplementationDrift(doc) {
|
|
19
|
+
const hasLink = typeof doc.implementedIn === "string" && doc.implementedIn.trim().length > 0;
|
|
20
|
+
if (doc.status === "implemented")
|
|
21
|
+
return hasLink ? "in-sync" : "implemented-without-link";
|
|
22
|
+
return hasLink ? "unpropagated-delta" : "never-implemented";
|
|
23
|
+
}
|
|
24
|
+
//# sourceMappingURL=driftSignal.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"driftSignal.js","sourceRoot":"","sources":["../../src/catalog/driftSignal.ts"],"names":[],"mappings":"AAAA,+BAA+B;AAC/B,4FAA4F;AAC5F,uFAAuF;AACvF,2FAA2F;AAC3F,iGAAiG;AACjG,yFAAyF;AACzF,kCAAkC;AAClC,EAAE;AACF,6FAA6F;AAC7F,iGAAiG;AACjG,iGAAiG;AACjG,mGAAmG;AACnG,2FAA2F;AAe3F;;;GAGG;AACH,MAAM,UAAU,2BAA2B,CAAC,GAA+C;IACzF,MAAM,OAAO,GAAG,OAAO,GAAG,CAAC,aAAa,KAAK,QAAQ,IAAI,GAAG,CAAC,aAAa,CAAC,IAAI,EAAE,CAAC,MAAM,GAAG,CAAC,CAAC;IAC7F,IAAI,GAAG,CAAC,MAAM,KAAK,aAAa;QAAE,OAAO,OAAO,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,0BAA0B,CAAC;IAC1F,OAAO,OAAO,CAAC,CAAC,CAAC,oBAAoB,CAAC,CAAC,CAAC,mBAAmB,CAAC;AAC9D,CAAC"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { NormalizedModel } from "../model/model.js";
|
|
2
|
+
import { RefsResult } from "../model/refs.js";
|
|
3
|
+
import { Diagnostic } from "../model/validate.js";
|
|
4
|
+
/**
|
|
5
|
+
* Resolve every slice's doc and flag genuine status/implementedIn incoherence. `baseDir` is the
|
|
6
|
+
* `.em` file's directory (doc paths, like every other doc/note path in em, are relative to it).
|
|
7
|
+
*/
|
|
8
|
+
export declare function validateFrontmatterCoherence(model: NormalizedModel, refs: RefsResult, baseDir: string): Diagnostic[];
|