@milehimikey/em 1.12.0 → 1.13.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (105) hide show
  1. package/.claude/skills/event-modeling/SKILL.md +4 -2
  2. package/.claude/skills/event-modeling-conform/SKILL.md +20 -9
  3. package/.claude/skills/event-modeling-conform/reference/conform.md +107 -10
  4. package/.claude/skills/event-modeling-design/SKILL.md +10 -2
  5. package/.claude/skills/event-modeling-discover/SKILL.md +1 -1
  6. package/.claude/skills/event-modeling-implement/SKILL.md +1 -1
  7. package/.claude/skills/event-modeling-implement/reference/implement.md +12 -6
  8. package/.claude/skills/event-modeling-review/SKILL.md +5 -1
  9. package/.claude/skills/event-modeling-shared/reference/em-dsl.md +40 -2
  10. package/.claude/skills/event-modeling-shared/reference/operating-principles.md +13 -3
  11. package/.claude/skills/event-modeling-shared/reference/slice-doc-schema.md +30 -0
  12. package/.claude/skills/event-modeling-shared/templates/conformance-report.md +8 -1
  13. package/.claude/skills/event-modeling-shared/templates/state.md +3 -0
  14. package/README.md +1 -0
  15. package/dist/catalog/docJoin.d.ts +9 -0
  16. package/dist/catalog/docJoin.js +6 -0
  17. package/dist/catalog/docJoin.js.map +1 -1
  18. package/dist/catalog/driftSignal.d.ts +11 -5
  19. package/dist/catalog/driftSignal.js +18 -4
  20. package/dist/catalog/driftSignal.js.map +1 -1
  21. package/dist/catalog/modelVersionValidate.d.ts +9 -0
  22. package/dist/catalog/modelVersionValidate.js +38 -0
  23. package/dist/catalog/modelVersionValidate.js.map +1 -0
  24. package/dist/catalog/sliceDoc.d.ts +12 -0
  25. package/dist/catalog/sliceDoc.js +15 -0
  26. package/dist/catalog/sliceDoc.js.map +1 -1
  27. package/dist/cli/ciInit.js +17 -0
  28. package/dist/cli/ciInit.js.map +1 -1
  29. package/dist/cli/conformScope.d.ts +1 -0
  30. package/dist/cli/conformScope.js +3 -1
  31. package/dist/cli/conformScope.js.map +1 -1
  32. package/dist/cli/conformSupersede.d.ts +24 -2
  33. package/dist/cli/conformSupersede.js +44 -3
  34. package/dist/cli/conformSupersede.js.map +1 -1
  35. package/dist/cli/findings.d.ts +134 -0
  36. package/dist/cli/findings.js +355 -0
  37. package/dist/cli/findings.js.map +1 -0
  38. package/dist/cli/metrics.d.ts +93 -0
  39. package/dist/cli/metrics.js +331 -0
  40. package/dist/cli/metrics.js.map +1 -0
  41. package/dist/cli/modelVersion.d.ts +139 -0
  42. package/dist/cli/modelVersion.js +256 -0
  43. package/dist/cli/modelVersion.js.map +1 -0
  44. package/dist/cli/reratify.d.ts +17 -0
  45. package/dist/cli/reratify.js +25 -1
  46. package/dist/cli/reratify.js.map +1 -1
  47. package/dist/cli/skillCheck.js +2 -2
  48. package/dist/cli/skillCheck.js.map +1 -1
  49. package/dist/cli/sliceConform.d.ts +50 -0
  50. package/dist/cli/sliceConform.js +206 -0
  51. package/dist/cli/sliceConform.js.map +1 -0
  52. package/dist/cli/sliceIndex.d.ts +2 -0
  53. package/dist/cli/sliceIndex.js +15 -3
  54. package/dist/cli/sliceIndex.js.map +1 -1
  55. package/dist/cli/sliceNew.d.ts +12 -0
  56. package/dist/cli/sliceNew.js +23 -0
  57. package/dist/cli/sliceNew.js.map +1 -1
  58. package/dist/cli/sliceStubAll.d.ts +76 -0
  59. package/dist/cli/sliceStubAll.js +189 -0
  60. package/dist/cli/sliceStubAll.js.map +1 -0
  61. package/dist/cli/stateFile.d.ts +63 -7
  62. package/dist/cli/stateFile.js +179 -18
  63. package/dist/cli/stateFile.js.map +1 -1
  64. package/dist/cli/status.d.ts +79 -1
  65. package/dist/cli/status.js +134 -15
  66. package/dist/cli/status.js.map +1 -1
  67. package/dist/cli/upgrade.d.ts +122 -0
  68. package/dist/cli/upgrade.js +476 -0
  69. package/dist/cli/upgrade.js.map +1 -0
  70. package/dist/cli.js +524 -36
  71. package/dist/cli.js.map +1 -1
  72. package/dist/emit/freshnessJson.d.ts +1 -1
  73. package/dist/emit/freshnessJson.js +3 -1
  74. package/dist/emit/freshnessJson.js.map +1 -1
  75. package/dist/emit/json.d.ts +11 -1
  76. package/dist/emit/json.js +35 -1
  77. package/dist/emit/json.js.map +1 -1
  78. package/dist/emit/metricsJson.d.ts +7 -0
  79. package/dist/emit/metricsJson.js +27 -0
  80. package/dist/emit/metricsJson.js.map +1 -0
  81. package/dist/emit/statusJson.d.ts +1 -1
  82. package/dist/emit/statusJson.js +17 -1
  83. package/dist/emit/statusJson.js.map +1 -1
  84. package/dist/emit/upgradeJson.d.ts +5 -0
  85. package/dist/emit/upgradeJson.js +26 -0
  86. package/dist/emit/upgradeJson.js.map +1 -0
  87. package/dist/emit/validateJson.d.ts +6 -2
  88. package/dist/emit/validateJson.js +12 -5
  89. package/dist/emit/validateJson.js.map +1 -1
  90. package/dist/mcp/server.d.ts +1 -1
  91. package/dist/mcp/server.js +114 -3
  92. package/dist/mcp/server.js.map +1 -1
  93. package/dist/model/rules.d.ts +7 -0
  94. package/dist/model/rules.js +12 -0
  95. package/dist/model/rules.js.map +1 -1
  96. package/dist/model/validate.d.ts +20 -0
  97. package/dist/model/validate.js +94 -45
  98. package/dist/model/validate.js.map +1 -1
  99. package/dist/templates.d.ts +2 -2
  100. package/dist/templates.js +7 -1
  101. package/dist/templates.js.map +1 -1
  102. package/dist/util/semver.d.ts +19 -0
  103. package/dist/util/semver.js +54 -0
  104. package/dist/util/semver.js.map +1 -0
  105. package/package.json +1 -1
