@milehimikey/em 1.9.1 → 1.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/skills/event-modeling/SKILL.md +1 -1
- package/.claude/skills/event-modeling-conform/SKILL.md +5 -1
- package/.claude/skills/event-modeling-design/SKILL.md +31 -12
- package/.claude/skills/event-modeling-discover/SKILL.md +1 -1
- package/.claude/skills/event-modeling-implement/SKILL.md +14 -3
- package/.claude/skills/event-modeling-implement/reference/implement.md +73 -6
- package/.claude/skills/event-modeling-review/SKILL.md +14 -3
- package/.claude/skills/event-modeling-shared/reference/em-dsl.md +110 -11
- package/.claude/skills/event-modeling-shared/reference/slice-doc-schema.md +4 -1
- package/.claude/skills/event-modeling-shared/templates/constitution.md +111 -0
- package/.claude/skills/event-modeling-shared/templates/model-readme.md +2 -2
- package/.claude/skills/event-modeling-shared/templates/slice.md +4 -0
- package/README.md +8 -1
- package/dist/catalog/docJoin.d.ts +8 -0
- package/dist/catalog/docJoin.js +4 -0
- package/dist/catalog/docJoin.js.map +1 -1
- package/dist/catalog/sliceDoc.d.ts +8 -0
- package/dist/catalog/sliceDoc.js +2 -0
- package/dist/catalog/sliceDoc.js.map +1 -1
- package/dist/cli/ciInit.d.ts +10 -3
- package/dist/cli/ciInit.js +22 -14
- package/dist/cli/ciInit.js.map +1 -1
- package/dist/cli/ledgerCheck.d.ts +72 -0
- package/dist/cli/ledgerCheck.js +122 -1
- package/dist/cli/ledgerCheck.js.map +1 -1
- package/dist/cli/ratify.d.ts +37 -5
- package/dist/cli/ratify.js +106 -9
- package/dist/cli/ratify.js.map +1 -1
- package/dist/cli/reratify.js +14 -5
- package/dist/cli/reratify.js.map +1 -1
- package/dist/cli/review.d.ts +38 -0
- package/dist/cli/review.js +160 -0
- package/dist/cli/review.js.map +1 -0
- package/dist/cli/sliceIndex.d.ts +3 -0
- package/dist/cli/sliceIndex.js +5 -4
- package/dist/cli/sliceIndex.js.map +1 -1
- package/dist/cli/stateFile.d.ts +10 -0
- package/dist/cli/stateFile.js +14 -0
- package/dist/cli/stateFile.js.map +1 -1
- package/dist/cli/status.d.ts +44 -3
- package/dist/cli/status.js +75 -18
- package/dist/cli/status.js.map +1 -1
- package/dist/cli/systemInputs.d.ts +34 -0
- package/dist/cli/systemInputs.js +129 -0
- package/dist/cli/systemInputs.js.map +1 -0
- package/dist/cli.js +362 -23
- package/dist/cli.js.map +1 -1
- package/dist/emit/freshnessJson.d.ts +1 -1
- package/dist/emit/freshnessJson.js +5 -1
- package/dist/emit/freshnessJson.js.map +1 -1
- package/dist/emit/json.d.ts +37 -1
- package/dist/emit/json.js +67 -1
- package/dist/emit/json.js.map +1 -1
- package/dist/emit/ledgerJson.d.ts +1 -1
- package/dist/emit/ledgerJson.js +6 -1
- package/dist/emit/ledgerJson.js.map +1 -1
- package/dist/emit/queryJson.d.ts +6 -0
- package/dist/emit/queryJson.js +33 -0
- package/dist/emit/queryJson.js.map +1 -0
- package/dist/emit/statusJson.d.ts +1 -1
- package/dist/emit/statusJson.js +5 -1
- package/dist/emit/statusJson.js.map +1 -1
- package/dist/emit/systemJson.d.ts +8 -0
- package/dist/emit/systemJson.js +38 -0
- package/dist/emit/systemJson.js.map +1 -0
- package/dist/mcp/server.d.ts +1 -1
- package/dist/mcp/server.js +195 -9
- package/dist/mcp/server.js.map +1 -1
- package/dist/model/edges.d.ts +28 -1
- package/dist/model/edges.js +77 -15
- package/dist/model/edges.js.map +1 -1
- package/dist/model/model.d.ts +6 -0
- package/dist/model/model.js +2 -0
- package/dist/model/model.js.map +1 -1
- package/dist/model/qualifiedRef.d.ts +47 -0
- package/dist/model/qualifiedRef.js +100 -0
- package/dist/model/qualifiedRef.js.map +1 -0
- package/dist/model/queryIndex.d.ts +53 -0
- package/dist/model/queryIndex.js +114 -0
- package/dist/model/queryIndex.js.map +1 -0
- package/dist/model/rules.d.ts +88 -0
- package/dist/model/rules.js +101 -0
- package/dist/model/rules.js.map +1 -1
- package/dist/model/sliceDocIndex.d.ts +30 -0
- package/dist/model/sliceDocIndex.js +110 -0
- package/dist/model/sliceDocIndex.js.map +1 -0
- package/dist/model/validate.js +85 -11
- package/dist/model/validate.js.map +1 -1
- package/dist/parser/ast.d.ts +25 -0
- package/dist/parser/parser.d.ts +2 -0
- package/dist/parser/parser.js +104 -34
- package/dist/parser/parser.js.map +1 -1
- package/dist/query/format.d.ts +8 -0
- package/dist/query/format.js +80 -0
- package/dist/query/format.js.map +1 -0
- package/dist/query/pipeline.d.ts +17 -0
- package/dist/query/pipeline.js +38 -0
- package/dist/query/pipeline.js.map +1 -0
- package/dist/query/system.d.ts +60 -0
- package/dist/query/system.js +87 -0
- package/dist/query/system.js.map +1 -0
- package/dist/query/verbs.d.ts +100 -0
- package/dist/query/verbs.js +306 -0
- package/dist/query/verbs.js.map +1 -0
- package/dist/render/drawEdges.js +15 -4
- package/dist/render/drawEdges.js.map +1 -1
- package/dist/render/drawNotes.d.ts +13 -3
- package/dist/render/drawNotes.js +96 -20
- package/dist/render/drawNotes.js.map +1 -1
- package/dist/render/render.js +6 -5
- package/dist/render/render.js.map +1 -1
- package/dist/render/sliceDiagram.js +1 -0
- package/dist/render/sliceDiagram.js.map +1 -1
- package/dist/system/manifest.d.ts +45 -0
- package/dist/system/manifest.js +178 -0
- package/dist/system/manifest.js.map +1 -0
- package/dist/system/verify.d.ts +88 -0
- package/dist/system/verify.js +263 -0
- package/dist/system/verify.js.map +1 -0
- package/dist/templates.d.ts +14 -1
- package/dist/templates.js +134 -5
- package/dist/templates.js.map +1 -1
- package/dist/util/slug.js +16 -10
- package/dist/util/slug.js.map +1 -1
- package/package.json +10 -1
package/dist/cli.js
CHANGED
|
@@ -28,12 +28,12 @@ import { validateDocModelConsistency } from "./catalog/docModelConsistencyValida
|
|
|
28
28
|
import { validateOrphanedSliceDocs } from "./catalog/orphanedSliceDocValidate.js";
|
|
29
29
|
import { validateSliceReady, computeSliceReadyGates } from "./catalog/sliceReadyValidate.js";
|
|
30
30
|
import { detectSliceDocCollisions } from "./catalog/modelCollisionValidate.js";
|
|
31
|
-
import { checkLedger } from "./cli/ledgerCheck.js";
|
|
31
|
+
import { checkLedger, readLedgerWaiverTrailers, applyLedgerWaivers } from "./cli/ledgerCheck.js";
|
|
32
32
|
import { planMigration, verifyMigration } from "./cli/migrateReactionShape.js";
|
|
33
33
|
import { buildLedgerJson } from "./emit/ledgerJson.js";
|
|
34
34
|
import { buildCoverageReport } from "./cli/coverage.js";
|
|
35
35
|
import { buildCoverageJson } from "./emit/coverageJson.js";
|
|
36
|
-
import { resolveSliceStatusFacts, countOpenIssues, resolveConformanceEntry, aggregateInvariantTotals, buildStatusReport, formatStatusText, formatStatusMarkdown, buildStatusBadge, formatConformancePart, } from "./cli/status.js";
|
|
36
|
+
import { resolveSliceStatusFacts, countOpenIssues, resolveConformanceEntry, aggregateInvariantTotals, buildStatusReport, formatStatusText, formatStatusMarkdown, buildStatusBadge, formatConformancePart, findSpecifyRoot, } from "./cli/status.js";
|
|
37
37
|
import { buildStatusJson } from "./emit/statusJson.js";
|
|
38
38
|
import { buildFreshnessJson } from "./emit/freshnessJson.js";
|
|
39
39
|
import { planSkillSyncBundle, applySkillSyncBundle } from "./cli/skillSync.js";
|
|
@@ -52,6 +52,7 @@ import { planCatalogArgs } from "./cli/catalog-inputs.js";
|
|
|
52
52
|
import { runSliceIndex } from "./cli/sliceIndex.js";
|
|
53
53
|
import { runMarkImplemented } from "./cli/markImplemented.js";
|
|
54
54
|
import { runRatify } from "./cli/ratify.js";
|
|
55
|
+
import { runReview } from "./cli/review.js";
|
|
55
56
|
import { runConformSupersede } from "./cli/conformSupersede.js";
|
|
56
57
|
import { buildConformScope, changedPathsSince, resolveSliceDocFacts, seedAsisModel } from "./cli/conformScope.js";
|
|
57
58
|
import { buildSliceDocContent, isSlicePattern, sliceDocKey, SLICE_PATTERNS } from "./cli/sliceNew.js";
|
|
@@ -59,13 +60,21 @@ import { wireSliceNote } from "./cli/sliceLink.js";
|
|
|
59
60
|
import { listModelCommits } from "./cli/changelog-git.js";
|
|
60
61
|
import { buildChangelogDoc } from "./cli/changelogBuild.js";
|
|
61
62
|
import { runReratify } from "./cli/reratify.js";
|
|
62
|
-
import { STATE_FILE_NAME, PHASES, isPhase, loadStateFile, parseState, setPhase, setConformance, setReview, isValidDateString, } from "./cli/stateFile.js";
|
|
63
|
-
import { STARTER_EM, starterEmFor, scaffoldReadme, scaffoldStateFile } from "./templates.js";
|
|
63
|
+
import { STATE_FILE_NAME, PHASES, isPhase, loadStateFile, parseState, setPhase, setConformance, setReview, isValidDateString, modelPathMismatch, } from "./cli/stateFile.js";
|
|
64
|
+
import { STARTER_EM, starterEmFor, scaffoldReadme, scaffoldStateFile, scaffoldConstitution } from "./templates.js";
|
|
64
65
|
import { kebabSlug } from "./util/slug.js";
|
|
65
66
|
import { ciWorkflowPath, conformWorkflowPath, buildCiWorkflowFile, buildConformWorkflowFile, ciManagedBody, conformManagedBody, planCiFile, applyCiFile, findUnsafeCiInitArg, CI_WORKFLOW_MARKER, CONFORM_WORKFLOW_MARKER, } from "./cli/ciInit.js";
|
|
66
67
|
import { RULES } from "./model/rules.js";
|
|
67
68
|
import { USAGE_PHASES, isUsagePhase, sortUsagePhases, appendUsageLogEntry, findStateFiles, aggregateUsageReport, formatUsageReportText, } from "./cli/usageLog.js";
|
|
68
69
|
import { buildUsageReportJson } from "./emit/usageReportJson.js";
|
|
70
|
+
import { compileForQuery } from "./query/pipeline.js";
|
|
71
|
+
import { buildQuerySystem } from "./query/system.js";
|
|
72
|
+
import { queryConsumers, queryProducers, queryDownstream, queryUpstream, querySlices, queryInvariant, queryField, queryPath, } from "./query/verbs.js";
|
|
73
|
+
import { formatConsumers, formatProducers, formatClosure, formatSlices, formatInvariant, formatField, formatPath, } from "./query/format.js";
|
|
74
|
+
import { buildQueryJson } from "./emit/queryJson.js";
|
|
75
|
+
import { loadSystem } from "./cli/systemInputs.js";
|
|
76
|
+
import { verifySystem } from "./system/verify.js";
|
|
77
|
+
import { buildSystemJson } from "./emit/systemJson.js";
|
|
69
78
|
const program = new Command();
|
|
70
79
|
// Single source of truth for the version (also read by src/emit/json.ts for
|
|
71
80
|
// `generator.version` in `em export`): package.json, one level up from both
|
|
@@ -90,7 +99,9 @@ program
|
|
|
90
99
|
});
|
|
91
100
|
program
|
|
92
101
|
.command("scaffold")
|
|
93
|
-
.description("scaffold a full project: <slug>/<slug>.em, README.md, .event-modeling.md " +
|
|
102
|
+
.description("scaffold a full project: <slug>/<slug>.em, README.md, .event-modeling.md, and " +
|
|
103
|
+
"constitution.md (the implementation constitution — skipped, with a note, in a spec-kit " +
|
|
104
|
+
"project, where `.specify/memory/constitution.md` IS that document) " +
|
|
94
105
|
"(see docs/cli.md — for just a starter .em, use `em init`; for a multi-model project, " +
|
|
95
106
|
"pass --under to nest it under a shared parent directory)")
|
|
96
107
|
.argument("<name>", "model display name — kebab-cased for the directory and file names, used as-is for titles/prose")
|
|
@@ -114,7 +125,18 @@ program
|
|
|
114
125
|
writeFileSync(join(dirPath, `${slugName}.em`), starterEmFor(name));
|
|
115
126
|
writeFileSync(join(dirPath, "README.md"), scaffoldReadme(name, slugName));
|
|
116
127
|
writeFileSync(join(dirPath, STATE_FILE_NAME), scaffoldStateFile(name, slugName, today));
|
|
128
|
+
// MIL-202: the implementation constitution. One document per project, never two — in a
|
|
129
|
+
// spec-kit project `.specify/memory/constitution.md` already IS that slot, so em defers to it
|
|
130
|
+
// and writes nothing rather than seeding a second, competing copy beside the model. Same
|
|
131
|
+
// walk-up resolution `em status` reports with (src/cli/status.ts's resolveConstitution).
|
|
132
|
+
const specifyRoot = findSpecifyRoot(dirPath);
|
|
133
|
+
if (!specifyRoot)
|
|
134
|
+
writeFileSync(join(dirPath, "constitution.md"), scaffoldConstitution(name));
|
|
117
135
|
console.log(`scaffolded ${dirPath}/`);
|
|
136
|
+
if (specifyRoot) {
|
|
137
|
+
console.log("note: .specify/ found — the implementation constitution is .specify/memory/constitution.md " +
|
|
138
|
+
"(spec-kit's file); em writes no second copy");
|
|
139
|
+
}
|
|
118
140
|
});
|
|
119
141
|
program
|
|
120
142
|
.command("render")
|
|
@@ -512,17 +534,56 @@ slice
|
|
|
512
534
|
? `marked implemented: ${result.path} (implementedIn: ${prUrl})`
|
|
513
535
|
: `already implemented (no-op): ${result.path}`);
|
|
514
536
|
});
|
|
537
|
+
slice
|
|
538
|
+
.command("review")
|
|
539
|
+
.description("flip a slice doc's frontmatter to `status: reviewed` and record `reviewedBy:`/`reviewedOn:` " +
|
|
540
|
+
"— the FIRST of the two human gates (MIL-201, docs/process.md#the-slice-lifecycle-gates): " +
|
|
541
|
+
"the room walked this slice and every open question it raised is resolved. Ratification is " +
|
|
542
|
+
"a separate, later gate (`em slice ratify`). Legal from `status: draft`; idempotent on the " +
|
|
543
|
+
"same --by/--on pair; refuses to overwrite a different one already recorded; refuses a doc " +
|
|
544
|
+
"already `ready-to-implement`/`implemented`; never touches `version:` or the doc body")
|
|
545
|
+
.argument("<file>", "input .em file")
|
|
546
|
+
.argument("<slice-key>", "slice export key (kebab-case)")
|
|
547
|
+
.requiredOption("--by <name>", "the reviewer's (or facilitator's) name")
|
|
548
|
+
.option("--on <date>", "review date, YYYY-MM-DD (default: today)")
|
|
549
|
+
.action((file, sliceKey, opts) => {
|
|
550
|
+
const { model, refs, diagnostics } = compileFile(file);
|
|
551
|
+
printDiagnostics(diagnostics);
|
|
552
|
+
// Scoped the same way `em slice ratify`/`em slice mark-implemented`/`em export --slice`/
|
|
553
|
+
// `em validate --slice-ready` are: only an error concerning THIS slice refuses.
|
|
554
|
+
const scopedErrors = diagnostics.filter((d) => d.severity === "error" && d.refs?.some((r) => r === sliceKey || r.startsWith(`${sliceKey}/`)));
|
|
555
|
+
if (scopedErrors.length > 0) {
|
|
556
|
+
console.error(`em slice review: slice "${sliceKey}" has errors — fix them first`);
|
|
557
|
+
process.exit(1);
|
|
558
|
+
}
|
|
559
|
+
const today = new Date().toISOString().slice(0, 10);
|
|
560
|
+
const reviewedOn = opts.on ?? today;
|
|
561
|
+
if (opts.on !== undefined && !isValidDateString(opts.on)) {
|
|
562
|
+
console.error(`em slice review: invalid --on date "${opts.on}" — expected YYYY-MM-DD`);
|
|
563
|
+
process.exit(1);
|
|
564
|
+
}
|
|
565
|
+
const result = runReview(model, refs, dirname(file), sliceKey, opts.by, reviewedOn);
|
|
566
|
+
if (!result.ok) {
|
|
567
|
+
console.error(`em slice review: ${result.message}`);
|
|
568
|
+
process.exit(1);
|
|
569
|
+
}
|
|
570
|
+
console.log(result.changed
|
|
571
|
+
? `reviewed: ${result.path} (reviewedBy: ${opts.by}, reviewedOn: ${reviewedOn})`
|
|
572
|
+
: `already reviewed (no-op): ${result.path}`);
|
|
573
|
+
});
|
|
515
574
|
slice
|
|
516
575
|
.command("ratify")
|
|
517
576
|
.description("flip a slice doc's frontmatter to `status: ready-to-implement` and record `ratifiedBy:`/" +
|
|
518
|
-
"`ratifiedOn:` — the handoff sign-off (MIL-165, docs/process.md#
|
|
519
|
-
"makes who ratified, and when, a first-class recorded fact.
|
|
520
|
-
"
|
|
521
|
-
"
|
|
577
|
+
"`ratifiedOn:` — the handoff sign-off (MIL-165, docs/process.md#the-slice-lifecycle-gates) " +
|
|
578
|
+
"that makes who ratified, and when, a first-class recorded fact. Refuses a doc that never " +
|
|
579
|
+
"passed the review gate (`em slice review`) unless --skip-review is passed. Idempotent on " +
|
|
580
|
+
"the same --by/--on pair; refuses to overwrite a different one already recorded; never " +
|
|
581
|
+
"touches `version:` or the doc body")
|
|
522
582
|
.argument("<file>", "input .em file")
|
|
523
583
|
.argument("<slice-key>", "slice export key (kebab-case)")
|
|
524
584
|
.requiredOption("--by <name>", "the ratifier's name")
|
|
525
585
|
.option("--on <date>", "ratification date, YYYY-MM-DD (default: today)")
|
|
586
|
+
.option("--skip-review", "ratify without a recorded review — prints a loud notice on stderr")
|
|
526
587
|
.action((file, sliceKey, opts) => {
|
|
527
588
|
const { model, refs, diagnostics } = compileFile(file);
|
|
528
589
|
printDiagnostics(diagnostics);
|
|
@@ -539,11 +600,23 @@ slice
|
|
|
539
600
|
console.error(`em slice ratify: invalid --on date "${opts.on}" — expected YYYY-MM-DD`);
|
|
540
601
|
process.exit(1);
|
|
541
602
|
}
|
|
542
|
-
const result = runRatify(model, refs, dirname(file), sliceKey, opts.by, ratifiedOn);
|
|
603
|
+
const result = runRatify(model, refs, dirname(file), sliceKey, opts.by, ratifiedOn, opts.skipReview === true);
|
|
543
604
|
if (!result.ok) {
|
|
544
605
|
console.error(`em slice ratify: ${result.message}`);
|
|
545
606
|
process.exit(1);
|
|
546
607
|
}
|
|
608
|
+
// Loud, on stderr, exactly when --skip-review is what let this through — the skip is a
|
|
609
|
+
// visible act in the terminal and in CI logs, and deliberately leaves no trace in the doc.
|
|
610
|
+
if (result.skippedReviewFrom !== null) {
|
|
611
|
+
console.error(`notice: --skip-review — ratifying "${sliceKey}" without a recorded review ` +
|
|
612
|
+
`(status was ${result.skippedReviewFrom})`);
|
|
613
|
+
}
|
|
614
|
+
// MIL-198: advisory only, never refuses — ratifying ahead of an upstream slice the model's
|
|
615
|
+
// own timeline puts before this one (query index `in` adjacency, one hop, other slices only)
|
|
616
|
+
// is sometimes deliberate; this just makes it visible in the terminal and CI logs.
|
|
617
|
+
for (const upstream of result.upstreamWarnings) {
|
|
618
|
+
console.error(`warn: ratifying "${sliceKey}" ahead of upstream slice "${upstream.sliceKey}" (status: ${upstream.status})`);
|
|
619
|
+
}
|
|
547
620
|
console.log(result.changed
|
|
548
621
|
? `ratified: ${result.path} (ratifiedBy: ${opts.by}, ratifiedOn: ${ratifiedOn})`
|
|
549
622
|
: `already ratified (no-op): ${result.path}`);
|
|
@@ -553,8 +626,9 @@ slice
|
|
|
553
626
|
.description("bump `version:` and flip a shipped slice doc's frontmatter back to `status: " +
|
|
554
627
|
"ready-to-implement` — the re-ratification mechanical edit (MIL-161, mirrors `em slice " +
|
|
555
628
|
"mark-implemented`). Only applies to a doc at `status: implemented`; clears any stale " +
|
|
556
|
-
"`ratifiedBy:`/`ratifiedOn:` (they describe the PRIOR version's
|
|
557
|
-
"`em slice ratify --by` applies cleanly
|
|
629
|
+
"`ratifiedBy:`/`ratifiedOn:`/`reviewedBy:`/`reviewedOn:` (they describe the PRIOR version's " +
|
|
630
|
+
"review and sign-off) so a follow-up `em slice ratify --by` applies cleanly — and needs no " +
|
|
631
|
+
"fresh review; never touches `implementedIn:` or the doc body")
|
|
558
632
|
.argument("<file>", "input .em file")
|
|
559
633
|
.argument("<slice-key>", "slice export key (kebab-case)")
|
|
560
634
|
.action((file, sliceKey) => {
|
|
@@ -757,7 +831,14 @@ program
|
|
|
757
831
|
console.error(`em conform-scope: ${parsed.message}`);
|
|
758
832
|
process.exit(1);
|
|
759
833
|
}
|
|
760
|
-
|
|
834
|
+
// MIL-179: the state file names exactly one model in `Model file:`; a sibling this run
|
|
835
|
+
// isn't describing (e.g. a `--seed-asis` scratch copy) must not inherit its conformance
|
|
836
|
+
// record. Same check `em status`'s resolveConformanceEntry runs (PR #116).
|
|
837
|
+
const stateFileMismatch = modelPathMismatch(parsed.state.modelPath, file);
|
|
838
|
+
if (stateFileMismatch) {
|
|
839
|
+
console.error(stateFileMismatch);
|
|
840
|
+
}
|
|
841
|
+
const lastConformance = stateFileMismatch ? null : parsed.state.lastConformance;
|
|
761
842
|
const { facts, diagnostics: docDiags } = resolveSliceDocFacts(model, refs, dirname(file));
|
|
762
843
|
printDiagnostics(newDiagnostics(docDiags, diagnostics));
|
|
763
844
|
const skipGit = !!opts.full || lastConformance === null;
|
|
@@ -772,6 +853,9 @@ program
|
|
|
772
853
|
}
|
|
773
854
|
const scope = buildConformScope(facts, lastConformance, changedPaths, !!opts.full);
|
|
774
855
|
const output = { ...scope };
|
|
856
|
+
if (stateFileMismatch) {
|
|
857
|
+
output.stateFile = { error: stateFileMismatch };
|
|
858
|
+
}
|
|
775
859
|
if (opts.seedAsis) {
|
|
776
860
|
output.seeded = seedAsisModel(file);
|
|
777
861
|
}
|
|
@@ -1023,6 +1107,9 @@ program
|
|
|
1023
1107
|
if (plan.refusals.length > 0)
|
|
1024
1108
|
process.exitCode = 1;
|
|
1025
1109
|
});
|
|
1110
|
+
function describeLedgerWaiveSource(source) {
|
|
1111
|
+
return source.source === "flag" ? "--waive" : `trailer ${source.commit.slice(0, 7)}`;
|
|
1112
|
+
}
|
|
1026
1113
|
program
|
|
1027
1114
|
.command("ledger")
|
|
1028
1115
|
.description("check slice docs' version: field agrees with their content across two git revisions " +
|
|
@@ -1030,6 +1117,8 @@ program
|
|
|
1030
1117
|
.argument("<file>", "anchor .em file — locates slices/ relative to it; never parsed")
|
|
1031
1118
|
.option("--from <rev>", "baseline revision")
|
|
1032
1119
|
.option("--to <rev>", "compare revision (default: current working tree)")
|
|
1120
|
+
.option("--waive <slice-key>", "excuse a doc-content-without-version-bump finding for this slice key (repeatable; MIL-185, " +
|
|
1121
|
+
"see docs/cli.md) — never waives a version regression or bump-without-content-change", (value, previous) => [...previous, value], [])
|
|
1033
1122
|
.option("--json", "print a JSON document instead of the text report (see docs/cli.md)")
|
|
1034
1123
|
.action((file, opts) => {
|
|
1035
1124
|
if (!opts.from) {
|
|
@@ -1037,18 +1126,30 @@ program
|
|
|
1037
1126
|
process.exit(1);
|
|
1038
1127
|
}
|
|
1039
1128
|
const to = opts.to ?? null;
|
|
1040
|
-
const
|
|
1129
|
+
const rawResult = checkLedger(file, opts.from, to);
|
|
1130
|
+
// Commit trailers apply only to CI/manual runs against a real range — the working-tree
|
|
1131
|
+
// comparison itself (`to === null`) still resolves the trailer range as `from..HEAD`, since
|
|
1132
|
+
// an uncommitted change has no commit to carry a trailer; that gap is `--waive`'s job (see
|
|
1133
|
+
// docs/cli.md).
|
|
1134
|
+
const trailerWaivers = readLedgerWaiverTrailers(file, opts.from, to);
|
|
1135
|
+
const { result, unknownWaivers } = applyLedgerWaivers(rawResult, opts.waive, trailerWaivers);
|
|
1136
|
+
for (const note of unknownWaivers) {
|
|
1137
|
+
console.error(`note: waiver for unknown slice "${note.sliceKey}" ignored`);
|
|
1138
|
+
}
|
|
1041
1139
|
if (opts.json) {
|
|
1042
1140
|
process.stdout.write(buildLedgerJson(result, opts.from, to) + "\n");
|
|
1043
1141
|
}
|
|
1044
1142
|
else {
|
|
1045
1143
|
for (const f of result.findings)
|
|
1046
1144
|
console.log(f.message);
|
|
1145
|
+
for (const w of result.waived)
|
|
1146
|
+
console.log(`waived: ${w.message} (waived by ${describeLedgerWaiveSource(w.waivedBy)})`);
|
|
1147
|
+
const waivedNote = result.waived.length > 0 ? `, ${result.waived.length} waived` : "";
|
|
1047
1148
|
if (result.findings.length === 0) {
|
|
1048
|
-
console.log(`ok — ledger agrees (${result.checkedCount} slice doc(s) checked)`);
|
|
1149
|
+
console.log(`ok — ledger agrees (${result.checkedCount} slice doc(s) checked${waivedNote})`);
|
|
1049
1150
|
}
|
|
1050
1151
|
else {
|
|
1051
|
-
console.log(`${result.findings.length} ledger mismatch(es)`);
|
|
1152
|
+
console.log(`${result.findings.length} ledger mismatch(es)${waivedNote}`);
|
|
1052
1153
|
}
|
|
1053
1154
|
}
|
|
1054
1155
|
// Set the code rather than process.exit(): same rationale as em diff/em glossary — stdout
|
|
@@ -1240,6 +1341,207 @@ program
|
|
|
1240
1341
|
console.log(formatConformancePart(entry));
|
|
1241
1342
|
}
|
|
1242
1343
|
});
|
|
1344
|
+
// `em query` (MIL-168): deterministic graph queries over the compiled model — scoped, token-
|
|
1345
|
+
// cheap answers instead of a whole-model `em export`. Compiles every input file through the
|
|
1346
|
+
// LIGHTER query sub-pipeline (query/pipeline.ts: parse -> normalize -> refs -> validate against
|
|
1347
|
+
// an empty grid -> ModelIndex — no layout allocation, no DOT emission), same "refuse on compile
|
|
1348
|
+
// errors" gate every JSON surface uses, then hands the built `QuerySystem` to whichever verb
|
|
1349
|
+
// function ran (query/verbs.ts) — the exact same functions the MCP `query` tool calls (MCP
|
|
1350
|
+
// parity, docs/mcp.md). `<files...>` variadic, multi-model, same as `em status`; refs in output
|
|
1351
|
+
// are `<modelKey>:<sliceKey>/<kind>.<slug>`-qualified whenever more than one file is given (see
|
|
1352
|
+
// query/system.ts's header for why), bare/unqualified otherwise.
|
|
1353
|
+
function compileFilesForQuery(files) {
|
|
1354
|
+
const entries = files.map((file) => {
|
|
1355
|
+
const source = readFileOrExit(file);
|
|
1356
|
+
let compiled;
|
|
1357
|
+
try {
|
|
1358
|
+
compiled = compileForQuery(source, dirname(file));
|
|
1359
|
+
}
|
|
1360
|
+
catch (e) {
|
|
1361
|
+
if (e instanceof ParseError) {
|
|
1362
|
+
console.error(`parse error in ${file} ${e.message}`);
|
|
1363
|
+
process.exit(1);
|
|
1364
|
+
}
|
|
1365
|
+
throw e;
|
|
1366
|
+
}
|
|
1367
|
+
printDiagnosticsFor(file, compiled.diagnostics);
|
|
1368
|
+
if (hasErrors(compiled.diagnostics)) {
|
|
1369
|
+
console.error(`em query: not querying — "${file}" has errors — fix them first`);
|
|
1370
|
+
process.exit(1);
|
|
1371
|
+
}
|
|
1372
|
+
return { file, model: compiled.model, refs: compiled.refs, index: compiled.index };
|
|
1373
|
+
});
|
|
1374
|
+
const system = buildQuerySystem(entries);
|
|
1375
|
+
// MIL-193: a `duplicate-model-key` collision is attributed to the file that had to take the
|
|
1376
|
+
// `~n` suffix (its key is `refs[0]`), printed exactly like that file's own compile warnings.
|
|
1377
|
+
for (const d of system.diagnostics) {
|
|
1378
|
+
const owner = system.entries.find((e) => e.modelKey === d.refs?.[0]);
|
|
1379
|
+
printDiagnosticsFor(owner?.file ?? files.join(", "), [d]);
|
|
1380
|
+
}
|
|
1381
|
+
return system;
|
|
1382
|
+
}
|
|
1383
|
+
/** Runs one verb end to end: compile every file, call `fn`, then print — text by default,
|
|
1384
|
+
* `--json` for `emit/queryJson.ts`'s versioned document. A verb-level error (bad/ambiguous ref,
|
|
1385
|
+
* unknown invariant ID, ...) is `console.error` + exit 1; a legitimately empty result set still
|
|
1386
|
+
* exits 0 (MIL-168's own contract — "an event with no consumers prints 'none' and exits 0"). */
|
|
1387
|
+
function runQueryVerb(files, verb, args, json, fn, formatFn) {
|
|
1388
|
+
const system = compileFilesForQuery(files);
|
|
1389
|
+
const result = fn(system);
|
|
1390
|
+
if (!result.ok) {
|
|
1391
|
+
console.error(result.error);
|
|
1392
|
+
process.exit(1);
|
|
1393
|
+
}
|
|
1394
|
+
if (json)
|
|
1395
|
+
process.stdout.write(buildQueryJson(verb, files, args, result.results) + "\n");
|
|
1396
|
+
else
|
|
1397
|
+
console.log(formatFn(result.results));
|
|
1398
|
+
}
|
|
1399
|
+
function parseDepthOpt(depth, verb) {
|
|
1400
|
+
if (depth === undefined)
|
|
1401
|
+
return undefined;
|
|
1402
|
+
// Decimal digits only — `Number()` would also accept "1e1", "0x10", " 5", which are not what
|
|
1403
|
+
// a hop count looks like on a command line.
|
|
1404
|
+
if (!/^[1-9][0-9]*$/.test(depth)) {
|
|
1405
|
+
console.error(`em query ${verb}: --depth must be a positive integer`);
|
|
1406
|
+
process.exit(1);
|
|
1407
|
+
}
|
|
1408
|
+
return Number(depth);
|
|
1409
|
+
}
|
|
1410
|
+
const query = program.command("query").description("deterministic graph queries over the compiled model (MIL-168, see docs/cli.md)");
|
|
1411
|
+
query
|
|
1412
|
+
.command("consumers")
|
|
1413
|
+
.description("views/reactions consuming an event, plus their slices")
|
|
1414
|
+
.argument("<files...>", "input .em files")
|
|
1415
|
+
.requiredOption("--event <ref-or-name>", "the event's export ref or display name")
|
|
1416
|
+
.option("--json", "print a JSON document instead of the text report")
|
|
1417
|
+
.action((files, opts) => {
|
|
1418
|
+
runQueryVerb(files, "consumers", { event: opts.event }, opts.json, (s) => queryConsumers(s, opts.event), formatConsumers);
|
|
1419
|
+
});
|
|
1420
|
+
query
|
|
1421
|
+
.command("producers")
|
|
1422
|
+
.description("commands producing an event, plus their slices and ui triggers")
|
|
1423
|
+
.argument("<files...>", "input .em files")
|
|
1424
|
+
.requiredOption("--event <ref-or-name>", "the event's export ref or display name")
|
|
1425
|
+
.option("--json", "print a JSON document instead of the text report")
|
|
1426
|
+
.action((files, opts) => {
|
|
1427
|
+
runQueryVerb(files, "producers", { event: opts.event }, opts.json, (s) => queryProducers(s, opts.event), formatProducers);
|
|
1428
|
+
});
|
|
1429
|
+
query
|
|
1430
|
+
.command("downstream")
|
|
1431
|
+
.description("transitive closure along legal edges from an element — impact analysis")
|
|
1432
|
+
.argument("<files...>", "input .em files")
|
|
1433
|
+
.requiredOption("--of <ref-or-name>", "the starting element's export ref or display name")
|
|
1434
|
+
.option("--depth <n>", "limit traversal to n hops (default: unlimited)")
|
|
1435
|
+
.option("--json", "print a JSON document instead of the text report")
|
|
1436
|
+
.action((files, opts) => {
|
|
1437
|
+
const depth = parseDepthOpt(opts.depth, "downstream");
|
|
1438
|
+
runQueryVerb(files, "downstream", { of: opts.of, depth: depth ?? null }, opts.json, (s) => queryDownstream(s, opts.of, depth), formatClosure);
|
|
1439
|
+
});
|
|
1440
|
+
query
|
|
1441
|
+
.command("upstream")
|
|
1442
|
+
.description("transitive closure against legal-edge direction from an element")
|
|
1443
|
+
.argument("<files...>", "input .em files")
|
|
1444
|
+
.requiredOption("--of <ref-or-name>", "the starting element's export ref or display name")
|
|
1445
|
+
.option("--depth <n>", "limit traversal to n hops (default: unlimited)")
|
|
1446
|
+
.option("--json", "print a JSON document instead of the text report")
|
|
1447
|
+
.action((files, opts) => {
|
|
1448
|
+
const depth = parseDepthOpt(opts.depth, "upstream");
|
|
1449
|
+
runQueryVerb(files, "upstream", { of: opts.of, depth: depth ?? null }, opts.json, (s) => queryUpstream(s, opts.of, depth), formatClosure);
|
|
1450
|
+
});
|
|
1451
|
+
query
|
|
1452
|
+
.command("slices")
|
|
1453
|
+
.description("filtered slice list — pattern/status/context/persona/tag filters AND-combine")
|
|
1454
|
+
.argument("<files...>", "input .em files")
|
|
1455
|
+
.option("--pattern <p>", "state-change | state-view | automation | translation | unclassified")
|
|
1456
|
+
.option("--status <s>", "the slice's joined doc status (draft, reviewed, ready-to-implement, implemented, ...)")
|
|
1457
|
+
.option("--context <c>", "match a slice with an event in this @Context")
|
|
1458
|
+
.option("--persona <p>", "match a slice with a ui in this @Persona")
|
|
1459
|
+
.option("--tag <t>", "match a slice with an event carrying this tag key")
|
|
1460
|
+
.option("--json", "print a JSON document instead of the text report")
|
|
1461
|
+
.action((files, opts) => {
|
|
1462
|
+
const filters = { pattern: opts.pattern, status: opts.status, context: opts.context, persona: opts.persona, tag: opts.tag };
|
|
1463
|
+
runQueryVerb(files, "slices", filters, opts.json, (s) => querySlices(s, filters), formatSlices);
|
|
1464
|
+
});
|
|
1465
|
+
query
|
|
1466
|
+
.command("invariant")
|
|
1467
|
+
.description("declaring slice + doc facts for one INV-* id, and (with --tests) its test citations")
|
|
1468
|
+
.argument("<files...>", "input .em files")
|
|
1469
|
+
.requiredOption("--id <inv-id>", "the INV-* id to look up")
|
|
1470
|
+
.option("--tests <dir>", "directory to scan for test files citing this id")
|
|
1471
|
+
.option("--json", "print a JSON document instead of the text report")
|
|
1472
|
+
.action((files, opts) => {
|
|
1473
|
+
if (opts.tests) {
|
|
1474
|
+
if (!existsSync(opts.tests)) {
|
|
1475
|
+
console.error(`em query invariant: --tests directory not found: ${opts.tests}`);
|
|
1476
|
+
process.exit(1);
|
|
1477
|
+
}
|
|
1478
|
+
if (!statSync(opts.tests).isDirectory()) {
|
|
1479
|
+
console.error(`em query invariant: --tests is not a directory: ${opts.tests}`);
|
|
1480
|
+
process.exit(1);
|
|
1481
|
+
}
|
|
1482
|
+
}
|
|
1483
|
+
runQueryVerb(files, "invariant", { id: opts.id, tests: opts.tests ?? null }, opts.json, (s) => queryInvariant(s, opts.id, opts.tests), formatInvariant);
|
|
1484
|
+
});
|
|
1485
|
+
query
|
|
1486
|
+
.command("field")
|
|
1487
|
+
.description("one field's facts on an element — type, tag/assigned markers, renamed-from chain")
|
|
1488
|
+
.argument("<files...>", "input .em files")
|
|
1489
|
+
.requiredOption("--of <element-ref>", "the element's export ref or display name")
|
|
1490
|
+
.requiredOption("--name <field>", "the field's name")
|
|
1491
|
+
.option("--json", "print a JSON document instead of the text report")
|
|
1492
|
+
.action((files, opts) => {
|
|
1493
|
+
runQueryVerb(files, "field", { of: opts.of, name: opts.name }, opts.json, (s) => queryField(s, opts.of, opts.name), formatField);
|
|
1494
|
+
});
|
|
1495
|
+
query
|
|
1496
|
+
.command("path")
|
|
1497
|
+
.description("shortest path between two elements through the six legal connection types")
|
|
1498
|
+
.argument("<files...>", "input .em files")
|
|
1499
|
+
.requiredOption("--from <ref-or-name>", "the starting element's export ref or display name")
|
|
1500
|
+
.requiredOption("--to <ref-or-name>", "the ending element's export ref or display name")
|
|
1501
|
+
.option("--json", "print a JSON document instead of the text report")
|
|
1502
|
+
.action((files, opts) => {
|
|
1503
|
+
runQueryVerb(files, "path", { from: opts.from, to: opts.to }, opts.json, (s) => queryPath(s, opts.from, opts.to), formatPath);
|
|
1504
|
+
});
|
|
1505
|
+
program
|
|
1506
|
+
.command("system")
|
|
1507
|
+
.description("verify a seam manifest (system.yaml) against its models' exports — every `public` event/view " +
|
|
1508
|
+
"bound to a reaction in another model, the cross-model half of \"both ends of a flow\" (MIL-194, see docs/cli.md)")
|
|
1509
|
+
.argument("<manifest>", "seam manifest path (YAML, or JSON); each model's `source` resolves relative to it")
|
|
1510
|
+
// Same reasoning as `em validate`'s allowExcessArguments(false) (MIL-123): a second positional
|
|
1511
|
+
// would be silently dropped, and "one manifest per run" is the whole contract here.
|
|
1512
|
+
.allowExcessArguments(false)
|
|
1513
|
+
.option("--json", "print a JSON document instead of the text report (see docs/cli.md)")
|
|
1514
|
+
.action((manifest, opts) => {
|
|
1515
|
+
const loaded = loadSystem(manifest);
|
|
1516
|
+
if (!loaded.ok) {
|
|
1517
|
+
// Same refusal posture as `em export`/`em status`: a manifest that can't be parsed, or a
|
|
1518
|
+
// source that can't be read/compiled, is not a system to verify — no document, exit 1.
|
|
1519
|
+
printSystemDiagnostics(loaded.diagnostics);
|
|
1520
|
+
console.error(`em system: not verifying — ${manifest} could not be loaded; fix the above first`);
|
|
1521
|
+
process.exit(1);
|
|
1522
|
+
}
|
|
1523
|
+
const report = verifySystem(loaded.manifest, loaded.models, manifest);
|
|
1524
|
+
if (opts.json) {
|
|
1525
|
+
// `em validate --json`'s convention: diagnostics still go to stderr in the human format,
|
|
1526
|
+
// the document (which carries the same diagnostics) to stdout; exit code unchanged.
|
|
1527
|
+
printSystemDiagnostics(report.diagnostics);
|
|
1528
|
+
process.stdout.write(buildSystemJson(manifest, loaded.manifestText, report) + "\n");
|
|
1529
|
+
}
|
|
1530
|
+
else {
|
|
1531
|
+
const verified = report.seams.filter((s) => s.status === "verified").length;
|
|
1532
|
+
const failing = report.seams.length - verified;
|
|
1533
|
+
const label = report.name === null ? "system" : `system "${report.name}"`;
|
|
1534
|
+
console.log(`${label}: ${report.models.length} model${report.models.length === 1 ? "" : "s"}, ` +
|
|
1535
|
+
`${report.seams.length} seam${report.seams.length === 1 ? "" : "s"} (${verified} verified, ${failing} failing)`);
|
|
1536
|
+
printSystemDiagnostics(report.diagnostics);
|
|
1537
|
+
if (report.diagnostics.length === 0)
|
|
1538
|
+
console.log("ok — no issues");
|
|
1539
|
+
}
|
|
1540
|
+
// Set the code rather than process.exit(): same rationale as em diff/em ledger — stdout to
|
|
1541
|
+
// a pipe (a --json document) shouldn't risk truncation.
|
|
1542
|
+
if (hasErrors(report.diagnostics))
|
|
1543
|
+
process.exitCode = 1;
|
|
1544
|
+
});
|
|
1243
1545
|
// Shared by install/sync/check: the `.claude/skills/` root bundled with whatever em package is
|
|
1244
1546
|
// actually running (works whether em was installed from npm or run from a checkout, and
|
|
1245
1547
|
// regardless of a symlinked global install — see pkgDir's own resolution above for
|
|
@@ -1264,9 +1566,9 @@ program
|
|
|
1264
1566
|
});
|
|
1265
1567
|
program
|
|
1266
1568
|
.command("mcp")
|
|
1267
|
-
.description("start an MCP (Model Context Protocol) server over stdio,
|
|
1268
|
-
"
|
|
1269
|
-
"
|
|
1569
|
+
.description("start an MCP (Model Context Protocol) server over stdio (MIL-21) — a structured, agent-" +
|
|
1570
|
+
"facing alternative to shelling out to `em`; see docs/mcp.md for the full, current tool " +
|
|
1571
|
+
"table (the list changes as commands gain MCP parity, so it's not repeated here — MIL-187). " +
|
|
1270
1572
|
"Equivalent to running the `em-mcp` bin directly")
|
|
1271
1573
|
.action(async () => {
|
|
1272
1574
|
// A 3-line wrapper only, for discoverability — the server itself (src/mcp/) never imports
|
|
@@ -1288,8 +1590,14 @@ const skill = program
|
|
|
1288
1590
|
.description("manage Claude Code skills bundled with em");
|
|
1289
1591
|
skill
|
|
1290
1592
|
.command("install")
|
|
1291
|
-
.description("copy the event-modeling skill bundle into .claude/skills/ (event-modeling, event-modeling-
|
|
1292
|
-
|
|
1593
|
+
.description("copy the event-modeling skill bundle into .claude/skills/ (event-modeling, event-modeling-" +
|
|
1594
|
+
"discover/-design/-implement/-conform/-review, event-modeling-shared); across a structural " +
|
|
1595
|
+
"bundle change (e.g. the MIL-157 split of the old single event-modeling/ directory into " +
|
|
1596
|
+
"this six-directory bundle), `em skill sync` is the migration path — --force now performs " +
|
|
1597
|
+
"the same reconcile (MIL-180)")
|
|
1598
|
+
.option("-f, --force", "overwrite an existing installation, reconciling added/updated/removed files within the " +
|
|
1599
|
+
"bundle's own managed directories the same way `em skill sync` does — never touching an " +
|
|
1600
|
+
"unrelated sibling skill (MIL-180)")
|
|
1293
1601
|
.option("--no-agents-md", "skip writing/updating the AGENTS.md agent-contract section (on by default, MIL-129)")
|
|
1294
1602
|
.action(async (opts) => {
|
|
1295
1603
|
const srcRoot = packagedSkillsRoot();
|
|
@@ -1299,6 +1607,31 @@ skill
|
|
|
1299
1607
|
console.log(`skill already installed at ${anchor}`);
|
|
1300
1608
|
console.log("re-run with --force to overwrite");
|
|
1301
1609
|
}
|
|
1610
|
+
else if (existsSync(anchor)) {
|
|
1611
|
+
// --force on an existing installation (MIL-180): reconcile via the same plan/apply
|
|
1612
|
+
// `em skill sync` uses instead of a raw recursive copy, so a file that no longer exists
|
|
1613
|
+
// in the packaged bundle (e.g. left behind by the MIL-157 6-directory split) is removed
|
|
1614
|
+
// from the vendored copy too. Scoped to EM_ALL_SKILL_BUNDLE_DIRS only, exactly like sync
|
|
1615
|
+
// — an unrelated sibling skill under .claude/skills/ is never walked, never touched.
|
|
1616
|
+
await mkdir(destRoot, { recursive: true });
|
|
1617
|
+
const bundlePlan = planSkillSyncBundle(srcRoot, destRoot, EM_ALL_SKILL_BUNDLE_DIRS);
|
|
1618
|
+
const totalChanges = bundlePlan.reduce((n, { plan }) => n + plan.changes.length, 0);
|
|
1619
|
+
const totalUnchanged = bundlePlan.reduce((n, { plan }) => n + plan.unchangedCount, 0);
|
|
1620
|
+
const removedCount = bundlePlan.reduce((n, { plan }) => n + plan.changes.filter((c) => c.kind === "removed").length, 0);
|
|
1621
|
+
applySkillSyncBundle(bundlePlan, srcRoot, destRoot);
|
|
1622
|
+
for (const { dirName, plan } of bundlePlan) {
|
|
1623
|
+
for (const c of plan.changes)
|
|
1624
|
+
console.log(`${c.kind}: ${dirName}/${c.relPath}`);
|
|
1625
|
+
}
|
|
1626
|
+
if (totalChanges === 0) {
|
|
1627
|
+
console.log(`installed event-modeling skill bundle → ${destRoot} — already up to date (${totalUnchanged} file(s))`);
|
|
1628
|
+
}
|
|
1629
|
+
else {
|
|
1630
|
+
console.log(`installed event-modeling skill bundle → ${destRoot} — ${totalChanges} file(s) changed ` +
|
|
1631
|
+
`(${removedCount} removed), ${totalUnchanged} unchanged`);
|
|
1632
|
+
}
|
|
1633
|
+
console.log("in Claude Code, run /event-modeling to start a guided session");
|
|
1634
|
+
}
|
|
1302
1635
|
else {
|
|
1303
1636
|
await mkdir(destRoot, { recursive: true });
|
|
1304
1637
|
for (const name of EM_ALL_SKILL_BUNDLE_DIRS) {
|
|
@@ -1385,11 +1718,11 @@ ci.command("init")
|
|
|
1385
1718
|
const files = [
|
|
1386
1719
|
[
|
|
1387
1720
|
ciPath,
|
|
1388
|
-
planCiFile(ciPath, buildCiWorkflowFile(model, opts.tests, PKG_VERSION), ciManagedBody(model, opts.tests), CI_WORKFLOW_MARKER, !!opts.force),
|
|
1721
|
+
planCiFile(ciPath, buildCiWorkflowFile(model, opts.tests, PKG_VERSION), ciManagedBody(model, opts.tests, PKG_VERSION), CI_WORKFLOW_MARKER, !!opts.force),
|
|
1389
1722
|
],
|
|
1390
1723
|
[
|
|
1391
1724
|
conformPath,
|
|
1392
|
-
planCiFile(conformPath, buildConformWorkflowFile(model, PKG_VERSION), conformManagedBody(model), CONFORM_WORKFLOW_MARKER, !!opts.force),
|
|
1725
|
+
planCiFile(conformPath, buildConformWorkflowFile(model, PKG_VERSION), conformManagedBody(model, PKG_VERSION), CONFORM_WORKFLOW_MARKER, !!opts.force),
|
|
1393
1726
|
],
|
|
1394
1727
|
];
|
|
1395
1728
|
if (opts.check) {
|
|
@@ -1618,6 +1951,12 @@ function printDiagnostics(diags) {
|
|
|
1618
1951
|
console.warn(line);
|
|
1619
1952
|
}
|
|
1620
1953
|
}
|
|
1954
|
+
/** `em system`'s diagnostics carry their own `file` (manifest or model source) — print each in
|
|
1955
|
+
* the same `<file>: <severity>:<line> <message>` shape printDiagnosticsFor uses. */
|
|
1956
|
+
function printSystemDiagnostics(diags) {
|
|
1957
|
+
for (const d of diags)
|
|
1958
|
+
printDiagnosticsFor(d.file, [d]);
|
|
1959
|
+
}
|
|
1621
1960
|
/** Same as printDiagnostics, prefixed with the file each diagnostic came from —
|
|
1622
1961
|
* worth doing once N input files can be more than the two `em diff` compares,
|
|
1623
1962
|
* where ambiguity about which side a diagnostic belongs to gets materially worse. */
|