@milehimikey/em 1.6.1 → 1.7.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 (105) hide show
  1. package/.claude/skills/event-modeling/SKILL.md +94 -49
  2. package/.claude/skills/event-modeling/reference/conform.md +30 -8
  3. package/.claude/skills/event-modeling/reference/em-dsl.md +194 -73
  4. package/.claude/skills/event-modeling/reference/extract.md +14 -11
  5. package/.claude/skills/event-modeling/reference/implement.md +172 -0
  6. package/.claude/skills/event-modeling/reference/methodology.md +40 -33
  7. package/.claude/skills/event-modeling/templates/conformance-report.md +15 -9
  8. package/.claude/skills/event-modeling/templates/model-readme.md +7 -7
  9. package/.claude/skills/event-modeling/templates/slice.md +73 -7
  10. package/.claude/skills/event-modeling/templates/state.md +7 -6
  11. package/README.md +1 -0
  12. package/dist/catalog/build.d.ts +5 -0
  13. package/dist/catalog/build.js +1 -3
  14. package/dist/catalog/build.js.map +1 -1
  15. package/dist/catalog/classify.d.ts +7 -8
  16. package/dist/catalog/classify.js +7 -8
  17. package/dist/catalog/classify.js.map +1 -1
  18. package/dist/catalog/docJoin.d.ts +39 -0
  19. package/dist/catalog/docJoin.js +86 -0
  20. package/dist/catalog/docJoin.js.map +1 -0
  21. package/dist/catalog/driftSignal.d.ts +16 -0
  22. package/dist/catalog/driftSignal.js +24 -0
  23. package/dist/catalog/driftSignal.js.map +1 -0
  24. package/dist/catalog/frontmatterCoherenceValidate.d.ts +8 -0
  25. package/dist/catalog/frontmatterCoherenceValidate.js +52 -0
  26. package/dist/catalog/frontmatterCoherenceValidate.js.map +1 -0
  27. package/dist/catalog/lineageValidate.d.ts +9 -0
  28. package/dist/catalog/lineageValidate.js +161 -0
  29. package/dist/catalog/lineageValidate.js.map +1 -0
  30. package/dist/catalog/readSliceDoc.d.ts +2 -0
  31. package/dist/catalog/readSliceDoc.js +21 -0
  32. package/dist/catalog/readSliceDoc.js.map +1 -0
  33. package/dist/catalog/sliceDoc.d.ts +94 -3
  34. package/dist/catalog/sliceDoc.js +157 -7
  35. package/dist/catalog/sliceDoc.js.map +1 -1
  36. package/dist/catalog/sliceReadyValidate.d.ts +11 -0
  37. package/dist/catalog/sliceReadyValidate.js +85 -0
  38. package/dist/catalog/sliceReadyValidate.js.map +1 -0
  39. package/dist/cli/diff-inputs.d.ts +20 -0
  40. package/dist/cli/diff-inputs.js +75 -18
  41. package/dist/cli/diff-inputs.js.map +1 -1
  42. package/dist/cli/ledgerCheck.d.ts +38 -0
  43. package/dist/cli/ledgerCheck.js +116 -0
  44. package/dist/cli/ledgerCheck.js.map +1 -0
  45. package/dist/cli/skillCheck.d.ts +18 -0
  46. package/dist/cli/skillCheck.js +63 -0
  47. package/dist/cli/skillCheck.js.map +1 -0
  48. package/dist/cli/skillSync.d.ts +22 -0
  49. package/dist/cli/skillSync.js +69 -0
  50. package/dist/cli/skillSync.js.map +1 -0
  51. package/dist/cli/skillVersionCheck.d.ts +32 -0
  52. package/dist/cli/skillVersionCheck.js +145 -0
  53. package/dist/cli/skillVersionCheck.js.map +1 -0
  54. package/dist/cli.d.ts +3 -1
  55. package/dist/cli.js +203 -32
  56. package/dist/cli.js.map +1 -1
  57. package/dist/emit/diffJson.d.ts +1 -1
  58. package/dist/emit/diffJson.js +18 -14
  59. package/dist/emit/diffJson.js.map +1 -1
  60. package/dist/emit/dot.js +46 -5
  61. package/dist/emit/dot.js.map +1 -1
  62. package/dist/emit/json.d.ts +10 -25
  63. package/dist/emit/json.js +70 -103
  64. package/dist/emit/json.js.map +1 -1
  65. package/dist/emit/ledgerJson.d.ts +5 -0
  66. package/dist/emit/ledgerJson.js +25 -0
  67. package/dist/emit/ledgerJson.js.map +1 -0
  68. package/dist/emit/skillCheckJson.d.ts +5 -0
  69. package/dist/emit/skillCheckJson.js +32 -0
  70. package/dist/emit/skillCheckJson.js.map +1 -0
  71. package/dist/model/diff.d.ts +44 -1
  72. package/dist/model/diff.js +51 -9
  73. package/dist/model/diff.js.map +1 -1
  74. package/dist/model/edges.js +17 -17
  75. package/dist/model/edges.js.map +1 -1
  76. package/dist/model/model.d.ts +1 -1
  77. package/dist/model/refs.d.ts +23 -0
  78. package/dist/model/refs.js +75 -0
  79. package/dist/model/refs.js.map +1 -0
  80. package/dist/model/rules.d.ts +253 -0
  81. package/dist/model/rules.js +254 -0
  82. package/dist/model/rules.js.map +1 -0
  83. package/dist/model/validate.d.ts +23 -1
  84. package/dist/model/validate.js +207 -69
  85. package/dist/model/validate.js.map +1 -1
  86. package/dist/parser/ast.d.ts +3 -2
  87. package/dist/parser/lexer.d.ts +17 -2
  88. package/dist/parser/lexer.js +119 -13
  89. package/dist/parser/lexer.js.map +1 -1
  90. package/dist/parser/parser.js +75 -34
  91. package/dist/parser/parser.js.map +1 -1
  92. package/dist/pipeline.d.ts +9 -0
  93. package/dist/pipeline.js +4 -2
  94. package/dist/pipeline.js.map +1 -1
  95. package/dist/render/sliceDiagram.js +33 -35
  96. package/dist/render/sliceDiagram.js.map +1 -1
  97. package/dist/render/sliceStatus.js +1 -1
  98. package/dist/render/sliceStatus.js.map +1 -1
  99. package/dist/util/isMainModule.d.ts +14 -0
  100. package/dist/util/isMainModule.js +33 -0
  101. package/dist/util/isMainModule.js.map +1 -0
  102. package/dist/util/walkDir.d.ts +4 -0
  103. package/dist/util/walkDir.js +28 -0
  104. package/dist/util/walkDir.js.map +1 -0
  105. package/package.json +5 -2
