@milehimikey/em 1.7.2 → 1.8.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (119) 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 +132 -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 +71 -0
  36. package/dist/cli/coverage.js +177 -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/diffJson.d.ts +9 -1
  59. package/dist/emit/diffJson.js +44 -1
  60. package/dist/emit/diffJson.js.map +1 -1
  61. package/dist/emit/dot.js +41 -10
  62. package/dist/emit/dot.js.map +1 -1
  63. package/dist/emit/json.d.ts +77 -4
  64. package/dist/emit/json.js +78 -4
  65. package/dist/emit/json.js.map +1 -1
  66. package/dist/emit/validateJson.d.ts +52 -0
  67. package/dist/emit/validateJson.js +120 -0
  68. package/dist/emit/validateJson.js.map +1 -0
  69. package/dist/mcp/main.d.ts +2 -0
  70. package/dist/mcp/main.js +20 -0
  71. package/dist/mcp/main.js.map +1 -0
  72. package/dist/mcp/server.d.ts +11 -0
  73. package/dist/mcp/server.js +260 -0
  74. package/dist/mcp/server.js.map +1 -0
  75. package/dist/model/model.d.ts +30 -1
  76. package/dist/model/model.js +26 -0
  77. package/dist/model/model.js.map +1 -1
  78. package/dist/model/rules.d.ts +114 -0
  79. package/dist/model/rules.js +116 -4
  80. package/dist/model/rules.js.map +1 -1
  81. package/dist/model/validate.js +92 -8
  82. package/dist/model/validate.js.map +1 -1
  83. package/dist/parser/ast.d.ts +47 -0
  84. package/dist/parser/lexer.d.ts +16 -0
  85. package/dist/parser/lexer.js +44 -2
  86. package/dist/parser/lexer.js.map +1 -1
  87. package/dist/parser/parser.js +354 -20
  88. package/dist/parser/parser.js.map +1 -1
  89. package/dist/render/drawNotes.d.ts +11 -1
  90. package/dist/render/drawNotes.js +28 -3
  91. package/dist/render/drawNotes.js.map +1 -1
  92. package/dist/render/render.js +116 -23
  93. package/dist/render/render.js.map +1 -1
  94. package/dist/render/serve.d.ts +4 -0
  95. package/dist/render/serve.js +27 -239
  96. package/dist/render/serve.js.map +1 -1
  97. package/dist/render/sliceOverlay.d.ts +7 -2
  98. package/dist/render/sliceOverlay.js +19 -7
  99. package/dist/render/sliceOverlay.js.map +1 -1
  100. package/dist/render/sliceStatus.d.ts +7 -3
  101. package/dist/render/sliceStatus.js +48 -12
  102. package/dist/render/sliceStatus.js.map +1 -1
  103. package/dist/render/viewerHtml.d.ts +1 -0
  104. package/dist/render/viewerHtml.js +5 -0
  105. package/dist/render/viewerHtml.js.map +1 -0
  106. package/dist/render/watch.d.ts +10 -0
  107. package/dist/render/watch.js +29 -0
  108. package/dist/render/watch.js.map +1 -1
  109. package/dist/templates.d.ts +20 -1
  110. package/dist/templates.js +172 -3
  111. package/dist/templates.js.map +1 -1
  112. package/dist/util/markers.d.ts +13 -0
  113. package/dist/util/markers.js +30 -0
  114. package/dist/util/markers.js.map +1 -0
  115. package/dist/util/walkDir.d.ts +10 -1
  116. package/dist/util/walkDir.js +11 -7
  117. package/dist/util/walkDir.js.map +1 -1
  118. package/package.json +12 -3
  119. 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,71 @@
