@milehimikey/em 1.13.1 → 1.14.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/agents/em-critic.md +26 -0
- package/.claude/agents/em-implementer.md +34 -0
- package/.claude/agents/em-reviewer.md +31 -0
- package/.claude/agents/em-validator.md +27 -0
- package/.claude/skills/event-modeling/SKILL.md +50 -11
- package/.claude/skills/event-modeling-conform/SKILL.md +1 -1
- package/.claude/skills/event-modeling-design/SKILL.md +126 -18
- package/.claude/skills/event-modeling-discover/SKILL.md +1 -1
- package/.claude/skills/event-modeling-engagement/SKILL.md +192 -0
- package/.claude/skills/event-modeling-engagement/reference/engagement.md +143 -0
- package/.claude/skills/event-modeling-implement/SKILL.md +1 -1
- package/.claude/skills/event-modeling-implement/reference/implement.md +38 -11
- package/.claude/skills/event-modeling-review/SKILL.md +1 -1
- package/.claude/skills/event-modeling-shared/reference/em-dsl.md +274 -189
- package/.claude/skills/event-modeling-shared/reference/operating-principles.md +61 -0
- package/.claude/skills/event-modeling-shared/reference/slice-doc-schema.md +108 -5
- package/.claude/skills/event-modeling-shared/templates/constitution.md +18 -1
- package/.claude/skills/event-modeling-shared/templates/slice.md +64 -39
- package/.claude/skills/event-modeling-shared/templates/state.md +5 -1
- package/.claude-plugin/marketplace.json +14 -0
- package/README.md +25 -11
- package/dist/catalog/apiFirst.d.ts +28 -0
- package/dist/catalog/apiFirst.js +49 -0
- package/dist/catalog/apiFirst.js.map +1 -0
- package/dist/catalog/docJoin.d.ts +13 -0
- package/dist/catalog/docJoin.js +11 -0
- package/dist/catalog/docJoin.js.map +1 -1
- package/dist/catalog/docModelConsistencyValidate.js +44 -4
- package/dist/catalog/docModelConsistencyValidate.js.map +1 -1
- package/dist/catalog/invariantsValidate.d.ts +4 -0
- package/dist/catalog/invariantsValidate.js +102 -0
- package/dist/catalog/invariantsValidate.js.map +1 -0
- package/dist/catalog/scenarios.d.ts +15 -0
- package/dist/catalog/scenarios.js +97 -0
- package/dist/catalog/scenarios.js.map +1 -0
- package/dist/catalog/sliceDoc.d.ts +8 -0
- package/dist/catalog/sliceDoc.js +9 -0
- package/dist/catalog/sliceDoc.js.map +1 -1
- package/dist/catalog/sliceReadyValidate.d.ts +31 -4
- package/dist/catalog/sliceReadyValidate.js +66 -8
- package/dist/catalog/sliceReadyValidate.js.map +1 -1
- package/dist/catalog/sliceSections.d.ts +55 -0
- package/dist/catalog/sliceSections.js +317 -0
- package/dist/catalog/sliceSections.js.map +1 -0
- package/dist/cli/agentsMd.js +7 -1
- package/dist/cli/agentsMd.js.map +1 -1
- package/dist/cli/api.d.ts +90 -0
- package/dist/cli/api.js +318 -0
- package/dist/cli/api.js.map +1 -0
- package/dist/cli/ciInit.d.ts +82 -21
- package/dist/cli/ciInit.js +468 -64
- package/dist/cli/ciInit.js.map +1 -1
- package/dist/cli/conformScope.d.ts +28 -0
- package/dist/cli/conformScope.js +67 -0
- package/dist/cli/conformScope.js.map +1 -1
- package/dist/cli/coverage.d.ts +9 -0
- package/dist/cli/coverage.js +60 -8
- package/dist/cli/coverage.js.map +1 -1
- package/dist/cli/engagement.d.ts +210 -0
- package/dist/cli/engagement.js +444 -0
- package/dist/cli/engagement.js.map +1 -0
- package/dist/cli/engagementFile.d.ts +86 -0
- package/dist/cli/engagementFile.js +255 -0
- package/dist/cli/engagementFile.js.map +1 -0
- package/dist/cli/metrics.d.ts +10 -0
- package/dist/cli/metrics.js +49 -1
- package/dist/cli/metrics.js.map +1 -1
- package/dist/cli/pluginPin.d.ts +55 -0
- package/dist/cli/pluginPin.js +188 -0
- package/dist/cli/pluginPin.js.map +1 -0
- package/dist/cli/ratify.d.ts +27 -2
- package/dist/cli/ratify.js +112 -13
- package/dist/cli/ratify.js.map +1 -1
- package/dist/cli/reratify.d.ts +3 -2
- package/dist/cli/reratify.js +36 -14
- package/dist/cli/reratify.js.map +1 -1
- package/dist/cli/scopeInputs.d.ts +48 -0
- package/dist/cli/scopeInputs.js +176 -0
- package/dist/cli/scopeInputs.js.map +1 -0
- package/dist/cli/skillCheck.d.ts +15 -1
- package/dist/cli/skillCheck.js +22 -1
- package/dist/cli/skillCheck.js.map +1 -1
- package/dist/cli/skillDirs.d.ts +5 -0
- package/dist/cli/skillDirs.js +13 -1
- package/dist/cli/skillDirs.js.map +1 -1
- package/dist/cli/skillSync.d.ts +4 -0
- package/dist/cli/skillSync.js +23 -0
- package/dist/cli/skillSync.js.map +1 -1
- package/dist/cli/sliceNew.d.ts +11 -7
- package/dist/cli/sliceNew.js +91 -18
- package/dist/cli/sliceNew.js.map +1 -1
- package/dist/cli/sliceSync.d.ts +50 -0
- package/dist/cli/sliceSync.js +170 -0
- package/dist/cli/sliceSync.js.map +1 -0
- package/dist/cli/status.d.ts +24 -1
- package/dist/cli/status.js +20 -1
- package/dist/cli/status.js.map +1 -1
- package/dist/cli/systemInputs.d.ts +53 -10
- package/dist/cli/systemInputs.js +186 -11
- package/dist/cli/systemInputs.js.map +1 -1
- package/dist/cli/upgrade.d.ts +16 -3
- package/dist/cli/upgrade.js +366 -30
- package/dist/cli/upgrade.js.map +1 -1
- package/dist/cli/usageLog.d.ts +4 -2
- package/dist/cli/usageLog.js +4 -2
- package/dist/cli/usageLog.js.map +1 -1
- package/dist/cli.js +554 -94
- package/dist/cli.js.map +1 -1
- package/dist/emit/api.d.ts +29 -0
- package/dist/emit/api.js +220 -0
- package/dist/emit/api.js.map +1 -0
- package/dist/emit/apiCheckJson.d.ts +3 -0
- package/dist/emit/apiCheckJson.js +21 -0
- package/dist/emit/apiCheckJson.js.map +1 -0
- package/dist/emit/codeownersJson.d.ts +3 -0
- package/dist/emit/codeownersJson.js +19 -0
- package/dist/emit/codeownersJson.js.map +1 -0
- package/dist/emit/diffJson.d.ts +1 -1
- package/dist/emit/diffJson.js +8 -1
- package/dist/emit/diffJson.js.map +1 -1
- package/dist/emit/engagementJson.d.ts +8 -0
- package/dist/emit/engagementJson.js +46 -0
- package/dist/emit/engagementJson.js.map +1 -0
- package/dist/emit/glossaryJson.d.ts +1 -1
- package/dist/emit/glossaryJson.js +4 -1
- package/dist/emit/glossaryJson.js.map +1 -1
- package/dist/emit/json.d.ts +17 -1
- package/dist/emit/json.js +30 -1
- package/dist/emit/json.js.map +1 -1
- package/dist/emit/metricsJson.d.ts +1 -1
- package/dist/emit/metricsJson.js +4 -1
- package/dist/emit/metricsJson.js.map +1 -1
- package/dist/emit/queryJson.d.ts +1 -1
- package/dist/emit/queryJson.js +7 -1
- package/dist/emit/queryJson.js.map +1 -1
- package/dist/emit/scopeJson.d.ts +12 -0
- package/dist/emit/scopeJson.js +26 -0
- package/dist/emit/scopeJson.js.map +1 -0
- package/dist/emit/skillCheckJson.d.ts +3 -2
- package/dist/emit/skillCheckJson.js +10 -2
- package/dist/emit/skillCheckJson.js.map +1 -1
- package/dist/emit/statusJson.d.ts +1 -1
- package/dist/emit/statusJson.js +15 -1
- package/dist/emit/statusJson.js.map +1 -1
- package/dist/emit/systemJson.d.ts +15 -8
- package/dist/emit/systemJson.js +28 -13
- package/dist/emit/systemJson.js.map +1 -1
- package/dist/emit/validateJson.d.ts +3 -3
- package/dist/emit/validateJson.js +10 -4
- package/dist/emit/validateJson.js.map +1 -1
- package/dist/mcp/server.d.ts +1 -1
- package/dist/mcp/server.js +163 -31
- package/dist/mcp/server.js.map +1 -1
- package/dist/model/diff.d.ts +4 -1
- package/dist/model/diff.js +44 -8
- package/dist/model/diff.js.map +1 -1
- package/dist/model/glossary.d.ts +2 -0
- package/dist/model/glossary.js +1 -0
- package/dist/model/glossary.js.map +1 -1
- package/dist/model/model.d.ts +55 -2
- package/dist/model/model.js +57 -0
- package/dist/model/model.js.map +1 -1
- package/dist/model/qualifiedRef.d.ts +18 -0
- package/dist/model/qualifiedRef.js +26 -0
- package/dist/model/qualifiedRef.js.map +1 -1
- package/dist/model/queryIndex.d.ts +9 -1
- package/dist/model/queryIndex.js +18 -2
- package/dist/model/queryIndex.js.map +1 -1
- package/dist/model/rules.d.ts +143 -2
- package/dist/model/rules.js +162 -3
- package/dist/model/rules.js.map +1 -1
- package/dist/model/validate.d.ts +40 -0
- package/dist/model/validate.js +147 -1
- package/dist/model/validate.js.map +1 -1
- package/dist/parser/ast.d.ts +31 -3
- package/dist/parser/parser.js +148 -14
- package/dist/parser/parser.js.map +1 -1
- package/dist/query/format.js +1 -1
- package/dist/query/format.js.map +1 -1
- package/dist/query/verbs.d.ts +6 -0
- package/dist/query/verbs.js +6 -2
- package/dist/query/verbs.js.map +1 -1
- package/dist/system/adaptation.d.ts +14 -0
- package/dist/system/adaptation.js +49 -0
- package/dist/system/adaptation.js.map +1 -0
- package/dist/system/codeowners.d.ts +77 -0
- package/dist/system/codeowners.js +242 -0
- package/dist/system/codeowners.js.map +1 -0
- package/dist/system/manifest.d.ts +10 -5
- package/dist/system/manifest.js +54 -26
- package/dist/system/manifest.js.map +1 -1
- package/dist/system/migrateManifest.d.ts +28 -0
- package/dist/system/migrateManifest.js +155 -0
- package/dist/system/migrateManifest.js.map +1 -0
- package/dist/system/scope.d.ts +46 -0
- package/dist/system/scope.js +132 -0
- package/dist/system/scope.js.map +1 -0
- package/dist/system/verify.d.ts +50 -13
- package/dist/system/verify.js +179 -58
- package/dist/system/verify.js.map +1 -1
- package/dist/templates.d.ts +2 -2
- package/dist/templates.js +23 -2
- package/dist/templates.js.map +1 -1
- package/package.json +5 -2
- package/plugin/.claude-plugin/plugin.json +18 -0
- package/plugin/agents/em-critic.md +27 -0
- package/plugin/agents/em-implementer.md +35 -0
- package/plugin/agents/em-reviewer.md +32 -0
- package/plugin/agents/em-validator.md +28 -0
- package/plugin/shared/reference/em-dsl.md +866 -0
- package/plugin/shared/reference/methodology.md +329 -0
- package/plugin/shared/reference/operating-principles.md +222 -0
- package/plugin/shared/reference/slice-doc-schema.md +566 -0
- package/plugin/shared/templates/conformance-report.md +85 -0
- package/plugin/shared/templates/constitution.md +136 -0
- package/plugin/shared/templates/model-readme.md +46 -0
- package/plugin/shared/templates/slice.md +251 -0
- package/plugin/shared/templates/state.md +77 -0
- package/plugin/skills/conform/SKILL.md +93 -0
- package/plugin/skills/conform/reference/conform.md +428 -0
- package/plugin/skills/design/SKILL.md +297 -0
- package/plugin/skills/discover/SKILL.md +90 -0
- package/plugin/skills/discover/reference/extract.md +147 -0
- package/plugin/skills/engagement/SKILL.md +193 -0
- package/plugin/skills/engagement/reference/engagement.md +144 -0
- package/plugin/skills/event-modeling/SKILL.md +132 -0
- package/plugin/skills/implement/SKILL.md +61 -0
- package/plugin/skills/implement/reference/implement.md +439 -0
- package/plugin/skills/review/SKILL.md +74 -0
- package/dist/emit/typespec.d.ts +0 -26
- package/dist/emit/typespec.js +0 -356
- package/dist/emit/typespec.js.map +0 -1
package/dist/cli/ciInit.js
CHANGED
|
@@ -23,8 +23,10 @@
|
|
|
23
23
|
// team that would rather pin the vanilla preset and gate on drift — opt-in, the same posture
|
|
24
24
|
// docs/ci.md already recommends for `em skill check` ("if you'd rather pin ... add `em skill
|
|
25
25
|
// check` as its own gate instead of silently overwriting").
|
|
26
|
-
import { existsSync, readFileSync, writeFileSync } from "node:fs";
|
|
27
|
-
import { dirname, join } from "node:path";
|
|
26
|
+
import { existsSync, readFileSync, statSync, writeFileSync } from "node:fs";
|
|
27
|
+
import { dirname, isAbsolute, join, relative, resolve, sep } from "node:path";
|
|
28
|
+
import { parseManifest } from "../system/manifest.js";
|
|
29
|
+
import { pluginInstallCommands } from "./pluginPin.js";
|
|
28
30
|
import { applyMarker, markerPair, markerRegex } from "../util/markers.js";
|
|
29
31
|
export const CI_WORKFLOW_MARKER = "em-ci";
|
|
30
32
|
export const CONFORM_WORKFLOW_MARKER = "em-conform";
|
|
@@ -44,6 +46,106 @@ export function findUnsafeCiInitArg(value) {
|
|
|
44
46
|
return UNSAFE_ARG_CHARS.test(value) ? value : null;
|
|
45
47
|
}
|
|
46
48
|
// ---- em-ci.yml (PR gates + push-triggered badge rebuild) ----
|
|
49
|
+
/** The `skill-check` job - identical in the single-model and multi-model presets (it checks the
|
|
50
|
+
* repo-wide skill install). Runs when the repo vendors the skill bundle OR pins the em plugin in
|
|
51
|
+
* `.claude/settings.json` (MIL-231); a plugin repo first registers the pinned plugin on the runner,
|
|
52
|
+
* because `em skill check --ci` fails when the plugin is not registered on the machine. Ends
|
|
53
|
+
* without a trailing blank line. */
|
|
54
|
+
function skillCheckJob(em, emVersion) {
|
|
55
|
+
const [addCmd, installCmd] = pluginInstallCommands(emVersion);
|
|
56
|
+
return ` skill-check:
|
|
57
|
+
name: em skill check (vendored skill / plugin drift)
|
|
58
|
+
if: github.event_name == 'pull_request'
|
|
59
|
+
runs-on: ubuntu-latest
|
|
60
|
+
steps:
|
|
61
|
+
- uses: actions/checkout@v4
|
|
62
|
+
- uses: actions/setup-node@v4
|
|
63
|
+
with:
|
|
64
|
+
node-version: 20
|
|
65
|
+
- name: Register the pinned em plugin
|
|
66
|
+
run: |
|
|
67
|
+
if grep -Eq '"em-[0-9]+-[0-9]+-[0-9]+"' .claude/settings.json 2>/dev/null; then
|
|
68
|
+
npm i -g @anthropic-ai/claude-code
|
|
69
|
+
${addCmd}
|
|
70
|
+
${installCmd}
|
|
71
|
+
else
|
|
72
|
+
echo "no em plugin pinned in .claude/settings.json - nothing to register"
|
|
73
|
+
fi
|
|
74
|
+
- name: Check the em skills match installed em
|
|
75
|
+
run: |
|
|
76
|
+
if [ -d .claude/skills/event-modeling ] || grep -Eq '"em-[0-9]+-[0-9]+-[0-9]+"' .claude/settings.json 2>/dev/null; then
|
|
77
|
+
${em} skill check --ci
|
|
78
|
+
else
|
|
79
|
+
echo "no vendored skill and no em plugin pin - skipping (install the em plugin to opt in, docs/ai-workflow.md)"
|
|
80
|
+
fi`;
|
|
81
|
+
}
|
|
82
|
+
/** The `system` job (MIL-239 consumer adaptation, MIL-231 wiring) - multi-model only. Fetches full
|
|
83
|
+
* history because the failure message names the producer commit. */
|
|
84
|
+
function systemJob(em) {
|
|
85
|
+
return ` system:
|
|
86
|
+
name: "em system (consumer adaptation, seam verification)"
|
|
87
|
+
if: github.event_name == 'pull_request'
|
|
88
|
+
runs-on: ubuntu-latest
|
|
89
|
+
steps:
|
|
90
|
+
- uses: actions/checkout@v4
|
|
91
|
+
with:
|
|
92
|
+
fetch-depth: 0
|
|
93
|
+
- uses: actions/setup-node@v4
|
|
94
|
+
with:
|
|
95
|
+
node-version: 20
|
|
96
|
+
- name: Check seams and consumer adaptation
|
|
97
|
+
run: ${em} system .`;
|
|
98
|
+
}
|
|
99
|
+
/** The `system-scope` job (MIL-240 seam-crossing gate, MIL-231 wiring) - multi-model only; needs
|
|
100
|
+
* the base revision. */
|
|
101
|
+
function systemScopeJob(em) {
|
|
102
|
+
return ` system-scope:
|
|
103
|
+
name: "em system scope (seam-crossing change set)"
|
|
104
|
+
if: github.event_name == 'pull_request'
|
|
105
|
+
runs-on: ubuntu-latest
|
|
106
|
+
steps:
|
|
107
|
+
- uses: actions/checkout@v4
|
|
108
|
+
with:
|
|
109
|
+
fetch-depth: 0
|
|
110
|
+
- uses: actions/setup-node@v4
|
|
111
|
+
with:
|
|
112
|
+
node-version: 20
|
|
113
|
+
- name: Check the change set against the seams
|
|
114
|
+
run: |
|
|
115
|
+
base="\${{ github.event.pull_request.base.sha }}"
|
|
116
|
+
${em} system scope --base "$base"`;
|
|
117
|
+
}
|
|
118
|
+
/** The `codeowners-check` job (MIL-234 command, MIL-231 wiring) - multi-model presets only: a
|
|
119
|
+
* single-model repo has no seams to route review for. One job for the whole model set. */
|
|
120
|
+
function codeownersCheckJob(em) {
|
|
121
|
+
return ` codeowners-check:
|
|
122
|
+
name: "em system codeowners --check (review routing current)"
|
|
123
|
+
if: github.event_name == 'pull_request'
|
|
124
|
+
runs-on: ubuntu-latest
|
|
125
|
+
steps:
|
|
126
|
+
- uses: actions/checkout@v4
|
|
127
|
+
- uses: actions/setup-node@v4
|
|
128
|
+
with:
|
|
129
|
+
node-version: 20
|
|
130
|
+
- name: Check the generated CODEOWNERS block is current
|
|
131
|
+
run: ${em} system codeowners --check .`;
|
|
132
|
+
}
|
|
133
|
+
/** The `glossary` job - identical in both presets (it already spans every tracked `*.em`). */
|
|
134
|
+
function glossaryJob(em) {
|
|
135
|
+
return ` glossary:
|
|
136
|
+
name: "em glossary --fail-on-conflicts (cross-model vocabulary)"
|
|
137
|
+
if: github.event_name == 'pull_request'
|
|
138
|
+
runs-on: ubuntu-latest
|
|
139
|
+
steps:
|
|
140
|
+
- uses: actions/checkout@v4
|
|
141
|
+
- uses: actions/setup-node@v4
|
|
142
|
+
with:
|
|
143
|
+
node-version: 20
|
|
144
|
+
- name: Check glossary consistency across models
|
|
145
|
+
run: |
|
|
146
|
+
mapfile -t models < <(git ls-files '*.em')
|
|
147
|
+
${em} glossary "\${models[@]}" --fail-on-conflicts`;
|
|
148
|
+
}
|
|
47
149
|
/** The managed block only (no header/`on:`/`jobs:` scaffolding) — what gets written between
|
|
48
150
|
* the marker pair, both for a fresh file and to patch an existing marked one in place.
|
|
49
151
|
*
|
|
@@ -85,6 +187,24 @@ export function ciManagedBody(model, testsDir, emVersion) {
|
|
|
85
187
|
done
|
|
86
188
|
exit $status
|
|
87
189
|
|
|
190
|
+
api-check:
|
|
191
|
+
name: "em api check (contract current; additive/breaking annotation)"
|
|
192
|
+
if: github.event_name == 'pull_request'
|
|
193
|
+
runs-on: ubuntu-latest
|
|
194
|
+
steps:
|
|
195
|
+
- uses: actions/checkout@v4
|
|
196
|
+
with:
|
|
197
|
+
fetch-depth: 0
|
|
198
|
+
- uses: actions/setup-node@v4
|
|
199
|
+
with:
|
|
200
|
+
node-version: 20
|
|
201
|
+
- name: Check the generated contract is current
|
|
202
|
+
# Fails only when contracts/<model key>.tsp is missing or stale (run em api generate).
|
|
203
|
+
# The additive/breaking lines are annotation for the reviewer, never a gate (MIL-237).
|
|
204
|
+
run: |
|
|
205
|
+
base="\${{ github.event.pull_request.base.sha }}"
|
|
206
|
+
${em} api check "${model}" --base "$base"
|
|
207
|
+
|
|
88
208
|
slice-index:
|
|
89
209
|
name: "em slice index --check (README table drift)"
|
|
90
210
|
if: github.event_name == 'pull_request'
|
|
@@ -126,22 +246,7 @@ export function ciManagedBody(model, testsDir, emVersion) {
|
|
|
126
246
|
- name: Check slice doc version/content agreement
|
|
127
247
|
run: ${em} ledger "${model}" --from "\${{ github.event.pull_request.base.sha }}"
|
|
128
248
|
|
|
129
|
-
|
|
130
|
-
name: em skill check (vendored skill drift)
|
|
131
|
-
if: github.event_name == 'pull_request'
|
|
132
|
-
runs-on: ubuntu-latest
|
|
133
|
-
steps:
|
|
134
|
-
- uses: actions/checkout@v4
|
|
135
|
-
- uses: actions/setup-node@v4
|
|
136
|
-
with:
|
|
137
|
-
node-version: 20
|
|
138
|
-
- name: Check vendored skill matches installed em
|
|
139
|
-
run: |
|
|
140
|
-
if [ -d .claude/skills/event-modeling ]; then
|
|
141
|
-
${em} skill check
|
|
142
|
-
else
|
|
143
|
-
echo "no vendored skill at .claude/skills/event-modeling - skipping (run em skill install to opt in)"
|
|
144
|
-
fi
|
|
249
|
+
${skillCheckJob(em, emVersion)}
|
|
145
250
|
|
|
146
251
|
upgrade-check:
|
|
147
252
|
name: "em upgrade --check (advisory - hard incompatibilities only, MIL-219)"
|
|
@@ -160,19 +265,7 @@ export function ciManagedBody(model, testsDir, emVersion) {
|
|
|
160
265
|
# steps are applicable" case. See docs/upgrading.md.
|
|
161
266
|
run: ${em} upgrade "${model}" --check
|
|
162
267
|
|
|
163
|
-
|
|
164
|
-
name: "em glossary --fail-on-conflicts (cross-model vocabulary)"
|
|
165
|
-
if: github.event_name == 'pull_request'
|
|
166
|
-
runs-on: ubuntu-latest
|
|
167
|
-
steps:
|
|
168
|
-
- uses: actions/checkout@v4
|
|
169
|
-
- uses: actions/setup-node@v4
|
|
170
|
-
with:
|
|
171
|
-
node-version: 20
|
|
172
|
-
- name: Check glossary consistency across models
|
|
173
|
-
run: |
|
|
174
|
-
mapfile -t models < <(git ls-files '*.em')
|
|
175
|
-
${em} glossary "\${models[@]}" --fail-on-conflicts
|
|
268
|
+
${glossaryJob(em)}
|
|
176
269
|
|
|
177
270
|
status-badge:
|
|
178
271
|
name: rebuild status badge (advisory - publish only, never a gate)
|
|
@@ -233,12 +326,199 @@ export function buildCiWorkflowFile(model, testsDir, emVersion) {
|
|
|
233
326
|
const { start, end } = markerPair(CI_WORKFLOW_MARKER, "hash");
|
|
234
327
|
return `${ciWorkflowHeader(model, emVersion)}${start}\n${ciManagedBody(model, testsDir, emVersion)}\n${end}\n`;
|
|
235
328
|
}
|
|
329
|
+
/** Deterministic, valid GitHub Actions job-id suffix per model key. Keys are kebab-case with an
|
|
330
|
+
* optional `~N` collision suffix (MIL-193), and `~` is not an Actions id character, so it
|
|
331
|
+
* becomes `-`; if two keys then collide (`a-2` and `a~2`), the later one in key order gets a
|
|
332
|
+
* numeric suffix until it is unique. Keys are processed in sorted order, so the mapping never
|
|
333
|
+
* depends on manifest order. */
|
|
334
|
+
export function ciJobIds(keys) {
|
|
335
|
+
const ids = new Map();
|
|
336
|
+
const used = new Set();
|
|
337
|
+
for (const key of [...keys].sort(compareKeys)) {
|
|
338
|
+
const base = key.toLowerCase().replace(/[^a-z0-9-]/g, "-");
|
|
339
|
+
let id = base;
|
|
340
|
+
for (let n = 2; used.has(id); n++)
|
|
341
|
+
id = `${base}-${n}`;
|
|
342
|
+
used.add(id);
|
|
343
|
+
ids.set(key, id);
|
|
344
|
+
}
|
|
345
|
+
return ids;
|
|
346
|
+
}
|
|
347
|
+
function compareKeys(a, b) {
|
|
348
|
+
return a < b ? -1 : a > b ? 1 : 0;
|
|
349
|
+
}
|
|
350
|
+
/** Models in the order the generators emit them (key order, not manifest order) so reordering
|
|
351
|
+
* the manifest never reads as drift. */
|
|
352
|
+
function sortedModels(models) {
|
|
353
|
+
return [...models].sort((a, b) => compareKeys(a.key, b.key));
|
|
354
|
+
}
|
|
355
|
+
/** The directory a model's design artifacts live in (R6: `dirname(<model>.em)`). */
|
|
356
|
+
export function ciModelDir(modelPath) {
|
|
357
|
+
const d = dirname(modelPath);
|
|
358
|
+
return d === "" ? "." : d;
|
|
359
|
+
}
|
|
360
|
+
function modelJobs(m, id, testsDir, em) {
|
|
361
|
+
const dir = ciModelDir(m.path);
|
|
362
|
+
const pathspec = dir === "." ? "'*.em'" : `"${dir}/*.em"`;
|
|
363
|
+
const badge = dir === "." ? "status-badge.svg" : `${dir}/status-badge.svg`;
|
|
364
|
+
return ` validate-${id}:
|
|
365
|
+
name: "em validate (changed .em files) [${m.key}]"
|
|
366
|
+
if: github.event_name == 'pull_request'
|
|
367
|
+
runs-on: ubuntu-latest
|
|
368
|
+
steps:
|
|
369
|
+
- uses: actions/checkout@v4
|
|
370
|
+
with:
|
|
371
|
+
fetch-depth: 0
|
|
372
|
+
- uses: actions/setup-node@v4
|
|
373
|
+
with:
|
|
374
|
+
node-version: 20
|
|
375
|
+
- name: Validate changed models
|
|
376
|
+
run: |
|
|
377
|
+
set -e
|
|
378
|
+
base="\${{ github.event.pull_request.base.sha }}"
|
|
379
|
+
head="\${{ github.event.pull_request.head.sha }}"
|
|
380
|
+
changed=$(git diff --name-only "$base" "$head" -- ${pathspec})
|
|
381
|
+
if [ -z "$changed" ]; then
|
|
382
|
+
echo "no .em files changed"
|
|
383
|
+
exit 0
|
|
384
|
+
fi
|
|
385
|
+
status=0
|
|
386
|
+
for f in $changed; do
|
|
387
|
+
echo "::group::em validate $f"
|
|
388
|
+
${em} validate "$f" || status=1
|
|
389
|
+
${em} validate "$f" --list-issues
|
|
390
|
+
echo "::endgroup::"
|
|
391
|
+
done
|
|
392
|
+
exit $status
|
|
393
|
+
|
|
394
|
+
api-check-${id}:
|
|
395
|
+
name: "em api check (contract current; additive/breaking annotation) [${m.key}]"
|
|
396
|
+
if: github.event_name == 'pull_request'
|
|
397
|
+
runs-on: ubuntu-latest
|
|
398
|
+
steps:
|
|
399
|
+
- uses: actions/checkout@v4
|
|
400
|
+
with:
|
|
401
|
+
fetch-depth: 0
|
|
402
|
+
- uses: actions/setup-node@v4
|
|
403
|
+
with:
|
|
404
|
+
node-version: 20
|
|
405
|
+
- name: Check the generated contract is current
|
|
406
|
+
# Fails only when contracts/<model key>.tsp is missing or stale (run em api generate).
|
|
407
|
+
# The additive/breaking lines are annotation for the reviewer, never a gate (MIL-237).
|
|
408
|
+
run: |
|
|
409
|
+
base="\${{ github.event.pull_request.base.sha }}"
|
|
410
|
+
${em} api check "${m.path}" --base "$base"
|
|
411
|
+
|
|
412
|
+
slice-index-${id}:
|
|
413
|
+
name: "em slice index --check (README table drift) [${m.key}]"
|
|
414
|
+
if: github.event_name == 'pull_request'
|
|
415
|
+
runs-on: ubuntu-latest
|
|
416
|
+
steps:
|
|
417
|
+
- uses: actions/checkout@v4
|
|
418
|
+
- uses: actions/setup-node@v4
|
|
419
|
+
with:
|
|
420
|
+
node-version: 20
|
|
421
|
+
- name: Check README Slices table is current
|
|
422
|
+
run: ${em} slice index "${m.path}" --check
|
|
423
|
+
|
|
424
|
+
coverage-${id}:
|
|
425
|
+
name: "em coverage --strict (invariant citations) [${m.key}]"
|
|
426
|
+
if: github.event_name == 'pull_request'
|
|
427
|
+
runs-on: ubuntu-latest
|
|
428
|
+
steps:
|
|
429
|
+
- uses: actions/checkout@v4
|
|
430
|
+
- uses: actions/setup-node@v4
|
|
431
|
+
with:
|
|
432
|
+
node-version: 20
|
|
433
|
+
- name: Check invariant test coverage
|
|
434
|
+
# Counts INV-* IDs in \`implemented\` slice docs only (MIL-207) - a ready-to-implement
|
|
435
|
+
# doc has nothing yet to cite it, so this job is green on a fresh scaffold and on a
|
|
436
|
+
# doc-only ratification PR. Pass --include-ready for the older, forward-looking report.
|
|
437
|
+
run: ${em} coverage "${m.path}" --tests "${testsDir}" --strict
|
|
438
|
+
|
|
439
|
+
ledger-${id}:
|
|
440
|
+
name: "em ledger (slice doc version/content agreement) [${m.key}]"
|
|
441
|
+
if: github.event_name == 'pull_request'
|
|
442
|
+
runs-on: ubuntu-latest
|
|
443
|
+
steps:
|
|
444
|
+
- uses: actions/checkout@v4
|
|
445
|
+
with:
|
|
446
|
+
fetch-depth: 0
|
|
447
|
+
- uses: actions/setup-node@v4
|
|
448
|
+
with:
|
|
449
|
+
node-version: 20
|
|
450
|
+
- name: Check slice doc version/content agreement
|
|
451
|
+
run: ${em} ledger "${m.path}" --from "\${{ github.event.pull_request.base.sha }}"
|
|
452
|
+
|
|
453
|
+
upgrade-check-${id}:
|
|
454
|
+
name: "em upgrade --check (advisory - hard incompatibilities only, MIL-219) [${m.key}]"
|
|
455
|
+
if: github.event_name == 'pull_request'
|
|
456
|
+
runs-on: ubuntu-latest
|
|
457
|
+
steps:
|
|
458
|
+
- uses: actions/checkout@v4
|
|
459
|
+
with:
|
|
460
|
+
fetch-depth: 0
|
|
461
|
+
- uses: actions/setup-node@v4
|
|
462
|
+
with:
|
|
463
|
+
node-version: 20
|
|
464
|
+
- name: Check for hard upgrade incompatibilities
|
|
465
|
+
# Advisory: exits non-zero ONLY on a hard incompatibility (an unparseable state file, an
|
|
466
|
+
# old .em shape em migrate itself refuses) - never on the ordinary "some mechanical
|
|
467
|
+
# steps are applicable" case. See docs/upgrading.md.
|
|
468
|
+
run: ${em} upgrade "${m.path}" --check
|
|
469
|
+
|
|
470
|
+
status-badge-${id}:
|
|
471
|
+
name: "rebuild status badge (advisory - publish only, never a gate) [${m.key}]"
|
|
472
|
+
if: github.event_name == 'push'
|
|
473
|
+
runs-on: ubuntu-latest
|
|
474
|
+
permissions:
|
|
475
|
+
contents: write
|
|
476
|
+
steps:
|
|
477
|
+
- uses: actions/checkout@v4
|
|
478
|
+
- uses: actions/setup-node@v4
|
|
479
|
+
with:
|
|
480
|
+
node-version: 20
|
|
481
|
+
- name: Rebuild status badge
|
|
482
|
+
run: ${em} status "${m.path}" --tests "${testsDir}" --badge -o "${badge}"
|
|
483
|
+
- name: Commit badge if changed
|
|
484
|
+
# Every model has its own badge job and they run in parallel on a push, so rebase onto
|
|
485
|
+
# whatever a sibling job pushed first.
|
|
486
|
+
run: |
|
|
487
|
+
git add -- "${badge}"
|
|
488
|
+
if git diff --cached --quiet -- "${badge}"; then
|
|
489
|
+
echo "${badge} unchanged"
|
|
490
|
+
exit 0
|
|
491
|
+
fi
|
|
492
|
+
git config user.name "em-ci"
|
|
493
|
+
git config user.email "em-ci@users.noreply.github.com"
|
|
494
|
+
git commit -m "em ci: rebuild status badge [skip ci]"
|
|
495
|
+
git pull --rebase origin "$GITHUB_REF_NAME"
|
|
496
|
+
git push`;
|
|
497
|
+
}
|
|
498
|
+
/** The managed block for a whole system: per-model `validate-`/`slice-index-`/`coverage-`/
|
|
499
|
+
* `ledger-`/`upgrade-check-`/`status-badge-<id>` jobs, then ONE `skill-check` and ONE
|
|
500
|
+
* `glossary` (both repo-wide). Same version-pin discipline as `ciManagedBody`. */
|
|
501
|
+
export function ciManagedBodyMulti(models, testsDir, emVersion) {
|
|
502
|
+
const em = `npx @milehimikey/em@${emVersion}`;
|
|
503
|
+
const sorted = sortedModels(models);
|
|
504
|
+
const ids = ciJobIds(sorted.map((m) => m.key));
|
|
505
|
+
const perModel = sorted.map((m) => modelJobs(m, ids.get(m.key), testsDir, em));
|
|
506
|
+
return [...perModel, codeownersCheckJob(em), systemJob(em), systemScopeJob(em), skillCheckJob(em, emVersion), glossaryJob(em)].join("\n\n");
|
|
507
|
+
}
|
|
508
|
+
export function buildCiWorkflowFileMulti(arg, models, testsDir, emVersion) {
|
|
509
|
+
const { start, end } = markerPair(CI_WORKFLOW_MARKER, "hash");
|
|
510
|
+
return `${ciWorkflowHeader(arg, emVersion)}${start}\n${ciManagedBodyMulti(models, testsDir, emVersion)}\n${end}\n`;
|
|
511
|
+
}
|
|
236
512
|
// ---- em-conform.yml (scheduled, advisory-only conformance cadence) ----
|
|
237
513
|
/** The managed block only (no header/`on:`/`permissions:`/`jobs:` scaffolding) — what gets
|
|
238
514
|
* written between the marker pair, both for a fresh file and to patch an existing marked one
|
|
239
515
|
* in place. */
|
|
240
|
-
export function conformManagedBody(model, emVersion) {
|
|
516
|
+
export function conformManagedBody(model, emVersion, usePlugin = false) {
|
|
241
517
|
const modelDir = dirname(model) === "." ? "." : dirname(model);
|
|
518
|
+
// MIL-231: a repo that declares the em plugin installs it (pinned to this em's version) and runs
|
|
519
|
+
// /em:conform; any other repo keeps the vendored bundle install and /event-modeling conform.
|
|
520
|
+
const install = usePlugin ? `${pluginInstallCommands(emVersion).join(" && ")}` : "em skill install --force";
|
|
521
|
+
const invoke = usePlugin ? "/em:conform" : "/event-modeling conform";
|
|
242
522
|
return ` conform:
|
|
243
523
|
runs-on: ubuntu-latest
|
|
244
524
|
steps:
|
|
@@ -253,8 +533,8 @@ export function conformManagedBody(model, emVersion) {
|
|
|
253
533
|
- name: Run conform phase
|
|
254
534
|
run: |
|
|
255
535
|
npm i -g @milehimikey/em@${emVersion} @anthropic-ai/claude-code
|
|
256
|
-
|
|
257
|
-
claude -p "
|
|
536
|
+
${install}
|
|
537
|
+
claude -p "${invoke}" \\
|
|
258
538
|
--allowedTools "Bash(em:*),Bash(git:*),Read,Grep,Glob,Write,Edit"
|
|
259
539
|
env:
|
|
260
540
|
ANTHROPIC_API_KEY: \${{ secrets.ANTHROPIC_API_KEY }}
|
|
@@ -296,34 +576,108 @@ permissions:
|
|
|
296
576
|
jobs:
|
|
297
577
|
`;
|
|
298
578
|
}
|
|
299
|
-
export function buildConformWorkflowFile(model, emVersion) {
|
|
579
|
+
export function buildConformWorkflowFile(model, emVersion, usePlugin = false) {
|
|
300
580
|
const { start, end } = markerPair(CONFORM_WORKFLOW_MARKER, "hash");
|
|
301
|
-
return `${conformWorkflowHeader(model, emVersion)}${start}\n${conformManagedBody(model, emVersion)}\n${end}\n`;
|
|
581
|
+
return `${conformWorkflowHeader(model, emVersion)}${start}\n${conformManagedBody(model, emVersion, usePlugin)}\n${end}\n`;
|
|
582
|
+
}
|
|
583
|
+
/** Conformance for a whole system: the same single `conform` job fanned out over a matrix of
|
|
584
|
+
* model directories (`MODEL_DIR: ${{ matrix.model }}`). Directories, not models: two models in
|
|
585
|
+
* one directory share a conformance folder, so the matrix lists each directory once. */
|
|
586
|
+
export function conformManagedBodyMulti(models, emVersion, usePlugin = false) {
|
|
587
|
+
const dirs = [...new Set(models.map((m) => ciModelDir(m.path)))].sort(compareKeys);
|
|
588
|
+
const single = conformManagedBody("x/x.em", emVersion, usePlugin);
|
|
589
|
+
const strategy = ` strategy:\n fail-fast: false\n matrix:\n model:\n${dirs.map((d) => ` - ${JSON.stringify(d)}`).join("\n")}\n`;
|
|
590
|
+
return single
|
|
591
|
+
.replace(" runs-on: ubuntu-latest\n", ` runs-on: ubuntu-latest\n${strategy}`)
|
|
592
|
+
.replace(/MODEL_DIR: x$/, "MODEL_DIR: ${{ matrix.model }}");
|
|
593
|
+
}
|
|
594
|
+
export function buildConformWorkflowFileMulti(arg, models, emVersion, usePlugin = false) {
|
|
595
|
+
const { start, end } = markerPair(CONFORM_WORKFLOW_MARKER, "hash");
|
|
596
|
+
return `${conformWorkflowHeader(arg, emVersion)}${start}\n${conformManagedBodyMulti(models, emVersion, usePlugin)}\n${end}\n`;
|
|
597
|
+
}
|
|
598
|
+
function dedupe(values) {
|
|
599
|
+
return [...new Set(values)];
|
|
302
600
|
}
|
|
303
601
|
/**
|
|
304
|
-
*
|
|
305
|
-
* (MIL-256). Deliberately content-derived rather than a new marker
|
|
306
|
-
* 1.13.0 and earlier carry no machine-readable model, but every one
|
|
307
|
-
* the per-model `run:` lines
|
|
308
|
-
* `
|
|
309
|
-
*
|
|
310
|
-
*
|
|
311
|
-
*
|
|
602
|
+
* The model SET an existing managed block was generated for, read from the block's own content
|
|
603
|
+
* (MIL-256, widened to sets by MIL-233). Deliberately content-derived rather than a new marker
|
|
604
|
+
* line: blocks written by em 1.13.0 and earlier carry no machine-readable model, but every one
|
|
605
|
+
* of them bakes the model into the per-model `run:` lines. em-ci.yml: every
|
|
606
|
+
* `slice index "<model>" --check` line (one per model); em-conform.yml: every `matrix.model`
|
|
607
|
+
* entry of a multi-model block, else the single `MODEL_DIR: <dir>` line. None of these patterns
|
|
608
|
+
* includes the `npx @milehimikey/em@<ver>` pin, so a version difference can never read as a
|
|
609
|
+
* different model. Returns `[]` when the block was hand-edited past recognition - callers then
|
|
610
|
+
* treat it as covering the requested models (the conservative, pre-MIL-256 behavior) rather than
|
|
611
|
+
* refusing on a guess.
|
|
312
612
|
*/
|
|
313
|
-
export function
|
|
613
|
+
export function managedBlockModels(markerName, blockBody) {
|
|
314
614
|
if (markerName === CONFORM_WORKFLOW_MARKER) {
|
|
615
|
+
const lines = blockBody.split("\n");
|
|
616
|
+
const at = lines.findIndex((l) => /^\s*matrix:\s*$/.test(l));
|
|
617
|
+
if (at >= 0) {
|
|
618
|
+
const flow = /^\s*model:\s*\[(.*)\]\s*$/.exec(lines[at + 1] ?? "");
|
|
619
|
+
if (flow)
|
|
620
|
+
return dedupe(flow[1].split(",").map((v) => unquoteScalar(v.trim())).filter((v) => v !== ""));
|
|
621
|
+
if (/^\s*model:\s*$/.test(lines[at + 1] ?? "")) {
|
|
622
|
+
const out = [];
|
|
623
|
+
for (const l of lines.slice(at + 2)) {
|
|
624
|
+
const item = /^\s*- (.+?)\s*$/.exec(l);
|
|
625
|
+
if (!item)
|
|
626
|
+
break;
|
|
627
|
+
out.push(unquoteScalar(item[1]));
|
|
628
|
+
}
|
|
629
|
+
return dedupe(out);
|
|
630
|
+
}
|
|
631
|
+
}
|
|
315
632
|
const m = /^\s*MODEL_DIR: (.+?)\s*$/m.exec(blockBody);
|
|
316
|
-
return m ? m[1] :
|
|
633
|
+
return m && !m[1].startsWith("${{") ? [m[1]] : [];
|
|
317
634
|
}
|
|
318
|
-
|
|
319
|
-
|
|
635
|
+
return dedupe([...blockBody.matchAll(/\bslice index "([^"]*)" --check\b/g)].map((m) => m[1]));
|
|
636
|
+
}
|
|
637
|
+
function unquoteScalar(v) {
|
|
638
|
+
if (v.startsWith('"') && v.endsWith('"') && v.length >= 2) {
|
|
639
|
+
try {
|
|
640
|
+
return JSON.parse(v);
|
|
641
|
+
}
|
|
642
|
+
catch {
|
|
643
|
+
return v.slice(1, -1);
|
|
644
|
+
}
|
|
645
|
+
}
|
|
646
|
+
return v;
|
|
320
647
|
}
|
|
321
648
|
function existingBlockBody(content, markerName) {
|
|
322
649
|
const m = markerRegex(markerName, "hash").exec(content);
|
|
323
650
|
return m ? m[2] : null;
|
|
324
651
|
}
|
|
325
|
-
/**
|
|
326
|
-
|
|
652
|
+
/** The model set of an existing workflow file's managed block (`[]` when unmarked/unreadable). */
|
|
653
|
+
export function fileBlockModels(content, markerName) {
|
|
654
|
+
const body = existingBlockBody(content, markerName);
|
|
655
|
+
return body === null ? [] : managedBlockModels(markerName, body);
|
|
656
|
+
}
|
|
657
|
+
/** `--check`'s extra detail for a stale multi-model block: which models the manifest adds and
|
|
658
|
+
* which the workflow still names that the manifest no longer does. `null` when the sets agree. */
|
|
659
|
+
export function describeSetChange(previous, requested) {
|
|
660
|
+
const added = requested.filter((m) => !previous.includes(m));
|
|
661
|
+
const removed = previous.filter((m) => !requested.includes(m));
|
|
662
|
+
if (added.length === 0 && removed.length === 0)
|
|
663
|
+
return null;
|
|
664
|
+
const parts = [];
|
|
665
|
+
if (added.length > 0)
|
|
666
|
+
parts.push(`no section for ${added.join(", ")}`);
|
|
667
|
+
if (removed.length > 0)
|
|
668
|
+
parts.push(`section for ${removed.join(", ")} is not in the manifest`);
|
|
669
|
+
return parts.join("; ");
|
|
670
|
+
}
|
|
671
|
+
/** The one-line explanation of an `other-models` refusal (also printed by `--check`), ending in
|
|
672
|
+
* what to do about it. Names both sets. */
|
|
673
|
+
export function describeOtherModels(path, status) {
|
|
674
|
+
if (status.narrowing) {
|
|
675
|
+
return `${path} covers ${status.previous.length} models (${status.previous.join(", ")}) - re-run with the system manifest, or --force to narrow it to ${status.requested.join(", ")}`;
|
|
676
|
+
}
|
|
677
|
+
return `${path} was generated for ${status.previous.join(", ")}, not ${status.requested.join(", ")} - re-run with --force to replace it`;
|
|
678
|
+
}
|
|
679
|
+
/** `--force` over another model set's block also retargets the generated header's own wording
|
|
680
|
+
* (`em ci init <arg>` on `#` comment lines above the start marker) at the new argument, taken
|
|
327
681
|
* from the freshly generated header. Only those exact generated phrasings are touched; a
|
|
328
682
|
* header the repo reworded, and everything below the start marker, is left alone. */
|
|
329
683
|
function retargetHeader(content, generated, markerName) {
|
|
@@ -345,13 +699,18 @@ function retargetHeader(content, generated, markerName) {
|
|
|
345
699
|
* the text between the markers, reused to patch an existing marked file in place without
|
|
346
700
|
* touching whatever a repo added around it.
|
|
347
701
|
*
|
|
348
|
-
* MIL-256
|
|
349
|
-
*
|
|
350
|
-
*
|
|
351
|
-
*
|
|
352
|
-
*
|
|
702
|
+
* Block identity (MIL-256 #174, MIL-233): a block is identified by the SET of models it names
|
|
703
|
+
* (`managedBlockModels`). The requested set wins unless that would lose models by accident:
|
|
704
|
+
* - disjoint sets are `other-models` (refused without `force`, `replace-models` with it);
|
|
705
|
+
* - `multi` false (a single `<model.em>` argument) against a block naming several models is
|
|
706
|
+
* `other-models` too (`narrowing`) - the single-model form must never silently narrow it;
|
|
707
|
+
* - otherwise (same set, superset, subset or overlap, requested from the system manifest) the
|
|
708
|
+
* block is regenerated for the requested set - `stale` when anything differs, e.g. a version
|
|
709
|
+
* pin, an added model or a removed one.
|
|
710
|
+
* `em upgrade`'s ci-block step re-derives its arguments from the existing file (and, for a
|
|
711
|
+
* multi-model block, from the manifest), so it only ever plans the same set.
|
|
353
712
|
*/
|
|
354
|
-
export function planCiFile(path, generated, managedBody, markerName, force) {
|
|
713
|
+
export function planCiFile(path, generated, managedBody, markerName, force, multi = false) {
|
|
355
714
|
if (!existsSync(path))
|
|
356
715
|
return { kind: "create", content: generated };
|
|
357
716
|
const original = readFileSync(path, "utf8");
|
|
@@ -361,19 +720,64 @@ export function planCiFile(path, generated, managedBody, markerName, force) {
|
|
|
361
720
|
}
|
|
362
721
|
if (updated === original)
|
|
363
722
|
return { kind: "ok", content: original };
|
|
364
|
-
const
|
|
365
|
-
const
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
723
|
+
const previous = fileBlockModels(original, markerName);
|
|
724
|
+
const requested = managedBlockModels(markerName, managedBody);
|
|
725
|
+
if (previous.length > 0 && requested.length > 0) {
|
|
726
|
+
const overlap = requested.some((m) => previous.includes(m));
|
|
727
|
+
const narrowing = overlap && !multi && previous.length > 1;
|
|
728
|
+
if (!overlap || narrowing) {
|
|
729
|
+
if (!force)
|
|
730
|
+
return { kind: "other-models", previous, requested, narrowing };
|
|
731
|
+
return { kind: "replace-models", content: retargetHeader(updated, generated, markerName), previous, requested, narrowing };
|
|
732
|
+
}
|
|
371
733
|
}
|
|
372
734
|
return { kind: "stale", content: updated, current: original };
|
|
373
735
|
}
|
|
374
736
|
export function applyCiFile(path, status) {
|
|
375
|
-
if (status.kind === "create" || status.kind === "stale" || status.kind === "would-replace" || status.kind === "replace-
|
|
737
|
+
if (status.kind === "create" || status.kind === "stale" || status.kind === "would-replace" || status.kind === "replace-models") {
|
|
376
738
|
writeFileSync(path, status.content, "utf8");
|
|
377
739
|
}
|
|
378
740
|
}
|
|
741
|
+
/** Read the system manifest at `target` (a file, or a directory holding `system.yaml`) and turn
|
|
742
|
+
* its `models:` into `CiModel`s whose paths are relative to `baseDir` - the repo root the
|
|
743
|
+
* workflows run from. Reuses MIL-235's `parseManifest`; compiling the models is not needed to
|
|
744
|
+
* write a workflow, so a model that does not compile does not block it. Every path goes through
|
|
745
|
+
* `findUnsafeCiInitArg`; a source must be a `.em` file inside `baseDir`. */
|
|
746
|
+
export function ciModelsFromManifest(target, baseDir) {
|
|
747
|
+
let manifestPath = target;
|
|
748
|
+
try {
|
|
749
|
+
if (statSync(target).isDirectory())
|
|
750
|
+
manifestPath = join(target, "system.yaml");
|
|
751
|
+
}
|
|
752
|
+
catch {
|
|
753
|
+
return { ok: false, message: `cannot read ${target}` };
|
|
754
|
+
}
|
|
755
|
+
let text;
|
|
756
|
+
try {
|
|
757
|
+
text = readFileSync(manifestPath, "utf8");
|
|
758
|
+
}
|
|
759
|
+
catch {
|
|
760
|
+
return { ok: false, message: `cannot read ${manifestPath}` };
|
|
761
|
+
}
|
|
762
|
+
const parsed = parseManifest(text);
|
|
763
|
+
if (!parsed.ok) {
|
|
764
|
+
return { ok: false, message: `${manifestPath} is not a valid system manifest: ${parsed.diagnostics.map((d) => d.message).join("; ")}` };
|
|
765
|
+
}
|
|
766
|
+
const models = [];
|
|
767
|
+
for (const entry of parsed.manifest.models) {
|
|
768
|
+
if (!entry.source.endsWith(".em")) {
|
|
769
|
+
return { ok: false, message: `${manifestPath}: model "${entry.key}" source ${entry.source} is not a .em file - the CI jobs run em against .em sources` };
|
|
770
|
+
}
|
|
771
|
+
const rel = relative(resolve(baseDir), resolve(dirname(manifestPath), entry.source)).split(sep).join("/");
|
|
772
|
+
if (rel === "" || rel.startsWith("..") || isAbsolute(rel)) {
|
|
773
|
+
return { ok: false, message: `${manifestPath}: model "${entry.key}" source ${entry.source} is outside the repository root (${baseDir})` };
|
|
774
|
+
}
|
|
775
|
+
const unsafe = findUnsafeCiInitArg(rel);
|
|
776
|
+
if (unsafe) {
|
|
777
|
+
return { ok: false, message: `${manifestPath}: model "${entry.key}" path must not contain '"', '\`', '$', or a newline: ${unsafe}` };
|
|
778
|
+
}
|
|
779
|
+
models.push({ key: entry.key, path: rel });
|
|
780
|
+
}
|
|
781
|
+
return { ok: true, models, manifestPath };
|
|
782
|
+
}
|
|
379
783
|
//# sourceMappingURL=ciInit.js.map
|