@milehimikey/em 1.7.2 → 1.8.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.
Files changed (112) hide show
  1. package/.claude/skills/event-modeling/SKILL.md +92 -65
  2. package/.claude/skills/event-modeling/reference/conform.md +45 -20
  3. package/.claude/skills/event-modeling/reference/em-dsl.md +120 -55
  4. package/.claude/skills/event-modeling/reference/extract.md +5 -4
  5. package/.claude/skills/event-modeling/reference/implement.md +34 -22
  6. package/.claude/skills/event-modeling/templates/model-readme.md +8 -5
  7. package/.claude/skills/event-modeling/templates/slice.md +19 -5
  8. package/README.md +6 -4
  9. package/dist/catalog/docJoin.d.ts +28 -3
  10. package/dist/catalog/docJoin.js +82 -13
  11. package/dist/catalog/docJoin.js.map +1 -1
  12. package/dist/catalog/docModelConsistencyValidate.d.ts +8 -0
  13. package/dist/catalog/docModelConsistencyValidate.js +335 -0
  14. package/dist/catalog/docModelConsistencyValidate.js.map +1 -0
  15. package/dist/catalog/noteBindingValidate.d.ts +8 -0
  16. package/dist/catalog/noteBindingValidate.js +133 -0
  17. package/dist/catalog/noteBindingValidate.js.map +1 -0
  18. package/dist/catalog/pages.js +3 -3
  19. package/dist/catalog/pages.js.map +1 -1
  20. package/dist/catalog/sliceDoc.d.ts +14 -0
  21. package/dist/catalog/sliceDoc.js +22 -0
  22. package/dist/catalog/sliceDoc.js.map +1 -1
  23. package/dist/catalog/sliceReadyValidate.d.ts +18 -0
  24. package/dist/catalog/sliceReadyValidate.js +25 -1
  25. package/dist/catalog/sliceReadyValidate.js.map +1 -1
  26. package/dist/cli/agentsMd.d.ts +15 -0
  27. package/dist/cli/agentsMd.js +72 -0
  28. package/dist/cli/agentsMd.js.map +1 -0
  29. package/dist/cli/conformScope.d.ts +86 -0
  30. package/dist/cli/conformScope.js +156 -0
  31. package/dist/cli/conformScope.js.map +1 -0
  32. package/dist/cli/contract.d.ts +7 -0
  33. package/dist/cli/contract.js +27 -0
  34. package/dist/cli/contract.js.map +1 -0
  35. package/dist/cli/coverage.d.ts +69 -0
  36. package/dist/cli/coverage.js +157 -0
  37. package/dist/cli/coverage.js.map +1 -0
  38. package/dist/cli/markImplemented.d.ts +34 -0
  39. package/dist/cli/markImplemented.js +180 -0
  40. package/dist/cli/markImplemented.js.map +1 -0
  41. package/dist/cli/migrateReactionShape.d.ts +62 -0
  42. package/dist/cli/migrateReactionShape.js +283 -0
  43. package/dist/cli/migrateReactionShape.js.map +1 -0
  44. package/dist/cli/sliceIndex.d.ts +49 -0
  45. package/dist/cli/sliceIndex.js +115 -0
  46. package/dist/cli/sliceIndex.js.map +1 -0
  47. package/dist/cli/sliceNew.d.ts +18 -0
  48. package/dist/cli/sliceNew.js +53 -0
  49. package/dist/cli/sliceNew.js.map +1 -0
  50. package/dist/cli/stateFile.d.ts +74 -0
  51. package/dist/cli/stateFile.js +191 -0
  52. package/dist/cli/stateFile.js.map +1 -0
  53. package/dist/cli.js +535 -43
  54. package/dist/cli.js.map +1 -1
  55. package/dist/emit/coverageJson.d.ts +8 -0
  56. package/dist/emit/coverageJson.js +30 -0
  57. package/dist/emit/coverageJson.js.map +1 -0
  58. package/dist/emit/dot.js +41 -10
  59. package/dist/emit/dot.js.map +1 -1
  60. package/dist/emit/json.d.ts +72 -4
  61. package/dist/emit/json.js +73 -4
  62. package/dist/emit/json.js.map +1 -1
  63. package/dist/emit/validateJson.d.ts +52 -0
  64. package/dist/emit/validateJson.js +120 -0
  65. package/dist/emit/validateJson.js.map +1 -0
  66. package/dist/mcp/main.d.ts +2 -0
  67. package/dist/mcp/main.js +20 -0
  68. package/dist/mcp/main.js.map +1 -0
  69. package/dist/mcp/server.d.ts +11 -0
  70. package/dist/mcp/server.js +260 -0
  71. package/dist/mcp/server.js.map +1 -0
  72. package/dist/model/model.d.ts +30 -1
  73. package/dist/model/model.js +26 -0
  74. package/dist/model/model.js.map +1 -1
  75. package/dist/model/rules.d.ts +114 -0
  76. package/dist/model/rules.js +116 -4
  77. package/dist/model/rules.js.map +1 -1
  78. package/dist/model/validate.js +83 -7
  79. package/dist/model/validate.js.map +1 -1
  80. package/dist/parser/ast.d.ts +41 -0
  81. package/dist/parser/lexer.d.ts +16 -0
  82. package/dist/parser/lexer.js +44 -2
  83. package/dist/parser/lexer.js.map +1 -1
  84. package/dist/parser/parser.js +340 -20
  85. package/dist/parser/parser.js.map +1 -1
  86. package/dist/render/drawNotes.js +8 -1
  87. package/dist/render/drawNotes.js.map +1 -1
  88. package/dist/render/render.js +85 -17
  89. package/dist/render/render.js.map +1 -1
  90. package/dist/render/serve.d.ts +4 -0
  91. package/dist/render/serve.js +23 -239
  92. package/dist/render/serve.js.map +1 -1
  93. package/dist/render/sliceStatus.d.ts +1 -1
  94. package/dist/render/sliceStatus.js +35 -6
  95. package/dist/render/sliceStatus.js.map +1 -1
  96. package/dist/render/viewerHtml.d.ts +1 -0
  97. package/dist/render/viewerHtml.js +5 -0
  98. package/dist/render/viewerHtml.js.map +1 -0
  99. package/dist/render/watch.d.ts +10 -0
  100. package/dist/render/watch.js +29 -0
  101. package/dist/render/watch.js.map +1 -1
  102. package/dist/templates.d.ts +19 -0
  103. package/dist/templates.js +169 -0
  104. package/dist/templates.js.map +1 -1
  105. package/dist/util/markers.d.ts +13 -0
  106. package/dist/util/markers.js +30 -0
  107. package/dist/util/markers.js.map +1 -0
  108. package/dist/util/walkDir.d.ts +10 -1
  109. package/dist/util/walkDir.js +11 -7
  110. package/dist/util/walkDir.js.map +1 -1
  111. package/package.json +12 -3
  112. package/.claude/skills/event-modeling/templates/live.html +0 -117
