@milehimikey/em 1.6.0 → 1.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/skills/event-modeling/SKILL.md +60 -12
- package/.claude/skills/event-modeling/reference/conform.md +30 -8
- package/.claude/skills/event-modeling/reference/em-dsl.md +145 -23
- package/.claude/skills/event-modeling/reference/extract.md +2 -1
- package/.claude/skills/event-modeling/reference/implement.md +172 -0
- package/.claude/skills/event-modeling/reference/methodology.md +5 -0
- package/.claude/skills/event-modeling/templates/conformance-report.md +15 -9
- package/.claude/skills/event-modeling/templates/model-readme.md +3 -3
- package/.claude/skills/event-modeling/templates/slice.md +70 -4
- package/.claude/skills/event-modeling/templates/state.md +6 -5
- package/README.md +1 -0
- package/dist/catalog/build.d.ts +5 -0
- package/dist/catalog/build.js +1 -3
- package/dist/catalog/build.js.map +1 -1
- package/dist/catalog/docJoin.d.ts +39 -0
- package/dist/catalog/docJoin.js +86 -0
- package/dist/catalog/docJoin.js.map +1 -0
- package/dist/catalog/driftSignal.d.ts +16 -0
- package/dist/catalog/driftSignal.js +24 -0
- package/dist/catalog/driftSignal.js.map +1 -0
- package/dist/catalog/frontmatterCoherenceValidate.d.ts +8 -0
- package/dist/catalog/frontmatterCoherenceValidate.js +52 -0
- package/dist/catalog/frontmatterCoherenceValidate.js.map +1 -0
- package/dist/catalog/lineageValidate.d.ts +9 -0
- package/dist/catalog/lineageValidate.js +161 -0
- package/dist/catalog/lineageValidate.js.map +1 -0
- package/dist/catalog/readSliceDoc.d.ts +2 -0
- package/dist/catalog/readSliceDoc.js +21 -0
- package/dist/catalog/readSliceDoc.js.map +1 -0
- package/dist/catalog/sliceDoc.d.ts +94 -3
- package/dist/catalog/sliceDoc.js +157 -7
- package/dist/catalog/sliceDoc.js.map +1 -1
- package/dist/catalog/sliceReadyValidate.d.ts +11 -0
- package/dist/catalog/sliceReadyValidate.js +85 -0
- package/dist/catalog/sliceReadyValidate.js.map +1 -0
- package/dist/cli/diff-inputs.d.ts +20 -0
- package/dist/cli/diff-inputs.js +75 -18
- package/dist/cli/diff-inputs.js.map +1 -1
- package/dist/cli/ledgerCheck.d.ts +38 -0
- package/dist/cli/ledgerCheck.js +116 -0
- package/dist/cli/ledgerCheck.js.map +1 -0
- package/dist/cli/skillCheck.d.ts +18 -0
- package/dist/cli/skillCheck.js +63 -0
- package/dist/cli/skillCheck.js.map +1 -0
- package/dist/cli/skillSync.d.ts +22 -0
- package/dist/cli/skillSync.js +69 -0
- package/dist/cli/skillSync.js.map +1 -0
- package/dist/cli/skillVersionCheck.d.ts +32 -0
- package/dist/cli/skillVersionCheck.js +145 -0
- package/dist/cli/skillVersionCheck.js.map +1 -0
- package/dist/cli.d.ts +3 -1
- package/dist/cli.js +199 -32
- package/dist/cli.js.map +1 -1
- package/dist/emit/diffJson.d.ts +1 -1
- package/dist/emit/diffJson.js +18 -14
- package/dist/emit/diffJson.js.map +1 -1
- package/dist/emit/json.d.ts +10 -25
- package/dist/emit/json.js +70 -103
- package/dist/emit/json.js.map +1 -1
- package/dist/emit/ledgerJson.d.ts +5 -0
- package/dist/emit/ledgerJson.js +25 -0
- package/dist/emit/ledgerJson.js.map +1 -0
- package/dist/emit/skillCheckJson.d.ts +5 -0
- package/dist/emit/skillCheckJson.js +32 -0
- package/dist/emit/skillCheckJson.js.map +1 -0
- package/dist/model/diff.d.ts +44 -1
- package/dist/model/diff.js +51 -9
- package/dist/model/diff.js.map +1 -1
- package/dist/model/model.d.ts +1 -1
- package/dist/model/refs.d.ts +23 -0
- package/dist/model/refs.js +75 -0
- package/dist/model/refs.js.map +1 -0
- package/dist/model/rules.d.ts +252 -0
- package/dist/model/rules.js +253 -0
- package/dist/model/rules.js.map +1 -0
- package/dist/model/validate.d.ts +23 -1
- package/dist/model/validate.js +184 -49
- package/dist/model/validate.js.map +1 -1
- package/dist/parser/ast.d.ts +3 -2
- package/dist/parser/parser.js +36 -14
- package/dist/parser/parser.js.map +1 -1
- package/dist/pipeline.d.ts +9 -0
- package/dist/pipeline.js +4 -2
- package/dist/pipeline.js.map +1 -1
- package/dist/render/sliceStatus.js +1 -1
- package/dist/render/sliceStatus.js.map +1 -1
- package/dist/util/isMainModule.d.ts +14 -0
- package/dist/util/isMainModule.js +33 -0
- package/dist/util/isMainModule.js.map +1 -0
- package/dist/util/walkDir.d.ts +4 -0
- package/dist/util/walkDir.js +28 -0
- package/dist/util/walkDir.js.map +1 -0
- package/package.json +5 -2
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
// `em validate`'s frontmatter-coherence check (MIL-85): warns when a slice doc's `status` and
|
|
3
|
+
// `implementedIn` are genuinely incoherent — `status: implemented` with no `implementedIn` link
|
|
4
|
+
// at all. Deliberately narrow: re-ratifying a shipped slice flips `status` back off
|
|
5
|
+
// `implemented` while `implementedIn` keeps naming the prior version's PR (docs/slice-doc-
|
|
6
|
+
// schema.md, "`status` under re-ratification") — that combination is the EXPECTED drift signal
|
|
7
|
+
// of an unpropagated delta, not incoherence, and must stay silent here. See
|
|
8
|
+
// catalog/driftSignal.ts for the shared classification this check (and `em export`'s
|
|
9
|
+
// `slice.doc.driftSignal`) both read.
|
|
10
|
+
//
|
|
11
|
+
// Same fs-aware shape as catalog/lineageValidate.ts (MIL-84): a sibling module to
|
|
12
|
+
// model/validate.ts, not folded into it, because resolving a slice's doc needs `baseDir`/fs
|
|
13
|
+
// access that the rest of validate deliberately never touches.
|
|
14
|
+
//
|
|
15
|
+
// Requires usable frontmatter (`hasUsableFrontmatter()`, sliceDoc.ts) — the exact same gate
|
|
16
|
+
// `em export`'s docJoin.ts uses for `frontmatter-invalid`, shared as one function precisely so
|
|
17
|
+
// the two can't silently disagree on what counts as a readable doc. `implementedIn` is a
|
|
18
|
+
// frontmatter-only key (MIL-90/91) with no legacy body-label form (MIL-86's accepted-input
|
|
19
|
+
// dialect: `- **Status:** ...`, no frontmatter block), and every canonical field this check
|
|
20
|
+
// reads is itself frontmatter-only — so a doc with no fence, or one missing any required key,
|
|
21
|
+
// has nothing reliable enough to classify. Flagging it as incoherent would be a false positive
|
|
22
|
+
// against a doc that's merely unreadable, not one that's actually incoherent; stay silent
|
|
23
|
+
// rather than guess, matching export's classification of the same doc.
|
|
24
|
+
import { pushDiag } from "../model/rules.js";
|
|
25
|
+
import { classifyImplementationDrift } from "./driftSignal.js";
|
|
26
|
+
import { readSliceDoc } from "./readSliceDoc.js";
|
|
27
|
+
import { hasUsableFrontmatter } from "./sliceDoc.js";
|
|
28
|
+
/**
|
|
29
|
+
* Resolve every slice's doc and flag genuine status/implementedIn incoherence. `baseDir` is the
|
|
30
|
+
* `.em` file's directory (doc paths, like every other doc/note path in em, are relative to it).
|
|
31
|
+
*/
|
|
32
|
+
export function validateFrontmatterCoherence(model, refs, baseDir) {
|
|
33
|
+
const diags = [];
|
|
34
|
+
model.slices.forEach((slice, i) => {
|
|
35
|
+
const sliceKey = refs.sliceKeys[i];
|
|
36
|
+
const doc = readSliceDoc(baseDir, sliceKey);
|
|
37
|
+
if (!doc || !hasUsableFrontmatter(doc))
|
|
38
|
+
return;
|
|
39
|
+
const drift = classifyImplementationDrift(doc);
|
|
40
|
+
if (drift !== "implemented-without-link")
|
|
41
|
+
return;
|
|
42
|
+
// "unpropagated-delta" falls through here silently — that's the point: a re-ratified slice
|
|
43
|
+
// whose implementedIn still names prior work is expected, not a defect.
|
|
44
|
+
pushDiag(diags, "frontmatter-coherence-implemented-without-link", {
|
|
45
|
+
message: `slice "${sliceKey}" has status: implemented but no implementedIn link (v${doc.version ?? "?"})`,
|
|
46
|
+
line: slice.line,
|
|
47
|
+
refs: [sliceKey],
|
|
48
|
+
});
|
|
49
|
+
});
|
|
50
|
+
return diags;
|
|
51
|
+
}
|
|
52
|
+
//# sourceMappingURL=frontmatterCoherenceValidate.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"frontmatterCoherenceValidate.js","sourceRoot":"","sources":["../../src/catalog/frontmatterCoherenceValidate.ts"],"names":[],"mappings":"AAAA,+BAA+B;AAC/B,8FAA8F;AAC9F,gGAAgG;AAChG,oFAAoF;AACpF,2FAA2F;AAC3F,+FAA+F;AAC/F,4EAA4E;AAC5E,qFAAqF;AACrF,sCAAsC;AACtC,EAAE;AACF,kFAAkF;AAClF,4FAA4F;AAC5F,+DAA+D;AAC/D,EAAE;AACF,4FAA4F;AAC5F,+FAA+F;AAC/F,yFAAyF;AACzF,2FAA2F;AAC3F,4FAA4F;AAC5F,8FAA8F;AAC9F,+FAA+F;AAC/F,0FAA0F;AAC1F,uEAAuE;AAKvE,OAAO,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AAC7C,OAAO,EAAE,2BAA2B,EAAE,MAAM,kBAAkB,CAAC;AAC/D,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AACjD,OAAO,EAAE,oBAAoB,EAAE,MAAM,eAAe,CAAC;AAErD;;;GAGG;AACH,MAAM,UAAU,4BAA4B,CAAC,KAAsB,EAAE,IAAgB,EAAE,OAAe;IACpG,MAAM,KAAK,GAAiB,EAAE,CAAC;IAE/B,KAAK,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,KAAK,EAAE,CAAC,EAAE,EAAE;QAChC,MAAM,QAAQ,GAAG,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC;QACnC,MAAM,GAAG,GAAG,YAAY,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAC;QAC5C,IAAI,CAAC,GAAG,IAAI,CAAC,oBAAoB,CAAC,GAAG,CAAC;YAAE,OAAO;QAE/C,MAAM,KAAK,GAAG,2BAA2B,CAAC,GAAG,CAAC,CAAC;QAC/C,IAAI,KAAK,KAAK,0BAA0B;YAAE,OAAO;QACjD,2FAA2F;QAC3F,wEAAwE;QAExE,QAAQ,CAAC,KAAK,EAAE,gDAAgD,EAAE;YAChE,OAAO,EAAE,UAAU,QAAQ,yDAAyD,GAAG,CAAC,OAAO,IAAI,GAAG,GAAG;YACzG,IAAI,EAAE,KAAK,CAAC,IAAI;YAChB,IAAI,EAAE,CAAC,QAAQ,CAAC;SACjB,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,OAAO,KAAK,CAAC;AACf,CAAC"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { NormalizedModel } from "../model/model.js";
|
|
2
|
+
import { RefsResult } from "../model/refs.js";
|
|
3
|
+
import { Diagnostic } from "../model/validate.js";
|
|
4
|
+
/**
|
|
5
|
+
* Resolve every slice's lineage refs against the current tree and report what the current tree
|
|
6
|
+
* can prove wrong. `baseDir` is the `.em` file's directory (doc paths, like every other
|
|
7
|
+
* doc/note path in em, are relative to it).
|
|
8
|
+
*/
|
|
9
|
+
export declare function validateLineage(model: NormalizedModel, refs: RefsResult, baseDir: string): Diagnostic[];
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
// `em validate`'s lineage-ref resolution (MIL-84): checks what the *current tree* can prove
|
|
3
|
+
// wrong about a slice doc's `split-from`/`merged-from`/`superseded-by` frontmatter, and stays
|
|
4
|
+
// silent about what it can't. Ratified ruling (Linear MIL-84, 2026-08-13):
|
|
5
|
+
//
|
|
6
|
+
// Backward lineage refs (`split-from`, `merged-from`) are claims about history; for
|
|
7
|
+
// rename/merge/remove the predecessor is *supposed* to be absent from the current tree —
|
|
8
|
+
// `merged-from: cart@v3` on a doc whose predecessor was legitimately removed references an
|
|
9
|
+
// absent key forever, that's steady state, not a defect. So a backward ref naming a key
|
|
10
|
+
// absent from the current model gets NO diagnostic, not even a warning.
|
|
11
|
+
//
|
|
12
|
+
// What *is* checked (all errors, all current-tree-checkable): malformed ref grammar;
|
|
13
|
+
// self-reference/cycles; a dangling forward ref (`superseded-by` naming an absent key —
|
|
14
|
+
// forward refs point at the *present*, so absence is a real broken reference); impossible
|
|
15
|
+
// version arithmetic when the referenced key resolves in-tree (a ref to a version number
|
|
16
|
+
// higher than the target doc's own current `version:` — a reference to a future that hasn't
|
|
17
|
+
// happened).
|
|
18
|
+
//
|
|
19
|
+
// Deep historical verification (did slice@vN actually, once, exist?) is deliberately out of
|
|
20
|
+
// scope here — a same-commit authoring convention substitutes for it (see
|
|
21
|
+
// docs/slice-doc-schema.md's lineage section): the lineage ref is written in the same
|
|
22
|
+
// ratification commit that performs the operation it records, so the PR diff already contains
|
|
23
|
+
// both sides of the claim. `em ledger` (MIL-89, src/cli/ledgerCheck.ts) is that CI-recipe-tier,
|
|
24
|
+
// two-revision shape for a related but different invariant — version:/content agreement, not
|
|
25
|
+
// lineage-ref resolution — kept opt-in and separate for the same reason: it needs git history,
|
|
26
|
+
// and this command stays a fast function of the current tree.
|
|
27
|
+
import { pushDiag } from "../model/rules.js";
|
|
28
|
+
import { readSliceDoc } from "./readSliceDoc.js";
|
|
29
|
+
function edgesOf(ownerKey, ownerLine, doc) {
|
|
30
|
+
const edges = [];
|
|
31
|
+
if (doc.splitFrom)
|
|
32
|
+
edges.push({ ownerKey, ownerLine, field: "split-from", ref: doc.splitFrom });
|
|
33
|
+
for (const ref of doc.mergedFrom)
|
|
34
|
+
edges.push({ ownerKey, ownerLine, field: "merged-from", ref });
|
|
35
|
+
for (const ref of doc.supersededBy)
|
|
36
|
+
edges.push({ ownerKey, ownerLine, field: "superseded-by", ref });
|
|
37
|
+
return edges;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Resolve every slice's lineage refs against the current tree and report what the current tree
|
|
41
|
+
* can prove wrong. `baseDir` is the `.em` file's directory (doc paths, like every other
|
|
42
|
+
* doc/note path in em, are relative to it).
|
|
43
|
+
*/
|
|
44
|
+
export function validateLineage(model, refs, baseDir) {
|
|
45
|
+
const diags = [];
|
|
46
|
+
const inModel = new Set(refs.sliceKeys);
|
|
47
|
+
// Cache doc reads: a slice can be visited both as an edge owner and as another edge's
|
|
48
|
+
// resolved target, and there's no reason to read the same file twice.
|
|
49
|
+
const docCache = new Map();
|
|
50
|
+
const docFor = (sliceKey) => {
|
|
51
|
+
if (!docCache.has(sliceKey))
|
|
52
|
+
docCache.set(sliceKey, readSliceDoc(baseDir, sliceKey));
|
|
53
|
+
return docCache.get(sliceKey);
|
|
54
|
+
};
|
|
55
|
+
const edges = [];
|
|
56
|
+
model.slices.forEach((slice, i) => {
|
|
57
|
+
const sliceKey = refs.sliceKeys[i];
|
|
58
|
+
const doc = docFor(sliceKey);
|
|
59
|
+
if (doc)
|
|
60
|
+
edges.push(...edgesOf(sliceKey, slice.line, doc));
|
|
61
|
+
});
|
|
62
|
+
// Malformed grammar, self-reference, and dangling-forward are each a single edge-local check.
|
|
63
|
+
// Version arithmetic and cycles need every edge whose target resolves in-model, gathered
|
|
64
|
+
// first, so they're handled in a second pass below.
|
|
65
|
+
const resolvableEdges = [];
|
|
66
|
+
for (const edge of edges) {
|
|
67
|
+
if (edge.ref.sliceKey === null) {
|
|
68
|
+
pushDiag(diags, "lineage-ref-malformed", {
|
|
69
|
+
message: `slice "${edge.ownerKey}"'s ${edge.field} value "${edge.ref.raw}" doesn't match the <slice-key>@v<N> grammar`,
|
|
70
|
+
line: edge.ownerLine,
|
|
71
|
+
refs: [edge.ownerKey],
|
|
72
|
+
});
|
|
73
|
+
continue;
|
|
74
|
+
}
|
|
75
|
+
if (edge.ref.sliceKey === edge.ownerKey) {
|
|
76
|
+
pushDiag(diags, "lineage-ref-cycle", {
|
|
77
|
+
message: `slice "${edge.ownerKey}"'s ${edge.field} names itself (${edge.ref.raw})`,
|
|
78
|
+
line: edge.ownerLine,
|
|
79
|
+
refs: [edge.ownerKey],
|
|
80
|
+
});
|
|
81
|
+
continue;
|
|
82
|
+
}
|
|
83
|
+
if (edge.field === "superseded-by" && !inModel.has(edge.ref.sliceKey)) {
|
|
84
|
+
pushDiag(diags, "lineage-forward-dangling", {
|
|
85
|
+
message: `slice "${edge.ownerKey}"'s superseded-by names "${edge.ref.sliceKey}", which isn't a slice in the current model`,
|
|
86
|
+
line: edge.ownerLine,
|
|
87
|
+
refs: [edge.ownerKey],
|
|
88
|
+
});
|
|
89
|
+
continue;
|
|
90
|
+
}
|
|
91
|
+
if (inModel.has(edge.ref.sliceKey))
|
|
92
|
+
resolvableEdges.push(edge);
|
|
93
|
+
// else: a backward ref (split-from/merged-from) naming a key absent from the current
|
|
94
|
+
// model — the expected steady state for a real split/merge/remove. No diagnostic.
|
|
95
|
+
}
|
|
96
|
+
// Version arithmetic: only checkable when the target resolves in-model AND its own doc has a
|
|
97
|
+
// readable numeric version. Silently skip otherwise — can't check what isn't there.
|
|
98
|
+
for (const edge of resolvableEdges) {
|
|
99
|
+
const targetDoc = docFor(edge.ref.sliceKey);
|
|
100
|
+
if (!targetDoc || targetDoc.version === null)
|
|
101
|
+
continue;
|
|
102
|
+
if (edge.ref.version > targetDoc.version) {
|
|
103
|
+
pushDiag(diags, "lineage-version-impossible", {
|
|
104
|
+
message: `slice "${edge.ownerKey}"'s ${edge.field} names "${edge.ref.sliceKey}"@v${edge.ref.version}, but "${edge.ref.sliceKey}" is only at v${targetDoc.version}`,
|
|
105
|
+
line: edge.ownerLine,
|
|
106
|
+
refs: [edge.ownerKey, edge.ref.sliceKey],
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
// Multi-node cycles: adjacency over edges whose target resolves in-model (self-loops already
|
|
111
|
+
// reported above, and excluded here so they aren't double-counted as a 1-node "cycle"). 3-color
|
|
112
|
+
// DFS, same shape as model/validate.ts's findTypeCycles(), adapted to string slice keys.
|
|
113
|
+
const adjacency = new Map();
|
|
114
|
+
for (const edge of resolvableEdges) {
|
|
115
|
+
if (edge.ref.sliceKey === edge.ownerKey)
|
|
116
|
+
continue;
|
|
117
|
+
const list = adjacency.get(edge.ownerKey);
|
|
118
|
+
if (list)
|
|
119
|
+
list.push(edge.ref.sliceKey);
|
|
120
|
+
else
|
|
121
|
+
adjacency.set(edge.ownerKey, [edge.ref.sliceKey]);
|
|
122
|
+
}
|
|
123
|
+
const color = new Map(); // 0 = white, 1 = gray (on stack), 2 = black (done)
|
|
124
|
+
const stack = [];
|
|
125
|
+
const reportedCycles = new Set();
|
|
126
|
+
const lineOf = new Map();
|
|
127
|
+
model.slices.forEach((slice, i) => lineOf.set(refs.sliceKeys[i], slice.line));
|
|
128
|
+
function visit(key) {
|
|
129
|
+
color.set(key, 1);
|
|
130
|
+
stack.push(key);
|
|
131
|
+
for (const dep of adjacency.get(key) ?? []) {
|
|
132
|
+
const c = color.get(dep) ?? 0;
|
|
133
|
+
if (c === 0) {
|
|
134
|
+
visit(dep);
|
|
135
|
+
}
|
|
136
|
+
else if (c === 1) {
|
|
137
|
+
const idx = stack.indexOf(dep);
|
|
138
|
+
const cyclePath = [...stack.slice(idx), dep];
|
|
139
|
+
const dedupeKey = [...new Set(cyclePath)].sort().join(",");
|
|
140
|
+
if (!reportedCycles.has(dedupeKey)) {
|
|
141
|
+
reportedCycles.add(dedupeKey);
|
|
142
|
+
const cycleRefs = [...new Set(cyclePath)];
|
|
143
|
+
pushDiag(diags, "lineage-ref-cycle", {
|
|
144
|
+
message: `slice "${dep}" is part of a lineage cycle: ${cyclePath.join(" -> ")}`,
|
|
145
|
+
line: lineOf.get(dep),
|
|
146
|
+
refs: cycleRefs,
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
// c === 2 (black): already fully explored via another path — not a new cycle.
|
|
151
|
+
}
|
|
152
|
+
stack.pop();
|
|
153
|
+
color.set(key, 2);
|
|
154
|
+
}
|
|
155
|
+
for (const key of adjacency.keys()) {
|
|
156
|
+
if ((color.get(key) ?? 0) === 0)
|
|
157
|
+
visit(key);
|
|
158
|
+
}
|
|
159
|
+
return diags;
|
|
160
|
+
}
|
|
161
|
+
//# sourceMappingURL=lineageValidate.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"lineageValidate.js","sourceRoot":"","sources":["../../src/catalog/lineageValidate.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
// Lowest-level slice-doc read: given a base dir and a slice's export key,
|
|
3
|
+
// read+parse `slices/<sliceKey>.md` if it exists. Never throws — a missing
|
|
4
|
+
// file is routine (not every slice has reached specify yet), so this is a
|
|
5
|
+
// plain `null`, not an exception or a diagnostic. Shared by `em export`'s
|
|
6
|
+
// doc join (catalog/docJoin.ts, which additionally gates on a `note` binding
|
|
7
|
+
// and produces export-shaped diagnostics for a *missing-but-bound* doc — a
|
|
8
|
+
// concern this module doesn't have), `em diff`'s lineage annotation
|
|
9
|
+
// (model/diff.ts, via cli.ts), and `em validate`'s lineage-ref resolution
|
|
10
|
+
// (catalog/lineageValidate.ts) — three callers with different reactions to
|
|
11
|
+
// "no doc here," so the read itself stays opinion-free.
|
|
12
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
13
|
+
import { join } from "node:path";
|
|
14
|
+
import { parseSliceDoc } from "./sliceDoc.js";
|
|
15
|
+
export function readSliceDoc(baseDir, sliceKey) {
|
|
16
|
+
const path = join(baseDir, "slices", `${sliceKey}.md`);
|
|
17
|
+
if (!existsSync(path))
|
|
18
|
+
return null;
|
|
19
|
+
return parseSliceDoc(readFileSync(path, "utf8"));
|
|
20
|
+
}
|
|
21
|
+
//# sourceMappingURL=readSliceDoc.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"readSliceDoc.js","sourceRoot":"","sources":["../../src/catalog/readSliceDoc.ts"],"names":[],"mappings":"AAAA,+BAA+B;AAC/B,0EAA0E;AAC1E,2EAA2E;AAC3E,0EAA0E;AAC1E,0EAA0E;AAC1E,6EAA6E;AAC7E,2EAA2E;AAC3E,oEAAoE;AACpE,0EAA0E;AAC1E,2EAA2E;AAC3E,wDAAwD;AAExD,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACnD,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,aAAa,EAAY,MAAM,eAAe,CAAC;AAExD,MAAM,UAAU,YAAY,CAAC,OAAe,EAAE,QAAgB;IAC5D,MAAM,IAAI,GAAG,IAAI,CAAC,OAAO,EAAE,QAAQ,EAAE,GAAG,QAAQ,KAAK,CAAC,CAAC;IACvD,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC;QAAE,OAAO,IAAI,CAAC;IACnC,OAAO,aAAa,CAAC,YAAY,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC,CAAC;AACnD,CAAC"}
|
|
@@ -1,10 +1,101 @@
|
|
|
1
|
+
/** A parsed `<slice-key>@v<N>` lineage reference. */
|
|
2
|
+
export interface SliceRef {
|
|
3
|
+
/** The exact frontmatter value this ref was read from, e.g. "checkout@v4".
|
|
4
|
+
* Always present, even when the grammar didn't match — nothing is silently
|
|
5
|
+
* dropped the way an unrecognized top-level key is. */
|
|
6
|
+
raw: string;
|
|
7
|
+
/** The referenced slice's doc key (its kebab-case filename stem), lowercased,
|
|
8
|
+
* or null if `raw` doesn't match the `<slice-key>@v<N>` grammar. Whether
|
|
9
|
+
* this actually names a real slice/version is NOT checked here — that's
|
|
10
|
+
* MIL-84 (lineage-aware `em diff`/`em validate`). */
|
|
11
|
+
sliceKey: string | null;
|
|
12
|
+
/** The referenced version number, or null if `raw` didn't match the grammar. */
|
|
13
|
+
version: number | null;
|
|
14
|
+
}
|
|
1
15
|
export interface SliceDoc {
|
|
2
16
|
/** The doc's raw markdown, as read from disk. */
|
|
3
17
|
raw: string;
|
|
4
|
-
/**
|
|
5
|
-
*
|
|
18
|
+
/** The doc's body text — everything after the frontmatter fence (or the whole doc, if no
|
|
19
|
+
* fence) — the same text `html`/`openQuestionsTotal` are already derived from. Exposed so a
|
|
20
|
+
* two-revision content-agreement check (MIL-89, `em ledger`) can diff body text across git
|
|
21
|
+
* revisions without frontmatter noise leaking in. */
|
|
22
|
+
body: string;
|
|
23
|
+
/** Lowercased status value from frontmatter `status:` (canonical) or a legacy
|
|
24
|
+
* `- **Status:** ...` bullet line, or null if neither is found (a freeform
|
|
25
|
+
* doc that doesn't follow the slice.md template). */
|
|
6
26
|
status: string | null;
|
|
7
|
-
/**
|
|
27
|
+
/** This slice's own ratified-content version, from frontmatter `version:`
|
|
28
|
+
* (e.g. `version: 1`) — a cache of git truth (docset module 06), not
|
|
29
|
+
* derived or validated against git history. Null when absent or
|
|
30
|
+
* non-numeric. Distinct from the frontmatter dialect's own `schemaVersion`,
|
|
31
|
+
* which this parser still doesn't expose. */
|
|
32
|
+
version: number | null;
|
|
33
|
+
/** Lineage: this doc split off exactly one prior slice, from frontmatter
|
|
34
|
+
* `split-from:`. Null when this doc wasn't produced by a split. */
|
|
35
|
+
splitFrom: SliceRef | null;
|
|
36
|
+
/** Lineage: this doc was produced by merging one or more prior slices, from
|
|
37
|
+
* frontmatter `merged-from:` (comma-separated). Empty array when absent —
|
|
38
|
+
* the common case. */
|
|
39
|
+
mergedFrom: SliceRef[];
|
|
40
|
+
/** Lineage: this doc has been superseded by one or more successor slices —
|
|
41
|
+
* a rename, or the retired side of a split/merge — from frontmatter
|
|
42
|
+
* `superseded-by:` (comma-separated). Empty array when absent — the
|
|
43
|
+
* common case; most slices are never superseded. */
|
|
44
|
+
supersededBy: SliceRef[];
|
|
45
|
+
/** Frontmatter `implementedIn:` — free text, typically a PR/commit link — or null
|
|
46
|
+
* when absent. No legacy bullet-line form, same as `version`/lineage. */
|
|
47
|
+
implementedIn: string | null;
|
|
48
|
+
/** True when a well-formed leading `---`/`---` frontmatter fence was found and
|
|
49
|
+
* closed — independent of which keys it contained. False for a legacy
|
|
50
|
+
* status-bullet-only doc, a doc with no frontmatter at all, or an
|
|
51
|
+
* unterminated fence (splitFrontmatter() already treats that as "absent"). */
|
|
52
|
+
frontmatterPresent: boolean;
|
|
53
|
+
/** Which of REQUIRED_FRONTMATTER_KEYS were absent (or present-but-empty, which
|
|
54
|
+
* the parser already treats as absent) from the frontmatter. Empty whenever
|
|
55
|
+
* frontmatterPresent is false too — check frontmatterPresent separately to
|
|
56
|
+
* distinguish "no frontmatter at all" from "every required key present".
|
|
57
|
+
* Presence-only: doesn't validate `pattern`'s/`status`'s enum values, matching
|
|
58
|
+
* this parser's existing lenient, informational treatment of those keys. */
|
|
59
|
+
missingRequiredFields: string[];
|
|
60
|
+
/** The whole doc rendered to HTML, with any leading frontmatter block stripped
|
|
61
|
+
* first (otherwise its `---` fences render as stray <hr>s and the raw
|
|
62
|
+
* `key: value` lines leak into the body). */
|
|
8
63
|
html: string;
|
|
64
|
+
/** Count of GFM task-list items (`- [ ]` / `- [x]`) found under the `## Open Questions`
|
|
65
|
+
* heading (case-insensitive), up to the next `#`/`##` heading or EOF. Both 0 when the doc
|
|
66
|
+
* has no such heading — nothing for MIL-87's readiness check to block on. See
|
|
67
|
+
* `openQuestionsUnchecked` for how many of these are still unresolved. */
|
|
68
|
+
openQuestionsTotal: number;
|
|
69
|
+
/** Of `openQuestionsTotal`, how many are still `- [ ]` (unchecked). 0 when every Open
|
|
70
|
+
* Question has been checked off, or when there's no Open Questions section at all. */
|
|
71
|
+
openQuestionsUnchecked: number;
|
|
9
72
|
}
|
|
73
|
+
/** Frontmatter keys docs/slice-doc-schema.md's required-vs-optional table marks
|
|
74
|
+
* required at every `status` — the mechanical basis for `frontmatter-invalid`
|
|
75
|
+
* (MIL-91). `implementedIn` is deliberately excluded: its requirement depends on
|
|
76
|
+
* whether the slice has *ever* reached `implemented`, which is git history this
|
|
77
|
+
* parser has no access to and doesn't attempt to check. */
|
|
78
|
+
export declare const REQUIRED_FRONTMATTER_KEYS: readonly ["schemaversion", "pattern", "swimlane", "status", "version"];
|
|
79
|
+
/** True when a doc's frontmatter is well-formed enough to trust its canonical fields: a closed
|
|
80
|
+
* fence was found AND every REQUIRED_FRONTMATTER_KEYS entry is present. The single gate `em
|
|
81
|
+
* export`'s doc join (MIL-91, `frontmatter-invalid`) and `em validate`'s frontmatter-coherence
|
|
82
|
+
* check (MIL-85) both call, instead of each re-deriving `!frontmatterPresent ||
|
|
83
|
+
* missingRequiredFields.length > 0` and risking the two silently drifting apart on what counts
|
|
84
|
+
* as "usable" as the required-fields list evolves. A doc that fails this — no fence at all
|
|
85
|
+
* (e.g. MIL-86's legacy body-label dialect), or missing any required key — has nothing reliable
|
|
86
|
+
* enough to classify; treat it as unreadable rather than guess. */
|
|
87
|
+
export declare function hasUsableFrontmatter(doc: Pick<SliceDoc, "frontmatterPresent" | "missingRequiredFields">): boolean;
|
|
88
|
+
/**
|
|
89
|
+
* Parses a single `<slice-key>@v<N>` lineage reference (`split-from`, and each
|
|
90
|
+
* comma-separated item of `merged-from`/`superseded-by`). Never throws: a
|
|
91
|
+
* value that doesn't match the grammar still comes back with the original
|
|
92
|
+
* text in `raw` and null `sliceKey`/`version`, so a malformed value stays
|
|
93
|
+
* visible — to a human reading the doc, or to MIL-84's validation — instead
|
|
94
|
+
* of being silently dropped like an unrecognized `status`. Exported so MIL-84
|
|
95
|
+
* (lineage diff/validate) and MIL-91 (export join) share this grammar instead
|
|
96
|
+
* of re-deriving it. `sliceKey` matches `kebabSlug()`'s output
|
|
97
|
+
* (src/util/slug.ts) — lowercase alphanumerics/hyphens, no assumption it
|
|
98
|
+
* starts with a letter.
|
|
99
|
+
*/
|
|
100
|
+
export declare function parseSliceRef(raw: string): SliceRef;
|
|
10
101
|
export declare function parseSliceDoc(markdown: string): SliceDoc;
|
package/dist/catalog/sliceDoc.js
CHANGED
|
@@ -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
|
|
7
|
-
//
|
|
8
|
-
//
|
|
9
|
-
//
|
|
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
|
|
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
|
-
|
|
17
|
-
|
|
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,
|
|
1
|
+
{"version":3,"file":"sliceDoc.js","sourceRoot":"","sources":["../../src/catalog/sliceDoc.ts"],"names":[],"mappings":"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"}
|
|
@@ -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[];
|