1
+ import { NormalizedModel } from "../model/model.js";
2
+ import { RefsResult } from "../model/refs.js";
3
+ /** The stable INV token format. `INV-` then one or more alphanumeric/hyphen segments — matches
4
+ * both `INV-1`/`INV-2` (the template's bare numbering) and `INV-CHK-4`/`INV-CHK-3a` (the
5
+ * per-slice mnemonic style reference/conform.md's own example uses). Exported so tests can
6
+ * assert against the exact grammar. */
7
+ export declare const INV_TOKEN_RE: RegExp;
8
+ /**
9
+ * Extract every distinct `INV-*` token this slice doc *defines*, in first-occurrence order (a
10
+ * rule stated once in `## Invariants` and re-declared in a later `## Delta` section dedupes to
11
+ * its first appearance — order is for stable, readable output, not a claim about which mention is
12
+ * canonical). Scoped to `OWNERSHIP_HEADING_RE`'s sections only, so a doc's prose narratively
13
+ * citing another slice's ID elsewhere (Dependencies, Open Questions, ...) doesn't cross-credit
14
+ * that ID as this slice's own (MIL-149) — the section ends at the next `#`/`##` heading or EOF,
15
+ * same boundary rule sliceDoc.ts's countOpenQuestions() uses, so a `### Added`/`#### Requirement`
16
+ * subheading under `## Delta` stays in scope while a sibling `## Scenarios` does not.
17
+ */
18
+ export declare function extractInvariantIds(body: string): string[];
19
+ export interface Citation {
20
+ /** Path relative to the `--tests <dir>` root, POSIX forward slashes — same convention
21
+ * walkDir() already returns. */
22
+ file: string;
23
+ /** 1-based line number within `file`. */
24
+ line: number;
25
+ }
26
+ /**
27
+ * Scan every text file under `testsDir` for lines citing any of `ids`, word-boundary-anchored so
28
+ * `INV-KEY-1` doesn't match inside `INV-KEY-12` (both boundary sides land on a hyphen/alnum
29
+ * transition since the ID alphabet is exactly `[A-Za-z0-9-]`, so no escaping is needed to build
30
+ * the per-ID regex). Returns every citing `{file, line}`, in walk order — a line citing more than
31
+ * one ID is recorded under each. `ids` with zero test-dir mentions still get a `[]` entry, never
32
+ * an absent map key, so a caller never has to guard a lookup with `?? []`.
33
+ */
34
+ export declare function scanTestCitations(testsDir: string, ids: readonly string[]): Map<string, Citation[]>;
35
+ export interface InvariantCoverage {
36
+ id: string;
37
+ cited: boolean;
38
+ citations: Citation[];
39
+ }
40
+ export interface SliceCoverage {
41
+ key: string;
42
+ /** The joined doc's `status`, or null when no usable doc was found — carried even for an
43
+ * out-of-scope slice, for transparency (a reader can see *why* it was skipped). */
44
+ status: string | null;
45
+ /** The resolveSliceDocJoin() `DocReason` behind `status: null` — `"no-doc-bound"`,
46
+ * `"binding-missing-file"`, or `"frontmatter-invalid"` — or null when the doc joined cleanly
47
+ * (whether or not the slice ended up in scope; a draft slice with a perfectly good doc still
48
+ * has `docReason: null`). Lets an out-of-scope reader tell "nothing bound yet" apart from
49
+ * "bound but broken" without re-running the join themselves. */
50
+ docReason: string | null;
51
+ /** True when this slice's doc was found, its frontmatter usable, and `status` is
52
+ * `ready-to-implement` or `implemented` — the only slices `invariants` is populated for. */
53
+ inScope: boolean;
54
+ invariants: InvariantCoverage[];
55
+ }
56
+ export interface CoverageReport {
57
+ slices: SliceCoverage[];
58
+ /** Total invariant IDs across every in-scope slice (sum of `slices[*].invariants.length`). */
59
+ totalInvariants: number;
60
+ /** Of `totalInvariants`, how many have zero citations. */
61
+ uncoveredCount: number;
62
+ }
63
+ /**
64
+ * Assemble the full coverage report: for every slice in the model, resolve its doc via the same
65
+ * note-binding join `em export`/`--slice-ready` use (resolveSliceDocJoin), decide in-scope status,
66
+ * extract invariant IDs for in-scope slices, then scan `testsDir` once for every ID across the
67
+ * whole model (cheaper than one scan per slice, and citations are looked up per-slice from the
68
+ * single resulting map). `baseDir` is the `.em` file's directory, same convention every doc/note
69
+ * path in `em` uses.
70
+ */
71
+ export declare function buildCoverageReport(model: NormalizedModel, refs: RefsResult, baseDir: string, testsDir: string): CoverageReport;
@@ -0,0 +1,177 @@
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 own Invariants/Delta sections define
7
+ // (MIL-149: not every ID its prose merely mentions — a doc narratively citing another slice's ID
8
+ // doesn't own it), then scan a test tree for lines that cite it.
9
+ //
10
+ // Deliberately a *second*, independent reader of doc bodies, alongside `em export`'s doc join
11
+ // (docJoin.ts, MIL-91) — not a change to it. `em export`'s frontmatter-only contract is
12
+ // untouched: this module never adds a frontmatter field, and it reads `SliceDoc.body` (already
13
+ // exposed for `em ledger`'s content comparison, catalog/sliceDoc.ts) rather than reshaping the
14
+ // export document. Checks that an ID is *cited*, nothing about whether the citing test is good
15
+ // or passing — that stays with review and CI respectively; no judgment mechanized here.
16
+ //
17
+ // Token format: `INV-<KEY>-n` where `<KEY>` is present in nothing textually — the actual convention
18
+ // found in the codebase (templates/slice.md's own Invariants section, reference/conform.md's
19
+ // worked example `INV-CHK-4`) uses either a bare running number (`INV-1`, `INV-2`) or a per-slice
20
+ // mnemonic prefix (`INV-CHK-3`, `INV-CHK-3a` for a rename) — IDs are hand-authored per
21
+ // docs/slice-doc-schema.md, "give each a stable ID", not machine-generated, so no single fixed
22
+ // shape is enforced by any `em` command. INV_TOKEN_RE below matches both: `INV-` followed by a
23
+ // greedy run of alphanumeric/hyphen segments. Greedy is what keeps `INV-KEY-1` from partially
24
+ // matching inside `INV-KEY-12` during *extraction* (`\d+` inside the run always consumes the full
25
+ // digit sequence); citation *scanning* below re-anchors with `\b` on the exact ID string for the
26
+ // same reason, since that direction is a substring search rather than a token match.
27
+ import { readFileSync } from "node:fs";
28
+ import { join } from "node:path";
29
+ import { resolveSliceDocJoin } from "../catalog/docJoin.js";
30
+ import { readSliceDoc } from "../catalog/readSliceDoc.js";
31
+ import { walkDir } from "../util/walkDir.js";
32
+ /** The stable INV token format. `INV-` then one or more alphanumeric/hyphen segments — matches
33
+ * both `INV-1`/`INV-2` (the template's bare numbering) and `INV-CHK-4`/`INV-CHK-3a` (the
34
+ * per-slice mnemonic style reference/conform.md's own example uses). Exported so tests can
35
+ * assert against the exact grammar. */
36
+ export const INV_TOKEN_RE = /\bINV-[A-Za-z0-9]+(?:-[A-Za-z0-9]+)*\b/g;
37
+ /** Headings whose content *defines* (or, for `## Delta`, legitimately re-declares) an invariant
38
+ * ID for the slice doc it appears in — `## Invariants / Business Rules` itself, and `## Delta`'s
39
+ * Added/Modified/Renamed requirement subsections, which introduce/rename IDs "from Invariants
40
+ * below" on re-ratification (see templates/slice.md). Every other section — Intent, Scenarios,
41
+ * Trigger & Actor, Dependencies & Read Models Affected, Open Questions, ... — is prose that may
42
+ * narratively *cite* another slice's ID without this doc owning it (MIL-149: such a mention was
43
+ * being cross-credited to the citing slice's own coverage ledger). */
44
+ const OWNERSHIP_HEADING_RE = /^##\s+(?:Invariants(?:\s*\/\s*Business Rules)?|Delta)\s*$/i;
45
+ /** Statuses `reference/implement.md`'s coverage gate applies to — a slice hasn't reached the
46
+ * point of needing test citations before `ready-to-implement`, and stays in scope forever after
47
+ * once `implemented` (tests shouldn't regress away from citing an invariant just because the
48
+ * slice shipped). Anything else (`draft`, `reviewed`, or no doc at all) is out of scope. */
49
+ const IN_SCOPE_STATUSES = new Set(["ready-to-implement", "implemented"]);
50
+ /**
51
+ * Extract every distinct `INV-*` token this slice doc *defines*, in first-occurrence order (a
52
+ * rule stated once in `## Invariants` and re-declared in a later `## Delta` section dedupes to
53
+ * its first appearance — order is for stable, readable output, not a claim about which mention is
54
+ * canonical). Scoped to `OWNERSHIP_HEADING_RE`'s sections only, so a doc's prose narratively
55
+ * citing another slice's ID elsewhere (Dependencies, Open Questions, ...) doesn't cross-credit
56
+ * that ID as this slice's own (MIL-149) — the section ends at the next `#`/`##` heading or EOF,
57
+ * same boundary rule sliceDoc.ts's countOpenQuestions() uses, so a `### Added`/`#### Requirement`
58
+ * subheading under `## Delta` stays in scope while a sibling `## Scenarios` does not.
59
+ */
60
+ export function extractInvariantIds(body) {
61
+ const seen = new Set();
62
+ const ids = [];
63
+ let inOwnershipSection = false;
64
+ for (const line of body.split(/\r?\n/)) {
65
+ if (/^#{1,2}\s/.test(line)) {
66
+ inOwnershipSection = OWNERSHIP_HEADING_RE.test(line);
67
+ continue;
68
+ }
69
+ if (!inOwnershipSection)
70
+ continue;
71
+ for (const m of line.matchAll(INV_TOKEN_RE)) {
72
+ if (!seen.has(m[0])) {
73
+ seen.add(m[0]);
74
+ ids.push(m[0]);
75
+ }
76
+ }
77
+ }
78
+ return ids;
79
+ }
80
+ // Directory segments never worth descending into for citations: dependency trees
81
+ // (node_modules) and VCS metadata (.git) can be enormous and never contain a test file worth
82
+ // scanning. Passed to walkDir() as a skipDir predicate so these subtrees are pruned during
83
+ // descent rather than walked in full and filtered out afterward — the skill walkers
84
+ // (skillSync.ts/skillCheck.ts) never encounter this problem in the first place, since the
85
+ // bundled skill directory they walk never contains a node_modules or .git of its own.
86
+ const SKIP_DIR_SEGMENTS = new Set(["node_modules", ".git"]);
87
+ /** Cheap binary sniff: a NUL byte anywhere in the first few KB is not valid UTF-8/ASCII text —
88
+ * every text source file this check cares about (and every test fixture worth citing an
89
+ * invariant from) will fail this in exactly zero legitimate cases. */
90
+ function isLikelyBinary(buf) {
91
+ const len = Math.min(buf.length, 8000);
92
+ for (let i = 0; i < len; i++) {
93
+ if (buf[i] === 0)
94
+ return true;
95
+ }
96
+ return false;
97
+ }
98
+ /**
99
+ * Scan every text file under `testsDir` for lines citing any of `ids`, word-boundary-anchored so
100
+ * `INV-KEY-1` doesn't match inside `INV-KEY-12` (both boundary sides land on a hyphen/alnum
101
+ * transition since the ID alphabet is exactly `[A-Za-z0-9-]`, so no escaping is needed to build
102
+ * the per-ID regex). Returns every citing `{file, line}`, in walk order — a line citing more than
103
+ * one ID is recorded under each. `ids` with zero test-dir mentions still get a `[]` entry, never
104
+ * an absent map key, so a caller never has to guard a lookup with `?? []`.
105
+ */
106
+ export function scanTestCitations(testsDir, ids) {
107
+ const citations = new Map(ids.map((id) => [id, []]));
108
+ if (ids.length === 0)
109
+ return citations;
110
+ const idPatterns = ids.map((id) => [id, new RegExp(`\\b${id}\\b`)]);
111
+ for (const rel of walkDir(testsDir, { skipDir: (name) => SKIP_DIR_SEGMENTS.has(name) })) {
112
+ let buf;
113
+ try {
114
+ buf = readFileSync(join(testsDir, rel));
115
+ }
116
+ catch {
117
+ continue; // unreadable (permissions, broken symlink target, ...) — not this check's job
118
+ }
119
+ if (isLikelyBinary(buf))
120
+ continue;
121
+ const lines = buf.toString("utf8").split(/\r?\n/);
122
+ for (let i = 0; i < lines.length; i++) {
123
+ const line = lines[i];
124
+ for (const [id, re] of idPatterns) {
125
+ if (re.test(line))
126
+ citations.get(id).push({ file: rel, line: i + 1 });
127
+ }
128
+ }
129
+ }
130
+ return citations;
131
+ }
132
+ /**
133
+ * Assemble the full coverage report: for every slice in the model, resolve its doc via the same
134
+ * note-binding join `em export`/`--slice-ready` use (resolveSliceDocJoin), decide in-scope status,
135
+ * extract invariant IDs for in-scope slices, then scan `testsDir` once for every ID across the
136
+ * whole model (cheaper than one scan per slice, and citations are looked up per-slice from the
137
+ * single resulting map). `baseDir` is the `.em` file's directory, same convention every doc/note
138
+ * path in `em` uses.
139
+ */
140
+ export function buildCoverageReport(model, refs, baseDir, testsDir) {
141
+ const scoped = [];
142
+ const allIds = new Set();
143
+ for (let i = 0; i < model.slices.length; i++) {
144
+ const slice = model.slices[i];
145
+ const key = refs.sliceKeys[i];
146
+ const { doc } = resolveSliceDocJoin(slice, key, baseDir, (id) => refs.refById.get(id));
147
+ const inScope = doc.reason === null && doc.status !== null && IN_SCOPE_STATUSES.has(doc.status);
148
+ let ids = [];
149
+ if (inScope) {
150
+ // Re-derive the bound doc's key from doc.path rather than assuming it's this slice's own
151
+ // — MIL-121's ratified cross-binding can resolve `doc` to a DIFFERENT slice's doc, same
152
+ // re-derivation sliceReadyValidate.ts uses for Open Questions.
153
+ const boundKey = doc.path.replace(/^slices\//, "").replace(/\.md$/, "");
154
+ const parsed = readSliceDoc(baseDir, boundKey);
155
+ if (parsed)
156
+ ids = extractInvariantIds(parsed.body);
157
+ }
158
+ scoped.push({ key, status: doc.status, docReason: doc.reason, inScope, ids });
159
+ for (const id of ids)
160
+ allIds.add(id);
161
+ }
162
+ const citations = scanTestCitations(testsDir, [...allIds]);
163
+ let totalInvariants = 0;
164
+ let uncoveredCount = 0;
165
+ const slices = scoped.map(({ key, status, docReason, inScope, ids }) => {
166
+ const invariants = ids.map((id) => {
167
+ const cs = citations.get(id) ?? [];
168
+ totalInvariants++;
169
+ if (cs.length === 0)
170
+ uncoveredCount++;
171
+ return { id, cited: cs.length > 0, citations: cs };
172
+ });
173
+ return { key, status, docReason, inScope, invariants };
174
+ });
175
+ return { slices, totalInvariants, uncoveredCount };
176
+ }
177
+ //# 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;