@@ -0,0 +1,156 @@
1
+ // SPDX-License-Identifier: MIT
2
+ // `em conform-scope` (MIL-100): mechanizes the mechanical half of reference/conform.md step 1
3
+ // ("Scope") — everything that was previously prose telling the agent to shell out to git and
4
+ // eyeball `implementedIn:` links by hand.
5
+ //
6
+ // Three pieces, each reusing an existing module rather than re-deriving it:
7
+ // - `Last conformance:` comes from `stateFile.ts`'s `parseState` (MIL-99) — this module never
8
+ // parses the state file's bullets itself.
9
+ // - Changed paths come from `git diff --name-only <revision>..HEAD` in the target repo, via
10
+ // the same injectable `GitRunner`/`realGit` convention diff-inputs.ts/changelog-git.ts use
11
+ // (so every failure branch — no repo, unknown revision — is unit-testable without a real
12
+ // repository).
13
+ // - A changed path maps to a slice only when its doc's `implementedIn` (from
14
+ // catalog/docJoin.ts's join — the same one `em export`/`em slice index` use, never a second
15
+ // parse) is textually, deterministically a repo-relative path that's a prefix of (or equal
16
+ // to) that changed path. A URL (PR/commit link) carries no path information we can trust, so
17
+ // it deterministically matches nothing. Anything left over is `unmappedPaths` — the agent's
18
+ // judgment queue (grep fallback, evidence walks); this module never guesses.
19
+ //
20
+ // `--seed-asis` mechanizes the OTHER mechanical convention step 1 describes: seeding
21
+ // `<model>-asis.em` as a byte copy of the canonical model, and making sure `*-asis.em` is
22
+ // gitignored (idempotent).
23
+ import { copyFileSync, existsSync, readFileSync, writeFileSync } from "node:fs";
24
+ import { basename, dirname, extname, join, resolve } from "node:path";
25
+ import { resolveSliceDocJoin } from "../catalog/docJoin.js";
26
+ import { realGit } from "./diff-inputs.js";
27
+ const URL_LIKE = /^[a-z][a-z0-9+.-]*:\/\//i; // scheme://... (http, https, git, ssh, ...)
28
+ const SCP_LIKE = /^[\w.-]+@[\w.-]+:/; // git@host:owner/repo.git style — also carries no path
29
+ /** `implementedIn` -> the repo-relative path text worth prefix-matching against changed paths,
30
+ * or null when it's a URL/SCP-style link (a PR/commit/host reference) that carries no path
31
+ * information we can trust. Deliberately conservative — see this module's header comment. */
32
+ function pathCandidate(implementedIn) {
33
+ const trimmed = implementedIn.trim();
34
+ if (!trimmed || URL_LIKE.test(trimmed) || SCP_LIKE.test(trimmed))
35
+ return null;
36
+ return trimmed.replace(/^\.\//, "").replace(/\/+$/, "");
37
+ }
38
+ /** The changed paths one slice's `implementedIn` textually, deterministically names: equal to
39
+ * the candidate path, or nested under it (the candidate names a directory). No fuzzy matching,
40
+ * no grep — that judgment call stays with the agent (reference/conform.md step 1). */
41
+ export function matchImplementedInToPaths(implementedIn, changedPaths) {
42
+ if (!implementedIn)
43
+ return [];
44
+ const candidate = pathCandidate(implementedIn);
45
+ if (!candidate)
46
+ return [];
47
+ return changedPaths.filter((p) => p === candidate || p.startsWith(`${candidate}/`));
48
+ }
49
+ /**
50
+ * Build the `em conform-scope` JSON body — pure, no fs/git. `full` covers `--full`; a `null`
51
+ * `lastConformance` (the state file's `never` marker, first run) forces the same behavior
52
+ * regardless of `full`, matching reference/conform.md step 1 ("If it's `null` (first run) or
53
+ * the user passes `--full`, scope is every `implemented` slice"). In that mode `changedPaths`
54
+ * is `[]` (documented choice, not omitted — keeps the JSON shape stable across modes) and every
55
+ * `implemented`-status slice is a candidate with `matchedBy: "full"` and empty `paths`.
56
+ */
57
+ export function buildConformScope(slices, lastConformance, changedPaths, full) {
58
+ const lastConformanceOut = lastConformance ? { date: lastConformance.date, revision: lastConformance.revision } : null;
59
+ if (full || lastConformance === null) {
60
+ const candidateSlices = slices
61
+ .filter((s) => s.status === "implemented")
62
+ .map((s) => ({ key: s.key, matchedBy: "full", paths: [] }));
63
+ return { lastConformance: lastConformanceOut, changedPaths: [], candidateSlices, unmappedPaths: [] };
64
+ }
65
+ const matched = new Set();
66
+ const candidateSlices = [];
67
+ for (const s of slices) {
68
+ const paths = matchImplementedInToPaths(s.implementedIn, changedPaths);
69
+ if (paths.length === 0)
70
+ continue;
71
+ candidateSlices.push({ key: s.key, matchedBy: "implementedIn", paths });
72
+ for (const p of paths)
73
+ matched.add(p);
74
+ }
75
+ const unmappedPaths = changedPaths.filter((p) => !matched.has(p));
76
+ return { lastConformance: lastConformanceOut, changedPaths, candidateSlices, unmappedPaths };
77
+ }
78
+ /** Resolve every slice's doc-join facts in declaration order — exactly the two inputs
79
+ * `buildConformScope` needs (`status`, `implementedIn`), via the same `resolveSliceDocJoin`
80
+ * call `em export`/`em slice index` make, never a second doc parse. `diagnostics` carries any
81
+ * `binding-missing-file`/`frontmatter-invalid` warnings, for the caller to print the same way
82
+ * `em slice index` does. */
83
+ export function resolveSliceDocFacts(model, refs, baseDir) {
84
+ const diagnostics = [];
85
+ const facts = model.slices.map((slice, i) => {
86
+ const key = refs.sliceKeys[i];
87
+ const { doc, diagnostics: docDiags } = resolveSliceDocJoin(slice, key, baseDir, (id) => refs.refById.get(id));
88
+ diagnostics.push(...docDiags);
89
+ return { key, status: doc.status, implementedIn: doc.implementedIn };
90
+ });
91
+ return { facts, diagnostics };
92
+ }
93
+ /**
94
+ * `git diff --name-only <revision>..HEAD`, run from `repo` (any directory inside the target
95
+ * repo, not necessarily its root — same "anchor dir, resolve toplevel" convention
96
+ * diff-inputs.ts/changelog-git.ts use). Paths come back repo-root-relative — git's own default
97
+ * for `--name-only` without `--relative`.
98
+ */
99
+ export function changedPathsSince(repo, revision, runGit = realGit) {
100
+ const toplevel = runGit(["-C", repo, "rev-parse", "--show-toplevel"]);
101
+ if (toplevel.status !== 0) {
102
+ return { ok: false, message: `em conform-scope: ${repo} is not a git repository` };
103
+ }
104
+ const repoRoot = toplevel.stdout.trim();
105
+ const diff = runGit(["-C", repoRoot, "diff", "--name-only", `${revision}..HEAD`]);
106
+ if (diff.status !== 0) {
107
+ return {
108
+ ok: false,
109
+ message: `em conform-scope: git diff failed: ${(diff.stderr || "").trim() || `unknown revision "${revision}"`}`,
110
+ };
111
+ }
112
+ return {
113
+ ok: true,
114
+ paths: diff.stdout
115
+ .split("\n")
116
+ .map((s) => s.trim())
117
+ .filter(Boolean),
118
+ };
119
+ }
120
+ const ASIS_GITIGNORE_PATTERN = "*-asis.em";
121
+ /**
122
+ * `--seed-asis`: byte-copy `modelFile` to `<dirname(modelFile)>/<stem>-asis.em`, and make sure
123
+ * `*-asis.em` is gitignored. reference/conform.md's Conventions section says to add the pattern
124
+ * to "the repository's `.gitignore`" — read here as the git repo the model itself lives in
125
+ * (resolved the same "anchor dir, `rev-parse --show-toplevel`" way as `changedPathsSince`
126
+ * above, from the model's own directory — a different repo entirely from the `--repo` target
127
+ * codebase conform is checking), so the entry lands once, at that repo's root, covering every
128
+ * model the repo holds. Falls back to a `.gitignore` right next to the model when the model
129
+ * isn't inside a git repo at all. Idempotent: never appends a second `*-asis.em` line if one
130
+ * (exactly, on its own line) is already present anywhere in the file.
131
+ */
132
+ export function seedAsisModel(modelFile, runGit = realGit) {
133
+ const dir = dirname(modelFile);
134
+ const stem = basename(modelFile, extname(modelFile));
135
+ const asisPath = join(dir, `${stem}-asis.em`);
136
+ copyFileSync(modelFile, asisPath);
137
+ const toplevel = runGit(["-C", resolve(dir), "rev-parse", "--show-toplevel"]);
138
+ const gitignoreDir = toplevel.status === 0 ? toplevel.stdout.trim() : dir;
139
+ const gitignorePath = join(gitignoreDir, ".gitignore");
140
+ let gitignoreUpdated = false;
141
+ if (existsSync(gitignorePath)) {
142
+ const text = readFileSync(gitignorePath, "utf8");
143
+ const hasLine = text.split(/\r?\n/).some((l) => l.trim() === ASIS_GITIGNORE_PATTERN);
144
+ if (!hasLine) {
145
+ const sep = text.length === 0 || text.endsWith("\n") ? "" : "\n";
146
+ writeFileSync(gitignorePath, `${text}${sep}${ASIS_GITIGNORE_PATTERN}\n`);
147
+ gitignoreUpdated = true;
148
+ }
149
+ }
150
+ else {
151
+ writeFileSync(gitignorePath, `${ASIS_GITIGNORE_PATTERN}\n`);
152
+ gitignoreUpdated = true;
153
+ }
154
+ return { asisPath, gitignoreUpdated };
155
+ }
156
+ //# sourceMappingURL=conformScope.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"conformScope.js","sourceRoot":"","sources":["../../src/cli/conformScope.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,7 @@
1
+ /** Path to the packaged contract inside `packagedSkillDir` — the same directory `em skill
2
+ * install`/`sync`/`check` read from (src/cli.ts's packagedSkillDir()). */
3
+ export declare function contractPath(packagedSkillDir: string): string;
4
+ /** Pure: reads the packaged contract's exact bytes. No process.exit/console — src/cli.ts's
5
+ * action wraps fs errors (a corrupt/incomplete package install) the same way every other
6
+ * command's file reads do. */
7
+ export declare function readContract(packagedSkillDir: string): string;
@@ -0,0 +1,27 @@
1
+ // SPDX-License-Identifier: MIT
2
+ // `em contract` (MIL-129): prints the packaged implementation contract — reference/implement.md
3
+ // inside the skill directory bundled with whatever em package is actually running (see
4
+ // src/cli.ts's packagedSkillDir) — to stdout, verbatim.
5
+ //
6
+ // Why this exists: reference/implement.md opens by claiming the contract applies to "any
7
+ // implementing agent, whether or not the session started from `/event-modeling`" — but until
8
+ // this command, its only distribution channel was `em skill install` copying it into
9
+ // `.claude/skills/event-modeling/`, a path only Claude Code discovers. An agent that can run a
10
+ // shell but has no `.claude/` awareness had no route to the contract at all (Slicewright story
11
+ // gap G5). `em contract` needs no vendored skill copy, no repo scaffolding, nothing but the em
12
+ // binary itself — the file always comes from the installed package, not from any project's
13
+ // working tree.
14
+ import { readFileSync } from "node:fs";
15
+ import { join } from "node:path";
16
+ /** Path to the packaged contract inside `packagedSkillDir` — the same directory `em skill
17
+ * install`/`sync`/`check` read from (src/cli.ts's packagedSkillDir()). */
18
+ export function contractPath(packagedSkillDir) {
19
+ return join(packagedSkillDir, "reference", "implement.md");
20
+ }
21
+ /** Pure: reads the packaged contract's exact bytes. No process.exit/console — src/cli.ts's
22
+ * action wraps fs errors (a corrupt/incomplete package install) the same way every other
23
+ * command's file reads do. */
24
+ export function readContract(packagedSkillDir) {
25
+ return readFileSync(contractPath(packagedSkillDir), "utf8");
26
+ }
27
+ //# sourceMappingURL=contract.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"contract.js","sourceRoot":"","sources":["../../src/cli/contract.ts"],"names":[],"mappings":"AAAA,+BAA+B;AAC/B,gGAAgG;AAChG,uFAAuF;AACvF,wDAAwD;AACxD,EAAE;AACF,yFAAyF;AACzF,6FAA6F;AAC7F,qFAAqF;AACrF,+FAA+F;AAC/F,+FAA+F;AAC/F,+FAA+F;AAC/F,2FAA2F;AAC3F,gBAAgB;AAEhB,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACvC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAEjC;2EAC2E;AAC3E,MAAM,UAAU,YAAY,CAAC,gBAAwB;IACnD,OAAO,IAAI,CAAC,gBAAgB,EAAE,WAAW,EAAE,cAAc,CAAC,CAAC;AAC7D,CAAC;AAED;;+BAE+B;AAC/B,MAAM,UAAU,YAAY,CAAC,gBAAwB;IACnD,OAAO,YAAY,CAAC,YAAY,CAAC,gBAAgB,CAAC,EAAE,MAAM,CAAC,CAAC;AAC9D,CAAC"}
@@ -0,0 +1,69 @@
1
+ import { NormalizedModel } from "../model/model.js";
2
+ import { RefsResult } from "../model/refs.js";
3
+ /** The stable INV token format, scanned across a slice doc's whole body (not just the
4
+ * `## Invariants / Business Rules` section — a `## Delta` section's Added/Modified/Renamed
5
+ * requirement IDs are just as citable, see templates/slice.md). `INV-` then one or more
6
+ * alphanumeric/hyphen segments — matches both `INV-1`/`INV-2` (the template's bare numbering)
7
+ * and `INV-CHK-4`/`INV-CHK-3a` (the per-slice mnemonic style reference/conform.md's own example
8
+ * uses). Exported so tests can assert against the exact grammar. */
9
+ export declare const INV_TOKEN_RE: RegExp;
10
+ /**
11
+ * Extract every distinct `INV-*` token from a slice doc's body text, in first-occurrence order
12
+ * (a rule stated once in `## Invariants` and mentioned again in a later `## Delta` section dedupes
13
+ * to its first appearance — order is for stable, readable output, not a claim about which mention
14
+ * is canonical).
15
+ */
16
+ export declare function extractInvariantIds(body: string): string[];
17
+ export interface Citation {
18
+ /** Path relative to the `--tests <dir>` root, POSIX forward slashes — same convention
19
+ * walkDir() already returns. */
20
+ file: string;
21
+ /** 1-based line number within `file`. */
22
+ line: number;
23
+ }
24
+ /**
25
+ * Scan every text file under `testsDir` for lines citing any of `ids`, word-boundary-anchored so
26
+ * `INV-KEY-1` doesn't match inside `INV-KEY-12` (both boundary sides land on a hyphen/alnum
27
+ * transition since the ID alphabet is exactly `[A-Za-z0-9-]`, so no escaping is needed to build
28
+ * the per-ID regex). Returns every citing `{file, line}`, in walk order — a line citing more than
29
+ * one ID is recorded under each. `ids` with zero test-dir mentions still get a `[]` entry, never
30
+ * an absent map key, so a caller never has to guard a lookup with `?? []`.
31
+ */
32
+ export declare function scanTestCitations(testsDir: string, ids: readonly string[]): Map<string, Citation[]>;
33
+ export interface InvariantCoverage {
34
+ id: string;
35
+ cited: boolean;
36
+ citations: Citation[];
37
+ }
38
+ export interface SliceCoverage {
39
+ key: string;
40
+ /** The joined doc's `status`, or null when no usable doc was found — carried even for an
41
+ * out-of-scope slice, for transparency (a reader can see *why* it was skipped). */
42
+ status: string | null;
43
+ /** The resolveSliceDocJoin() `DocReason` behind `status: null` — `"no-doc-bound"`,
44
+ * `"binding-missing-file"`, or `"frontmatter-invalid"` — or null when the doc joined cleanly
45
+ * (whether or not the slice ended up in scope; a draft slice with a perfectly good doc still
46
+ * has `docReason: null`). Lets an out-of-scope reader tell "nothing bound yet" apart from
47
+ * "bound but broken" without re-running the join themselves. */
48
+ docReason: string | null;
49
+ /** True when this slice's doc was found, its frontmatter usable, and `status` is
50
+ * `ready-to-implement` or `implemented` — the only slices `invariants` is populated for. */
51
+ inScope: boolean;
52
+ invariants: InvariantCoverage[];
53
+ }
54
+ export interface CoverageReport {
55
+ slices: SliceCoverage[];
56
+ /** Total invariant IDs across every in-scope slice (sum of `slices[*].invariants.length`). */
57
+ totalInvariants: number;
58
+ /** Of `totalInvariants`, how many have zero citations. */
59
+ uncoveredCount: number;
60
+ }
61
+ /**
62
+ * Assemble the full coverage report: for every slice in the model, resolve its doc via the same
63
+ * note-binding join `em export`/`--slice-ready` use (resolveSliceDocJoin), decide in-scope status,
64
+ * extract invariant IDs for in-scope slices, then scan `testsDir` once for every ID across the
65
+ * whole model (cheaper than one scan per slice, and citations are looked up per-slice from the
66
+ * single resulting map). `baseDir` is the `.em` file's directory, same convention every doc/note
67
+ * path in `em` uses.
68
+ */
69
+ export declare function buildCoverageReport(model: NormalizedModel, refs: RefsResult, baseDir: string, testsDir: string): CoverageReport;
@@ -0,0 +1,157 @@
1
+ // SPDX-License-Identifier: MIT
2
+ // `em coverage` (MIL-130): a mechanical check for reference/implement.md's definition-of-done
3
+ // line "every INV-n has a test that cites its ID" — until now a pure honor system, since no `em`
4
+ // command related a slice doc's invariant IDs to a test suite. Both sides are stable strings
5
+ // (the doc's `INV-*` tokens, a test file's source text), so the citation check is deterministic
6
+ // and grep-shaped: extract every invariant ID a slice doc's body mentions, then scan a test tree
7
+ // for lines that cite it.
8
+ //
9
+ // Deliberately a *second*, independent reader of doc bodies, alongside `em export`'s doc join
10
+ // (docJoin.ts, MIL-91) — not a change to it. `em export`'s frontmatter-only contract is
11
+ // untouched: this module never adds a frontmatter field, and it reads `SliceDoc.body` (already
12
+ // exposed for `em ledger`'s content comparison, catalog/sliceDoc.ts) rather than reshaping the
13
+ // export document. Checks that an ID is *cited*, nothing about whether the citing test is good
14
+ // or passing — that stays with review and CI respectively; no judgment mechanized here.
15
+ //
16
+ // Token format: `INV-<KEY>-n` where `<KEY>` is present in nothing textually — the actual convention
17
+ // found in the codebase (templates/slice.md's own Invariants section, reference/conform.md's
18
+ // worked example `INV-CHK-4`) uses either a bare running number (`INV-1`, `INV-2`) or a per-slice
19
+ // mnemonic prefix (`INV-CHK-3`, `INV-CHK-3a` for a rename) — IDs are hand-authored per
20
+ // docs/slice-doc-schema.md, "give each a stable ID", not machine-generated, so no single fixed
21
+ // shape is enforced by any `em` command. INV_TOKEN_RE below matches both: `INV-` followed by a
22
+ // greedy run of alphanumeric/hyphen segments. Greedy is what keeps `INV-KEY-1` from partially
23
+ // matching inside `INV-KEY-12` during *extraction* (`\d+` inside the run always consumes the full
24
+ // digit sequence); citation *scanning* below re-anchors with `\b` on the exact ID string for the
25
+ // same reason, since that direction is a substring search rather than a token match.
26
+ import { readFileSync } from "node:fs";
27
+ import { join } from "node:path";
28
+ import { resolveSliceDocJoin } from "../catalog/docJoin.js";
29
+ import { readSliceDoc } from "../catalog/readSliceDoc.js";
30
+ import { walkDir } from "../util/walkDir.js";
31
+ /** The stable INV token format, scanned across a slice doc's whole body (not just the
32
+ * `## Invariants / Business Rules` section — a `## Delta` section's Added/Modified/Renamed
33
+ * requirement IDs are just as citable, see templates/slice.md). `INV-` then one or more
34
+ * alphanumeric/hyphen segments — matches both `INV-1`/`INV-2` (the template's bare numbering)
35
+ * and `INV-CHK-4`/`INV-CHK-3a` (the per-slice mnemonic style reference/conform.md's own example
36
+ * uses). Exported so tests can assert against the exact grammar. */
37
+ export const INV_TOKEN_RE = /\bINV-[A-Za-z0-9]+(?:-[A-Za-z0-9]+)*\b/g;
38
+ /** Statuses `reference/implement.md`'s coverage gate applies to — a slice hasn't reached the
39
+ * point of needing test citations before `ready-to-implement`, and stays in scope forever after
40
+ * once `implemented` (tests shouldn't regress away from citing an invariant just because the
41
+ * slice shipped). Anything else (`draft`, `reviewed`, or no doc at all) is out of scope. */
42
+ const IN_SCOPE_STATUSES = new Set(["ready-to-implement", "implemented"]);
43
+ /**
44
+ * Extract every distinct `INV-*` token from a slice doc's body text, in first-occurrence order
45
+ * (a rule stated once in `## Invariants` and mentioned again in a later `## Delta` section dedupes
46
+ * to its first appearance — order is for stable, readable output, not a claim about which mention
47
+ * is canonical).
48
+ */
49
+ export function extractInvariantIds(body) {
50
+ const seen = new Set();
51
+ const ids = [];
52
+ for (const m of body.matchAll(INV_TOKEN_RE)) {
53
+ if (!seen.has(m[0])) {
54
+ seen.add(m[0]);
55
+ ids.push(m[0]);
56
+ }
57
+ }
58
+ return ids;
59
+ }
60
+ // Directory segments never worth descending into for citations: dependency trees
61
+ // (node_modules) and VCS metadata (.git) can be enormous and never contain a test file worth
62
+ // scanning. Passed to walkDir() as a skipDir predicate so these subtrees are pruned during
63
+ // descent rather than walked in full and filtered out afterward — the skill walkers
64
+ // (skillSync.ts/skillCheck.ts) never encounter this problem in the first place, since the
65
+ // bundled skill directory they walk never contains a node_modules or .git of its own.
66
+ const SKIP_DIR_SEGMENTS = new Set(["node_modules", ".git"]);
67
+ /** Cheap binary sniff: a NUL byte anywhere in the first few KB is not valid UTF-8/ASCII text —
68
+ * every text source file this check cares about (and every test fixture worth citing an
69
+ * invariant from) will fail this in exactly zero legitimate cases. */
70
+ function isLikelyBinary(buf) {
71
+ const len = Math.min(buf.length, 8000);
72
+ for (let i = 0; i < len; i++) {
73
+ if (buf[i] === 0)
74
+ return true;
75
+ }
76
+ return false;
77
+ }
78
+ /**
79
+ * Scan every text file under `testsDir` for lines citing any of `ids`, word-boundary-anchored so
80
+ * `INV-KEY-1` doesn't match inside `INV-KEY-12` (both boundary sides land on a hyphen/alnum
81
+ * transition since the ID alphabet is exactly `[A-Za-z0-9-]`, so no escaping is needed to build
82
+ * the per-ID regex). Returns every citing `{file, line}`, in walk order — a line citing more than
83
+ * one ID is recorded under each. `ids` with zero test-dir mentions still get a `[]` entry, never
84
+ * an absent map key, so a caller never has to guard a lookup with `?? []`.
85
+ */
86
+ export function scanTestCitations(testsDir, ids) {
87
+ const citations = new Map(ids.map((id) => [id, []]));
88
+ if (ids.length === 0)
89
+ return citations;
90
+ const idPatterns = ids.map((id) => [id, new RegExp(`\\b${id}\\b`)]);
91
+ for (const rel of walkDir(testsDir, { skipDir: (name) => SKIP_DIR_SEGMENTS.has(name) })) {
92
+ let buf;
93
+ try {
94
+ buf = readFileSync(join(testsDir, rel));
95
+ }
96
+ catch {
97
+ continue; // unreadable (permissions, broken symlink target, ...) — not this check's job
98
+ }
99
+ if (isLikelyBinary(buf))
100
+ continue;
101
+ const lines = buf.toString("utf8").split(/\r?\n/);
102
+ for (let i = 0; i < lines.length; i++) {
103
+ const line = lines[i];
104
+ for (const [id, re] of idPatterns) {
105
+ if (re.test(line))
106
+ citations.get(id).push({ file: rel, line: i + 1 });
107
+ }
108
+ }
109
+ }
110
+ return citations;
111
+ }
112
+ /**
113
+ * Assemble the full coverage report: for every slice in the model, resolve its doc via the same
114
+ * note-binding join `em export`/`--slice-ready` use (resolveSliceDocJoin), decide in-scope status,
115
+ * extract invariant IDs for in-scope slices, then scan `testsDir` once for every ID across the
116
+ * whole model (cheaper than one scan per slice, and citations are looked up per-slice from the
117
+ * single resulting map). `baseDir` is the `.em` file's directory, same convention every doc/note
118
+ * path in `em` uses.
119
+ */
120
+ export function buildCoverageReport(model, refs, baseDir, testsDir) {
121
+ const scoped = [];
122
+ const allIds = new Set();
123
+ for (let i = 0; i < model.slices.length; i++) {
124
+ const slice = model.slices[i];
125
+ const key = refs.sliceKeys[i];
126
+ const { doc } = resolveSliceDocJoin(slice, key, baseDir, (id) => refs.refById.get(id));
127
+ const inScope = doc.reason === null && doc.status !== null && IN_SCOPE_STATUSES.has(doc.status);
128
+ let ids = [];
129
+ if (inScope) {
130
+ // Re-derive the bound doc's key from doc.path rather than assuming it's this slice's own
131
+ // — MIL-121's ratified cross-binding can resolve `doc` to a DIFFERENT slice's doc, same
132
+ // re-derivation sliceReadyValidate.ts uses for Open Questions.
133
+ const boundKey = doc.path.replace(/^slices\//, "").replace(/\.md$/, "");
134
+ const parsed = readSliceDoc(baseDir, boundKey);
135
+ if (parsed)
136
+ ids = extractInvariantIds(parsed.body);
137
+ }
138
+ scoped.push({ key, status: doc.status, docReason: doc.reason, inScope, ids });
139
+ for (const id of ids)
140
+ allIds.add(id);
141
+ }
142
+ const citations = scanTestCitations(testsDir, [...allIds]);
143
+ let totalInvariants = 0;
144
+ let uncoveredCount = 0;
145
+ const slices = scoped.map(({ key, status, docReason, inScope, ids }) => {
146
+ const invariants = ids.map((id) => {
147
+ const cs = citations.get(id) ?? [];
148
+ totalInvariants++;
149
+ if (cs.length === 0)
150
+ uncoveredCount++;
151
+ return { id, cited: cs.length > 0, citations: cs };
152
+ });
153
+ return { key, status, docReason, inScope, invariants };
154
+ });
155
+ return { slices, totalInvariants, uncoveredCount };
156
+ }
157
+ //# sourceMappingURL=coverage.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"coverage.js","sourceRoot":"","sources":["../../src/cli/coverage.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,34 @@
1
+ import { NormalizedModel } from "../model/model.js";
2
+ import { RefsResult } from "../model/refs.js";
3
+ export type ApplyFrontmatterResult = {
4
+ ok: true;
5
+ content: string;
6
+ changed: boolean;
7
+ } | {
8
+ ok: false;
9
+ message: string;
10
+ };
11
+ /**
12
+ * Pure text transform: flips `status:` to `implemented` and sets `implementedIn:` to `prUrl` in
13
+ * `raw`'s frontmatter block. Idempotent (re-applying the same URL is a no-op, `changed: false`,
14
+ * `content` returned byte-identical to `raw`); refuses (`ok: false`) rather than overwrite
15
+ * provenance when the doc is already `status: implemented` with a *different* `implementedIn`.
16
+ * No fs access — the caller reads/writes; see `runMarkImplemented` below.
17
+ */
18
+ export declare function applyImplementedFrontmatter(raw: string, prUrl: string): ApplyFrontmatterResult;
19
+ export type RunMarkImplementedResult = {
20
+ ok: true;
21
+ path: string;
22
+ changed: boolean;
23
+ } | {
24
+ ok: false;
25
+ message: string;
26
+ };
27
+ /**
28
+ * Resolves `sliceKey` to its bound doc via the same note-binding join `--slice-ready`/`em
29
+ * export` use (MIL-121 cross-binding included — the doc actually edited is whichever one the
30
+ * join resolved to, exactly like `sliceReadyValidate.ts` re-deriving its own read from
31
+ * `doc.path`), then reads/applies/writes it. `baseDir` is the `.em` file's directory, same
32
+ * convention every doc/note path in `em` uses.
33
+ */
34
+ export declare function runMarkImplemented(model: NormalizedModel, refs: RefsResult, baseDir: string, sliceKey: string, prUrl: string): RunMarkImplementedResult;