@@ -3,18 +3,168 @@
3
3
  // .claude/skills/event-modeling/templates/slice.md) for `em catalog`. `em`
4
4
  // has never parsed this markdown structurally before — it only checked the
5
5
  // file exists (cli.ts's warnMissingNotes). This is deliberately shallow: pull
6
- // the one bold-label line the index page needs (`- **Status:** ...`) with a
7
- // regex rather than a full frontmatter parser, and render the whole doc body
8
- // as HTML for the per-slice detail page. Pure — no fs access; the caller
9
- // reads the file and hands us its text.
6
+ // the status/version/lineage values the index page and downstream consumers
7
+ // need, and render the doc body as HTML for the per-slice detail page. Pure —
8
+ // no fs access; the caller reads the file and hands us its text.
9
+ //
10
+ // Two status dialects: a leading YAML frontmatter block (`status: ...`) is
11
+ // canonical (MIL-86); a `- **Status:** ...` bullet line is legacy/accepted
12
+ // input for docs written before the frontmatter dialect existed. Frontmatter
13
+ // wins when both are somehow present. No `yaml` dependency — the 1.1.1 revert
14
+ // (6de0a05) dropped it on purpose; frontmatter is a plain top-level-scalar
15
+ // line-scan, same spirit as the bullet-line regex it now sits alongside.
16
+ //
17
+ // `version` and the three lineage keys (`split-from`/`merged-from`/
18
+ // `superseded-by`) are canonical, frontmatter-only fields (MIL-90) — no
19
+ // legacy bullet-line form exists for them. Full schema: docs/slice-doc-schema.md.
20
+ // `#`-prefixed lines (the template's commented-out optional lineage guidance)
21
+ // are inert: they don't match the key regex below, so they're skipped exactly
22
+ // like any other non-`key: value` line — safe to leave in an unfinished doc.
23
+ //
24
+ // `implementedIn`, `frontmatterPresent`, and `missingRequiredFields` (MIL-91)
25
+ // exist so `em export`'s doc join (src/catalog/docJoin.ts) can mechanically
26
+ // classify a doc as `frontmatter-invalid` — no frontmatter fence at all, or
27
+ // missing one of the keys docs/slice-doc-schema.md says are required at every
28
+ // status — without export re-deriving frontmatter-shape rules of its own.
10
29
  import { marked } from "marked";
30
+ /** Frontmatter keys docs/slice-doc-schema.md's required-vs-optional table marks
31
+ * required at every `status` — the mechanical basis for `frontmatter-invalid`
32
+ * (MIL-91). `implementedIn` is deliberately excluded: its requirement depends on
33
+ * whether the slice has *ever* reached `implemented`, which is git history this
34
+ * parser has no access to and doesn't attempt to check. */
35
+ export const REQUIRED_FRONTMATTER_KEYS = ["schemaversion", "pattern", "swimlane", "status", "version"];
36
+ /** True when a doc's frontmatter is well-formed enough to trust its canonical fields: a closed
37
+ * fence was found AND every REQUIRED_FRONTMATTER_KEYS entry is present. The single gate `em
38
+ * export`'s doc join (MIL-91, `frontmatter-invalid`) and `em validate`'s frontmatter-coherence
39
+ * check (MIL-85) both call, instead of each re-deriving `!frontmatterPresent ||
40
+ * missingRequiredFields.length > 0` and risking the two silently drifting apart on what counts
41
+ * as "usable" as the required-fields list evolves. A doc that fails this — no fence at all
42
+ * (e.g. MIL-86's legacy body-label dialect), or missing any required key — has nothing reliable
43
+ * enough to classify; treat it as unreadable rather than guess. */
44
+ export function hasUsableFrontmatter(doc) {
45
+ return doc.frontmatterPresent && doc.missingRequiredFields.length === 0;
46
+ }
11
47
  const STATUS_LINE = /^-\s*\*\*Status:\*\*\s*(.+?)\s*$/im;
