@milehimikey/em 1.7.2 → 1.8.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/skills/event-modeling/SKILL.md +92 -65
- package/.claude/skills/event-modeling/reference/conform.md +45 -20
- package/.claude/skills/event-modeling/reference/em-dsl.md +132 -55
- package/.claude/skills/event-modeling/reference/extract.md +5 -4
- package/.claude/skills/event-modeling/reference/implement.md +34 -22
- package/.claude/skills/event-modeling/templates/model-readme.md +8 -5
- package/.claude/skills/event-modeling/templates/slice.md +19 -5
- package/README.md +6 -4
- package/dist/catalog/docJoin.d.ts +28 -3
- package/dist/catalog/docJoin.js +82 -13
- package/dist/catalog/docJoin.js.map +1 -1
- package/dist/catalog/docModelConsistencyValidate.d.ts +8 -0
- package/dist/catalog/docModelConsistencyValidate.js +335 -0
- package/dist/catalog/docModelConsistencyValidate.js.map +1 -0
- package/dist/catalog/noteBindingValidate.d.ts +8 -0
- package/dist/catalog/noteBindingValidate.js +133 -0
- package/dist/catalog/noteBindingValidate.js.map +1 -0
- package/dist/catalog/pages.js +3 -3
- package/dist/catalog/pages.js.map +1 -1
- package/dist/catalog/sliceDoc.d.ts +14 -0
- package/dist/catalog/sliceDoc.js +22 -0
- package/dist/catalog/sliceDoc.js.map +1 -1
- package/dist/catalog/sliceReadyValidate.d.ts +18 -0
- package/dist/catalog/sliceReadyValidate.js +25 -1
- package/dist/catalog/sliceReadyValidate.js.map +1 -1
- package/dist/cli/agentsMd.d.ts +15 -0
- package/dist/cli/agentsMd.js +72 -0
- package/dist/cli/agentsMd.js.map +1 -0
- package/dist/cli/conformScope.d.ts +86 -0
- package/dist/cli/conformScope.js +156 -0
- package/dist/cli/conformScope.js.map +1 -0
- package/dist/cli/contract.d.ts +7 -0
- package/dist/cli/contract.js +27 -0
- package/dist/cli/contract.js.map +1 -0
- package/dist/cli/coverage.d.ts +71 -0
- package/dist/cli/coverage.js +177 -0
- package/dist/cli/coverage.js.map +1 -0
- package/dist/cli/markImplemented.d.ts +34 -0
- package/dist/cli/markImplemented.js +180 -0
- package/dist/cli/markImplemented.js.map +1 -0
- package/dist/cli/migrateReactionShape.d.ts +62 -0
- package/dist/cli/migrateReactionShape.js +283 -0
- package/dist/cli/migrateReactionShape.js.map +1 -0
- package/dist/cli/sliceIndex.d.ts +49 -0
- package/dist/cli/sliceIndex.js +115 -0
- package/dist/cli/sliceIndex.js.map +1 -0
- package/dist/cli/sliceNew.d.ts +18 -0
- package/dist/cli/sliceNew.js +53 -0
- package/dist/cli/sliceNew.js.map +1 -0
- package/dist/cli/stateFile.d.ts +74 -0
- package/dist/cli/stateFile.js +191 -0
- package/dist/cli/stateFile.js.map +1 -0
- package/dist/cli.js +535 -43
- package/dist/cli.js.map +1 -1
- package/dist/emit/coverageJson.d.ts +8 -0
- package/dist/emit/coverageJson.js +30 -0
- package/dist/emit/coverageJson.js.map +1 -0
- package/dist/emit/diffJson.d.ts +9 -1
- package/dist/emit/diffJson.js +44 -1
- package/dist/emit/diffJson.js.map +1 -1
- package/dist/emit/dot.js +41 -10
- package/dist/emit/dot.js.map +1 -1
- package/dist/emit/json.d.ts +77 -4
- package/dist/emit/json.js +78 -4
- package/dist/emit/json.js.map +1 -1
- package/dist/emit/validateJson.d.ts +52 -0
- package/dist/emit/validateJson.js +120 -0
- package/dist/emit/validateJson.js.map +1 -0
- package/dist/mcp/main.d.ts +2 -0
- package/dist/mcp/main.js +20 -0
- package/dist/mcp/main.js.map +1 -0
- package/dist/mcp/server.d.ts +11 -0
- package/dist/mcp/server.js +260 -0
- package/dist/mcp/server.js.map +1 -0
- package/dist/model/model.d.ts +30 -1
- package/dist/model/model.js +26 -0
- package/dist/model/model.js.map +1 -1
- package/dist/model/rules.d.ts +114 -0
- package/dist/model/rules.js +116 -4
- package/dist/model/rules.js.map +1 -1
- package/dist/model/validate.js +92 -8
- package/dist/model/validate.js.map +1 -1
- package/dist/parser/ast.d.ts +47 -0
- package/dist/parser/lexer.d.ts +16 -0
- package/dist/parser/lexer.js +44 -2
- package/dist/parser/lexer.js.map +1 -1
- package/dist/parser/parser.js +354 -20
- package/dist/parser/parser.js.map +1 -1
- package/dist/render/drawNotes.d.ts +11 -1
- package/dist/render/drawNotes.js +28 -3
- package/dist/render/drawNotes.js.map +1 -1
- package/dist/render/render.js +116 -23
- package/dist/render/render.js.map +1 -1
- package/dist/render/serve.d.ts +4 -0
- package/dist/render/serve.js +27 -239
- package/dist/render/serve.js.map +1 -1
- package/dist/render/sliceOverlay.d.ts +7 -2
- package/dist/render/sliceOverlay.js +19 -7
- package/dist/render/sliceOverlay.js.map +1 -1
- package/dist/render/sliceStatus.d.ts +7 -3
- package/dist/render/sliceStatus.js +48 -12
- package/dist/render/sliceStatus.js.map +1 -1
- package/dist/render/viewerHtml.d.ts +1 -0
- package/dist/render/viewerHtml.js +5 -0
- package/dist/render/viewerHtml.js.map +1 -0
- package/dist/render/watch.d.ts +10 -0
- package/dist/render/watch.js +29 -0
- package/dist/render/watch.js.map +1 -1
- package/dist/templates.d.ts +20 -1
- package/dist/templates.js +172 -3
- package/dist/templates.js.map +1 -1
- package/dist/util/markers.d.ts +13 -0
- package/dist/util/markers.js +30 -0
- package/dist/util/markers.js.map +1 -0
- package/dist/util/walkDir.d.ts +10 -1
- package/dist/util/walkDir.js +11 -7
- package/dist/util/walkDir.js.map +1 -1
- package/package.json +12 -3
- package/.claude/skills/event-modeling/templates/live.html +0 -117
package/dist/model/rules.js
CHANGED
|
@@ -1,217 +1,326 @@
|
|
|
1
1
|
// SPDX-License-Identifier: MIT
|
|
2
2
|
// The single declarative registry of every diagnostic `code` `em validate` (and its fs-aware
|
|
3
|
-
// siblings in src/catalog/) can emit — severity, a short human title,
|
|
4
|
-
// separate from the dynamic per-instance `message` each call site
|
|
3
|
+
// siblings in src/catalog/) can emit — severity, a short human title, a static "fix" hint, and
|
|
4
|
+
// a usage-log category string, separate from the dynamic per-instance `message` each call site
|
|
5
|
+
// still writes inline (MIL-92).
|
|
5
6
|
//
|
|
6
7
|
// This exists so the skill's own reference docs can be *generated* from the same source the
|
|
7
8
|
// tool executes (scripts/generate-skill-docs.ts) instead of hand-copied and left to drift —
|
|
8
9
|
// the failure mode MIL-86 hit once already. `pushDiag`/`makeDiag` are the enforcement
|
|
9
10
|
// mechanism: severity is looked up here, never hand-typed a second time at the call site, so a
|
|
10
11
|
// code and its severity can't silently disagree, and a code used but never registered throws
|
|
11
|
-
// immediately rather than shipping an undocumented rule.
|
|
12
|
+
// immediately rather than shipping an undocumented rule. `usageCategory` is generated into
|
|
13
|
+
// docs/usage-data.md's Categories tables the same way (MIL-97) — the fixed vocabulary a
|
|
14
|
+
// `.event-modeling.md` Usage log line picks from, so it can't drift from RULES either.
|
|
12
15
|
//
|
|
13
16
|
// `optIn: true` marks the 4 codes `em validate --slice-ready <key>` adds (MIL-87) — never part
|
|
14
|
-
// of a plain `em validate` run, excluded from the generated base rule reference
|
|
17
|
+
// of a plain `em validate` run, excluded from the generated base rule reference (but still
|
|
18
|
+
// included in the usage-log Categories tables — a session can hit them too).
|
|
15
19
|
export const RULES = {
|
|
16
20
|
"grid-collision": {
|
|
17
21
|
severity: "error",
|
|
18
22
|
title: "Band collision",
|
|
19
23
|
fix: "Split the colliding elements into separate slices.",
|
|
24
|
+
usageCategory: "same-band collision",
|
|
20
25
|
},
|
|
21
26
|
"ui-shares-slice-with-automation": {
|
|
22
27
|
severity: "warning",
|
|
23
28
|
title: "`ui` shares slice with a reaction",
|
|
24
29
|
fix: "Move the `ui` to the slice that displays the read model, or drop it.",
|
|
30
|
+
usageCategory: "ui shares slice with reaction",
|
|
25
31
|
},
|
|
26
32
|
"both-ends-of-a-flow/command-no-event": {
|
|
27
33
|
severity: "warning",
|
|
28
34
|
title: "Command without event",
|
|
29
35
|
fix: "Add the event this command records.",
|
|
36
|
+
usageCategory: "command produces no event",
|
|
30
37
|
docAnchor: "both-ends-of-a-flow",
|
|
31
38
|
},
|
|
32
39
|
"view-no-source": {
|
|
33
40
|
severity: "warning",
|
|
34
41
|
title: "Read model without source",
|
|
35
42
|
fix: 'Add `from "Event"`, or place the view in a slice with an event.',
|
|
43
|
+
usageCategory: "read model has no source",
|
|
36
44
|
},
|
|
37
45
|
"view-from-unresolved": {
|
|
38
46
|
severity: "error",
|
|
39
47
|
title: "Unknown event source",
|
|
40
48
|
fix: "Fix the `from` reference to name an existing event.",
|
|
49
|
+
usageCategory: "view references unknown event",
|
|
41
50
|
},
|
|
42
51
|
"view-from-future-event": {
|
|
43
52
|
severity: "error",
|
|
44
53
|
title: "Backward timeline (view reads a future event)",
|
|
45
54
|
fix: "Move the source to a later `view X again` instance.",
|
|
55
|
+
usageCategory: "event feeds earlier view instance",
|
|
46
56
|
},
|
|
47
57
|
"fields-completeness/view-field-no-source": {
|
|
48
58
|
severity: "warning",
|
|
49
59
|
title: "View field with no source",
|
|
50
60
|
fix: "Add the field to a source event, or remove it from the view.",
|
|
61
|
+
usageCategory: "view field no source",
|
|
51
62
|
docAnchor: "fields-completeness",
|
|
52
63
|
},
|
|
53
64
|
"fields-completeness/event-field-no-source": {
|
|
54
65
|
severity: "warning",
|
|
55
66
|
title: "Event field not from a command",
|
|
56
67
|
fix: "Add the field to a command in the slice, or remove it from the event.",
|
|
68
|
+
usageCategory: "event field not provided by command",
|
|
57
69
|
docAnchor: "fields-completeness",
|
|
58
70
|
},
|
|
59
71
|
"reaction-from-unresolved": {
|
|
60
72
|
severity: "error",
|
|
61
73
|
title: "Unknown read-model source",
|
|
62
74
|
fix: "Project the event into a view first, or fix the `from` reference.",
|
|
75
|
+
usageCategory: "reaction references unknown read model",
|
|
63
76
|
},
|
|
64
77
|
"reaction-from-future-view": {
|
|
65
78
|
severity: "error",
|
|
66
79
|
title: "Backward timeline (reaction reads a future view)",
|
|
67
80
|
fix: "Declare the view in or before the reaction's slice.",
|
|
81
|
+
usageCategory: "reaction reads view before it exists",
|
|
68
82
|
},
|
|
69
83
|
"view-again-without-earlier": {
|
|
70
84
|
severity: "error",
|
|
71
85
|
title: "`again` without an earlier declaration",
|
|
72
86
|
fix: "Declare the view plainly the first time it appears.",
|
|
87
|
+
usageCategory: "view again with no earlier declaration",
|
|
73
88
|
},
|
|
74
89
|
"both-ends-of-a-flow/command-untriggered": {
|
|
75
90
|
severity: "warning",
|
|
76
91
|
title: "Command with no trigger",
|
|
77
92
|
fix: "Add a `ui` or the reaction that issues it, both in this slice.",
|
|
93
|
+
usageCategory: "command nothing triggers",
|
|
78
94
|
docAnchor: "both-ends-of-a-flow",
|
|
79
95
|
},
|
|
80
96
|
"both-ends-of-a-flow/reaction-no-command": {
|
|
81
97
|
severity: "warning",
|
|
82
98
|
title: "Reaction with no command",
|
|
83
99
|
fix: "Add the command it triggers, in this slice, or an explicit arrow to one.",
|
|
100
|
+
usageCategory: "reaction triggers no command",
|
|
84
101
|
docAnchor: "both-ends-of-a-flow",
|
|
85
102
|
},
|
|
86
103
|
"both-ends-of-a-flow/view-unconsumed": {
|
|
87
104
|
severity: "warning",
|
|
88
105
|
title: "Read model with no consumer",
|
|
89
106
|
fix: "Add a `ui` or reaction that consumes it, or drop this instance.",
|
|
107
|
+
usageCategory: "read model has no consumer",
|
|
90
108
|
docAnchor: "both-ends-of-a-flow",
|
|
91
109
|
},
|
|
92
110
|
"both-ends-of-a-flow/event-unread": {
|
|
93
111
|
severity: "warning",
|
|
94
112
|
title: "Event nobody reads",
|
|
95
113
|
fix: "Project it into a view, or reconsider recording it.",
|
|
114
|
+
usageCategory: "event not read by any read model",
|
|
96
115
|
docAnchor: "both-ends-of-a-flow",
|
|
97
116
|
},
|
|
98
117
|
"both-ends-of-a-flow/event-unproduced": {
|
|
99
118
|
severity: "warning",
|
|
100
119
|
title: "Event with no producing command",
|
|
101
120
|
fix: "Add the command that records it, or an explicit arrow from one.",
|
|
121
|
+
usageCategory: "event has no producing command",
|
|
102
122
|
docAnchor: "both-ends-of-a-flow",
|
|
103
123
|
},
|
|
104
124
|
"both-ends-of-a-flow/ui-unbacked": {
|
|
105
125
|
severity: "warning",
|
|
106
126
|
title: "`ui` with no read model or command",
|
|
107
127
|
fix: "Add a `view` it displays, or the command it triggers.",
|
|
128
|
+
usageCategory: "ui with no view or command",
|
|
108
129
|
docAnchor: "both-ends-of-a-flow",
|
|
109
130
|
},
|
|
110
131
|
"arrow-backward": {
|
|
111
132
|
severity: "error",
|
|
112
133
|
title: "Backward arrow",
|
|
113
134
|
fix: "Restructure so the target comes later.",
|
|
135
|
+
usageCategory: "arrow points backward",
|
|
114
136
|
},
|
|
115
137
|
"connection-legality/illegal-pair": {
|
|
116
138
|
severity: "error",
|
|
117
139
|
title: "Illegal connection",
|
|
118
140
|
fix: "Only ui→command→event→view→ui and view→reaction→command are legal — the message names the missing step.",
|
|
141
|
+
usageCategory: "illegal connection",
|
|
119
142
|
docAnchor: "connection-legality",
|
|
120
143
|
},
|
|
121
144
|
"arrow-unresolved-source": {
|
|
122
145
|
severity: "error",
|
|
123
146
|
title: "Arrow source unresolved",
|
|
124
147
|
fix: "Fix the arrow's source name.",
|
|
148
|
+
usageCategory: "arrow endpoint unresolved",
|
|
125
149
|
},
|
|
126
150
|
"arrow-unresolved-target": {
|
|
127
151
|
severity: "error",
|
|
128
152
|
title: "Arrow target unresolved",
|
|
129
153
|
fix: "Fix the arrow's target name.",
|
|
154
|
+
usageCategory: "arrow endpoint unresolved",
|
|
130
155
|
},
|
|
131
156
|
"open-issue": {
|
|
132
157
|
severity: "warning",
|
|
133
158
|
title: "Open issue",
|
|
134
159
|
fix: "Resolve the question, then remove the `issue` clause.",
|
|
160
|
+
usageCategory: "open issue",
|
|
135
161
|
},
|
|
136
162
|
"duplicate-name": {
|
|
137
163
|
severity: "warning",
|
|
138
164
|
title: "Duplicate name",
|
|
139
165
|
fix: "Rename one of the duplicates.",
|
|
166
|
+
usageCategory: "duplicate name referenced",
|
|
140
167
|
},
|
|
141
168
|
"translation-name-collision": {
|
|
142
169
|
severity: "warning",
|
|
143
170
|
title: "Translation name reused for different producers",
|
|
144
171
|
fix: "Use a distinct name per producer to avoid confusion.",
|
|
172
|
+
usageCategory: "translation name collision",
|
|
145
173
|
},
|
|
146
174
|
"duplicate-type-name": {
|
|
147
175
|
severity: "warning",
|
|
148
176
|
title: "Duplicate type name",
|
|
149
177
|
fix: "Rename one of the duplicate `type` declarations.",
|
|
178
|
+
usageCategory: "duplicate type name",
|
|
179
|
+
},
|
|
180
|
+
"tag-composite-unknown-field": {
|
|
181
|
+
severity: "error",
|
|
182
|
+
title: "Composite tag names an unknown field",
|
|
183
|
+
fix: "Fix the field name, or add it to the event's fields.",
|
|
184
|
+
usageCategory: "composite tag references unknown field",
|
|
185
|
+
},
|
|
186
|
+
"tag-duplicate-key": {
|
|
187
|
+
severity: "error",
|
|
188
|
+
title: "Duplicate tag key",
|
|
189
|
+
fix: "Rename one of the tags so every key on the event is unique.",
|
|
190
|
+
usageCategory: "duplicate tag key",
|
|
150
191
|
},
|
|
151
192
|
"type-cycle": {
|
|
152
193
|
severity: "error",
|
|
153
194
|
title: "Cyclic type reference",
|
|
154
195
|
fix: "Break the cycle, or route the self/mutual reference through an array.",
|
|
196
|
+
usageCategory: "type cycle",
|
|
155
197
|
},
|
|
156
198
|
"lineage-ref-malformed": {
|
|
157
199
|
severity: "error",
|
|
158
200
|
title: "Malformed lineage ref",
|
|
159
201
|
fix: "Fix the value to `<slice-key>@v<N>`, or remove it.",
|
|
202
|
+
usageCategory: "lineage ref malformed",
|
|
160
203
|
docAnchor: "lineage",
|
|
161
204
|
},
|
|
162
205
|
"lineage-ref-cycle": {
|
|
163
206
|
severity: "error",
|
|
164
207
|
title: "Lineage cycle",
|
|
165
208
|
fix: "Break the cycle — a slice can't be its own ancestor.",
|
|
209
|
+
usageCategory: "lineage ref cycle",
|
|
166
210
|
docAnchor: "lineage",
|
|
167
211
|
},
|
|
168
212
|
"lineage-forward-dangling": {
|
|
169
213
|
severity: "error",
|
|
170
214
|
title: "Dangling forward lineage ref",
|
|
171
215
|
fix: "Fix the key, or remove the stale successor.",
|
|
216
|
+
usageCategory: "lineage forward dangling",
|
|
172
217
|
docAnchor: "lineage",
|
|
173
218
|
},
|
|
174
219
|
"lineage-version-impossible": {
|
|
175
220
|
severity: "error",
|
|
176
221
|
title: "Impossible lineage version",
|
|
177
222
|
fix: "Fix the referenced version, or ratify the target slice first.",
|
|
223
|
+
usageCategory: "lineage version impossible",
|
|
178
224
|
docAnchor: "lineage",
|
|
179
225
|
},
|
|
180
226
|
"frontmatter-coherence-implemented-without-link": {
|
|
181
227
|
severity: "warning",
|
|
182
228
|
title: "Implemented without a link",
|
|
183
229
|
fix: "Add `implementedIn` once the slice ships.",
|
|
230
|
+
usageCategory: "implemented without link",
|
|
184
231
|
docAnchor: "frontmatter-coherence",
|
|
185
232
|
},
|
|
186
233
|
"binding-missing-file": {
|
|
187
234
|
severity: "warning",
|
|
188
235
|
title: "Doc binding points at a missing file",
|
|
189
236
|
fix: "Create the slice doc, or fix the `note` path.",
|
|
237
|
+
usageCategory: "doc binding points at missing file",
|
|
190
238
|
},
|
|
191
239
|
"frontmatter-invalid": {
|
|
192
240
|
severity: "warning",
|
|
193
241
|
title: "Invalid or missing frontmatter",
|
|
194
242
|
fix: "Add the required frontmatter keys, or add a frontmatter block.",
|
|
243
|
+
usageCategory: "invalid or missing frontmatter",
|
|
244
|
+
},
|
|
245
|
+
"note-binding-extra": {
|
|
246
|
+
severity: "warning",
|
|
247
|
+
title: "Extra doc-binding note, ignored",
|
|
248
|
+
fix: "Remove the note, or point it at the slice's actual bound doc.",
|
|
249
|
+
usageCategory: "extra doc-binding note ignored",
|
|
250
|
+
docAnchor: "note-binding-mismatch",
|
|
251
|
+
},
|
|
252
|
+
"note-binding-dangling": {
|
|
253
|
+
severity: "warning",
|
|
254
|
+
title: "Dangling cross-slice note",
|
|
255
|
+
fix: "Create the doc at that path, or fix/remove the note.",
|
|
256
|
+
usageCategory: "cross-slice note points nowhere",
|
|
257
|
+
docAnchor: "note-binding-mismatch",
|
|
258
|
+
},
|
|
259
|
+
"note-binding-unusable": {
|
|
260
|
+
severity: "warning",
|
|
261
|
+
title: "Cross-slice note to a doc with unusable frontmatter",
|
|
262
|
+
fix: "Fix that doc's frontmatter, or fix/remove the note.",
|
|
263
|
+
usageCategory: "cross-slice note targets unusable doc",
|
|
264
|
+
docAnchor: "note-binding-mismatch",
|
|
265
|
+
},
|
|
266
|
+
"note-binding-unratified": {
|
|
267
|
+
severity: "warning",
|
|
268
|
+
title: "Unratified cross-slice note",
|
|
269
|
+
fix: "Add `covers: <this-slice-key>` to that doc's frontmatter, or correct the note's path.",
|
|
270
|
+
usageCategory: "cross-slice note not ratified by target doc",
|
|
271
|
+
docAnchor: "note-binding-mismatch",
|
|
272
|
+
},
|
|
273
|
+
"doc-model-pattern-mismatch": {
|
|
274
|
+
severity: "warning",
|
|
275
|
+
title: "Doc/model pattern mismatch",
|
|
276
|
+
fix: "Fix the doc's `pattern:` frontmatter to match the model, or restructure the slice to match the doc.",
|
|
277
|
+
usageCategory: "doc pattern disagrees with model",
|
|
278
|
+
docAnchor: "doc-model-consistency",
|
|
279
|
+
},
|
|
280
|
+
"doc-model-element-not-in-model": {
|
|
281
|
+
severity: "warning",
|
|
282
|
+
title: "Doc names an element the model doesn't have",
|
|
283
|
+
fix: "Add the element to the model, or fix/remove it in the doc.",
|
|
284
|
+
usageCategory: "doc element missing from model",
|
|
285
|
+
docAnchor: "doc-model-consistency",
|
|
286
|
+
},
|
|
287
|
+
"doc-model-element-not-in-doc": {
|
|
288
|
+
severity: "warning",
|
|
289
|
+
title: "Model element the doc doesn't mention",
|
|
290
|
+
fix: "Add the matching marker to the doc, or remove the element from the model.",
|
|
291
|
+
usageCategory: "model element missing from doc",
|
|
292
|
+
docAnchor: "doc-model-consistency",
|
|
293
|
+
},
|
|
294
|
+
"doc-model-field-mismatch": {
|
|
295
|
+
severity: "warning",
|
|
296
|
+
title: "Doc/model field mismatch",
|
|
297
|
+
fix: "Reconcile the field table with the model's fields — names and types.",
|
|
298
|
+
usageCategory: "doc field table disagrees with model",
|
|
299
|
+
docAnchor: "doc-model-consistency",
|
|
195
300
|
},
|
|
196
301
|
"duplicate-slice-name": {
|
|
197
302
|
severity: "warning",
|
|
198
303
|
title: "Duplicate slice name",
|
|
199
304
|
fix: "Rename the slice so its export key is unique.",
|
|
305
|
+
usageCategory: "duplicate slice name",
|
|
200
306
|
},
|
|
201
307
|
"duplicate-element-ref": {
|
|
202
308
|
severity: "warning",
|
|
203
309
|
title: "Duplicate element ref",
|
|
204
310
|
fix: "Rename the element so its export ref is unique.",
|
|
311
|
+
usageCategory: "duplicate element ref",
|
|
205
312
|
},
|
|
206
313
|
"duplicate-type-ref": {
|
|
207
314
|
severity: "warning",
|
|
208
315
|
title: "Duplicate type ref",
|
|
209
316
|
fix: "Rename the type so its export ref is unique.",
|
|
317
|
+
usageCategory: "duplicate type ref",
|
|
210
318
|
},
|
|
211
319
|
"slice-ready-unknown-slice": {
|
|
212
320
|
severity: "error",
|
|
213
321
|
title: "Unknown --slice-ready key",
|
|
214
322
|
fix: "Pass a valid export key that exists in this model.",
|
|
323
|
+
usageCategory: "slice-ready key does not exist",
|
|
215
324
|
docAnchor: "slice-readiness",
|
|
216
325
|
optIn: true,
|
|
217
326
|
},
|
|
@@ -219,6 +328,7 @@ export const RULES = {
|
|
|
219
328
|
severity: "warning",
|
|
220
329
|
title: "No doc bound",
|
|
221
330
|
fix: 'Bind a slice doc via `note "slices/<key>.md"`.',
|
|
331
|
+
usageCategory: "slice-ready slice has no doc bound",
|
|
222
332
|
docAnchor: "slice-readiness",
|
|
223
333
|
optIn: true,
|
|
224
334
|
},
|
|
@@ -226,6 +336,7 @@ export const RULES = {
|
|
|
226
336
|
severity: "warning",
|
|
227
337
|
title: "Not ready-to-implement",
|
|
228
338
|
fix: "Set `status: ready-to-implement`.",
|
|
339
|
+
usageCategory: "slice-ready slice not ready-to-implement",
|
|
229
340
|
docAnchor: "slice-readiness",
|
|
230
341
|
optIn: true,
|
|
231
342
|
},
|
|
@@ -233,6 +344,7 @@ export const RULES = {
|
|
|
233
344
|
severity: "warning",
|
|
234
345
|
title: "Unchecked Open Questions",
|
|
235
346
|
fix: "Check off the remaining Open Questions.",
|
|
347
|
+
usageCategory: "slice-ready slice has unchecked open questions",
|
|
236
348
|
docAnchor: "slice-readiness",
|
|
237
349
|
optIn: true,
|
|
238
350
|
},
|
package/dist/model/rules.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"rules.js","sourceRoot":"","sources":["../../src/model/rules.ts"],"names":[],"mappings":"AAAA,+BAA+B;AAC/B,6FAA6F;AAC7F,+FAA+F;AAC/F,
|
|
1
|
+
{"version":3,"file":"rules.js","sourceRoot":"","sources":["../../src/model/rules.ts"],"names":[],"mappings":"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"}
|
package/dist/model/validate.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// SPDX-License-Identifier: MIT
|
|
2
2
|
// Event-modeling rule checks over the normalized model + grid.
|
|
3
3
|
import { AUTOMATION_KINDS } from "../parser/ast.js";
|
|
4
|
-
import { normalizeName, resolveTypeRef } from "./model.js";
|
|
4
|
+
import { collectTags, normalizeName, resolveTypeRef } from "./model.js";
|
|
5
5
|
import { pushDiag } from "./rules.js";
|
|
6
6
|
export function validate(model, grid, refs) {
|
|
7
7
|
const diags = [];
|
|
@@ -125,7 +125,9 @@ export function validate(model, grid, refs) {
|
|
|
125
125
|
// Fields completeness: every event field in a slice should trace to a field on
|
|
126
126
|
// one of that slice's commands (unioned) — again, only once both sides declare
|
|
127
127
|
// `{ fields }`: the event, and *every* command in the slice. A fieldless
|
|
128
|
-
// command may be the field's provider, so a mixed slice stays silent.
|
|
128
|
+
// command may be the field's provider, so a mixed slice stays silent. A field
|
|
129
|
+
// marked `assigned` (MIL-148) opts out of this check entirely — it's the
|
|
130
|
+
// model's own record that the server/handler sets the field, not the command.
|
|
129
131
|
if (commands.length > 0) {
|
|
130
132
|
const commandsDeclareFields = commands.every((c) => (c.fields ?? []).length > 0);
|
|
131
133
|
if (commandsDeclareFields) {
|
|
@@ -140,6 +142,12 @@ export function validate(model, grid, refs) {
|
|
|
140
142
|
if (!evt.fields || evt.fields.length === 0)
|
|
141
143
|
continue;
|
|
142
144
|
for (const f of evt.fields) {
|
|
145
|
+
// `assigned` fields (MIL-148) are system-assigned — set by the server/handler,
|
|
146
|
+
// never supplied by the triggering command — so they never trace to a command
|
|
147
|
+
// field and never warn here. They stay fully visible to the view↔event check
|
|
148
|
+
// above, which is a separate loop this one doesn't touch.
|
|
149
|
+
if (f.assigned)
|
|
150
|
+
continue;
|
|
143
151
|
if (!commandFieldUnion.has(normalizeName(f.name))) {
|
|
144
152
|
pushDiag(diags, "fields-completeness/event-field-no-source", {
|
|
145
153
|
message: `event "${evt.name}" field "${f.name}" not provided by ${byCommands}`,
|
|
@@ -304,18 +312,40 @@ export function validate(model, grid, refs) {
|
|
|
304
312
|
// complete State View is event -> read model -> ui (or -> reaction, the Automation pattern).
|
|
305
313
|
const consumedViews = new Set();
|
|
306
314
|
for (const el of model.elements) {
|
|
307
|
-
//
|
|
315
|
+
// The renderer draws a reaction's `from` arrow to the nearest view instance at-or-before
|
|
316
|
+
// it — that resolution is unchanged. But em's span-1/wire-once DSL rules force an
|
|
317
|
+
// accumulating view across multiple slice instances, each re-declaring it with `from`
|
|
318
|
+
// naming only the newly adjacent event, and the consuming reaction legitimately sits only
|
|
319
|
+
// at the LAST instance. Crediting just that nearest instance (as before MIL-75) left every
|
|
320
|
+
// earlier "fold" instance falsely warning "no consumer," even though it feeds the instance
|
|
321
|
+
// the reaction reads. So for a reaction (AUTOMATION_KINDS), every instance at-or-before it
|
|
322
|
+
// counts as consumed, not just the nearest one (MIL-75). Instances strictly after the last
|
|
323
|
+
// consuming reaction still warn — they accumulate state nothing later reads — and a
|
|
324
|
+
// logical view no reaction ever reads still warns on every instance.
|
|
308
325
|
if (el.kind === "view")
|
|
309
326
|
continue;
|
|
310
327
|
for (const name of el.from ?? []) {
|
|
311
328
|
const bucket = model.byName.get(normalizeName(name));
|
|
312
329
|
if (!bucket)
|
|
313
330
|
continue;
|
|
314
|
-
const
|
|
315
|
-
|
|
316
|
-
.
|
|
317
|
-
|
|
318
|
-
|
|
331
|
+
const views = bucket.filter((x) => x.kind === "view");
|
|
332
|
+
const atOrBefore = views
|
|
333
|
+
.filter((x) => x.sliceIndex <= el.sliceIndex)
|
|
334
|
+
.sort((a, b) => b.sliceIndex - a.sliceIndex);
|
|
335
|
+
if (atOrBefore.length === 0) {
|
|
336
|
+
// A reaction before any instance of the view (separately flagged by
|
|
337
|
+
// reaction-from-future-view) — credit the first instance, same as pre-MIL-75 behavior.
|
|
338
|
+
// There's no instance at-or-before to propagate backward from.
|
|
339
|
+
if (views[0])
|
|
340
|
+
consumedViews.add(views[0].id);
|
|
341
|
+
}
|
|
342
|
+
else if (AUTOMATION_KINDS.has(el.kind)) {
|
|
343
|
+
for (const v of atOrBefore)
|
|
344
|
+
consumedViews.add(v.id);
|
|
345
|
+
}
|
|
346
|
+
else {
|
|
347
|
+
consumedViews.add(atOrBefore[0].id);
|
|
348
|
+
}
|
|
319
349
|
}
|
|
320
350
|
}
|
|
321
351
|
for (const a of model.arrows) {
|
|
@@ -469,6 +499,60 @@ export function validate(model, grid, refs) {
|
|
|
469
499
|
refs: group.map((t) => refOf(t.id)),
|
|
470
500
|
});
|
|
471
501
|
}
|
|
502
|
+
// Event tag metadata (MIL-66): identity (inline field `tag`), composite (`tag X from a, b`),
|
|
503
|
+
// and external (`tag X external "..."`) tag keys. Only ever populated on events (`tag`
|
|
504
|
+
// clauses are a parse error on any other kind), so no kind filter is needed to make either
|
|
505
|
+
// loop below a no-op elsewhere.
|
|
506
|
+
for (const el of model.elements) {
|
|
507
|
+
// A composite tag's field list must name fields that actually exist on the event — checked
|
|
508
|
+
// against `el.tags` directly (not the merged `collectTags` view) since only composite
|
|
509
|
+
// clauses carry a field list to validate, and each needs its own `line` for the diagnostic.
|
|
510
|
+
const fieldNames = new Set((el.fields ?? []).map((f) => normalizeName(f.name)));
|
|
511
|
+
for (const t of el.tags ?? []) {
|
|
512
|
+
if (t.kind !== "composite")
|
|
513
|
+
continue;
|
|
514
|
+
for (const fname of t.fields ?? []) {
|
|
515
|
+
if (fieldNames.has(normalizeName(fname)))
|
|
516
|
+
continue;
|
|
517
|
+
pushDiag(diags, "tag-composite-unknown-field", {
|
|
518
|
+
message: `event "${el.name}" tag "${t.key}" names field "${fname}", which isn't declared ` +
|
|
519
|
+
`on this event`,
|
|
520
|
+
line: t.line,
|
|
521
|
+
refs: [refOf(el.id)],
|
|
522
|
+
});
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
// Duplicate tag key — inline identity keys and element-level (composite/external) keys
|
|
526
|
+
// counted together, since they all share one namespace on the event.
|
|
527
|
+
const tags = collectTags(el);
|
|
528
|
+
if (tags.length === 0)
|
|
529
|
+
continue;
|
|
530
|
+
const keyCounts = new Map();
|
|
531
|
+
for (const t of tags) {
|
|
532
|
+
const k = normalizeName(t.key);
|
|
533
|
+
const entry = keyCounts.get(k);
|
|
534
|
+
if (entry)
|
|
535
|
+
entry.count++;
|
|
536
|
+
else
|
|
537
|
+
keyCounts.set(k, { display: t.key, count: 1 });
|
|
538
|
+
}
|
|
539
|
+
for (const { display, count } of keyCounts.values()) {
|
|
540
|
+
if (count <= 1)
|
|
541
|
+
continue;
|
|
542
|
+
// Anchor to the offending clause's own line, like `tag-composite-unknown-field` does,
|
|
543
|
+
// rather than the event's header line — look up the LAST element-level `tag` clause
|
|
544
|
+
// (`el.tags`, which carries a `line`) whose key normalizes to this duplicate, falling
|
|
545
|
+
// back to `el.line` when every occurrence is an inline field `tag` instead (fields carry
|
|
546
|
+
// no line of their own).
|
|
547
|
+
const normalizedKey = normalizeName(display);
|
|
548
|
+
const lastClause = [...(el.tags ?? [])].reverse().find((t) => normalizeName(t.key) === normalizedKey);
|
|
549
|
+
pushDiag(diags, "tag-duplicate-key", {
|
|
550
|
+
message: `event "${el.name}" declares tag key "${display}" ${count} times; tag keys must be unique per event`,
|
|
551
|
+
line: lastClause?.line ?? el.line,
|
|
552
|
+
refs: [refOf(el.id)],
|
|
553
|
+
});
|
|
554
|
+
}
|
|
555
|
+
}
|
|
472
556
|
// Ambiguous names (used by arrows / view sources) get a heads-up.
|
|
473
557
|
for (const [key, els] of model.byName) {
|
|
474
558
|
// Later \`again\` instances of a view are the SAME logical read model reappearing on the
|