@@ -0,0 +1,134 @@
1
+ export declare const FINDINGS_SCHEMA_VERSION = "1.0";
2
+ /** reference/conform.md's "The three conformance surfaces" table, plus `other` for anything that
3
+ * doesn't fit (a run-metadata note, a tooling problem noted in the report's run metadata). */
4
+ export declare const FINDING_SURFACES: readonly ["structural", "spec", "internal", "other"];
5
+ export type FindingSurface = (typeof FINDING_SURFACES)[number];
6
+ /** Who/what a ruled finding says is wrong — `none` when the finding is Accepted divergence/
7
+ * Unpropagated delta/Extraction uncertainty and nothing needs to change. Null (not yet ruled)
8
+ * is the initial state every finding is written in. */
9
+ export declare const FINDING_LOCI: readonly ["model", "doc", "code", "none"];
10
+ export type FindingLocus = (typeof FINDING_LOCI)[number];
11
+ export interface Finding {
12
+ id: number;
13
+ surface: FindingSurface;
14
+ /** The classification from reference/conform.md step 4 (Real drift, Model gap, Internal
15
+ * inconsistency, Accepted divergence, Unpropagated delta, Extraction uncertainty) — free text,
16
+ * not a closed enum here, so the report's own vocabulary is never fought by the JSON's schema. */
17
+ class: string;
18
+ /** The slice this finding concerns, or null for a finding that isn't attributable to one slice
19
+ * (a whole-model observation, a cross-cutting tooling problem, ...). A null-slice finding
20
+ * counts as in scope for EVERY slice's unruled-findings check (`unruledFindingsInScope`) —
21
+ * it's ambiguous which slice(s) it blocks, so it blocks all of them until ruled. */
22
+ slice: string | null;
23
+ evidence: string;
24
+ /** `null` until a human rules on this finding (docs/process.md's "Ruling on conformance
25
+ * findings") — `em conform-supersede --locus <l> --by <name>` is the one write path. */
26
+ locus: FindingLocus | null;
27
+ resolvedBy: string | null;
28
+ resolvedOn: string | null;
29
+ }
30
+ export interface FindingsDoc {
31
+ findingsSchemaVersion: string;
32
+ model: string;
33
+ report: string;
34
+ revision: string;
35
+ findings: Finding[];
36
+ }
37
+ /** Serialize a `FindingsDoc` deterministically — see module header. */
38
+ export declare function serializeFindingsDoc(doc: FindingsDoc): string;
39
+ /**
40
+ * Shape-validate an already-`JSON.parse`d value against the findings schema — the mechanical
41
+ * check both `em conform-findings check` (a headless run verifying what it just wrote) and every
42
+ * reader below (`lookupFindingsBesideReport`) apply. Never throws; every failure is a plain-text
43
+ * message in `errors`, in file order (document-level problems first, then one finding at a time).
44
+ */
45
+ export declare function validateFindingsShape(input: unknown): {
46
+ ok: true;
47
+ doc: FindingsDoc;
48
+ } | {
49
+ ok: false;
50
+ errors: string[];
51
+ };
52
+ /** `conformance/<date>-report.md` -> `conformance/<date>-findings.json` — the sibling-path
53
+ * convention every consumer of the findings record uses. Null when `reportPath` doesn't match
54
+ * the conventional `<date>-report.md` naming (an older/hand-named report) — callers treat that
55
+ * the same as "no findings file", the migration path for a report predating this feature. */
56
+ export declare function findingsPathForReport(reportPath: string): string | null;
57
+ export type FindingsLookup = {
58
+ kind: "absent";
59
+ } | {
60
+ kind: "invalid";
61
+ path: string;
62
+ message: string;
63
+ } | {
64
+ kind: "found";
65
+ path: string;
66
+ doc: FindingsDoc;
67
+ };
68
+ /**
69
+ * Resolve the findings JSON beside `reportPath` (relative to `baseDir`, the `.em` file's
70
+ * directory — same convention every doc/note/report path in em uses), reading and shape-
71
+ * validating it. `absent` covers both "no sibling-path convention to try" (a hand-named report)
72
+ * and "the file just isn't there" — both are the SAME migration-path signal to every caller:
73
+ * warn once, fall back to the pre-MIL-214 behavior. `invalid` is a genuine problem (the file
74
+ * exists but doesn't parse or fails shape validation) — callers refuse rather than silently
75
+ * treating a broken findings file as if it were absent.
76
+ */
77
+ export declare function lookupFindingsBesideReport(baseDir: string, reportPath: string): FindingsLookup;
78
+ /** Every `conformance/*-findings.json` beside `baseDir`, newest filename first, that parses and
79
+ * shape-validates — a malformed candidate is silently skipped (`em conform-findings check` is
80
+ * the loud diagnosis for a broken file, not this scan). Shared by `cli/sliceConform.ts`'s
81
+ * `findLatestFindingsForRevision` (filters to one `revision`) and `em slice reratify`'s advisory
82
+ * (`reratifyAdvisory`, cli/reratify.ts — looks across every past run, not just one revision).
83
+ * Returns `[]` when `conformance/` doesn't exist. */
84
+ export declare function listAllFindingsFiles(baseDir: string): {
85
+ path: string;
86
+ doc: FindingsDoc;
87
+ }[];
88
+ /** Every finding still blocking `inScope` — `locus === null` (never ruled) AND (`slice === null`,
89
+ * ambiguous/whole-model, counts as in scope for everything, OR `slice` names a key in
90
+ * `inScope`). The one predicate `em slice conform` (a single-key `inScope`), `em state
91
+ * set-conformance` (every `implemented` slice), and `em status`'s `unruledFindings` count all
92
+ * share — never re-derived three different ways. */
93
+ export declare function unruledFindingsInScope(findings: Finding[], inScope: ReadonlySet<string>): Finding[];
94
+ /** Parse a `--findings` spec (`"1-3"`, `"1, 2, 4"`, `"1-3, 7"`) into a deduped, ascending list of
95
+ * finding ids. Accepts the same conservative charset `applySupersededBanner`'s `FINDINGS_RE`
96
+ * already gates on (digits/commas/spaces/either dash style) — this just additionally parses the
97
+ * numbers out rather than treating the spec as opaque banner text. Returns null for anything
98
+ * that doesn't parse cleanly (an empty spec, a reversed range, a stray token) — same
99
+ * never-guess discipline every other em parser holds. */
100
+ export declare function parseFindingsSpec(spec: string): number[] | null;
101
+ export type ApplyFindingsRulingResult = {
102
+ ok: true;
103
+ doc: FindingsDoc;
104
+ changed: boolean;
105
+ } | {
106
+ ok: false;
107
+ message: string;
108
+ };
109
+ /**
110
+ * Pure transform: records `locus`/`resolvedBy`/`resolvedOn` on every finding named by `ids` in
111
+ * `doc`. Refuses (`ok: false`, no partial write) if any named id doesn't exist, or if any named
112
+ * finding already carries a DIFFERENT locus — the same refuse-different-identity discipline
113
+ * `ratify.ts` holds for `ratifiedBy`/`ratifiedOn` (docs/process.md: a ruling is recorded once,
114
+ * with a name and a date; changing it needs a human decision, not a silent overwrite). Idempotent
115
+ * per-finding: a finding already carrying the exact same locus/resolvedBy/resolvedOn is left
116
+ * alone (doesn't count toward `changed`).
117
+ */
118
+ export declare function applyFindingsRuling(doc: FindingsDoc, ids: number[], locus: FindingLocus, resolvedBy: string, resolvedOn: string): ApplyFindingsRulingResult;
119
+ export type CheckFindingsResult = {
120
+ ok: true;
121
+ path: string;
122
+ findingsCount: number;
123
+ } | {
124
+ ok: false;
125
+ path: string;
126
+ errors: string[];
127
+ };
128
+ /** `em conform-findings check <path>` — read `path` off disk, parse, and shape-validate. Never
129
+ * throws; every failure comes back as `errors`, one message per problem. */
130
+ export declare function checkFindingsFile(path: string): CheckFindingsResult;
131
+ /** `em conform-findings check --json` document shape — a small envelope, no schema-version field
132
+ * of its own (this IS the check tool for `findingsSchemaVersion`; the tool's own output isn't a
133
+ * long-lived emitted artifact the way a report/status document is). */
134
+ export declare function buildCheckFindingsJson(result: CheckFindingsResult): string;
@@ -0,0 +1,355 @@
1
+ // SPDX-License-Identifier: MIT
2
+ // The conformance findings record (MIL-214): `conformance/<date>-findings.json`, sibling of that
3
+ // same date's `<date>-report.md`. Structured data for facts the report's markdown carried only
4
+ // as prose — one entry per `### n.` finding in the report, ids matching. Written directly by the
5
+ // conform skill (a dedicated `em conform-findings <model> <report> --add` writer was ruled out —
6
+ // too fiddly for an agent mid-run); `em` provides a read-only shape validator
7
+ // (`em conform-findings check <path>`, cli.ts) so a headless run can verify what it wrote, plus
8
+ // the consumers that read the file back: `em conform-supersede --locus/--by` (records a ruling
9
+ // via `applyFindingsRuling`), `em slice conform` (cli/sliceConform.ts — refuses when an in-scope
10
+ // finding is still unruled), `em state set-conformance` (refuses/--partial on the same basis,
11
+ // model-wide), and `em status` (unruledFindings per model). All four reuse `unruledFindingsInScope`
12
+ // below rather than re-deriving "is this finding still blocking" four different ways.
13
+ //
14
+ // Surface names use reference/conform.md's own vocabulary ("The three conformance surfaces"
15
+ // table): `structural` (`.em` <-> code), `spec` (slice doc <-> code), `internal` (slice doc <->
16
+ // `.em`) — plus `other` for anything that doesn't fit those three (a run-metadata note, a
17
+ // tooling problem, ...).
18
+ //
19
+ // Deterministic serialization: 2-space indent, keys in a fixed alphabetical order (both at the
20
+ // document level and within each finding), entries sorted by `id` ascending, trailing newline —
21
+ // this file is written directly to disk (not printed to stdout like every other em JSON
22
+ // surface), so it carries its own trailing newline rather than relying on a CLI caller to add
23
+ // one. `serializeFindingsDoc` is the ONE write path every producer of this file's bytes must go
24
+ // through — never a bare `JSON.stringify`, which would leave key order at the mercy of
25
+ // insertion/iteration order and break the "byte-deterministic for identical input" contract
26
+ // every em-emitted document holds.
27
+ import { existsSync, readFileSync, readdirSync } from "node:fs";
28
+ import { join } from "node:path";
29
+ import { isValidDateString } from "./stateFile.js";
30
+ export const FINDINGS_SCHEMA_VERSION = "1.0";
31
+ /** reference/conform.md's "The three conformance surfaces" table, plus `other` for anything that
32
+ * doesn't fit (a run-metadata note, a tooling problem noted in the report's run metadata). */
33
+ export const FINDING_SURFACES = ["structural", "spec", "internal", "other"];
34
+ /** Who/what a ruled finding says is wrong — `none` when the finding is Accepted divergence/
35
+ * Unpropagated delta/Extraction uncertainty and nothing needs to change. Null (not yet ruled)
36
+ * is the initial state every finding is written in. */
37
+ export const FINDING_LOCI = ["model", "doc", "code", "none"];
38
+ /** Serialize a `FindingsDoc` deterministically — see module header. */
39
+ export function serializeFindingsDoc(doc) {
40
+ const sorted = [...doc.findings].sort((a, b) => a.id - b.id);
41
+ const canonical = {
42
+ findings: sorted.map((f) => ({
43
+ class: f.class,
44
+ evidence: f.evidence,
45
+ id: f.id,
46
+ locus: f.locus,
47
+ resolvedBy: f.resolvedBy,
48
+ resolvedOn: f.resolvedOn,
49
+ slice: f.slice,
50
+ surface: f.surface,
51
+ })),
52
+ findingsSchemaVersion: doc.findingsSchemaVersion,
53
+ model: doc.model,
54
+ report: doc.report,
55
+ revision: doc.revision,
56
+ };
57
+ return `${JSON.stringify(canonical, null, 2)}\n`;
58
+ }
59
+ function isNonEmptyString(v) {
60
+ return typeof v === "string" && v.length > 0;
61
+ }
62
+ /**
63
+ * Shape-validate an already-`JSON.parse`d value against the findings schema — the mechanical
64
+ * check both `em conform-findings check` (a headless run verifying what it just wrote) and every
65
+ * reader below (`lookupFindingsBesideReport`) apply. Never throws; every failure is a plain-text
66
+ * message in `errors`, in file order (document-level problems first, then one finding at a time).
67
+ */
68
+ export function validateFindingsShape(input) {
69
+ const errors = [];
70
+ if (typeof input !== "object" || input === null || Array.isArray(input)) {
71
+ return { ok: false, errors: ["top-level value must be a JSON object"] };
72
+ }
73
+ const obj = input;
74
+ if (!isNonEmptyString(obj.findingsSchemaVersion))
75
+ errors.push('"findingsSchemaVersion" must be a non-empty string');
76
+ if (!isNonEmptyString(obj.model))
77
+ errors.push('"model" must be a non-empty string');
78
+ if (!isNonEmptyString(obj.report))
79
+ errors.push('"report" must be a non-empty string');
80
+ if (!isNonEmptyString(obj.revision))
81
+ errors.push('"revision" must be a non-empty string');
82
+ const findingsRaw = obj.findings;
83
+ const findings = [];
84
+ if (!Array.isArray(findingsRaw)) {
85
+ errors.push('"findings" must be an array');
86
+ }
87
+ else {
88
+ let prevId = null;
89
+ const seenIds = new Set();
90
+ findingsRaw.forEach((entry, i) => {
91
+ const where = `findings[${i}]`;
92
+ if (typeof entry !== "object" || entry === null || Array.isArray(entry)) {
93
+ errors.push(`${where} must be an object`);
94
+ return;
95
+ }
96
+ const f = entry;
97
+ const id = f.id;
98
+ if (typeof id !== "number" || !Number.isInteger(id)) {
99
+ errors.push(`${where}.id must be an integer`);
100
+ }
101
+ else {
102
+ if (seenIds.has(id))
103
+ errors.push(`${where}.id ${id} is a duplicate — ids must be unique`);
104
+ seenIds.add(id);
105
+ if (prevId !== null && id <= prevId)
106
+ errors.push(`${where}.id ${id} is out of order — findings must be sorted by id ascending`);
107
+ prevId = id;
108
+ }
109
+ if (typeof f.surface !== "string" || !FINDING_SURFACES.includes(f.surface)) {
110
+ errors.push(`${where}.surface must be one of: ${FINDING_SURFACES.join(", ")}`);
111
+ }
112
+ if (!isNonEmptyString(f.class))
113
+ errors.push(`${where}.class must be a non-empty string`);
114
+ if (f.slice !== null && typeof f.slice !== "string")
115
+ errors.push(`${where}.slice must be a string or null`);
116
+ if (!isNonEmptyString(f.evidence))
117
+ errors.push(`${where}.evidence must be a non-empty string`);
118
+ const locusOk = f.locus === null || (typeof f.locus === "string" && FINDING_LOCI.includes(f.locus));
119
+ if (!locusOk)
120
+ errors.push(`${where}.locus must be null or one of: ${FINDING_LOCI.join(", ")}`);
121
+ const resolvedByOk = f.resolvedBy === null || isNonEmptyString(f.resolvedBy);
122
+ if (!resolvedByOk)
123
+ errors.push(`${where}.resolvedBy must be a non-empty string or null`);
124
+ const resolvedOnOk = f.resolvedOn === null || (typeof f.resolvedOn === "string" && isValidDateString(f.resolvedOn));
125
+ if (!resolvedOnOk)
126
+ errors.push(`${where}.resolvedOn must be a YYYY-MM-DD string or null`);
127
+ // A ruled finding (non-null locus) is a human decision, recorded with who and when — same
128
+ // discipline every other ratify/review/rule act in em holds (docs/process.md's "ratified
129
+ // means... recorded with a name and a date").
130
+ if (locusOk && f.locus !== null) {
131
+ if (f.resolvedBy === null || !isNonEmptyString(f.resolvedBy))
132
+ errors.push(`${where}.resolvedBy is required once locus is set`);
133
+ if (f.resolvedOn === null || !(typeof f.resolvedOn === "string" && isValidDateString(f.resolvedOn))) {
134
+ errors.push(`${where}.resolvedOn is required once locus is set`);
135
+ }
136
+ }
137
+ findings.push({
138
+ id: typeof id === "number" ? id : NaN,
139
+ surface: (typeof f.surface === "string" ? f.surface : "other"),
140
+ class: typeof f.class === "string" ? f.class : "",
141
+ slice: f.slice ?? null,
142
+ evidence: typeof f.evidence === "string" ? f.evidence : "",
143
+ locus: f.locus ?? null,
144
+ resolvedBy: f.resolvedBy ?? null,
145
+ resolvedOn: f.resolvedOn ?? null,
146
+ });
147
+ });
148
+ }
149
+ if (errors.length > 0)
150
+ return { ok: false, errors };
151
+ return {
152
+ ok: true,
153
+ doc: {
154
+ findingsSchemaVersion: obj.findingsSchemaVersion,
155
+ model: obj.model,
156
+ report: obj.report,
157
+ revision: obj.revision,
158
+ findings,
159
+ },
160
+ };
161
+ }
162
+ const REPORT_SUFFIX_RE = /-report\.md$/;
163
+ /** `conformance/<date>-report.md` -> `conformance/<date>-findings.json` — the sibling-path
164
+ * convention every consumer of the findings record uses. Null when `reportPath` doesn't match
165
+ * the conventional `<date>-report.md` naming (an older/hand-named report) — callers treat that
166
+ * the same as "no findings file", the migration path for a report predating this feature. */
167
+ export function findingsPathForReport(reportPath) {
168
+ return REPORT_SUFFIX_RE.test(reportPath) ? reportPath.replace(REPORT_SUFFIX_RE, "-findings.json") : null;
169
+ }
170
+ /**
171
+ * Resolve the findings JSON beside `reportPath` (relative to `baseDir`, the `.em` file's
172
+ * directory — same convention every doc/note/report path in em uses), reading and shape-
173
+ * validating it. `absent` covers both "no sibling-path convention to try" (a hand-named report)
174
+ * and "the file just isn't there" — both are the SAME migration-path signal to every caller:
175
+ * warn once, fall back to the pre-MIL-214 behavior. `invalid` is a genuine problem (the file
176
+ * exists but doesn't parse or fails shape validation) — callers refuse rather than silently
177
+ * treating a broken findings file as if it were absent.
178
+ */
179
+ export function lookupFindingsBesideReport(baseDir, reportPath) {
180
+ const findingsRel = findingsPathForReport(reportPath);
181
+ if (findingsRel === null)
182
+ return { kind: "absent" };
183
+ const absPath = join(baseDir, findingsRel);
184
+ if (!existsSync(absPath))
185
+ return { kind: "absent" };
186
+ let raw;
187
+ try {
188
+ raw = readFileSync(absPath, "utf8");
189
+ }
190
+ catch (e) {
191
+ return { kind: "invalid", path: findingsRel, message: `could not read ${findingsRel}: ${e.message}` };
192
+ }
193
+ let parsed;
194
+ try {
195
+ parsed = JSON.parse(raw);
196
+ }
197
+ catch {
198
+ return { kind: "invalid", path: findingsRel, message: `${findingsRel} is not valid JSON` };
199
+ }
200
+ const shape = validateFindingsShape(parsed);
201
+ if (!shape.ok)
202
+ return { kind: "invalid", path: findingsRel, message: `${findingsRel}: ${shape.errors.join("; ")}` };
203
+ return { kind: "found", path: findingsRel, doc: shape.doc };
204
+ }
205
+ /** Every `conformance/*-findings.json` beside `baseDir`, newest filename first, that parses and
206
+ * shape-validates — a malformed candidate is silently skipped (`em conform-findings check` is
207
+ * the loud diagnosis for a broken file, not this scan). Shared by `cli/sliceConform.ts`'s
208
+ * `findLatestFindingsForRevision` (filters to one `revision`) and `em slice reratify`'s advisory
209
+ * (`reratifyAdvisory`, cli/reratify.ts — looks across every past run, not just one revision).
210
+ * Returns `[]` when `conformance/` doesn't exist. */
211
+ export function listAllFindingsFiles(baseDir) {
212
+ const dir = join(baseDir, "conformance");
213
+ let entries;
214
+ try {
215
+ entries = readdirSync(dir);
216
+ }
217
+ catch {
218
+ return [];
219
+ }
220
+ const names = entries.filter((f) => /^\d{4}-\d{2}-\d{2}-findings\.json$/.test(f)).sort().reverse();
221
+ const results = [];
222
+ for (const name of names) {
223
+ const relPath = `conformance/${name}`;
224
+ let raw;
225
+ try {
226
+ raw = readFileSync(join(dir, name), "utf8");
227
+ }
228
+ catch {
229
+ continue;
230
+ }
231
+ let parsed;
232
+ try {
233
+ parsed = JSON.parse(raw);
234
+ }
235
+ catch {
236
+ continue;
237
+ }
238
+ const shape = validateFindingsShape(parsed);
239
+ if (shape.ok)
240
+ results.push({ path: relPath, doc: shape.doc });
241
+ }
242
+ return results;
243
+ }
244
+ /** Every finding still blocking `inScope` — `locus === null` (never ruled) AND (`slice === null`,
245
+ * ambiguous/whole-model, counts as in scope for everything, OR `slice` names a key in
246
+ * `inScope`). The one predicate `em slice conform` (a single-key `inScope`), `em state
247
+ * set-conformance` (every `implemented` slice), and `em status`'s `unruledFindings` count all
248
+ * share — never re-derived three different ways. */
249
+ export function unruledFindingsInScope(findings, inScope) {
250
+ return findings.filter((f) => f.locus === null && (f.slice === null || inScope.has(f.slice)));
251
+ }
252
+ /** Parse a `--findings` spec (`"1-3"`, `"1, 2, 4"`, `"1-3, 7"`) into a deduped, ascending list of
253
+ * finding ids. Accepts the same conservative charset `applySupersededBanner`'s `FINDINGS_RE`
254
+ * already gates on (digits/commas/spaces/either dash style) — this just additionally parses the
255
+ * numbers out rather than treating the spec as opaque banner text. Returns null for anything
256
+ * that doesn't parse cleanly (an empty spec, a reversed range, a stray token) — same
257
+ * never-guess discipline every other em parser holds. */
258
+ export function parseFindingsSpec(spec) {
259
+ const trimmed = spec.trim();
260
+ if (!trimmed)
261
+ return null;
262
+ const ids = new Set();
263
+ for (const part of trimmed.split(",")) {
264
+ const p = part.trim();
265
+ if (!p)
266
+ continue;
267
+ const range = /^(\d+)\s*[-–—]\s*(\d+)$/.exec(p);
268
+ if (range) {
269
+ const lo = Number(range[1]);
270
+ const hi = Number(range[2]);
271
+ if (lo > hi)
272
+ return null;
273
+ for (let n = lo; n <= hi; n++)
274
+ ids.add(n);
275
+ continue;
276
+ }
277
+ const single = /^(\d+)$/.exec(p);
278
+ if (!single)
279
+ return null;
280
+ ids.add(Number(single[1]));
281
+ }
282
+ return ids.size > 0 ? [...ids].sort((a, b) => a - b) : null;
283
+ }
284
+ /**
285
+ * Pure transform: records `locus`/`resolvedBy`/`resolvedOn` on every finding named by `ids` in
286
+ * `doc`. Refuses (`ok: false`, no partial write) if any named id doesn't exist, or if any named
287
+ * finding already carries a DIFFERENT locus — the same refuse-different-identity discipline
288
+ * `ratify.ts` holds for `ratifiedBy`/`ratifiedOn` (docs/process.md: a ruling is recorded once,
289
+ * with a name and a date; changing it needs a human decision, not a silent overwrite). Idempotent
290
+ * per-finding: a finding already carrying the exact same locus/resolvedBy/resolvedOn is left
291
+ * alone (doesn't count toward `changed`).
292
+ */
293
+ export function applyFindingsRuling(doc, ids, locus, resolvedBy, resolvedOn) {
294
+ const trimmedBy = resolvedBy.trim();
295
+ if (!trimmedBy)
296
+ return { ok: false, message: "a resolver name is required (--by)" };
297
+ if (!isValidDateString(resolvedOn))
298
+ return { ok: false, message: `invalid date "${resolvedOn}" — expected YYYY-MM-DD` };
299
+ const byId = new Map(doc.findings.map((f) => [f.id, f]));
300
+ const missing = ids.filter((id) => !byId.has(id));
301
+ if (missing.length > 0) {
302
+ return { ok: false, message: `no finding(s) with id ${missing.join(", ")} in the findings record` };
303
+ }
304
+ for (const id of ids) {
305
+ const f = byId.get(id);
306
+ if (f.locus !== null && f.locus !== locus) {
307
+ return { ok: false, message: `finding ${id} already has locus "${f.locus}" — refusing to overwrite with "${locus}"` };
308
+ }
309
+ }
310
+ let changed = false;
311
+ const idSet = new Set(ids);
312
+ const findings = doc.findings.map((f) => {
313
+ if (!idSet.has(f.id))
314
+ return f;
315
+ if (f.locus === locus && f.resolvedBy === trimmedBy && f.resolvedOn === resolvedOn)
316
+ return f; // no-op
317
+ changed = true;
318
+ return { ...f, locus, resolvedBy: trimmedBy, resolvedOn };
319
+ });
320
+ return { ok: true, doc: { ...doc, findings }, changed };
321
+ }
322
+ /** `em conform-findings check <path>` — read `path` off disk, parse, and shape-validate. Never
323
+ * throws; every failure comes back as `errors`, one message per problem. */
324
+ export function checkFindingsFile(path) {
325
+ if (!existsSync(path))
326
+ return { ok: false, path, errors: [`no such file: ${path}`] };
327
+ let raw;
328
+ try {
329
+ raw = readFileSync(path, "utf8");
330
+ }
331
+ catch (e) {
332
+ return { ok: false, path, errors: [`could not read ${path}: ${e.message}`] };
333
+ }
334
+ let parsed;
335
+ try {
336
+ parsed = JSON.parse(raw);
337
+ }
338
+ catch {
339
+ return { ok: false, path, errors: [`${path} is not valid JSON`] };
340
+ }
341
+ const shape = validateFindingsShape(parsed);
342
+ if (!shape.ok)
343
+ return { ok: false, path, errors: shape.errors };
344
+ return { ok: true, path, findingsCount: shape.doc.findings.length };
345
+ }
346
+ /** `em conform-findings check --json` document shape — a small envelope, no schema-version field
347
+ * of its own (this IS the check tool for `findingsSchemaVersion`; the tool's own output isn't a
348
+ * long-lived emitted artifact the way a report/status document is). */
349
+ export function buildCheckFindingsJson(result) {
350
+ const doc = result.ok
351
+ ? { ok: true, path: result.path, findingsCount: result.findingsCount, errors: [] }
352
+ : { ok: false, path: result.path, findingsCount: null, errors: result.errors };
353
+ return JSON.stringify(doc, null, 2);
354
+ }
355
+ //# sourceMappingURL=findings.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"findings.js","sourceRoot":"","sources":["../../src/cli/findings.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,93 @@
1
+ import { GitRunner } from "./diff-inputs.js";
2
+ export interface TurnaroundStats {
3
+ count: number;
4
+ medianDays: number | null;
5
+ minDays: number | null;
6
+ maxDays: number | null;
7
+ }
8
+ export interface TurnaroundEvent {
9
+ commit: string;
10
+ date: string;
11
+ }
12
+ /** One slice's lifecycle-field-appearance events within the range, and the two turnaround
13
+ * gaps derived from them. An event is the FIRST commit in the range where the field flips
14
+ * from absent to present relative to what it was immediately before (the state at `--from`,
15
+ * or the previous commit walked) — a slice re-ratified more than once within the same range
16
+ * reports only its first cycle's events, a documented simplification (docs/cli.md). */
17
+ export interface SliceTurnaround {
18
+ key: string;
19
+ reviewedOn: TurnaroundEvent | null;
20
+ ratifiedOn: TurnaroundEvent | null;
21
+ implementedIn: TurnaroundEvent | null;
22
+ reviewToRatifyDays: number | null;
23
+ ratifyToImplementDays: number | null;
24
+ }
25
+ export interface RatificationTurnaroundMetric {
26
+ slices: SliceTurnaround[];
27
+ reviewToRatify: TurnaroundStats;
28
+ ratifyToImplement: TurnaroundStats;
29
+ }
30
+ export type RevisionSource = "findings" | "state" | null;
31
+ export interface ConformCadenceEntry {
32
+ /** The date encoded in the report's own filename (`conformance/<date>-report.md`) — the
33
+ * date the conform run itself recorded, not the (possibly later) commit date. */
34
+ date: string;
35
+ /** Repo-model-relative path, e.g. `conformance/2026-09-01-report.md` — same convention
36
+ * `Last conformance:`/`em conform-supersede` use. */
37
+ path: string;
38
+ /** The commit that added this report file. */
39
+ commit: string;
40
+ revision: string | null;
41
+ revisionSource: RevisionSource;
42
+ findingsCount: number | null;
43
+ ruledCount: number | null;
44
+ unruledCount: number | null;
45
+ daysSincePrevious: number | null;
46
+ }
47
+ export interface ConformCadenceMetric {
48
+ entries: ConformCadenceEntry[];
49
+ medianCadenceDays: number | null;
50
+ }
51
+ export interface StatusVsRealityPoint {
52
+ commit: string;
53
+ date: string;
54
+ disagreementCount: number;
55
+ unpropagatedCount: number;
56
+ }
57
+ export interface StatusVsRealityMetric {
58
+ series: StatusVsRealityPoint[];
59
+ current: {
60
+ disagreementCount: number;
61
+ unpropagatedCount: number;
62
+ };
63
+ }
64
+ export interface MetricsResult {
65
+ from: string;
66
+ to: string;
67
+ ratificationTurnaround: RatificationTurnaroundMetric;
68
+ conformCadence: ConformCadenceMetric;
69
+ /** Metric 3 name in the register: whether `status:`/`implementedIn:` agree with what a
70
+ * conform sweep would actually find — see `StatusVsRealityMetric`. */
71
+ statusVsReality: StatusVsRealityMetric;
72
+ /** Metric 4 — "does the readiness gate change what gets built" — is NOT computable from git
73
+ * history (no counterfactual repo without the gate exists to compare against); always
74
+ * `null`. See docs/usage-data.md. */
75
+ readinessGateEffect: null;
76
+ }
77
+ export type ComputeMetricsResult = {
78
+ ok: true;
79
+ result: MetricsResult;
80
+ } | {
81
+ ok: false;
82
+ message: string;
83
+ };
84
+ /**
85
+ * Compute `em metrics <file> --from <rev> --to <rev>` (MIL-170): the three git-history-
86
+ * computable pilot metrics, plus `readinessGateEffect: null` (Metric 4, not computable — see
87
+ * `MetricsResult`). `anchorFile` locates `slices/`/`conformance/`/`.event-modeling.md` relative
88
+ * to it, same convention `em ledger`/`em conform-scope` use — never parsed or compiled.
89
+ */
90
+ export declare function computeMetrics(anchorFile: string, from: string, to: string, runGit?: GitRunner): ComputeMetricsResult;
91
+ /** The `em metrics` text report — same "rollup line(s), then detail" spirit `em status`'s text
92
+ * form uses, kept plain (no table alignment) since the row counts are unbounded. */
93
+ export declare function formatMetricsText(m: MetricsResult): string;