@milehimikey/em 1.6.0 → 1.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/skills/event-modeling/SKILL.md +60 -12
- package/.claude/skills/event-modeling/reference/conform.md +30 -8
- package/.claude/skills/event-modeling/reference/em-dsl.md +145 -23
- package/.claude/skills/event-modeling/reference/extract.md +2 -1
- package/.claude/skills/event-modeling/reference/implement.md +172 -0
- package/.claude/skills/event-modeling/reference/methodology.md +5 -0
- package/.claude/skills/event-modeling/templates/conformance-report.md +15 -9
- package/.claude/skills/event-modeling/templates/model-readme.md +3 -3
- package/.claude/skills/event-modeling/templates/slice.md +70 -4
- package/.claude/skills/event-modeling/templates/state.md +6 -5
- package/README.md +1 -0
- package/dist/catalog/build.d.ts +5 -0
- package/dist/catalog/build.js +1 -3
- package/dist/catalog/build.js.map +1 -1
- package/dist/catalog/docJoin.d.ts +39 -0
- package/dist/catalog/docJoin.js +86 -0
- package/dist/catalog/docJoin.js.map +1 -0
- package/dist/catalog/driftSignal.d.ts +16 -0
- package/dist/catalog/driftSignal.js +24 -0
- package/dist/catalog/driftSignal.js.map +1 -0
- package/dist/catalog/frontmatterCoherenceValidate.d.ts +8 -0
- package/dist/catalog/frontmatterCoherenceValidate.js +52 -0
- package/dist/catalog/frontmatterCoherenceValidate.js.map +1 -0
- package/dist/catalog/lineageValidate.d.ts +9 -0
- package/dist/catalog/lineageValidate.js +161 -0
- package/dist/catalog/lineageValidate.js.map +1 -0
- package/dist/catalog/readSliceDoc.d.ts +2 -0
- package/dist/catalog/readSliceDoc.js +21 -0
- package/dist/catalog/readSliceDoc.js.map +1 -0
- package/dist/catalog/sliceDoc.d.ts +94 -3
- package/dist/catalog/sliceDoc.js +157 -7
- package/dist/catalog/sliceDoc.js.map +1 -1
- package/dist/catalog/sliceReadyValidate.d.ts +11 -0
- package/dist/catalog/sliceReadyValidate.js +85 -0
- package/dist/catalog/sliceReadyValidate.js.map +1 -0
- package/dist/cli/diff-inputs.d.ts +20 -0
- package/dist/cli/diff-inputs.js +75 -18
- package/dist/cli/diff-inputs.js.map +1 -1
- package/dist/cli/ledgerCheck.d.ts +38 -0
- package/dist/cli/ledgerCheck.js +116 -0
- package/dist/cli/ledgerCheck.js.map +1 -0
- package/dist/cli/skillCheck.d.ts +18 -0
- package/dist/cli/skillCheck.js +63 -0
- package/dist/cli/skillCheck.js.map +1 -0
- package/dist/cli/skillSync.d.ts +22 -0
- package/dist/cli/skillSync.js +69 -0
- package/dist/cli/skillSync.js.map +1 -0
- package/dist/cli/skillVersionCheck.d.ts +32 -0
- package/dist/cli/skillVersionCheck.js +145 -0
- package/dist/cli/skillVersionCheck.js.map +1 -0
- package/dist/cli.d.ts +3 -1
- package/dist/cli.js +199 -32
- package/dist/cli.js.map +1 -1
- package/dist/emit/diffJson.d.ts +1 -1
- package/dist/emit/diffJson.js +18 -14
- package/dist/emit/diffJson.js.map +1 -1
- package/dist/emit/json.d.ts +10 -25
- package/dist/emit/json.js +70 -103
- package/dist/emit/json.js.map +1 -1
- package/dist/emit/ledgerJson.d.ts +5 -0
- package/dist/emit/ledgerJson.js +25 -0
- package/dist/emit/ledgerJson.js.map +1 -0
- package/dist/emit/skillCheckJson.d.ts +5 -0
- package/dist/emit/skillCheckJson.js +32 -0
- package/dist/emit/skillCheckJson.js.map +1 -0
- package/dist/model/diff.d.ts +44 -1
- package/dist/model/diff.js +51 -9
- package/dist/model/diff.js.map +1 -1
- package/dist/model/model.d.ts +1 -1
- package/dist/model/refs.d.ts +23 -0
- package/dist/model/refs.js +75 -0
- package/dist/model/refs.js.map +1 -0
- package/dist/model/rules.d.ts +252 -0
- package/dist/model/rules.js +253 -0
- package/dist/model/rules.js.map +1 -0
- package/dist/model/validate.d.ts +23 -1
- package/dist/model/validate.js +184 -49
- package/dist/model/validate.js.map +1 -1
- package/dist/parser/ast.d.ts +3 -2
- package/dist/parser/parser.js +36 -14
- package/dist/parser/parser.js.map +1 -1
- package/dist/pipeline.d.ts +9 -0
- package/dist/pipeline.js +4 -2
- package/dist/pipeline.js.map +1 -1
- package/dist/render/sliceStatus.js +1 -1
- package/dist/render/sliceStatus.js.map +1 -1
- package/dist/util/isMainModule.d.ts +14 -0
- package/dist/util/isMainModule.js +33 -0
- package/dist/util/isMainModule.js.map +1 -0
- package/dist/util/walkDir.d.ts +4 -0
- package/dist/util/walkDir.js +28 -0
- package/dist/util/walkDir.js.map +1 -0
- package/package.json +5 -2
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { GitRunner } from "./diff-inputs.js";
|
|
2
|
+
export type SkillVersionFindingCode = "skill-version-stamp-not-bumped" | "skill-version-stamp-stray-bump" | "skill-version-stamp-removed";
|
|
3
|
+
export interface SkillVersionFinding {
|
|
4
|
+
code: SkillVersionFindingCode;
|
|
5
|
+
message: string;
|
|
6
|
+
oldPkgVersion: string;
|
|
7
|
+
newPkgVersion: string;
|
|
8
|
+
oldStamp: string | null;
|
|
9
|
+
newStamp: string | null;
|
|
10
|
+
}
|
|
11
|
+
/** Why no finding could be produced — a state the check can't prove anything from, not a
|
|
12
|
+
* defect. `stamp-missing` covers the field not existing at EITHER revision (never adopted in
|
|
13
|
+
* this span) — distinct from it existing at `from` and being deleted by `to`, which is a
|
|
14
|
+
* `skill-version-stamp-removed` finding, not a skip. */
|
|
15
|
+
export type SkillVersionSkipReason = "package-json-unreadable" | "skill-md-unreadable" | "stamp-missing";
|
|
16
|
+
export interface SkillVersionCheckResult {
|
|
17
|
+
finding: SkillVersionFinding | null;
|
|
18
|
+
skipped: SkillVersionSkipReason | null;
|
|
19
|
+
}
|
|
20
|
+
/** Pull `em-version: X` out of a `---\n...\n---` frontmatter block. Minimal and single-purpose
|
|
21
|
+
* — SKILL.md's frontmatter is two plain scalar keys plus a folded `description:` block, not
|
|
22
|
+
* the richer schema src/catalog/sliceDoc.ts parses, so this doesn't reuse that parser.
|
|
23
|
+
* Exported for reuse by src/cli/skillCheck.ts (MIL-93), which needs the same extraction to
|
|
24
|
+
* compare a vendored copy's stamp against the installed em's own version — no reason to
|
|
25
|
+
* duplicate the regex a second time. */
|
|
26
|
+
export declare function extractEmVersionStamp(content: string): string | null;
|
|
27
|
+
/**
|
|
28
|
+
* Compare `pkgJsonPath`'s version and `skillMdPath`'s `em-version:` stamp between revisions
|
|
29
|
+
* `from` and `to` (`to: null` = current working tree, matching `em diff`/`em ledger`). Pure
|
|
30
|
+
* aside from the injected `GitRunner` and fs reads — no process.exit, no console.
|
|
31
|
+
*/
|
|
32
|
+
export declare function checkSkillVersionStamp(pkgJsonPath: string, skillMdPath: string, from: string, to: string | null, runGit?: GitRunner): SkillVersionCheckResult;
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
// MIL-92's version-stamp release gate: checks that package.json's `version` and SKILL.md's
|
|
3
|
+
// `em-version:` frontmatter stamp move together across two revisions. Repo-internal — unlike
|
|
4
|
+
// `em ledger` (MIL-89, src/cli/ledgerCheck.ts), which consumer repos run against their own
|
|
5
|
+
// slice docs, this checks *this repo's own* package version against *this repo's own* bundled
|
|
6
|
+
// skill, so it's never a shipped `em` subcommand — see scripts/check-skill-version-stamp.ts for
|
|
7
|
+
// the thin CLI shim, and docs/ci.md's precedent framing for why a version/content-agreement
|
|
8
|
+
// check needs two revisions rather than being folded into `em validate`.
|
|
9
|
+
//
|
|
10
|
+
// Both sides here are version strings compared for "did it move," not ledger's version-vs-
|
|
11
|
+
// freeform-content comparison — the meaningful case is package.json's version changing without
|
|
12
|
+
// the stamp following it (`skill-version-stamp-not-bumped`). Two more are flagged as cheap extra
|
|
13
|
+
// coverage: a stray stamp edit with no matching package.json move (`-stray-bump`), and the
|
|
14
|
+
// stamp being deleted outright (`-removed`, unconditional — regardless of whether package.json
|
|
15
|
+
// also changed, since losing the mechanism entirely is a problem on its own; a code review on
|
|
16
|
+
// this very PR caught an earlier version that silently skipped the pkgChanged+deleted-stamp
|
|
17
|
+
// combination). The one case that stays clean rather than becoming a finding: the stamp's
|
|
18
|
+
// first-ever introduction (no prior value to compare against) — the normal one-time bootstrap
|
|
19
|
+
// of the field, not a stray edit (this check's own introducing PR, confirmed on CI, first).
|
|
20
|
+
import { readFileSync } from "node:fs";
|
|
21
|
+
import { realGit, resolveRevision } from "./diff-inputs.js";
|
|
22
|
+
/** `to: null` reads the working tree directly (fs); a revision string resolves via git — same
|
|
23
|
+
* `to: null` = working-tree convention `em diff`/`em ledger` use. */
|
|
24
|
+
function readAt(path, rev, runGit) {
|
|
25
|
+
if (rev === null) {
|
|
26
|
+
try {
|
|
27
|
+
return { ok: true, content: readFileSync(path, "utf8") };
|
|
28
|
+
}
|
|
29
|
+
catch {
|
|
30
|
+
return { ok: false };
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
const result = resolveRevision(path, rev, runGit);
|
|
34
|
+
return result.ok ? { ok: true, content: result.content } : { ok: false };
|
|
35
|
+
}
|
|
36
|
+
function extractPkgVersion(content) {
|
|
37
|
+
try {
|
|
38
|
+
const parsed = JSON.parse(content);
|
|
39
|
+
const version = parsed?.version;
|
|
40
|
+
return typeof version === "string" ? version : null;
|
|
41
|
+
}
|
|
42
|
+
catch {
|
|
43
|
+
return null;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
/** Pull `em-version: X` out of a `---\n...\n---` frontmatter block. Minimal and single-purpose
|
|
47
|
+
* — SKILL.md's frontmatter is two plain scalar keys plus a folded `description:` block, not
|
|
48
|
+
* the richer schema src/catalog/sliceDoc.ts parses, so this doesn't reuse that parser.
|
|
49
|
+
* Exported for reuse by src/cli/skillCheck.ts (MIL-93), which needs the same extraction to
|
|
50
|
+
* compare a vendored copy's stamp against the installed em's own version — no reason to
|
|
51
|
+
* duplicate the regex a second time. */
|
|
52
|
+
export function extractEmVersionStamp(content) {
|
|
53
|
+
const frontmatter = content.match(/^---\n([\s\S]*?)\n---/);
|
|
54
|
+
if (!frontmatter)
|
|
55
|
+
return null;
|
|
56
|
+
const line = frontmatter[1].split("\n").find((l) => /^em-version:\s*/.test(l));
|
|
57
|
+
if (!line)
|
|
58
|
+
return null;
|
|
59
|
+
return line
|
|
60
|
+
.replace(/^em-version:\s*/, "")
|
|
61
|
+
.trim()
|
|
62
|
+
.replace(/^["']|["']$/g, "");
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Compare `pkgJsonPath`'s version and `skillMdPath`'s `em-version:` stamp between revisions
|
|
66
|
+
* `from` and `to` (`to: null` = current working tree, matching `em diff`/`em ledger`). Pure
|
|
67
|
+
* aside from the injected `GitRunner` and fs reads — no process.exit, no console.
|
|
68
|
+
*/
|
|
69
|
+
export function checkSkillVersionStamp(pkgJsonPath, skillMdPath, from, to, runGit = realGit) {
|
|
70
|
+
const oldPkg = readAt(pkgJsonPath, from, runGit);
|
|
71
|
+
const newPkg = readAt(pkgJsonPath, to, runGit);
|
|
72
|
+
if (!oldPkg.ok || !newPkg.ok)
|
|
73
|
+
return { finding: null, skipped: "package-json-unreadable" };
|
|
74
|
+
const oldPkgVersion = extractPkgVersion(oldPkg.content);
|
|
75
|
+
const newPkgVersion = extractPkgVersion(newPkg.content);
|
|
76
|
+
if (oldPkgVersion === null || newPkgVersion === null)
|
|
77
|
+
return { finding: null, skipped: "package-json-unreadable" };
|
|
78
|
+
const oldSkill = readAt(skillMdPath, from, runGit);
|
|
79
|
+
const newSkill = readAt(skillMdPath, to, runGit);
|
|
80
|
+
if (!oldSkill.ok || !newSkill.ok)
|
|
81
|
+
return { finding: null, skipped: "skill-md-unreadable" };
|
|
82
|
+
const oldStamp = extractEmVersionStamp(oldSkill.content);
|
|
83
|
+
const newStamp = extractEmVersionStamp(newSkill.content);
|
|
84
|
+
// Never adopted anywhere in this revision span — nothing to compare. Distinct from the stamp
|
|
85
|
+
// being *removed* below: that's a regression, this is simply "the field doesn't exist yet."
|
|
86
|
+
if (oldStamp === null && newStamp === null)
|
|
87
|
+
return { finding: null, skipped: "stamp-missing" };
|
|
88
|
+
// The stamp's first-ever introduction (no em-version: key at `from`, one at `to`) has nothing
|
|
89
|
+
// to compare against either — not a stray edit, the normal one-time bootstrap of the field
|
|
90
|
+
// itself (e.g. this very check landing). Treat it as clean rather than a permanent
|
|
91
|
+
// false-positive on whichever PR happens to add the key.
|
|
92
|
+
if (oldStamp === null)
|
|
93
|
+
return { finding: null, skipped: null };
|
|
94
|
+
const pkgChanged = oldPkgVersion !== newPkgVersion;
|
|
95
|
+
// The stamp existed and is now gone entirely — a stronger signal than merely "left stale"
|
|
96
|
+
// (below), and one a naive "newStamp === null -> skip" early-out would otherwise swallow
|
|
97
|
+
// silently, including the worst case: package.json bumped in the very same change that
|
|
98
|
+
// deleted the stamp (caught in code review, MIL-92 PR #85 — no prior test combined
|
|
99
|
+
// pkgChanged: true with a deleted stamp). Always a finding, regardless of pkgChanged: removing
|
|
100
|
+
// the mechanism entirely is a problem on its own, not just when it coincides with a release.
|
|
101
|
+
if (newStamp === null) {
|
|
102
|
+
return {
|
|
103
|
+
skipped: null,
|
|
104
|
+
finding: {
|
|
105
|
+
code: "skill-version-stamp-removed",
|
|
106
|
+
message: pkgChanged
|
|
107
|
+
? `SKILL.md's em-version: stamp (was ${oldStamp}) was removed entirely while package.json bumped ${oldPkgVersion} -> ${newPkgVersion}`
|
|
108
|
+
: `SKILL.md's em-version: stamp (was ${oldStamp}) was removed entirely`,
|
|
109
|
+
oldPkgVersion,
|
|
110
|
+
newPkgVersion,
|
|
111
|
+
oldStamp,
|
|
112
|
+
newStamp: null,
|
|
113
|
+
},
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
const stampChanged = oldStamp !== newStamp;
|
|
117
|
+
if (pkgChanged && !stampChanged) {
|
|
118
|
+
return {
|
|
119
|
+
skipped: null,
|
|
120
|
+
finding: {
|
|
121
|
+
code: "skill-version-stamp-not-bumped",
|
|
122
|
+
message: `package.json bumped ${oldPkgVersion} -> ${newPkgVersion} but SKILL.md's em-version: stamp stayed at ${newStamp}`,
|
|
123
|
+
oldPkgVersion,
|
|
124
|
+
newPkgVersion,
|
|
125
|
+
oldStamp,
|
|
126
|
+
newStamp,
|
|
127
|
+
},
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
if (!pkgChanged && stampChanged) {
|
|
131
|
+
return {
|
|
132
|
+
skipped: null,
|
|
133
|
+
finding: {
|
|
134
|
+
code: "skill-version-stamp-stray-bump",
|
|
135
|
+
message: `SKILL.md's em-version: stamp changed (${oldStamp ?? "(none)"} -> ${newStamp}) but package.json's version didn't move (still ${newPkgVersion})`,
|
|
136
|
+
oldPkgVersion,
|
|
137
|
+
newPkgVersion,
|
|
138
|
+
oldStamp,
|
|
139
|
+
newStamp,
|
|
140
|
+
},
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
return { finding: null, skipped: null };
|
|
144
|
+
}
|
|
145
|
+
//# sourceMappingURL=skillVersionCheck.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"skillVersionCheck.js","sourceRoot":"","sources":["../../src/cli/skillVersionCheck.ts"],"names":[],"mappings":"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"}
|
package/dist/cli.d.ts
CHANGED
package/dist/cli.js
CHANGED
|
@@ -6,6 +6,7 @@ import { basename, dirname, extname, join, resolve } from "node:path";
|
|
|
6
6
|
import { fileURLToPath } from "node:url";
|
|
7
7
|
import { Command } from "commander";
|
|
8
8
|
import { compile } from "./pipeline.js";
|
|
9
|
+
import { isMainModule } from "./util/isMainModule.js";
|
|
9
10
|
import { ParseError } from "./parser/parser.js";
|
|
10
11
|
import { renderDot, layoutDot, composeSvg, writeRendered, formatFromPath } from "./render/render.js";
|
|
11
12
|
import { buildSliceDiagram } from "./render/sliceDiagram.js";
|
|
@@ -16,7 +17,16 @@ import { formatDiagnostic, hasErrors } from "./model/validate.js";
|
|
|
16
17
|
import { buildExport } from "./emit/json.js";
|
|
17
18
|
import { buildDiffJson } from "./emit/diffJson.js";
|
|
18
19
|
import { diffModels, formatModelDiff, hasChanges } from "./model/diff.js";
|
|
19
|
-
import { planDiffArgs, resolveRevision } from "./cli/diff-inputs.js";
|
|
20
|
+
import { planDiffArgs, resolveRevision, resolveDocAtRevision } from "./cli/diff-inputs.js";
|
|
21
|
+
import { readSliceDoc } from "./catalog/readSliceDoc.js";
|
|
22
|
+
import { validateLineage } from "./catalog/lineageValidate.js";
|
|
23
|
+
import { validateFrontmatterCoherence } from "./catalog/frontmatterCoherenceValidate.js";
|
|
24
|
+
import { validateSliceReady } from "./catalog/sliceReadyValidate.js";
|
|
25
|
+
import { checkLedger } from "./cli/ledgerCheck.js";
|
|
26
|
+
import { buildLedgerJson } from "./emit/ledgerJson.js";
|
|
27
|
+
import { planSkillSync, applySkillSync } from "./cli/skillSync.js";
|
|
28
|
+
import { checkSkillSync } from "./cli/skillCheck.js";
|
|
29
|
+
import { buildSkillCheckJson } from "./emit/skillCheckJson.js";
|
|
20
30
|
import { buildGlossary, detectKindConflicts, detectFieldTypeConflicts, hasConflicts, formatGlossarySummary, formatConflictLine, } from "./model/glossary.js";
|
|
21
31
|
import { buildGlossaryJson } from "./emit/glossaryJson.js";
|
|
22
32
|
import { planGlossaryArgs } from "./cli/glossary-inputs.js";
|
|
@@ -110,14 +120,14 @@ program
|
|
|
110
120
|
.argument("<file>", "input .em file")
|
|
111
121
|
.option("-o, --out <path>", "write to a file instead of stdout")
|
|
112
122
|
.action((file, opts) => {
|
|
113
|
-
const { model, diagnostics, source } = compileFile(file);
|
|
123
|
+
const { model, refs, diagnostics, source } = compileFile(file);
|
|
114
124
|
printDiagnostics(diagnostics);
|
|
115
125
|
if (hasErrors(diagnostics)) {
|
|
116
126
|
console.error("not exporting: fix the errors above");
|
|
117
127
|
process.exit(1);
|
|
118
128
|
}
|
|
119
|
-
const exported = buildExport(model, diagnostics, source, file);
|
|
120
|
-
printDiagnostics(exported.diagnostics
|
|
129
|
+
const exported = buildExport(model, refs, diagnostics, source, file);
|
|
130
|
+
printDiagnostics(newDiagnostics(exported.diagnostics, diagnostics));
|
|
121
131
|
if (opts.out) {
|
|
122
132
|
writeFileSync(opts.out, exported.text + "\n");
|
|
123
133
|
console.log(`wrote ${opts.out}`);
|
|
@@ -144,14 +154,22 @@ program
|
|
|
144
154
|
if (plan.form === "files") {
|
|
145
155
|
const oldSource = readFileOrExit(plan.oldFile);
|
|
146
156
|
const newSource = readFileOrExit(plan.newFile);
|
|
147
|
-
|
|
157
|
+
const lineage = {
|
|
158
|
+
oldDoc: (key) => readSliceDoc(dirname(plan.oldFile), key),
|
|
159
|
+
newDoc: (key) => readSliceDoc(dirname(plan.newFile), key),
|
|
160
|
+
};
|
|
161
|
+
runDiff(oldSource, plan.oldFile, newSource, plan.newFile, opts.exitCode, opts.json, lineage);
|
|
148
162
|
return;
|
|
149
163
|
}
|
|
150
164
|
const oldSource = readAtRevision(plan.file, plan.from);
|
|
151
165
|
const oldLabel = `${plan.file}@${plan.from}`;
|
|
152
166
|
const newSource = plan.to ? readAtRevision(plan.file, plan.to) : readFileOrExit(plan.file);
|
|
153
167
|
const newLabel = plan.to ? `${plan.file}@${plan.to}` : plan.file;
|
|
154
|
-
|
|
168
|
+
const lineage = {
|
|
169
|
+
oldDoc: (key) => resolveDocAtRevision(plan.file, key, plan.from),
|
|
170
|
+
newDoc: (key) => plan.to ? resolveDocAtRevision(plan.file, key, plan.to) : readSliceDoc(dirname(plan.file), key),
|
|
171
|
+
};
|
|
172
|
+
runDiff(oldSource, oldLabel, newSource, newLabel, opts.exitCode, opts.json, lineage);
|
|
155
173
|
});
|
|
156
174
|
program
|
|
157
175
|
.command("glossary")
|
|
@@ -226,14 +244,20 @@ program
|
|
|
226
244
|
process.exit(1);
|
|
227
245
|
}
|
|
228
246
|
const inputs = [];
|
|
247
|
+
// Tracked so the post-buildCatalog print below can skip diagnostics already printed
|
|
248
|
+
// here (ref-collision warnings now live in compileFile()'s own `diagnostics`, and
|
|
249
|
+
// buildCatalog() forwards those same, reference-identical objects rather than
|
|
250
|
+
// recomputing them — printing both lists unfiltered would double-print them).
|
|
251
|
+
const diagnosticsByFile = new Map();
|
|
229
252
|
let anyErrors = false;
|
|
230
253
|
for (const file of files) {
|
|
231
|
-
const { dot, model, grid, diagnostics } = compileFile(file, { keepEmptyLanes: opts.keepEmptyLanes });
|
|
254
|
+
const { dot, model, grid, diagnostics, refs } = compileFile(file, { keepEmptyLanes: opts.keepEmptyLanes });
|
|
232
255
|
printDiagnosticsFor(file, diagnostics);
|
|
256
|
+
diagnosticsByFile.set(file, diagnostics);
|
|
233
257
|
warnMissingNotes(file, model);
|
|
234
258
|
if (hasErrors(diagnostics))
|
|
235
259
|
anyErrors = true;
|
|
236
|
-
inputs.push({ file, model, grid, dot });
|
|
260
|
+
inputs.push({ file, model, grid, dot, refs });
|
|
237
261
|
}
|
|
238
262
|
if (anyErrors) {
|
|
239
263
|
console.error("not building catalog: fix the errors above");
|
|
@@ -245,8 +269,10 @@ program
|
|
|
245
269
|
title: opts.title,
|
|
246
270
|
keepEmptyLanes: opts.keepEmptyLanes,
|
|
247
271
|
});
|
|
248
|
-
for (const d of result.diagnostics)
|
|
249
|
-
|
|
272
|
+
for (const d of result.diagnostics) {
|
|
273
|
+
const already = diagnosticsByFile.get(d.file) ?? [];
|
|
274
|
+
printDiagnosticsFor(d.file, newDiagnostics(d.diagnostics, already));
|
|
275
|
+
}
|
|
250
276
|
const modelWord = result.models === 1 ? "model" : "models";
|
|
251
277
|
const sliceWord = result.slices === 1 ? "slice" : "slices";
|
|
252
278
|
console.log(`wrote ${opts.out}/ (${result.models} ${modelWord}, ${result.slices} ${sliceWord})`);
|
|
@@ -331,31 +357,109 @@ program
|
|
|
331
357
|
.argument("<file>", "input .em file")
|
|
332
358
|
.option("--list-issues", "print only open `issue` diagnostics (slice, element, line, text)")
|
|
333
359
|
.option("--list-divergences", "print only accepted-divergence annotations (slice, element, line, text) — never fails the build")
|
|
334
|
-
.option("--list-public", "print only events marked `public` (slice, name, line) — an integration-surface audit, never fails the build")
|
|
360
|
+
.option("--list-public", "print only events and views marked `public` (slice, kind, name, line) — an integration-surface audit, never fails the build")
|
|
335
361
|
.option("--fail-on-issues", "exit non-zero if the model has any open `issue`s (opt-in — issues are warnings and don't block by default)")
|
|
362
|
+
.option("--slice-ready <key>", "readiness gate for one slice (export key): status ready-to-implement, doc resolvable via " +
|
|
363
|
+
"note binding, zero unchecked Open Questions — exits non-zero if not ready (MIL-87)")
|
|
336
364
|
.action((file, opts) => {
|
|
337
|
-
const { model, diagnostics } = compileFile(file);
|
|
365
|
+
const { model, diagnostics, refs } = compileFile(file);
|
|
366
|
+
// Lineage-ref resolution (MIL-84) and frontmatter-coherence (MIL-85) are validate's
|
|
367
|
+
// fs-aware rules — every other check above is a pure function of the .em source. Both
|
|
368
|
+
// read slices/*.md alongside the model.
|
|
369
|
+
const allDiagnostics = [
|
|
370
|
+
...diagnostics,
|
|
371
|
+
...validateLineage(model, refs, dirname(file)),
|
|
372
|
+
...validateFrontmatterCoherence(model, refs, dirname(file)),
|
|
373
|
+
];
|
|
374
|
+
if (opts.sliceReady) {
|
|
375
|
+
// MIL-87: a targeted, single-slice readiness gate, not part of the unconditional
|
|
376
|
+
// diagnostic set above (see sliceReadyValidate.ts's header for why). Folds in MIL-85's
|
|
377
|
+
// frontmatter-coherence findings for free by filtering allDiagnostics' own refs, rather
|
|
378
|
+
// than re-deriving that classification here.
|
|
379
|
+
//
|
|
380
|
+
// "Concerns this slice" means a ref that either IS the bare slice key (lineage,
|
|
381
|
+
// frontmatter-coherence, and this module's own diagnostics all tag that way) OR starts
|
|
382
|
+
// with `<sliceKey>/` (every element-level ref from computeRefs()/model/validate.ts,
|
|
383
|
+
// e.g. an unknown-event error on a view inside this slice). An earlier version gated on
|
|
384
|
+
// "any error anywhere in the model," which silently failed the check — with zero
|
|
385
|
+
// diagnostics printed — on an unrelated slice's breakage; scoping to this slice alone
|
|
386
|
+
// matches the ticket's own scenario (check one slice while the rest of a large model is
|
|
387
|
+
// still WIP) and this module's own "single-slice" framing.
|
|
388
|
+
const readyDiagnostics = validateSliceReady(model, refs, dirname(file), opts.sliceReady);
|
|
389
|
+
const combined = [...allDiagnostics, ...readyDiagnostics];
|
|
390
|
+
const key = opts.sliceReady;
|
|
391
|
+
const scoped = combined.filter((d) => d.refs?.some((r) => r === key || r.startsWith(`${key}/`)));
|
|
392
|
+
printDiagnostics(scoped);
|
|
393
|
+
const ready = scoped.length === 0;
|
|
394
|
+
console.log(ready ? `slice "${key}" is ready-to-implement` : `slice "${key}" is NOT ready-to-implement`);
|
|
395
|
+
if (!ready)
|
|
396
|
+
process.exit(1);
|
|
397
|
+
return;
|
|
398
|
+
}
|
|
338
399
|
if (opts.listIssues || opts.listDivergences || opts.listPublic) {
|
|
339
400
|
if (opts.listIssues)
|
|
340
401
|
printIssues(model);
|
|
341
402
|
if (opts.listDivergences)
|
|
342
403
|
printDivergences(model);
|
|
343
404
|
if (opts.listPublic)
|
|
344
|
-
|
|
405
|
+
printPublicElements(model);
|
|
345
406
|
// Errors still fail the run below — surface them rather than exiting
|
|
346
407
|
// non-zero with nothing but the list on screen.
|
|
347
|
-
printDiagnostics(
|
|
408
|
+
printDiagnostics(allDiagnostics.filter((d) => d.severity === "error"));
|
|
348
409
|
}
|
|
349
410
|
else {
|
|
350
|
-
printDiagnostics(
|
|
351
|
-
if (
|
|
411
|
+
printDiagnostics(allDiagnostics);
|
|
412
|
+
if (allDiagnostics.length === 0)
|
|
352
413
|
console.log("ok — no issues");
|
|
353
414
|
}
|
|
354
|
-
if (hasErrors(
|
|
415
|
+
if (hasErrors(allDiagnostics))
|
|
355
416
|
process.exit(1);
|
|
356
417
|
if (opts.failOnIssues && model.elements.some((el) => el.issue))
|
|
357
418
|
process.exit(1);
|
|
358
419
|
});
|
|
420
|
+
program
|
|
421
|
+
.command("ledger")
|
|
422
|
+
.description("check slice docs' version: field agrees with their content across two git revisions " +
|
|
423
|
+
"(opt-in CI check, MIL-89 — never part of `em validate`, see docs/ci.md)")
|
|
424
|
+
.argument("<file>", "anchor .em file — locates slices/ relative to it; never parsed")
|
|
425
|
+
.option("--from <rev>", "baseline revision")
|
|
426
|
+
.option("--to <rev>", "compare revision (default: current working tree)")
|
|
427
|
+
.option("--json", "print a JSON document instead of the text report (see docs/cli.md)")
|
|
428
|
+
.action((file, opts) => {
|
|
429
|
+
if (!opts.from) {
|
|
430
|
+
console.error("em ledger: --from <rev> is required");
|
|
431
|
+
process.exit(1);
|
|
432
|
+
}
|
|
433
|
+
const to = opts.to ?? null;
|
|
434
|
+
const result = checkLedger(file, opts.from, to);
|
|
435
|
+
if (opts.json) {
|
|
436
|
+
process.stdout.write(buildLedgerJson(result, opts.from, to) + "\n");
|
|
437
|
+
}
|
|
438
|
+
else {
|
|
439
|
+
for (const f of result.findings)
|
|
440
|
+
console.log(f.message);
|
|
441
|
+
if (result.findings.length === 0) {
|
|
442
|
+
console.log(`ok — ledger agrees (${result.checkedCount} slice doc(s) checked)`);
|
|
443
|
+
}
|
|
444
|
+
else {
|
|
445
|
+
console.log(`${result.findings.length} ledger mismatch(es)`);
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
// Set the code rather than process.exit(): same rationale as em diff/em glossary — stdout
|
|
449
|
+
// to a pipe (a --json document) shouldn't risk truncation.
|
|
450
|
+
if (result.findings.length > 0)
|
|
451
|
+
process.exitCode = 1;
|
|
452
|
+
});
|
|
453
|
+
// Shared by install/sync/check: the skill directory bundled with whatever em package is
|
|
454
|
+
// actually running (works whether em was installed from npm or run from a checkout, and
|
|
455
|
+
// regardless of a symlinked global install — see pkgDir's own resolution above for
|
|
456
|
+
// PKG_VERSION) and the skill directory a consumer repo vendors it into.
|
|
457
|
+
function packagedSkillDir() {
|
|
458
|
+
return join(dirname(fileURLToPath(import.meta.url)), "..", ".claude", "skills", "event-modeling");
|
|
459
|
+
}
|
|
460
|
+
function vendoredSkillDir(repoRoot) {
|
|
461
|
+
return join(resolve(repoRoot), ".claude", "skills", "event-modeling");
|
|
462
|
+
}
|
|
359
463
|
const skill = program
|
|
360
464
|
.command("skill")
|
|
361
465
|
.description("manage Claude Code skills bundled with em");
|
|
@@ -364,9 +468,8 @@ skill
|
|
|
364
468
|
.description("copy the event-modeling skill into .claude/skills/event-modeling/")
|
|
365
469
|
.option("-f, --force", "overwrite an existing installation")
|
|
366
470
|
.action(async (opts) => {
|
|
367
|
-
const
|
|
368
|
-
const
|
|
369
|
-
const dest = join(process.cwd(), ".claude", "skills", "event-modeling");
|
|
471
|
+
const src = packagedSkillDir();
|
|
472
|
+
const dest = vendoredSkillDir(process.cwd());
|
|
370
473
|
if (existsSync(dest) && !opts.force) {
|
|
371
474
|
console.log(`skill already installed at ${dest}`);
|
|
372
475
|
console.log("re-run with --force to overwrite");
|
|
@@ -377,10 +480,59 @@ skill
|
|
|
377
480
|
console.log(`installed event-modeling skill → ${dest}`);
|
|
378
481
|
console.log("in Claude Code, run /event-modeling to start a guided session");
|
|
379
482
|
});
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
483
|
+
skill
|
|
484
|
+
.command("sync")
|
|
485
|
+
.description("update the vendored .claude/skills/event-modeling/ copy in [path] to match the installed " +
|
|
486
|
+
"em package (overwrites unconditionally; local edits are never merged, MIL-93)")
|
|
487
|
+
.argument("[path]", "consumer repo root", ".")
|
|
488
|
+
.action(async (path) => {
|
|
489
|
+
const packagedDir = packagedSkillDir();
|
|
490
|
+
const vendoredDir = vendoredSkillDir(path);
|
|
491
|
+
const plan = planSkillSync(packagedDir, vendoredDir);
|
|
492
|
+
if (plan.changes.length === 0) {
|
|
493
|
+
console.log(`up to date — ${vendoredDir} already matches the installed skill (${plan.unchangedCount} file(s))`);
|
|
494
|
+
return;
|
|
495
|
+
}
|
|
496
|
+
applySkillSync(plan, packagedDir, vendoredDir);
|
|
497
|
+
for (const c of plan.changes)
|
|
498
|
+
console.log(`${c.kind}: ${c.relPath}`);
|
|
499
|
+
console.log(`synced ${vendoredDir} — ${plan.changes.length} file(s) changed, ${plan.unchangedCount} unchanged`);
|
|
500
|
+
});
|
|
501
|
+
skill
|
|
502
|
+
.command("check")
|
|
503
|
+
.description("check the vendored .claude/skills/event-modeling/ copy in [path] for drift against the " +
|
|
504
|
+
"installed em package; exits non-zero on any mismatch (CI-ready, MIL-93)")
|
|
505
|
+
.argument("[path]", "consumer repo root", ".")
|
|
506
|
+
.option("--json", "print a JSON document instead of the text report (see docs/cli.md)")
|
|
507
|
+
.action((path, opts) => {
|
|
508
|
+
const packagedDir = packagedSkillDir();
|
|
509
|
+
const vendoredDir = vendoredSkillDir(path);
|
|
510
|
+
const result = checkSkillSync(packagedDir, vendoredDir, PKG_VERSION);
|
|
511
|
+
if (opts.json) {
|
|
512
|
+
process.stdout.write(buildSkillCheckJson(result, vendoredDir, PKG_VERSION) + "\n");
|
|
513
|
+
}
|
|
514
|
+
else {
|
|
515
|
+
for (const f of result.findings)
|
|
516
|
+
console.log(f.message);
|
|
517
|
+
console.log(result.ok ? `ok — vendored skill matches em ${PKG_VERSION}` : `${result.findings.length} mismatch(es)`);
|
|
518
|
+
}
|
|
519
|
+
// Set the code rather than process.exit(): same rationale as em ledger/em diff — stdout to
|
|
520
|
+
// a pipe (a --json document) shouldn't risk truncation.
|
|
521
|
+
if (!result.ok)
|
|
522
|
+
process.exitCode = 1;
|
|
383
523
|
});
|
|
524
|
+
// Exported so dev tooling (e.g. scripts/generate-skill-docs.ts) can introspect the registered
|
|
525
|
+
// commands/options without triggering a real CLI run. Guarded the same way Node's CJS
|
|
526
|
+
// `require.main === module` used to: only actually parse argv when this file is the process's
|
|
527
|
+
// entry module, not merely `import`ed. See util/isMainModule.ts for why this has to
|
|
528
|
+
// realpath-resolve argv[1] — a naive path comparison breaks the `npm i -g` symlink case.
|
|
529
|
+
export { program };
|
|
530
|
+
if (isMainModule(import.meta.url)) {
|
|
531
|
+
program.parseAsync().catch((e) => {
|
|
532
|
+
reportError(e);
|
|
533
|
+
process.exit(1);
|
|
534
|
+
});
|
|
535
|
+
}
|
|
384
536
|
// ---- helpers ----
|
|
385
537
|
function compileFile(file, opts = {}) {
|
|
386
538
|
let source;
|
|
@@ -448,27 +600,32 @@ function readAtRevision(file, rev) {
|
|
|
448
600
|
*/
|
|
449
601
|
function buildChangelogDoc(file, repoRoot, commits) {
|
|
450
602
|
const models = [];
|
|
603
|
+
const refsList = [];
|
|
451
604
|
const errors = [];
|
|
452
605
|
for (const c of commits) {
|
|
453
606
|
const rev = readFileAtCommit(repoRoot, c);
|
|
454
607
|
if (!rev.ok) {
|
|
455
608
|
models.push(null);
|
|
609
|
+
refsList.push(null);
|
|
456
610
|
errors.push(rev.message);
|
|
457
611
|
continue;
|
|
458
612
|
}
|
|
459
613
|
try {
|
|
460
|
-
const { model, diagnostics } = compile(rev.content);
|
|
614
|
+
const { model, refs, diagnostics } = compile(rev.content);
|
|
461
615
|
if (hasErrors(diagnostics)) {
|
|
462
616
|
models.push(null);
|
|
617
|
+
refsList.push(null);
|
|
463
618
|
errors.push(`validation errors at ${c.shortHash} — fix with \`em validate\` at that revision`);
|
|
464
619
|
}
|
|
465
620
|
else {
|
|
466
621
|
models.push(model);
|
|
622
|
+
refsList.push(refs);
|
|
467
623
|
errors.push(null);
|
|
468
624
|
}
|
|
469
625
|
}
|
|
470
626
|
catch (e) {
|
|
471
627
|
models.push(null);
|
|
628
|
+
refsList.push(null);
|
|
472
629
|
errors.push(e instanceof ParseError ? `parse error: ${e.message}` : e instanceof Error ? e.message : String(e));
|
|
473
630
|
}
|
|
474
631
|
}
|
|
@@ -486,7 +643,10 @@ function buildChangelogDoc(file, repoRoot, commits) {
|
|
|
486
643
|
entries.push({ ...base, diff: null, error: `no earlier parseable revision to diff against (${commits[0].shortHash}: ${errors[0]})` });
|
|
487
644
|
}
|
|
488
645
|
else {
|
|
489
|
-
entries.push({
|
|
646
|
+
entries.push({
|
|
647
|
+
...base,
|
|
648
|
+
diff: diffModels(models[prevParseable], models[i], refsList[prevParseable], refsList[i]),
|
|
649
|
+
});
|
|
490
650
|
}
|
|
491
651
|
if (models[i])
|
|
492
652
|
prevParseable = i;
|
|
@@ -498,7 +658,7 @@ function buildChangelogDoc(file, repoRoot, commits) {
|
|
|
498
658
|
return buildChangelog(entries, decisions, { file, intro, introError });
|
|
499
659
|
}
|
|
500
660
|
/** Shared body for both `em diff` forms: compile both sides, gate on errors, print, exit-code. */
|
|
501
|
-
function runDiff(oldSource, oldLabel, newSource, newLabel, exitCode, json) {
|
|
661
|
+
function runDiff(oldSource, oldLabel, newSource, newLabel, exitCode, json, lineage) {
|
|
502
662
|
const oldResult = compileSource(oldSource, oldLabel);
|
|
503
663
|
const newResult = compileSource(newSource, newLabel);
|
|
504
664
|
printDiagnostics(oldResult.diagnostics);
|
|
@@ -507,7 +667,7 @@ function runDiff(oldSource, oldLabel, newSource, newLabel, exitCode, json) {
|
|
|
507
667
|
console.error("not diffing: fix the errors above");
|
|
508
668
|
process.exit(1);
|
|
509
669
|
}
|
|
510
|
-
const diff = diffModels(oldResult.model, newResult.model);
|
|
670
|
+
const diff = diffModels(oldResult.model, newResult.model, oldResult.refs, newResult.refs, lineage);
|
|
511
671
|
if (json) {
|
|
512
672
|
const oldSide = { label: oldLabel, source: oldSource, diagnostics: oldResult.diagnostics };
|
|
513
673
|
const newSide = { label: newLabel, source: newSource, diagnostics: newResult.diagnostics };
|
|
@@ -561,18 +721,25 @@ function printDivergences(model) {
|
|
|
561
721
|
console.log(` divergence :${el.line} slice "${slice.name}" ${el.kind} "${el.name}": ${el.divergence}`);
|
|
562
722
|
}
|
|
563
723
|
}
|
|
564
|
-
/** Print only
|
|
565
|
-
function
|
|
566
|
-
const pub = model.elements.filter((el) => el.kind === "event"
|
|
724
|
+
/** Print only elements marked `public`: slice, kind, name, line — for an integration-surface audit. */
|
|
725
|
+
function printPublicElements(model) {
|
|
726
|
+
const pub = model.elements.filter((el) => el.public && (el.kind === "event" || el.kind === "view"));
|
|
567
727
|
if (pub.length === 0) {
|
|
568
|
-
console.log("no public
|
|
728
|
+
console.log("no public elements");
|
|
569
729
|
return;
|
|
570
730
|
}
|
|
571
731
|
for (const el of pub) {
|
|
572
732
|
const slice = model.slices[el.sliceIndex];
|
|
573
|
-
console.log(` public :${el.line} slice "${slice.name}"
|
|
733
|
+
console.log(` public :${el.line} slice "${slice.name}" ${el.kind} "${el.name}"`);
|
|
574
734
|
}
|
|
575
735
|
}
|
|
736
|
+
/** Diagnostics in `all` not already present (by reference identity) in `already` — avoids
|
|
737
|
+
* double-printing diagnostic objects forwarded unchanged from an earlier compile step (e.g.
|
|
738
|
+
* ref-collision warnings `buildExport()`/`buildCatalog()` fold in from the same `RefsResult`
|
|
739
|
+
* `compileFile()` already printed once). */
|
|
740
|
+
function newDiagnostics(all, already) {
|
|
741
|
+
return all.filter((d) => !already.includes(d));
|
|
742
|
+
}
|
|
576
743
|
function printDiagnostics(diags) {
|
|
577
744
|
if (diags.length === 0)
|
|
578
745
|
return;
|