48
+ const SLICE_REF = /^([a-z0-9]+(?:-[a-z0-9]+)*)@v(\d+)$/i;
49
+ const OPEN_QUESTIONS_HEADING = /^##\s+Open Questions\s*$/i;
50
+ const TASK_ITEM = /^[ \t]*[-*]\s*\[([ xX])\]/;
51
+ /**
52
+ * Counts GFM task-list items under a `## Open Questions` heading (MIL-87), scanning `body`
53
+ * (post-frontmatter, pre-HTML-render text — the same text `html` below is built from) rather
54
+ * than the rendered HTML, which would be needlessly fragile to scrape. The section ends at the
55
+ * next `#`/`##` heading or EOF; a doc with no such heading counts as zero/zero, not an error —
56
+ * most slice docs won't have open questions left by the time they're read.
57
+ */
58
+ function countOpenQuestions(body) {
59
+ const lines = body.split(/\r?\n/);
60
+ const start = lines.findIndex((l) => OPEN_QUESTIONS_HEADING.test(l));
61
+ if (start === -1)
62
+ return { openQuestionsTotal: 0, openQuestionsUnchecked: 0 };
63
+ let openQuestionsTotal = 0;
64
+ let openQuestionsUnchecked = 0;
65
+ for (let i = start + 1; i < lines.length; i++) {
66
+ if (/^#{1,2}\s/.test(lines[i]))
67
+ break;
68
+ const m = lines[i].match(TASK_ITEM);
69
+ if (!m)
70
+ continue;
71
+ openQuestionsTotal++;
72
+ if (m[1] === " ")
73
+ openQuestionsUnchecked++;
74
+ }
75
+ return { openQuestionsTotal, openQuestionsUnchecked };
76
+ }
77
+ /**
78
+ * Parses a single `<slice-key>@v<N>` lineage reference (`split-from`, and each
79
+ * comma-separated item of `merged-from`/`superseded-by`). Never throws: a
80
+ * value that doesn't match the grammar still comes back with the original
81
+ * text in `raw` and null `sliceKey`/`version`, so a malformed value stays
82
+ * visible — to a human reading the doc, or to MIL-84's validation — instead
83
+ * of being silently dropped like an unrecognized `status`. Exported so MIL-84
84
+ * (lineage diff/validate) and MIL-91 (export join) share this grammar instead
85
+ * of re-deriving it. `sliceKey` matches `kebabSlug()`'s output
86
+ * (src/util/slug.ts) — lowercase alphanumerics/hyphens, no assumption it
87
+ * starts with a letter.
88
+ */
89
+ export function parseSliceRef(raw) {
90
+ const trimmed = raw.trim();
91
+ const m = trimmed.match(SLICE_REF);
92
+ return m
93
+ ? { raw: trimmed, sliceKey: m[1].toLowerCase(), version: Number(m[2]) }
94
+ : { raw: trimmed, sliceKey: null, version: null };
95
+ }
96
+ function parseSliceRefList(raw) {
97
+ if (!raw)
98
+ return [];
99
+ return raw
100
+ .split(",")
101
+ .map((s) => s.trim())
102
+ .filter(Boolean)
103
+ .map(parseSliceRef);
104
+ }
105
+ function parseVersion(raw) {
106
+ if (!raw)
107
+ return null;
108
+ const n = Number(raw);
109
+ return Number.isInteger(n) && n > 0 ? n : null;
110
+ }
111
+ /**
112
+ * Splits a leading YAML frontmatter block (`---` ... `---`) off the doc, if
113
+ * present. Deliberately minimal: top-level scalar `key: value` lines only —
114
+ * no lists, no nesting. Returns the scalar fields (lowercased keys) and the
115
+ * remaining body with the frontmatter fences removed. An unterminated `---`
116
+ * fence is treated as "no frontmatter" (the whole doc is the body). Like
117
+ * every other frontmatter convention (Jekyll, Hugo, ...), the fence must be
118
+ * the literal first thing in the file — the template's own guidance comment
119
+ * must be deleted before the frontmatter counts as present.
120
+ */
121
+ function splitFrontmatter(markdown) {
122
+ if (!/^---\s*\r?\n/.test(markdown))
123
+ return { fields: new Map(), body: markdown, frontmatterPresent: false };
124
+ const lines = markdown.split(/\r?\n/);
125
+ const fields = new Map();
126
+ let closeIndex = -1;
127
+ for (let i = 1; i < lines.length; i++) {
128
+ if (/^---\s*$/.test(lines[i])) {
129
+ closeIndex = i;
130
+ break;
131
+ }
132
+ const m = lines[i].match(/^([A-Za-z][\w-]*):\s*(.*)$/);
133
+ if (!m)
134
+ continue;
135
+ const value = m[2].trim().replace(/^["']|["']$/g, "");
136
+ if (value)
137
+ fields.set(m[1].toLowerCase(), value);
138
+ }
139
+ if (closeIndex === -1)
140
+ return { fields: new Map(), body: markdown, frontmatterPresent: false };
141
+ return { fields, body: lines.slice(closeIndex + 1).join("\n"), frontmatterPresent: true };
142
+ }
12
143
  export function parseSliceDoc(markdown) {
13
- const match = markdown.match(STATUS_LINE);
144
+ const { fields, body, frontmatterPresent } = splitFrontmatter(markdown);
145
+ const legacyMatch = body.match(STATUS_LINE);
146
+ const frontmatterStatus = fields.get("status");
147
+ const status = frontmatterStatus
148
+ ? frontmatterStatus.toLowerCase()
149
+ : legacyMatch
150
+ ? legacyMatch[1].trim().toLowerCase()
151
+ : null;
152
+ const splitFromRaw = fields.get("split-from");
153
+ const { openQuestionsTotal, openQuestionsUnchecked } = countOpenQuestions(body);
14
154
  return {
15
155
  raw: markdown,
16
- status: match ? match[1].trim().toLowerCase() : null,
17
- html: marked.parse(markdown, { async: false }),
156
+ body,
157
+ status,
158
+ version: parseVersion(fields.get("version")),
159
+ splitFrom: splitFromRaw ? parseSliceRef(splitFromRaw) : null,
160
+ mergedFrom: parseSliceRefList(fields.get("merged-from")),
161
+ supersededBy: parseSliceRefList(fields.get("superseded-by")),
162
+ implementedIn: fields.get("implementedin") ?? null,
163
+ frontmatterPresent,
164
+ missingRequiredFields: REQUIRED_FRONTMATTER_KEYS.filter((k) => !fields.has(k)),
165
+ html: marked.parse(body, { async: false }),
166
+ openQuestionsTotal,
167
+ openQuestionsUnchecked,
18
168
  };
19
169
  }
20
170
  //# sourceMappingURL=sliceDoc.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"sliceDoc.js","sourceRoot":"","sources":["../../src/catalog/sliceDoc.ts"],"names":[],"mappings":"AAAA,+BAA+B;AAC/B,yDAAyD;AACzD,2EAA2E;AAC3E,2EAA2E;AAC3E,8EAA8E;AAC9E,4EAA4E;AAC5E,6EAA6E;AAC7E,yEAAyE;AACzE,wCAAwC;AAExC,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAYhC,MAAM,WAAW,GAAG,oCAAoC,CAAC;AAEzD,MAAM,UAAU,aAAa,CAAC,QAAgB;IAC5C,MAAM,KAAK,GAAG,QAAQ,CAAC,KAAK,CAAC,WAAW,CAAC,CAAC;IAC1C,OAAO;QACL,GAAG,EAAE,QAAQ;QACb,MAAM,EAAE,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC,IAAI;QACpD,IAAI,EAAE,MAAM,CAAC,KAAK,CAAC,QAAQ,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,CAAW;KACzD,CAAC;AACJ,CAAC"}
1
+ {"version":3,"file":"sliceDoc.js","sourceRoot":"","sources":["../../src/catalog/sliceDoc.ts"],"names":[],"mappings":"AAAA,+BAA+B;AAC/B,yDAAyD;AACzD,2EAA2E;AAC3E,2EAA2E;AAC3E,8EAA8E;AAC9E,4EAA4E;AAC5E,8EAA8E;AAC9E,iEAAiE;AACjE,EAAE;AACF,2EAA2E;AAC3E,2EAA2E;AAC3E,6EAA6E;AAC7E,8EAA8E;AAC9E,2EAA2E;AAC3E,yEAAyE;AACzE,EAAE;AACF,oEAAoE;AACpE,wEAAwE;AACxE,kFAAkF;AAClF,8EAA8E;AAC9E,8EAA8E;AAC9E,6EAA6E;AAC7E,EAAE;AACF,8EAA8E;AAC9E,4EAA4E;AAC5E,4EAA4E;AAC5E,8EAA8E;AAC9E,0EAA0E;AAE1E,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AA4EhC;;;;4DAI4D;AAC5D,MAAM,CAAC,MAAM,yBAAyB,GAAG,CAAC,eAAe,EAAE,SAAS,EAAE,UAAU,EAAE,QAAQ,EAAE,SAAS,CAAU,CAAC;AAEhH;;;;;;;oEAOoE;AACpE,MAAM,UAAU,oBAAoB,CAAC,GAAmE;IACtG,OAAO,GAAG,CAAC,kBAAkB,IAAI,GAAG,CAAC,qBAAqB,CAAC,MAAM,KAAK,CAAC,CAAC;AAC1E,CAAC;AAED,MAAM,WAAW,GAAG,oCAAoC,CAAC;AACzD,MAAM,SAAS,GAAG,sCAAsC,CAAC;AACzD,MAAM,sBAAsB,GAAG,2BAA2B,CAAC;AAC3D,MAAM,SAAS,GAAG,2BAA2B,CAAC;AAE9C;;;;;;GAMG;AACH,SAAS,kBAAkB,CAAC,IAAY;IACtC,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;IAClC,MAAM,KAAK,GAAG,KAAK,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,sBAAsB,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC;IACrE,IAAI,KAAK,KAAK,CAAC,CAAC;QAAE,OAAO,EAAE,kBAAkB,EAAE,CAAC,EAAE,sBAAsB,EAAE,CAAC,EAAE,CAAC;IAE9E,IAAI,kBAAkB,GAAG,CAAC,CAAC;IAC3B,IAAI,sBAAsB,GAAG,CAAC,CAAC;IAC/B,KAAK,IAAI,CAAC,GAAG,KAAK,GAAG,CAAC,EAAE,CAAC,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QAC9C,IAAI,WAAW,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;YAAE,MAAM;QACtC,MAAM,CAAC,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC;QACpC,IAAI,CAAC,CAAC;YAAE,SAAS;QACjB,kBAAkB,EAAE,CAAC;QACrB,IAAI,CAAC,CAAC,CAAC,CAAC,KAAK,GAAG;YAAE,sBAAsB,EAAE,CAAC;IAC7C,CAAC;IACD,OAAO,EAAE,kBAAkB,EAAE,sBAAsB,EAAE,CAAC;AACxD,CAAC;AAED;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,aAAa,CAAC,GAAW;IACvC,MAAM,OAAO,GAAG,GAAG,CAAC,IAAI,EAAE,CAAC;IAC3B,MAAM,CAAC,GAAG,OAAO,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC;IACnC,OAAO,CAAC;QACN,CAAC,CAAC,EAAE,GAAG,EAAE,OAAO,EAAE,QAAQ,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,WAAW,EAAE,EAAE,OAAO,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE;QACvE,CAAC,CAAC,EAAE,GAAG,EAAE,OAAO,EAAE,QAAQ,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;AACtD,CAAC;AAED,SAAS,iBAAiB,CAAC,GAAuB;IAChD,IAAI,CAAC,GAAG;QAAE,OAAO,EAAE,CAAC;IACpB,OAAO,GAAG;SACP,KAAK,CAAC,GAAG,CAAC;SACV,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;SACpB,MAAM,CAAC,OAAO,CAAC;SACf,GAAG,CAAC,aAAa,CAAC,CAAC;AACxB,CAAC;AAED,SAAS,YAAY,CAAC,GAAuB;IAC3C,IAAI,CAAC,GAAG;QAAE,OAAO,IAAI,CAAC;IACtB,MAAM,CAAC,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC;IACtB,OAAO,MAAM,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;AACjD,CAAC;AAED;;;;;;;;;GASG;AACH,SAAS,gBAAgB,CACvB,QAAgB;IAEhB,IAAI,CAAC,cAAc,CAAC,IAAI,CAAC,QAAQ,CAAC;QAAE,OAAO,EAAE,MAAM,EAAE,IAAI,GAAG,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,kBAAkB,EAAE,KAAK,EAAE,CAAC;IAE5G,MAAM,KAAK,GAAG,QAAQ,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;IACtC,MAAM,MAAM,GAAG,IAAI,GAAG,EAAkB,CAAC;IACzC,IAAI,UAAU,GAAG,CAAC,CAAC,CAAC;IACpB,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QACtC,IAAI,UAAU,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;YAC9B,UAAU,GAAG,CAAC,CAAC;YACf,MAAM;QACR,CAAC;QACD,MAAM,CAAC,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,4BAA4B,CAAC,CAAC;QACvD,IAAI,CAAC,CAAC;YAAE,SAAS;QACjB,MAAM,KAAK,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,OAAO,CAAC,cAAc,EAAE,EAAE,CAAC,CAAC;QACtD,IAAI,KAAK;YAAE,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,WAAW,EAAE,EAAE,KAAK,CAAC,CAAC;IACnD,CAAC;IACD,IAAI,UAAU,KAAK,CAAC,CAAC;QAAE,OAAO,EAAE,MAAM,EAAE,IAAI,GAAG,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,kBAAkB,EAAE,KAAK,EAAE,CAAC;IAC/F,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,KAAK,CAAC,KAAK,CAAC,UAAU,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,kBAAkB,EAAE,IAAI,EAAE,CAAC;AAC5F,CAAC;AAED,MAAM,UAAU,aAAa,CAAC,QAAgB;IAC5C,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,kBAAkB,EAAE,GAAG,gBAAgB,CAAC,QAAQ,CAAC,CAAC;IACxE,MAAM,WAAW,GAAG,IAAI,CAAC,KAAK,CAAC,WAAW,CAAC,CAAC;IAC5C,MAAM,iBAAiB,GAAG,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;IAC/C,MAAM,MAAM,GAAG,iBAAiB;QAC9B,CAAC,CAAC,iBAAiB,CAAC,WAAW,EAAE;QACjC,CAAC,CAAC,WAAW;YACX,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE;YACrC,CAAC,CAAC,IAAI,CAAC;IACX,MAAM,YAAY,GAAG,MAAM,CAAC,GAAG,CAAC,YAAY,CAAC,CAAC;IAC9C,MAAM,EAAE,kBAAkB,EAAE,sBAAsB,EAAE,GAAG,kBAAkB,CAAC,IAAI,CAAC,CAAC;IAChF,OAAO;QACL,GAAG,EAAE,QAAQ;QACb,IAAI;QACJ,MAAM;QACN,OAAO,EAAE,YAAY,CAAC,MAAM,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;QAC5C,SAAS,EAAE,YAAY,CAAC,CAAC,CAAC,aAAa,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC,IAAI;QAC5D,UAAU,EAAE,iBAAiB,CAAC,MAAM,CAAC,GAAG,CAAC,aAAa,CAAC,CAAC;QACxD,YAAY,EAAE,iBAAiB,CAAC,MAAM,CAAC,GAAG,CAAC,eAAe,CAAC,CAAC;QAC5D,aAAa,EAAE,MAAM,CAAC,GAAG,CAAC,eAAe,CAAC,IAAI,IAAI;QAClD,kBAAkB;QAClB,qBAAqB,EAAE,yBAAyB,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;QAC9E,IAAI,EAAE,MAAM,CAAC,KAAK,CAAC,IAAI,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,CAAW;QACpD,kBAAkB;QAClB,sBAAsB;KACvB,CAAC;AACJ,CAAC"}
@@ -0,0 +1,11 @@
1
+ import { NormalizedModel } from "../model/model.js";
2
+ import { RefsResult } from "../model/refs.js";
3
+ import { Diagnostic } from "../model/validate.js";
4
+ /**
5
+ * Readiness gate for one slice, named by its export key (`refs.sliceKeys`). `baseDir` is the
6
+ * `.em` file's directory, same convention every other doc/note path in em uses. Always warning-
7
+ * severity (fits validate's existing warning-producer shape) except for a bad `sliceKey` itself,
8
+ * which is an error — a CLI-argument mistake, not a model-quality finding. The `--slice-ready`
9
+ * flag in src/cli.ts is what decides whether these warnings gate the exit code.
10
+ */
11
+ export declare function validateSliceReady(model: NormalizedModel, refs: RefsResult, baseDir: string, sliceKey: string): Diagnostic[];
@@ -0,0 +1,85 @@
1
+ // SPDX-License-Identifier: MIT
2
+ // `em validate --slice-ready <key>` (MIL-87): the handoff gate without the bridge. Gives em
3
+ // itself a native version of the readiness check that used to live only in em-sdd-bridge's
4
+ // `assertReadyToImplement` — status: ready-to-implement AND every Open Question checked —
5
+ // so any toolchain reading slice docs directly (not just ones routed through the bridge) has
6
+ // a gate to enforce.
7
+ //
8
+ // Deliberately single-slice, opt-in — unlike catalog/lineageValidate.ts (MIL-84) and
9
+ // catalog/frontmatterCoherenceValidate.ts (MIL-85), which run unconditionally on every
10
+ // `em validate` because they only fire on genuine anomalies. "Not yet ready-to-implement" /
11
+ // "has unchecked Open Questions" is the NORMAL state of most slices most of the time (drafts,
12
+ // reviewed-but-not-ready-yet, ...) — splicing this into the unconditional diagnostic set would
13
+ // make plain `em validate` permanently noisy on any healthy in-progress model. So this only
14
+ // ever runs for the one slice named by `--slice-ready <key>`.
15
+ //
16
+ // "Note binding" (the ticket's own phrase) means reusing docJoin.ts's resolveSliceDocJoin — the
17
+ // same note-gated resolution `em export` (MIL-91) uses — rather than the filename-only
18
+ // readSliceDoc/existsSync convention `em catalog`/`em render` deliberately use instead (that
19
+ // convention has a tested invariant of ignoring `note` entirely; this module doesn't touch it).
20
+ // A slice with no `note "slices/<key>.md"` element bound to it isn't ready by definition: there's
21
+ // nothing ratified to check status/Open Questions against.
22
+ //
23
+ // version/status/link coherence (MIL-85) is folded in for free at the CLI layer (src/cli.ts):
24
+ // validateFrontmatterCoherence already runs unconditionally and already tags its diagnostics
25
+ // with `refs: [sliceKey]`, so the `--slice-ready` branch filters the full combined diagnostic
26
+ // set by that ref instead of this module re-deriving classifyImplementationDrift.
27
+ import { makeDiag, pushDiag } from "../model/rules.js";
28
+ import { resolveSliceDocJoin } from "./docJoin.js";
29
+ import { readSliceDoc } from "./readSliceDoc.js";
30
+ /**
31
+ * Readiness gate for one slice, named by its export key (`refs.sliceKeys`). `baseDir` is the
32
+ * `.em` file's directory, same convention every other doc/note path in em uses. Always warning-
33
+ * severity (fits validate's existing warning-producer shape) except for a bad `sliceKey` itself,
34
+ * which is an error — a CLI-argument mistake, not a model-quality finding. The `--slice-ready`
35
+ * flag in src/cli.ts is what decides whether these warnings gate the exit code.
36
+ */
37
+ export function validateSliceReady(model, refs, baseDir, sliceKey) {
38
+ const sliceIndex = refs.sliceKeys.indexOf(sliceKey);
39
+ if (sliceIndex === -1) {
40
+ return [
41
+ makeDiag("slice-ready-unknown-slice", {
42
+ message: `no slice with export key "${sliceKey}" in this model`,
43
+ refs: [sliceKey],
44
+ }),
45
+ ];
46
+ }
47
+ const slice = model.slices[sliceIndex];
48
+ const { doc, diagnostics: joinDiagnostics } = resolveSliceDocJoin(slice, sliceKey, baseDir, (id) => refs.refById.get(id));
49
+ if (doc.reason === "no-doc-bound") {
50
+ return [
51
+ makeDiag("slice-ready-no-doc-bound", {
52
+ message: `slice "${sliceKey}" has no doc bound via \`note "slices/${sliceKey}.md"\` — bind a slice doc before it can be ready-to-implement`,
53
+ line: slice.line,
54
+ refs: [sliceKey],
55
+ }),
56
+ ];
57
+ }
58
+ if (doc.reason === "binding-missing-file" || doc.reason === "frontmatter-invalid") {
59
+ // Reuse docJoin's own diagnostics verbatim — don't re-code binding-missing-file/
60
+ // frontmatter-invalid a second time under a slice-ready-specific code.
61
+ return joinDiagnostics;
62
+ }
63
+ // doc.reason === null: found, usable frontmatter.
64
+ const diags = [];
65
+ if (doc.status !== "ready-to-implement") {
66
+ pushDiag(diags, "slice-ready-status-not-ready", {
67
+ message: `slice "${sliceKey}" is status: ${doc.status ?? "(none)"}, not ready-to-implement`,
68
+ line: slice.line,
69
+ refs: [sliceKey],
70
+ });
71
+ }
72
+ // resolveSliceDocJoin's SliceDocExport deliberately never carries Open Questions counts (same
73
+ // hard boundary that keeps doc.html/doc.raw out of it) — re-read via readSliceDoc for the
74
+ // full SliceDoc. Non-null: doc.reason === null already implies the file exists and parses.
75
+ const parsed = readSliceDoc(baseDir, sliceKey);
76
+ if (parsed.openQuestionsUnchecked > 0) {
77
+ pushDiag(diags, "slice-ready-open-questions-unchecked", {
78
+ message: `slice "${sliceKey}" has ${parsed.openQuestionsUnchecked} of ${parsed.openQuestionsTotal} Open Question(s) unchecked`,
79
+ line: slice.line,
80
+ refs: [sliceKey],
81
+ });
82
+ }
83
+ return diags;
84
+ }
85
+ //# sourceMappingURL=sliceReadyValidate.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sliceReadyValidate.js","sourceRoot":"","sources":["../../src/catalog/sliceReadyValidate.ts"],"names":[],"mappings":"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"}
@@ -1,3 +1,4 @@
1
+ import { SliceDoc } from "../catalog/sliceDoc.js";
1
2
  /** Resolved `em diff` invocation, or a user-facing `error` for an invalid form. */
2
3
  export type DiffPlan = {
3
4
  error: string;
@@ -42,3 +43,22 @@ export type RevisionResult = {
42
43
  * unit-testable without a real repository.
43
44
  */
44
45
  export declare function resolveRevision(file: string, rev: string, runGit?: GitRunner): RevisionResult;
46
+ /**
47
+ * Resolve a slice's doc (`slices/<sliceKey>.md`, relative to `anchorFile`'s directory — same
48
+ * convention as every other doc/note path in em) at git revision `rev`. Unlike
49
+ * `resolveRevision`, a missing doc at a revision is routine (not every slice had a doc yet, or
50
+ * the doc simply isn't tracked) rather than a user-facing error — so every failure mode
51
+ * collapses to `null`, for `em diff`'s lineage annotation (MIL-84) to treat as "nothing to
52
+ * annotate with" rather than aborting the diff.
53
+ */
54
+ export declare function resolveDocAtRevision(anchorFile: string, sliceKey: string, rev: string, runGit?: GitRunner): SliceDoc | null;
55
+ /**
56
+ * Slice doc keys (filename stems under `slices/`, relative to `anchorFile`'s directory — same
57
+ * anchor convention as `resolveDocAtRevision`) present at git revision `rev`. Used by `em ledger`
58
+ * (MIL-89) to discover the candidate set of docs to compare across two revisions, reusing the
59
+ * same repo-root resolution `showAtRevision` does rather than a second, divergent way of finding
60
+ * "what slice docs exist here." Returns `[]` (not an error) when the repo can't be resolved or
61
+ * `slices/` didn't exist at `rev` — both routine (a model with no slice docs yet), matching
62
+ * `resolveDocAtRevision`'s "missing is routine" stance.
63
+ */
64
+ export declare function listSliceKeysAtRevision(anchorFile: string, rev: string, runGit?: GitRunner): string[];
@@ -4,7 +4,8 @@
4
4
  // unit-tested without spawning a subprocess or a real git repo (cli.ts keeps the
5
5
  // process.exit/console wiring; see test/diff-inputs.test.ts).
6
6
  import { spawnSync } from "node:child_process";
7
- import { dirname, resolve } from "node:path";
7
+ import { basename, dirname, resolve } from "node:path";
8
+ import { parseSliceDoc } from "../catalog/sliceDoc.js";
8
9
  /**
9
10
  * Validate the `em diff` argument/flag combination. Pure — no I/O. The two forms
10
11
  * (two files, or one file across git revisions) are mutually exclusive.
@@ -33,30 +34,86 @@ export const realGit = (args) => {
33
34
  const r = spawnSync("git", args, { encoding: "utf8" });
34
35
  return { status: r.status, stdout: r.stdout ?? "", stderr: r.stderr ?? "" };
35
36
  };
37
+ /**
38
+ * Read `targetAbs`'s content at git revision `rev`, resolving the repo root from
39
+ * `anchorFile`'s directory (a file known to be somewhere inside the repo — doesn't itself have
40
+ * to be `targetAbs`, e.g. a slice doc resolved relative to its `.em` file's location). The git
41
+ * runner is injectable so every failure branch is unit-testable without a real repository.
42
+ *
43
+ * Existence/path lookup uses `git ls-tree -r --name-only <rev>` — scoped to `rev` itself, not
44
+ * `git ls-files` (which reflects the *current* index/working tree). A doc that existed at
45
+ * `rev` but has since been deleted — the routine case for a `superseded-by`-carrying doc, which
46
+ * gets removed in the very commit that performs the split/merge/removal it records — would
47
+ * otherwise resolve to "not tracked" even though `git show <rev>:<path>` can read it just fine.
48
+ */
49
+ function showAtRevision(runGit, anchorFile, targetAbs, rev) {
50
+ const toplevel = runGit(["-C", dirname(resolve(anchorFile)), "rev-parse", "--show-toplevel"]);
51
+ if (toplevel.status !== 0)
52
+ return { ok: false, reason: "no-repo" };
53
+ const repoRoot = toplevel.stdout.trim();
54
+ const lsTree = runGit(["-C", repoRoot, "ls-tree", "-r", "--name-only", rev, "--", targetAbs]);
55
+ const relPath = lsTree.stdout.trim().split("\n")[0];
56
+ if (!relPath)
57
+ return { ok: false, reason: "not-tracked", repoRoot };
58
+ const show = runGit(["-C", repoRoot, "show", `${rev}:${relPath}`]);
59
+ if (show.status !== 0) {
60
+ return { ok: false, reason: "show-failed", relPath, detail: (show.stderr || "").trim() || "unknown git error" };
61
+ }
62
+ return { ok: true, content: show.stdout };
63
+ }
36
64
  /**
37
65
  * Resolve `file`'s content at git revision `rev` via `git show <rev>:<repo-relative-path>`.
38
66
  * The git runner is injectable so the not-a-repo / not-tracked / bad-rev branches are
39
67
  * unit-testable without a real repository.
40
68
  */
41
69
  export function resolveRevision(file, rev, runGit = realGit) {
42
- const abs = resolve(file);
43
- const toplevel = runGit(["-C", dirname(abs), "rev-parse", "--show-toplevel"]);
44
- if (toplevel.status !== 0) {
45
- return { ok: false, message: `em diff: ${file} is not inside a git repository (needed for --from/--to)` };
70
+ const result = showAtRevision(runGit, file, resolve(file), rev);
71
+ if (result.ok)
72
+ return result;
73
+ switch (result.reason) {
74
+ case "no-repo":
75
+ return { ok: false, message: `em diff: ${file} is not inside a git repository (needed for --from/--to)` };
76
+ case "not-tracked":
77
+ return { ok: false, message: `em diff: ${file} is not tracked by git in ${result.repoRoot}` };
78
+ case "show-failed":
79
+ return { ok: false, message: `em diff: cannot read ${result.relPath} at revision "${rev}": ${result.detail}` };
46
80
  }
81
+ }
82
+ /**
83
+ * Resolve a slice's doc (`slices/<sliceKey>.md`, relative to `anchorFile`'s directory — same
84
+ * convention as every other doc/note path in em) at git revision `rev`. Unlike
85
+ * `resolveRevision`, a missing doc at a revision is routine (not every slice had a doc yet, or
86
+ * the doc simply isn't tracked) rather than a user-facing error — so every failure mode
87
+ * collapses to `null`, for `em diff`'s lineage annotation (MIL-84) to treat as "nothing to
88
+ * annotate with" rather than aborting the diff.
89
+ */
90
+ export function resolveDocAtRevision(anchorFile, sliceKey, rev, runGit = realGit) {
91
+ const targetAbs = resolve(dirname(resolve(anchorFile)), "slices", `${sliceKey}.md`);
92
+ const result = showAtRevision(runGit, anchorFile, targetAbs, rev);
93
+ return result.ok ? parseSliceDoc(result.content) : null;
94
+ }
95
+ /**
96
+ * Slice doc keys (filename stems under `slices/`, relative to `anchorFile`'s directory — same
97
+ * anchor convention as `resolveDocAtRevision`) present at git revision `rev`. Used by `em ledger`
98
+ * (MIL-89) to discover the candidate set of docs to compare across two revisions, reusing the
99
+ * same repo-root resolution `showAtRevision` does rather than a second, divergent way of finding
100
+ * "what slice docs exist here." Returns `[]` (not an error) when the repo can't be resolved or
101
+ * `slices/` didn't exist at `rev` — both routine (a model with no slice docs yet), matching
102
+ * `resolveDocAtRevision`'s "missing is routine" stance.
103
+ */
104
+ export function listSliceKeysAtRevision(anchorFile, rev, runGit = realGit) {
105
+ const toplevel = runGit(["-C", dirname(resolve(anchorFile)), "rev-parse", "--show-toplevel"]);
106
+ if (toplevel.status !== 0)
107
+ return [];
47
108
  const repoRoot = toplevel.stdout.trim();
48
- const lsFiles = runGit(["-C", repoRoot, "ls-files", "--full-name", "--", abs]);
49
- const relPath = lsFiles.stdout.trim().split("\n")[0];
50
- if (!relPath) {
51
- return { ok: false, message: `em diff: ${file} is not tracked by git in ${repoRoot}` };
52
- }
53
- const show = runGit(["-C", repoRoot, "show", `${rev}:${relPath}`]);
54
- if (show.status !== 0) {
55
- return {
56
- ok: false,
57
- message: `em diff: cannot read ${relPath} at revision "${rev}": ${(show.stderr || "").trim() || "unknown git error"}`,
58
- };
59
- }
60
- return { ok: true, content: show.stdout };
109
+ const slicesDirAbs = resolve(dirname(resolve(anchorFile)), "slices");
110
+ const lsTree = runGit(["-C", repoRoot, "ls-tree", "-r", "--name-only", rev, "--", slicesDirAbs]);
111
+ if (lsTree.status !== 0)
112
+ return [];
113
+ return lsTree.stdout
114
+ .split("\n")
115
+ .map((line) => line.trim())
116
+ .filter((line) => line.endsWith(".md"))
117
+ .map((line) => basename(line, ".md"));
61
118
  }
62
119
  //# sourceMappingURL=diff-inputs.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"diff-inputs.js","sourceRoot":"","sources":["../../src/cli/diff-inputs.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"diff-inputs.js","sourceRoot":"","sources":["../../src/cli/diff-inputs.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,38 @@
1
+ import { GitRunner } from "./diff-inputs.js";
2
+ export type LedgerFindingCode = "ledger-content-without-version-bump" | "ledger-version-without-content-change" | "ledger-version-regression";
3
+ export interface LedgerFinding {
4
+ sliceKey: string;
5
+ code: LedgerFindingCode;
6
+ message: string;
7
+ oldVersion: number;
8
+ newVersion: number;
9
+ bodyChanged: boolean;
10
+ lineageChanged: boolean;
11
+ }
12
+ export type LedgerSkipReason = "no-prior-revision" | "deleted" | "frontmatter-invalid";
13
+ export interface LedgerSkip {
14
+ sliceKey: string;
15
+ reason: LedgerSkipReason;
16
+ }
17
+ export interface LedgerCheckResult {
18
+ findings: LedgerFinding[];
19
+ skipped: LedgerSkip[];
20
+ /** Total slice keys considered (the union of both revisions' `slices/*.md` — every key that
21
+ * produced either a finding, a skip, or a clean pass). Lets a caller report "N checked"
22
+ * without re-deriving it from findings.length + skipped.length + (silent clean count). */
23
+ checkedCount: number;
24
+ }
25
+ /**
26
+ * Compare every slice doc's version:/content agreement between two revisions of `anchorFile`'s
27
+ * repo. `to` null means "the current working tree" (same convention as `em diff`). Pure aside
28
+ * from the injected GitRunner and fs reads via readSliceDoc — no process.exit, no console.
29
+ *
30
+ * Deliberately excludes `status`/`implementedIn` from the content comparison: those change
31
+ * independently by design during re-ratification (docs/slice-doc-schema.md, "`status` under
32
+ * re-ratification") — including them would false-positive on every ordinary lifecycle
33
+ * transition. Also excludes `pattern`/`swimlane`/`schemaVersion`: decorative, not currently
34
+ * exposed on `SliceDoc` at all. Body comparison is `.trim()`-only — strips leading/trailing
35
+ * whitespace, avoiding a false positive from a trailing-newline/CRLF-only re-save, without
36
+ * risking a false negative on a real prose edit.
37
+ */
38
+ export declare function checkLedger(anchorFile: string, from: string, to: string | null, runGit?: GitRunner): LedgerCheckResult;
@@ -0,0 +1,116 @@
1
+ // SPDX-License-Identifier: MIT
2
+ // `em ledger` (MIL-89): checks that a slice doc's `version:` frontmatter field and its content
3
+ // (body + lineage refs) always change together across two git revisions — a version bump with
4
+ // no real content change is a no-op ledger entry, a content change with no version bump is a
5
+ // stale ratification signal, and a version going backwards is a typo/regression. Opt-in,
6
+ // CI-recipe tier — never folded into `em validate`, which stays a fast function of the current
7
+ // tree (see lineageValidate.ts's header comment, which named this exact shape during MIL-84).
8
+ //
9
+ // Sibling to diff-inputs.ts in role: testable input handling / git-revision logic factored out
10
+ // of cli.ts, with an injectable GitRunner and no process.exit/console — see
11
+ // test/ledgerCheck.test.ts. Reuses resolveDocAtRevision/listSliceKeysAtRevision rather than
12
+ // second, divergent git plumbing.
13
+ import { existsSync, readdirSync } from "node:fs";
14
+ import { basename, dirname, join, resolve } from "node:path";
15
+ import { hasUsableFrontmatter } from "../catalog/sliceDoc.js";
16
+ import { readSliceDoc } from "../catalog/readSliceDoc.js";
17
+ import { realGit, listSliceKeysAtRevision, resolveDocAtRevision } from "./diff-inputs.js";
18
+ /** Slice keys currently on disk under `<baseDir>/slices/*.md` — the working-tree counterpart to
19
+ * `listSliceKeysAtRevision`, used when `to` is null (compare against the working tree). */
20
+ function listSliceKeysOnDisk(baseDir) {
21
+ const dir = join(baseDir, "slices");
22
+ if (!existsSync(dir))
23
+ return [];
24
+ return readdirSync(dir)
25
+ .filter((name) => name.endsWith(".md"))
26
+ .map((name) => basename(name, ".md"));
27
+ }
28
+ /** True when two lineage refs (or their absence) carry the same declared text — compared on
29
+ * `raw` so a malformed ref (null sliceKey/version) still counts as a real change if its text
30
+ * changes, not silently treated as equal to another malformed ref. */
31
+ function refEqual(a, b) {
32
+ if (a === null || b === null)
33
+ return a === b;
34
+ return a.raw === b.raw;
35
+ }
36
+ function refListEqual(a, b) {
37
+ if (a.length !== b.length)
38
+ return false;
39
+ return a.every((ref, i) => refEqual(ref, b[i]));
40
+ }
41
+ /** True when either side's declared lineage (`split-from`/`merged-from`/`superseded-by`)
42
+ * differs — these describe what the doc *asserts*, same-commit-authoring convention means a
43
+ * legitimate change here always co-occurs with a version bump (docs/slice-doc-schema.md). */
44
+ function lineageChanged(oldDoc, newDoc) {
45
+ return (!refEqual(oldDoc.splitFrom, newDoc.splitFrom) ||
46
+ !refListEqual(oldDoc.mergedFrom, newDoc.mergedFrom) ||
47
+ !refListEqual(oldDoc.supersededBy, newDoc.supersededBy));
48
+ }
49
+ /**
50
+ * Compare every slice doc's version:/content agreement between two revisions of `anchorFile`'s
51
+ * repo. `to` null means "the current working tree" (same convention as `em diff`). Pure aside
52
+ * from the injected GitRunner and fs reads via readSliceDoc — no process.exit, no console.
53
+ *
54
+ * Deliberately excludes `status`/`implementedIn` from the content comparison: those change
55
+ * independently by design during re-ratification (docs/slice-doc-schema.md, "`status` under
56
+ * re-ratification") — including them would false-positive on every ordinary lifecycle
57
+ * transition. Also excludes `pattern`/`swimlane`/`schemaVersion`: decorative, not currently
58
+ * exposed on `SliceDoc` at all. Body comparison is `.trim()`-only — strips leading/trailing
59
+ * whitespace, avoiding a false positive from a trailing-newline/CRLF-only re-save, without
60
+ * risking a false negative on a real prose edit.
61
+ */
62
+ export function checkLedger(anchorFile, from, to, runGit = realGit) {
63
+ const baseDir = dirname(resolve(anchorFile));
64
+ const oldKeys = listSliceKeysAtRevision(anchorFile, from, runGit);
65
+ const newKeys = to ? listSliceKeysAtRevision(anchorFile, to, runGit) : listSliceKeysOnDisk(baseDir);
66
+ const keys = [...new Set([...oldKeys, ...newKeys])].sort();
67
+ const findings = [];
68
+ const skipped = [];
69
+ for (const sliceKey of keys) {
70
+ const oldDoc = resolveDocAtRevision(anchorFile, sliceKey, from, runGit);
71
+ if (!oldDoc) {
72
+ skipped.push({ sliceKey, reason: "no-prior-revision" });
73
+ continue;
74
+ }
75
+ const newDoc = to ? resolveDocAtRevision(anchorFile, sliceKey, to, runGit) : readSliceDoc(baseDir, sliceKey);
76
+ if (!newDoc) {
77
+ skipped.push({ sliceKey, reason: "deleted" });
78
+ continue;
79
+ }
80
+ // hasUsableFrontmatter() only checks that the `version` key is *present*
81
+ // (missingRequiredFields), not that its value parsed to a valid positive integer —
82
+ // `version: abc`/`version: 0`/`version: 1.5` all pass that gate with SliceDoc.version still
83
+ // null (see sliceDoc.ts's parseVersion()). lineageValidate.ts hits the same gap and handles
84
+ // it the same way: an explicit null check, routed to the same skip a missing key gets,
85
+ // rather than a non-null assertion that would corrupt a finding's message/classification.
86
+ if (!hasUsableFrontmatter(oldDoc) || !hasUsableFrontmatter(newDoc) || oldDoc.version === null || newDoc.version === null) {
87
+ skipped.push({ sliceKey, reason: "frontmatter-invalid" });
88
+ continue;
89
+ }
90
+ const oldVersion = oldDoc.version;
91
+ const newVersion = newDoc.version;
92
+ const bodyChanged = oldDoc.body.trim() !== newDoc.body.trim();
93
+ const docsLineageChanged = lineageChanged(oldDoc, newDoc);
94
+ const contentChanged = bodyChanged || docsLineageChanged;
95
+ let code = null;
96
+ if (newVersion < oldVersion) {
97
+ code = "ledger-version-regression";
98
+ }
99
+ else if (contentChanged && newVersion === oldVersion) {
100
+ code = "ledger-content-without-version-bump";
101
+ }
102
+ else if (!contentChanged && newVersion > oldVersion) {
103
+ code = "ledger-version-without-content-change";
104
+ }
105
+ if (!code)
106
+ continue;
107
+ const message = code === "ledger-version-regression"
108
+ ? `slice "${sliceKey}": version went backwards (v${oldVersion} -> v${newVersion})`
109
+ : code === "ledger-content-without-version-bump"
110
+ ? `slice "${sliceKey}": doc content changed but version: didn't bump (still v${oldVersion})`
111
+ : `slice "${sliceKey}": version: bumped (v${oldVersion} -> v${newVersion}) but doc content is unchanged`;
112
+ findings.push({ sliceKey, code, message, oldVersion, newVersion, bodyChanged, lineageChanged: docsLineageChanged });
113
+ }
114
+ return { findings, skipped, checkedCount: keys.length };
115
+ }
116
+ //# sourceMappingURL=ledgerCheck.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ledgerCheck.js","sourceRoot":"","sources":["../../src/cli/ledgerCheck.ts"],"names":[],"mappings":"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"}