@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,180 @@
1
+ // SPDX-License-Identifier: MIT
2
+ // `em slice mark-implemented` (MIL-103): promotes the lifecycle flip that used to live only in
3
+ // em-sdd-bridge's `em-sdd-mark-implemented <slice-key> <pr-url>` (see reference/implement.md §6)
4
+ // into `em` itself, the same way MIL-87 promoted `--slice-ready`. Sets exactly two frontmatter
5
+ // fields — `status: implemented` and `implementedIn: <pr-url>` — on the slice doc resolved from
6
+ // the key via the SAME note-binding resolution `--slice-ready`/`em export` use
7
+ // (catalog/docJoin.ts's resolveSliceDocJoin). Never touches `version:` (a bump here is an `em
8
+ // ledger` defect per docs/slice-doc-schema.md) and never touches the markdown body.
9
+ //
10
+ // Write strategy: surgical index-math splicing on the raw file text, not a parse+re-serialize
11
+ // (sliceDoc.ts's own parser is deliberately read-only and drops everything not in its `SliceDoc`
12
+ // shape — round-tripping through it would silently destroy comments, key order, spacing, and
13
+ // every field this command isn't allowed to touch). `locateFrontmatterInner` finds the fenced
14
+ // block's byte range using plain `indexOf` line-walking rather than `split(/\r?\n/)+join("\n")`
15
+ // (what sliceDoc.ts's own splitFrontmatter does for parsing) specifically because a split/join
16
+ // round-trip would silently normalize CRLF line endings across the WHOLE file the moment we
17
+ // write it back — fine for a read-only parse, not for a rewrite that must preserve the body
18
+ // byte-for-byte. Everything outside the two edited value spans — including the body, `version:`,
19
+ // the other frontmatter keys, and (best-effort) the file's own line-ending style — is copied
20
+ // through verbatim via string slicing, never reconstructed from parsed parts.
21
+ //
22
+ // Ported from the bridge's `src/lib/mark-implemented-doc.ts`, which targets the legacy
23
+ // `- **Status:** ...` / `- **Implemented in:** ...` bullet dialect; this module targets the
24
+ // canonical frontmatter dialect (MIL-86) instead, and folds in the note-binding resolution the
25
+ // bridge's caller (`mark-implemented.ts`) did via a separate `em export` shell-out.
26
+ import { readFileSync, writeFileSync } from "node:fs";
27
+ import { join } from "node:path";
28
+ import { resolveSliceDocJoin } from "../catalog/docJoin.js";
29
+ /** A field line's matched span within a frontmatter `inner` string: `key[:]<ws>` prefix
30
+ * (preserved verbatim on write) plus the value text that follows, lowercase-key-matched the
31
+ * same way sliceDoc.ts's own parser folds keys for lookup. Column-0 only — same grammar as
32
+ * sliceDoc.ts's `^([A-Za-z][\w-]*):\s*(.*)$`, so this never matches an indented/list-style line
33
+ * the parser wouldn't treat as a field either. */
34
+ function fieldLineRegex(key) {
35
+ // `[^\r\n]*` (not a lazy `.*?` + `\r?$`) deliberately -- JS regex `$`/multiline line-terminator
36
+ // semantics treat a lone `\r` as its own line boundary, which makes a trailing-`\r?$` anchor
37
+ // ambiguous on CRLF input. Excluding \r/\n from the value class sidesteps that entirely: the
38
+ // match always stops at the first \r or \n, full stop, no boundary-matching subtlety involved.
39
+ return new RegExp(`^(${key}[ \\t]*:[ \\t]*)([^\\r\\n]*)`, "im");
40
+ }
41
+ /** Strip a value the same way sliceDoc.ts's splitFrontmatter does when reading (surrounding
42
+ * quotes, then blank-after-trim collapses to "absent") — used here only to classify the
43
+ * *current* implementedIn value for the idempotent/refusal guard, matching what the parser
44
+ * would have reported for the same text. */
45
+ function normalizeFieldValue(raw) {
46
+ if (raw === undefined)
47
+ return null;
48
+ const v = raw.trim().replace(/^["']|["']$/g, "");
49
+ return v === "" ? null : v;
50
+ }
51
+ /** Byte offsets of the frontmatter block's inner lines (between the two `---` fences), located
52
+ * by walking lines with `indexOf` rather than `split(/\r?\n/)` — see the module header for why
53
+ * a rewrite can't afford the line-ending normalization a split/join would silently perform.
54
+ * `null` when there's no well-formed leading `---`/`---` fence, mirroring sliceDoc.ts's own
55
+ * "unterminated fence = no frontmatter" treatment. */
56
+ function locateFrontmatterInner(markdown) {
57
+ if (!/^---[ \t]*\r?\n/.test(markdown))
58
+ return null;
59
+ const innerStart = markdown.indexOf("\n") + 1;
60
+ let pos = innerStart;
61
+ while (pos <= markdown.length) {
62
+ const nl = markdown.indexOf("\n", pos);
63
+ const lineEnd = nl === -1 ? markdown.length : nl;
64
+ const line = markdown.slice(pos, lineEnd);
65
+ if (/^---[ \t]*\r?$/.test(line)) {
66
+ return { innerStart, innerEnd: pos };
67
+ }
68
+ if (nl === -1)
69
+ break;
70
+ pos = nl + 1;
71
+ }
72
+ return null;
73
+ }
74
+ /**
75
+ * Pure text transform: flips `status:` to `implemented` and sets `implementedIn:` to `prUrl` in
76
+ * `raw`'s frontmatter block. Idempotent (re-applying the same URL is a no-op, `changed: false`,
77
+ * `content` returned byte-identical to `raw`); refuses (`ok: false`) rather than overwrite
78
+ * provenance when the doc is already `status: implemented` with a *different* `implementedIn`.
79
+ * No fs access — the caller reads/writes; see `runMarkImplemented` below.
80
+ */
81
+ export function applyImplementedFrontmatter(raw, prUrl) {
82
+ const trimmedUrl = prUrl.trim();
83
+ if (!trimmedUrl)
84
+ return { ok: false, message: "a PR URL is required" };
85
+ // Refuse control characters (including an embedded \r/\n, which could splice a multi-line
86
+ // value into the frontmatter and corrupt the fence) and internal whitespace — a URL never
87
+ // legitimately contains either, so this is never a false positive, only a caller bug.
88
+ if (/[\x00-\x1f\x7f\x20]/.test(trimmedUrl)) {
89
+ return { ok: false, message: "PR URL must not contain control characters or whitespace" };
90
+ }
91
+ const range = locateFrontmatterInner(raw);
92
+ if (!range)
93
+ return { ok: false, message: "no frontmatter block found" };
94
+ const inner = raw.slice(range.innerStart, range.innerEnd);
95
+ const statusMatch = fieldLineRegex("status").exec(inner);
96
+ if (!statusMatch)
97
+ return { ok: false, message: "no `status:` field found in frontmatter" };
98
+ const implMatch = fieldLineRegex("implementedIn").exec(inner);
99
+ const currentStatus = normalizeFieldValue(statusMatch[2])?.toLowerCase() ?? null;
100
+ const currentImplementedIn = implMatch ? normalizeFieldValue(implMatch[2]) : null;
101
+ if (currentStatus === "implemented" && currentImplementedIn !== null) {
102
+ if (currentImplementedIn === trimmedUrl) {
103
+ return { ok: true, content: raw, changed: false }; // idempotent no-op
104
+ }
105
+ return {
106
+ ok: false,
107
+ message: `already marked implemented with a different URL (existing: ${currentImplementedIn}, ` +
108
+ `requested: ${trimmedUrl}) — refusing to overwrite`,
109
+ };
110
+ }
111
+ // Apply from the highest index first so an earlier edit's index stays valid — same convention
112
+ // the bridge's applyImplementedStatus uses. `implMatch` (if present) always sits at a higher
113
+ // or lower index than `statusMatch`; either way sorting by index descending keeps both safe to
114
+ // splice independently. When implMatch is absent, a fresh line is inserted right after the
115
+ // status line instead of a replacement.
116
+ let updatedInner;
117
+ if (implMatch) {
118
+ const edits = [
119
+ { index: statusMatch.index, oldLen: statusMatch[0].length, next: `${statusMatch[1]}implemented` },
120
+ { index: implMatch.index, oldLen: implMatch[0].length, next: `${implMatch[1]}${trimmedUrl}` },
121
+ ].sort((a, b) => b.index - a.index);
122
+ updatedInner = inner;
123
+ for (const edit of edits) {
124
+ updatedInner = updatedInner.slice(0, edit.index) + edit.next + updatedInner.slice(edit.index + edit.oldLen);
125
+ }
126
+ }
127
+ else {
128
+ const statusInsertEnd = statusMatch.index + statusMatch[0].length;
129
+ const rest = inner.slice(statusInsertEnd);
130
+ // Match the file's own line-ending style for the freshly-inserted line rather than
131
+ // hardcoding "\n" -- on a CRLF doc, `rest` starts with the "\r\n" that used to terminate the
132
+ // status line; reusing it keeps the new line consistent with its neighbors instead of being
133
+ // the one bare-LF line in an otherwise-CRLF file.
134
+ const eol = rest.startsWith("\r\n") ? "\r\n" : "\n";
135
+ updatedInner = inner.slice(0, statusMatch.index) + `${statusMatch[1]}implemented` + `${eol}implementedIn: ${trimmedUrl}` + rest;
136
+ }
137
+ const content = raw.slice(0, range.innerStart) + updatedInner + raw.slice(range.innerEnd);
138
+ return { ok: true, content, changed: true };
139
+ }
140
+ /**
141
+ * Resolves `sliceKey` to its bound doc via the same note-binding join `--slice-ready`/`em
142
+ * export` use (MIL-121 cross-binding included — the doc actually edited is whichever one the
143
+ * join resolved to, exactly like `sliceReadyValidate.ts` re-deriving its own read from
144
+ * `doc.path`), then reads/applies/writes it. `baseDir` is the `.em` file's directory, same
145
+ * convention every doc/note path in `em` uses.
146
+ */
147
+ export function runMarkImplemented(model, refs, baseDir, sliceKey, prUrl) {
148
+ const sliceIndex = refs.sliceKeys.indexOf(sliceKey);
149
+ if (sliceIndex === -1) {
150
+ return { ok: false, message: `no slice with export key "${sliceKey}" in this model` };
151
+ }
152
+ const slice = model.slices[sliceIndex];
153
+ const { doc } = resolveSliceDocJoin(slice, sliceKey, baseDir, (id) => refs.refById.get(id));
154
+ if (doc.reason === "no-doc-bound") {
155
+ return {
156
+ ok: false,
157
+ message: `slice "${sliceKey}" has no doc bound via \`note "slices/${sliceKey}.md"\` — bind a slice doc before marking it implemented`,
158
+ };
159
+ }
160
+ if (doc.reason === "binding-missing-file") {
161
+ return { ok: false, message: `slice "${sliceKey}" notes "${doc.path}" but no such file exists` };
162
+ }
163
+ if (doc.reason === "frontmatter-invalid") {
164
+ return {
165
+ ok: false,
166
+ message: `slice doc "${doc.path}" has missing or invalid frontmatter — run \`em validate\` for details`,
167
+ };
168
+ }
169
+ const absPath = join(baseDir, doc.path);
170
+ const raw = readFileSync(absPath, "utf8");
171
+ const result = applyImplementedFrontmatter(raw, prUrl);
172
+ if (!result.ok) {
173
+ return { ok: false, message: `${doc.path}: ${result.message}` };
174
+ }
175
+ if (result.changed) {
176
+ writeFileSync(absPath, result.content, "utf8");
177
+ }
178
+ return { ok: true, path: doc.path, changed: result.changed };
179
+ }
180
+ //# sourceMappingURL=markImplemented.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"markImplemented.js","sourceRoot":"","sources":["../../src/cli/markImplemented.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,62 @@
1
+ import { ElementNode } from "../parser/ast.js";
2
+ import type { Diagnostic } from "../model/validate.js";
3
+ export interface MigrationChange {
4
+ leadingSlice: string;
5
+ followingSlice: string;
6
+ reactionKind: string;
7
+ reactionName: string;
8
+ /** True when the leading slice held nothing but the reaction and was deleted outright. */
9
+ leadingSliceDeleted: boolean;
10
+ /** The view name added to the reaction's `from`, or null if none was needed/possible. */
11
+ addedFrom: string | null;
12
+ /** `element kind "name": note "slices/…"` lines for any note binding in either slice —
13
+ * surfaced so the report can point at docs that may need re-checking (see module header). */
14
+ noteHints: string[];
15
+ /** Human-readable one-line (plus note-hint lines) report of this change, ready to print —
16
+ * same convention as LedgerFinding.message in ledgerCheck.ts. */
17
+ message: string;
18
+ }
19
+ export interface MigrationRefusal {
20
+ leadingSlice: string;
21
+ leadingSliceLine: number;
22
+ followingSlice: string;
23
+ followingSliceLine: number;
24
+ reasons: string[];
25
+ /** Human-readable, ready-to-print report of this refusal — same convention as `message` above. */
26
+ message: string;
27
+ }
28
+ export interface MigrationPlan {
29
+ changes: MigrationChange[];
30
+ refusals: MigrationRefusal[];
31
+ /** The rewritten source, or null when there is nothing to change (changes.length === 0) —
32
+ * including the fully-idempotent "already migrated" case. */
33
+ rewritten: string | null;
34
+ }
35
+ export interface MigrationVerifyResult {
36
+ ok: boolean;
37
+ /** Error-severity diagnostics present after the rewrite that weren't present before it
38
+ * (matched by code+message, multiset-aware — see verifyMigration). Empty when ok. */
39
+ newErrors: Diagnostic[];
40
+ }
41
+ /**
42
+ * A single-line element's own source line — the only shape this module ever moves. Elements
43
+ * carrying a `{ fields }` block are refused rather than relocated (see planMigration): the
44
+ * canonical old-shape examples (docs/patterns.md, pre-MIL-120) never show a reaction with
45
+ * fields, and a multi-line block's `from` clause can legally sit on either its header or its
46
+ * closing line — correctly locating it isn't worth the risk on a shape that doesn't occur in
47
+ * practice. Kept exported for use by both planMigration and its tests.
48
+ */
49
+ export declare function isSingleLineReaction(el: ElementNode): boolean;
50
+ /**
51
+ * Detect and plan the rewrite for every old-shape site in `source`. Throws ParseError (from
52
+ * src/parser/parser.ts) if `source` itself doesn't parse — same contract as compile().
53
+ */
54
+ export declare function planMigration(source: string): MigrationPlan;
55
+ /**
56
+ * Re-parse + re-validate both `before` and `after`, and confirm the rewrite introduced no new
57
+ * error-severity diagnostic. Compared as a code+message multiset (not by count alone) so a
58
+ * genuinely new error can't hide behind an unrelated one the rewrite happened to fix. Never
59
+ * throws on `after` failing to parse/compile cleanly — that counts as new errors too, reported
60
+ * as a single synthetic entry, so a rewrite bug aborts loudly instead of crashing the CLI.
61
+ */
62
+ export declare function verifyMigration(before: string, after: string): MigrationVerifyResult;
@@ -0,0 +1,283 @@
1
+ // SPDX-License-Identifier: MIT
2
+ // `em migrate` (MIL-125): rewrites the OLD, pre-1.7.1 two-slice Automation/Translation shape —
3
+ // reaction + its read model in one slice, that reaction's command + event in the next, linked
4
+ // only by file-position adjacency — into the MIL-120 merged shape (reaction, command, and event
5
+ // sharing one slice). MIL-120 changed what `em validate` accepts; it deliberately didn't touch
6
+ // existing `.em` source text, since the restructuring has to happen in the source itself (see
7
+ // that commit's own message, which filed this ticket as the follow-up). This is `em`'s first
8
+ // command that mutates a `.em` file — see cli.ts's `migrate` registration for the CLI-shape
9
+ // rationale (dedicated command, dry-run by default).
10
+ //
11
+ // Text-level and line-based throughout, guided by (not derived from) the parse: every edit is a
12
+ // literal line range lifted from the original source and spliced elsewhere, so comments, blank
13
+ // lines, and indentation everywhere else in the file survive byte-for-byte. Nothing here ever
14
+ // regenerates a slice from the AST.
15
+ //
16
+ // Detection is deliberately narrow. A pair of adjacent slices only enters the refusal/migration
17
+ // path at all once BOTH of these hold — the signature of an attempted old-shape pairing:
18
+ // - the leading slice has no `command` but has at least one reaction-kind element
19
+ // (automation/processor/saga/translation)
20
+ // - the following slice has at least one `command`
21
+ // Every other adjacent pair (the overwhelming majority in a normal model) is silently skipped —
22
+ // refusing on every unrelated slice boundary would bury the real refusals in noise. Once a pair
23
+ // clears that bar, every deviation from the clean shape becomes a *refusal*, never a guess.
24
+ import { AUTOMATION_KINDS } from "../parser/ast.js";
25
+ import { parse } from "../parser/parser.js";
26
+ import { indexOfUnquoted, stripComment } from "../parser/lexer.js";
27
+ import { normalizeName } from "../model/model.js";
28
+ import { compile } from "../pipeline.js";
29
+ const isReaction = (e) => AUTOMATION_KINDS.has(e.kind);
30
+ const isCommand = (e) => e.kind === "command";
31
+ const isEvent = (e) => e.kind === "event";
32
+ const isView = (e) => e.kind === "view";
33
+ /** A `slices/<key>.md`-shaped note (docs/slice-doc-schema.md's canonical binding path). */
34
+ const NOTE_BINDING = /^slices\/.*\.md$/;
35
+ /**
36
+ * A single-line element's own source line — the only shape this module ever moves. Elements
37
+ * carrying a `{ fields }` block are refused rather than relocated (see planMigration): the
38
+ * canonical old-shape examples (docs/patterns.md, pre-MIL-120) never show a reaction with
39
+ * fields, and a multi-line block's `from` clause can legally sit on either its header or its
40
+ * closing line — correctly locating it isn't worth the risk on a shape that doesn't occur in
41
+ * practice. Kept exported for use by both planMigration and its tests.
42
+ */
43
+ export function isSingleLineReaction(el) {
44
+ return el.fields === undefined;
45
+ }
46
+ /** Escape `\` and `"` the same way the DSL's own quoted strings require (mirrors decodeQuoted's
47
+ * inverse in src/parser/lexer.ts) — so a view name containing either round-trips correctly. */
48
+ function escapeForQuotes(s) {
49
+ return s.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
50
+ }
51
+ /** Append (or extend) a `from "…"` clause on `line`, inserted before any trailing `# comment`
52
+ * so the comment survives untouched. `from`'s own grammar captures to end-of-line once it
53
+ * starts (src/parser/parser.ts's extractClauses), so for the single-line elements this module
54
+ * ever touches, appending at the end of the code portion is always correct — whether starting
55
+ * a fresh clause or adding one more name to an existing list. */
56
+ function appendFromClause(line, viewName, hasExistingFrom) {
57
+ const codePart = stripComment(line);
58
+ const commentPart = line.slice(codePart.length);
59
+ const trimmedCode = codePart.replace(/\s+$/, "");
60
+ const addition = hasExistingFrom ? `, "${escapeForQuotes(viewName)}"` : ` from "${escapeForQuotes(viewName)}"`;
61
+ const rebuilt = trimmedCode + addition;
62
+ return commentPart ? `${rebuilt} ${commentPart}` : rebuilt;
63
+ }
64
+ /** An element's own line range, 1-based inclusive. Every reaction this module moves is single-
65
+ * line (see isSingleLineReaction); this stays general (used for the leading slice's other
66
+ * elements too, via sliceExtent) so a fields-bearing view/command elsewhere in the pair doesn't
67
+ * throw off the slice's own closing-brace search. */
68
+ function elementExtent(lines, el) {
69
+ const startIdx = el.line - 1;
70
+ if (!el.fields)
71
+ return [el.line, el.line];
72
+ const headerStripped = stripComment(lines[startIdx]);
73
+ const braceAt = indexOfUnquoted(headerStripped, "{");
74
+ if (braceAt < 0)
75
+ return [el.line, el.line];
76
+ const after = headerStripped.slice(braceAt + 1);
77
+ if (indexOfUnquoted(after, "}") >= 0)
78
+ return [el.line, el.line]; // closes on its own line
79
+ for (let i = startIdx + 1; i < lines.length; i++) {
80
+ if (stripComment(lines[i]).trim().startsWith("}"))
81
+ return [el.line, i + 1];
82
+ }
83
+ return [el.line, el.line]; // unreachable for source that already parsed successfully
84
+ }
85
+ /** A slice block's own line range, 1-based inclusive — from its `slice "Name" {` line to the
86
+ * line whose stripped, trimmed content is exactly `}` (a slice's closing brace supports no
87
+ * trailing clauses, unlike an element's — see src/parser/parser.ts's block-closing logic).
88
+ * When `swallowTrailingBlank` is set (used only when the slice is about to be deleted whole),
89
+ * one immediately-following blank line is folded into the range too — the same collapse
90
+ * MIL-120's own doc rewrite made by hand (docs/patterns.md's "Carrier Webhook" removal),
91
+ * so deleting a slice doesn't leave a doubled blank-line gap where it used to sit. Never
92
+ * reaches past a second blank line, and never touches whatever precedes the slice. */
93
+ function sliceExtent(lines, slice, swallowTrailingBlank = false) {
94
+ let lastElementEnd = slice.line;
95
+ for (const el of slice.elements) {
96
+ const [, end] = elementExtent(lines, el);
97
+ if (end > lastElementEnd)
98
+ lastElementEnd = end;
99
+ }
100
+ for (let i = lastElementEnd; i < lines.length; i++) {
101
+ const t = stripComment(lines[i]).trim();
102
+ if (t.length === 0)
103
+ continue;
104
+ if (t === "}") {
105
+ const closeLine = i + 1; // 1-based
106
+ const nextIdx = closeLine; // 0-based index of the line right after the close
107
+ if (swallowTrailingBlank && lines[nextIdx] !== undefined && lines[nextIdx].trim() === "") {
108
+ return [slice.line, closeLine + 1];
109
+ }
110
+ return [slice.line, closeLine];
111
+ }
112
+ }
113
+ return [slice.line, lastElementEnd]; // unreachable for source that already parsed successfully
114
+ }
115
+ /** Rebuild `lines` with every 1-based inclusive range in `removals` dropped, and each entry of
116
+ * `insertions` (keyed by "insert before this 1-based line number") spliced in ahead of it — a
117
+ * single forward pass, so multiple independent sites in one file compose without needing to
118
+ * process edits in any particular order. */
119
+ function applyEdits(lines, removals, insertions) {
120
+ const out = [];
121
+ for (let ln = 1; ln <= lines.length; ln++) {
122
+ const toInsert = insertions.get(ln);
123
+ if (toInsert)
124
+ out.push(...toInsert);
125
+ const removed = removals.some(([s, e]) => ln >= s && ln <= e);
126
+ if (!removed)
127
+ out.push(lines[ln - 1]);
128
+ }
129
+ return out;
130
+ }
131
+ /**
132
+ * Detect and plan the rewrite for every old-shape site in `source`. Throws ParseError (from
133
+ * src/parser/parser.ts) if `source` itself doesn't parse — same contract as compile().
134
+ */
135
+ export function planMigration(source) {
136
+ const model = parse(source);
137
+ const eol = source.includes("\r\n") ? "\r\n" : "\n";
138
+ const lines = source.split(/\r?\n/);
139
+ const changes = [];
140
+ const refusals = [];
141
+ const removals = [];
142
+ const insertions = new Map();
143
+ for (let i = 0; i < model.slices.length - 1; i++) {
144
+ const leading = model.slices[i];
145
+ const following = model.slices[i + 1];
146
+ const leadingReactions = leading.elements.filter(isReaction);
147
+ const leadingHasCommand = leading.elements.some(isCommand);
148
+ const followingCommands = following.elements.filter(isCommand);
149
+ // Candidacy trigger (see module header) — anything short of this is an unrelated adjacent
150
+ // pair, skipped without comment.
151
+ if (leadingReactions.length === 0)
152
+ continue;
153
+ if (leadingHasCommand)
154
+ continue;
155
+ if (followingCommands.length === 0)
156
+ continue;
157
+ const reasons = [];
158
+ const leadingViews = leading.elements.filter(isView);
159
+ const leadingOther = leading.elements.filter((e) => !isView(e) && !isReaction(e));
160
+ if (leadingReactions.length > 1) {
161
+ reasons.push(`more than one reaction in leading slice "${leading.name}" (${leadingReactions
162
+ .map((r) => `${r.kind} "${r.name}"`)
163
+ .join(", ")})`);
164
+ }
165
+ if (leadingViews.length > 1 || leadingOther.length > 0) {
166
+ reasons.push(`leading slice "${leading.name}" has elements beyond one view and one reaction`);
167
+ }
168
+ if (following.elements.some(isReaction)) {
169
+ reasons.push(`following slice "${following.name}" already has a reaction`);
170
+ }
171
+ if (followingCommands.length > 1) {
172
+ reasons.push(`following slice "${following.name}" has more than one command`);
173
+ }
174
+ if (!following.elements.some(isEvent)) {
175
+ reasons.push(`following slice "${following.name}" has a command but no event`);
176
+ }
177
+ if (leadingReactions.length === 1 && !isSingleLineReaction(leadingReactions[0])) {
178
+ reasons.push(`reaction "${leadingReactions[0].name}" declares a \`{ fields }\` block — migrate this one by hand`);
179
+ }
180
+ if (reasons.length > 0) {
181
+ refusals.push({
182
+ leadingSlice: leading.name,
183
+ leadingSliceLine: leading.line,
184
+ followingSlice: following.name,
185
+ followingSliceLine: following.line,
186
+ reasons,
187
+ message: `refused slice "${leading.name}" / "${following.name}": ${reasons.join("; ")}`,
188
+ });
189
+ continue;
190
+ }
191
+ // Clean site.
192
+ const reaction = leadingReactions[0];
193
+ const command = followingCommands[0];
194
+ const view = leadingViews[0]; // undefined when the leading slice held only the reaction
195
+ const hasExistingFrom = (reaction.from ?? []).length > 0;
196
+ const alreadyNamesView = view
197
+ ? (reaction.from ?? []).some((n) => normalizeName(n) === normalizeName(view.name))
198
+ : false;
199
+ let reactionLine = lines[reaction.line - 1];
200
+ let addedFrom = null;
201
+ if (view && !alreadyNamesView) {
202
+ reactionLine = appendFromClause(reactionLine, view.name, hasExistingFrom);
203
+ addedFrom = view.name;
204
+ }
205
+ removals.push([reaction.line, reaction.line]);
206
+ insertions.set(command.line, [...(insertions.get(command.line) ?? []), reactionLine]);
207
+ const leadingSliceDeleted = !view;
208
+ if (leadingSliceDeleted) {
209
+ removals.push(sliceExtent(lines, leading, true));
210
+ }
211
+ const noteHints = [];
212
+ for (const el of [...leading.elements, ...following.elements]) {
213
+ if (el.note && NOTE_BINDING.test(el.note)) {
214
+ noteHints.push(`${el.kind} "${el.name}": note "${el.note}"`);
215
+ }
216
+ }
217
+ const leadingFate = leadingSliceDeleted
218
+ ? `leading slice "${leading.name}" deleted (now empty)`
219
+ : `leading slice "${leading.name}" kept (bare view)`;
220
+ const fromNote = addedFrom ? ` (added \`from "${addedFrom}"\`)` : "";
221
+ let message = `migrated slice "${leading.name}" / "${following.name}": moved ${reaction.kind} ` +
222
+ `"${reaction.name}" into "${following.name}"${fromNote}; ${leadingFate}`;
223
+ if (noteHints.length > 0) {
224
+ message += `\n doc bindings may need re-checking: ${noteHints.join("; ")}`;
225
+ }
226
+ changes.push({
227
+ leadingSlice: leading.name,
228
+ followingSlice: following.name,
229
+ reactionKind: reaction.kind,
230
+ reactionName: reaction.name,
231
+ leadingSliceDeleted,
232
+ addedFrom,
233
+ noteHints,
234
+ message,
235
+ });
236
+ }
237
+ if (changes.length === 0) {
238
+ return { changes, refusals, rewritten: null };
239
+ }
240
+ return { changes, refusals, rewritten: applyEdits(lines, removals, insertions).join(eol) };
241
+ }
242
+ /**
243
+ * Re-parse + re-validate both `before` and `after`, and confirm the rewrite introduced no new
244
+ * error-severity diagnostic. Compared as a code+message multiset (not by count alone) so a
245
+ * genuinely new error can't hide behind an unrelated one the rewrite happened to fix. Never
246
+ * throws on `after` failing to parse/compile cleanly — that counts as new errors too, reported
247
+ * as a single synthetic entry, so a rewrite bug aborts loudly instead of crashing the CLI.
248
+ */
249
+ export function verifyMigration(before, after) {
250
+ const beforeErrors = compile(before).diagnostics.filter((d) => d.severity === "error");
251
+ let afterErrors;
252
+ try {
253
+ afterErrors = compile(after).diagnostics.filter((d) => d.severity === "error");
254
+ }
255
+ catch (e) {
256
+ return {
257
+ ok: false,
258
+ newErrors: [
259
+ {
260
+ severity: "error",
261
+ code: "migrate-rewrite-unparseable",
262
+ message: `rewritten source failed to parse: ${e instanceof Error ? e.message : String(e)}`,
263
+ },
264
+ ],
265
+ };
266
+ }
267
+ const remaining = new Map();
268
+ for (const d of beforeErrors) {
269
+ const key = `${d.code}|${d.message}`;
270
+ remaining.set(key, (remaining.get(key) ?? 0) + 1);
271
+ }
272
+ const newErrors = [];
273
+ for (const d of afterErrors) {
274
+ const key = `${d.code}|${d.message}`;
275
+ const left = remaining.get(key) ?? 0;
276
+ if (left > 0)
277
+ remaining.set(key, left - 1);
278
+ else
279
+ newErrors.push(d);
280
+ }
281
+ return { ok: newErrors.length === 0, newErrors };
282
+ }
283
+ //# sourceMappingURL=migrateReactionShape.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"migrateReactionShape.js","sourceRoot":"","sources":["../../src/cli/migrateReactionShape.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,49 @@
1
+ import { NormalizedModel } from "../model/model.js";
2
+ import { RefsResult } from "../model/refs.js";
3
+ import { Diagnostic } from "../model/validate.js";
4
+ /** The marker name wrapping the Slices table: `<!-- GENERATED:slices:start -->` /
5
+ * `<!-- GENERATED:slices:end -->`, matching templates/model-readme.md. */
6
+ export declare const SLICE_INDEX_MARKER = "slices";
7
+ export interface SliceIndexRow {
8
+ index: number;
9
+ name: string;
10
+ pattern: string;
11
+ status: string;
12
+ implementedIn: string;
13
+ docPath: string;
14
+ }
15
+ export interface SliceIndexTable {
16
+ /** The full replacement body for the GENERATED:slices block — header + one row per slice
17
+ * (or just the header when the model has no slices yet). */
18
+ markdown: string;
19
+ rows: SliceIndexRow[];
20
+ /** binding-missing-file / frontmatter-invalid warnings from resolveSliceDocJoin — the same
21
+ * diagnostics `em export` surfaces for a broken doc binding. */
22
+ diagnostics: Diagnostic[];
23
+ }
24
+ /** Build the Slices table body for `model`. `baseDir` is the `.em` file's directory — same
25
+ * doc-resolution convention `em export`/`em catalog` use for `slices/<key>.md`. */
26
+ export declare function buildSliceIndexTable(model: NormalizedModel, refs: RefsResult, baseDir: string): SliceIndexTable;
27
+ export type SliceIndexResult = {
28
+ ok: true;
29
+ readmePath: string;
30
+ wrote: boolean;
31
+ changed: boolean;
32
+ table: SliceIndexTable;
33
+ } | {
34
+ ok: false;
35
+ kind: "missing-readme" | "missing-markers" | "stale";
36
+ message: string;
37
+ table: SliceIndexTable;
38
+ };
39
+ /**
40
+ * Rewrite (or, in `check` mode, verify) the GENERATED:slices block in `<dirname(emFilePath)>/
41
+ * README.md`. Never creates the README or the marker pair — both are user errors reported back
42
+ * via `ok: false`, not silently fixed: a missing README means the model wasn't scaffolded from
43
+ * templates/model-readme.md yet, and missing markers mean an older/hand-edited README that
44
+ * needs the marker pair added first (see the message text for exactly what to do).
45
+ *
46
+ * `check: true` never writes to disk; a stale table is reported as `kind: "stale"` instead
47
+ * (CI-ready — the caller exits non-zero without having touched the working tree).
48
+ */
49
+ export declare function runSliceIndex(model: NormalizedModel, refs: RefsResult, emFilePath: string, check: boolean): SliceIndexResult;