@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.
- package/.claude/skills/event-modeling/SKILL.md +4 -2
- package/.claude/skills/event-modeling-conform/SKILL.md +20 -9
- package/.claude/skills/event-modeling-conform/reference/conform.md +107 -10
- package/.claude/skills/event-modeling-design/SKILL.md +10 -2
- package/.claude/skills/event-modeling-discover/SKILL.md +1 -1
- package/.claude/skills/event-modeling-implement/SKILL.md +1 -1
- package/.claude/skills/event-modeling-implement/reference/implement.md +12 -6
- package/.claude/skills/event-modeling-review/SKILL.md +5 -1
- package/.claude/skills/event-modeling-shared/reference/em-dsl.md +40 -2
- package/.claude/skills/event-modeling-shared/reference/operating-principles.md +13 -3
- package/.claude/skills/event-modeling-shared/reference/slice-doc-schema.md +30 -0
- package/.claude/skills/event-modeling-shared/templates/conformance-report.md +8 -1
- package/.claude/skills/event-modeling-shared/templates/state.md +3 -0
- package/README.md +1 -0
- package/dist/catalog/docJoin.d.ts +9 -0
- package/dist/catalog/docJoin.js +6 -0
- package/dist/catalog/docJoin.js.map +1 -1
- package/dist/catalog/driftSignal.d.ts +11 -5
- package/dist/catalog/driftSignal.js +18 -4
- package/dist/catalog/driftSignal.js.map +1 -1
- package/dist/catalog/modelVersionValidate.d.ts +9 -0
- package/dist/catalog/modelVersionValidate.js +38 -0
- package/dist/catalog/modelVersionValidate.js.map +1 -0
- package/dist/catalog/sliceDoc.d.ts +12 -0
- package/dist/catalog/sliceDoc.js +15 -0
- package/dist/catalog/sliceDoc.js.map +1 -1
- package/dist/cli/ciInit.js +17 -0
- package/dist/cli/ciInit.js.map +1 -1
- package/dist/cli/conformScope.d.ts +1 -0
- package/dist/cli/conformScope.js +3 -1
- package/dist/cli/conformScope.js.map +1 -1
- package/dist/cli/conformSupersede.d.ts +24 -2
- package/dist/cli/conformSupersede.js +44 -3
- package/dist/cli/conformSupersede.js.map +1 -1
- package/dist/cli/findings.d.ts +134 -0
- package/dist/cli/findings.js +355 -0
- package/dist/cli/findings.js.map +1 -0
- package/dist/cli/metrics.d.ts +93 -0
- package/dist/cli/metrics.js +331 -0
- package/dist/cli/metrics.js.map +1 -0
- package/dist/cli/modelVersion.d.ts +139 -0
- package/dist/cli/modelVersion.js +256 -0
- package/dist/cli/modelVersion.js.map +1 -0
- package/dist/cli/reratify.d.ts +17 -0
- package/dist/cli/reratify.js +25 -1
- package/dist/cli/reratify.js.map +1 -1
- package/dist/cli/skillCheck.js +2 -2
- package/dist/cli/skillCheck.js.map +1 -1
- package/dist/cli/sliceConform.d.ts +50 -0
- package/dist/cli/sliceConform.js +206 -0
- package/dist/cli/sliceConform.js.map +1 -0
- package/dist/cli/sliceIndex.d.ts +2 -0
- package/dist/cli/sliceIndex.js +15 -3
- package/dist/cli/sliceIndex.js.map +1 -1
- package/dist/cli/sliceNew.d.ts +12 -0
- package/dist/cli/sliceNew.js +23 -0
- package/dist/cli/sliceNew.js.map +1 -1
- package/dist/cli/sliceStubAll.d.ts +76 -0
- package/dist/cli/sliceStubAll.js +189 -0
- package/dist/cli/sliceStubAll.js.map +1 -0
- package/dist/cli/stateFile.d.ts +63 -7
- package/dist/cli/stateFile.js +179 -18
- package/dist/cli/stateFile.js.map +1 -1
- package/dist/cli/status.d.ts +79 -1
- package/dist/cli/status.js +134 -15
- package/dist/cli/status.js.map +1 -1
- package/dist/cli/upgrade.d.ts +122 -0
- package/dist/cli/upgrade.js +476 -0
- package/dist/cli/upgrade.js.map +1 -0
- package/dist/cli.js +524 -36
- package/dist/cli.js.map +1 -1
- package/dist/emit/freshnessJson.d.ts +1 -1
- package/dist/emit/freshnessJson.js +3 -1
- package/dist/emit/freshnessJson.js.map +1 -1
- package/dist/emit/json.d.ts +11 -1
- package/dist/emit/json.js +35 -1
- package/dist/emit/json.js.map +1 -1
- package/dist/emit/metricsJson.d.ts +7 -0
- package/dist/emit/metricsJson.js +27 -0
- package/dist/emit/metricsJson.js.map +1 -0
- package/dist/emit/statusJson.d.ts +1 -1
- package/dist/emit/statusJson.js +17 -1
- package/dist/emit/statusJson.js.map +1 -1
- package/dist/emit/upgradeJson.d.ts +5 -0
- package/dist/emit/upgradeJson.js +26 -0
- package/dist/emit/upgradeJson.js.map +1 -0
- package/dist/emit/validateJson.d.ts +6 -2
- package/dist/emit/validateJson.js +12 -5
- package/dist/emit/validateJson.js.map +1 -1
- package/dist/mcp/server.d.ts +1 -1
- package/dist/mcp/server.js +114 -3
- package/dist/mcp/server.js.map +1 -1
- package/dist/model/rules.d.ts +7 -0
- package/dist/model/rules.js +12 -0
- package/dist/model/rules.js.map +1 -1
- package/dist/model/validate.d.ts +20 -0
- package/dist/model/validate.js +94 -45
- package/dist/model/validate.js.map +1 -1
- package/dist/templates.d.ts +2 -2
- package/dist/templates.js +7 -1
- package/dist/templates.js.map +1 -1
- package/dist/util/semver.d.ts +19 -0
- package/dist/util/semver.js +54 -0
- package/dist/util/semver.js.map +1 -0
- 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":"AAAA,+BAA+B;AAC/B,iGAAiG;AACjG,+FAA+F;AAC/F,iGAAiG;AACjG,iGAAiG;AACjG,8EAA8E;AAC9E,gGAAgG;AAChG,+FAA+F;AAC/F,iGAAiG;AACjG,8FAA8F;AAC9F,oGAAoG;AACpG,sFAAsF;AACtF,EAAE;AACF,4FAA4F;AAC5F,gGAAgG;AAChG,0FAA0F;AAC1F,yBAAyB;AACzB,EAAE;AACF,+FAA+F;AAC/F,gGAAgG;AAChG,wFAAwF;AACxF,8FAA8F;AAC9F,gGAAgG;AAChG,uFAAuF;AACvF,4FAA4F;AAC5F,mCAAmC;AAEnC,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AAChE,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AAEnD,MAAM,CAAC,MAAM,uBAAuB,GAAG,KAAK,CAAC;AAE7C;+FAC+F;AAC/F,MAAM,CAAC,MAAM,gBAAgB,GAAG,CAAC,YAAY,EAAE,MAAM,EAAE,UAAU,EAAE,OAAO,CAAU,CAAC;AAGrF;;wDAEwD;AACxD,MAAM,CAAC,MAAM,YAAY,GAAG,CAAC,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,CAAU,CAAC;AA+BtE,uEAAuE;AACvE,MAAM,UAAU,oBAAoB,CAAC,GAAgB;IACnD,MAAM,MAAM,GAAG,CAAC,GAAG,GAAG,CAAC,QAAQ,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,EAAE,CAAC,CAAC;IAC7D,MAAM,SAAS,GAAG;QAChB,QAAQ,EAAE,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;YAC3B,KAAK,EAAE,CAAC,CAAC,KAAK;YACd,QAAQ,EAAE,CAAC,CAAC,QAAQ;YACpB,EAAE,EAAE,CAAC,CAAC,EAAE;YACR,KAAK,EAAE,CAAC,CAAC,KAAK;YACd,UAAU,EAAE,CAAC,CAAC,UAAU;YACxB,UAAU,EAAE,CAAC,CAAC,UAAU;YACxB,KAAK,EAAE,CAAC,CAAC,KAAK;YACd,OAAO,EAAE,CAAC,CAAC,OAAO;SACnB,CAAC,CAAC;QACH,qBAAqB,EAAE,GAAG,CAAC,qBAAqB;QAChD,KAAK,EAAE,GAAG,CAAC,KAAK;QAChB,MAAM,EAAE,GAAG,CAAC,MAAM;QAClB,QAAQ,EAAE,GAAG,CAAC,QAAQ;KACvB,CAAC;IACF,OAAO,GAAG,IAAI,CAAC,SAAS,CAAC,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC;AACnD,CAAC;AAED,SAAS,gBAAgB,CAAC,CAAU;IAClC,OAAO,OAAO,CAAC,KAAK,QAAQ,IAAI,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC;AAC/C,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,qBAAqB,CAAC,KAAc;IAClD,MAAM,MAAM,GAAa,EAAE,CAAC;IAC5B,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;QACxE,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,CAAC,uCAAuC,CAAC,EAAE,CAAC;IAC1E,CAAC;IACD,MAAM,GAAG,GAAG,KAAgC,CAAC;IAE7C,IAAI,CAAC,gBAAgB,CAAC,GAAG,CAAC,qBAAqB,CAAC;QAAE,MAAM,CAAC,IAAI,CAAC,oDAAoD,CAAC,CAAC;IACpH,IAAI,CAAC,gBAAgB,CAAC,GAAG,CAAC,KAAK,CAAC;QAAE,MAAM,CAAC,IAAI,CAAC,oCAAoC,CAAC,CAAC;IACpF,IAAI,CAAC,gBAAgB,CAAC,GAAG,CAAC,MAAM,CAAC;QAAE,MAAM,CAAC,IAAI,CAAC,qCAAqC,CAAC,CAAC;IACtF,IAAI,CAAC,gBAAgB,CAAC,GAAG,CAAC,QAAQ,CAAC;QAAE,MAAM,CAAC,IAAI,CAAC,uCAAuC,CAAC,CAAC;IAE1F,MAAM,WAAW,GAAG,GAAG,CAAC,QAAQ,CAAC;IACjC,MAAM,QAAQ,GAAc,EAAE,CAAC;IAC/B,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,WAAW,CAAC,EAAE,CAAC;QAChC,MAAM,CAAC,IAAI,CAAC,6BAA6B,CAAC,CAAC;IAC7C,CAAC;SAAM,CAAC;QACN,IAAI,MAAM,GAAkB,IAAI,CAAC;QACjC,MAAM,OAAO,GAAG,IAAI,GAAG,EAAU,CAAC;QAClC,WAAW,CAAC,OAAO,CAAC,CAAC,KAAK,EAAE,CAAC,EAAE,EAAE;YAC/B,MAAM,KAAK,GAAG,YAAY,CAAC,GAAG,CAAC;YAC/B,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;gBACxE,MAAM,CAAC,IAAI,CAAC,GAAG,KAAK,oBAAoB,CAAC,CAAC;gBAC1C,OAAO;YACT,CAAC;YACD,MAAM,CAAC,GAAG,KAAgC,CAAC;YAC3C,MAAM,EAAE,GAAG,CAAC,CAAC,EAAE,CAAC;YAChB,IAAI,OAAO,EAAE,KAAK,QAAQ,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,EAAE,CAAC,EAAE,CAAC;gBACpD,MAAM,CAAC,IAAI,CAAC,GAAG,KAAK,wBAAwB,CAAC,CAAC;YAChD,CAAC;iBAAM,CAAC;gBACN,IAAI,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC;oBAAE,MAAM,CAAC,IAAI,CAAC,GAAG,KAAK,OAAO,EAAE,sCAAsC,CAAC,CAAC;gBAC1F,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;gBAChB,IAAI,MAAM,KAAK,IAAI,IAAI,EAAE,IAAI,MAAM;oBAAE,MAAM,CAAC,IAAI,CAAC,GAAG,KAAK,OAAO,EAAE,4DAA4D,CAAC,CAAC;gBAChI,MAAM,GAAG,EAAE,CAAC;YACd,CAAC;YACD,IAAI,OAAO,CAAC,CAAC,OAAO,KAAK,QAAQ,IAAI,CAAE,gBAAsC,CAAC,QAAQ,CAAC,CAAC,CAAC,OAAO,CAAC,EAAE,CAAC;gBAClG,MAAM,CAAC,IAAI,CAAC,GAAG,KAAK,4BAA4B,gBAAgB,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;YACjF,CAAC;YACD,IAAI,CAAC,gBAAgB,CAAC,CAAC,CAAC,KAAK,CAAC;gBAAE,MAAM,CAAC,IAAI,CAAC,GAAG,KAAK,mCAAmC,CAAC,CAAC;YACzF,IAAI,CAAC,CAAC,KAAK,KAAK,IAAI,IAAI,OAAO,CAAC,CAAC,KAAK,KAAK,QAAQ;gBAAE,MAAM,CAAC,IAAI,CAAC,GAAG,KAAK,iCAAiC,CAAC,CAAC;YAC5G,IAAI,CAAC,gBAAgB,CAAC,CAAC,CAAC,QAAQ,CAAC;gBAAE,MAAM,CAAC,IAAI,CAAC,GAAG,KAAK,sCAAsC,CAAC,CAAC;YAC/F,MAAM,OAAO,GAAG,CAAC,CAAC,KAAK,KAAK,IAAI,IAAI,CAAC,OAAO,CAAC,CAAC,KAAK,KAAK,QAAQ,IAAK,YAAkC,CAAC,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC;YAC3H,IAAI,CAAC,OAAO;gBAAE,MAAM,CAAC,IAAI,CAAC,GAAG,KAAK,kCAAkC,YAAY,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;YAC/F,MAAM,YAAY,GAAG,CAAC,CAAC,UAAU,KAAK,IAAI,IAAI,gBAAgB,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC;YAC7E,IAAI,CAAC,YAAY;gBAAE,MAAM,CAAC,IAAI,CAAC,GAAG,KAAK,gDAAgD,CAAC,CAAC;YACzF,MAAM,YAAY,GAAG,CAAC,CAAC,UAAU,KAAK,IAAI,IAAI,CAAC,OAAO,CAAC,CAAC,UAAU,KAAK,QAAQ,IAAI,iBAAiB,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC;YACpH,IAAI,CAAC,YAAY;gBAAE,MAAM,CAAC,IAAI,CAAC,GAAG,KAAK,iDAAiD,CAAC,CAAC;YAC1F,0FAA0F;YAC1F,yFAAyF;YACzF,8CAA8C;YAC9C,IAAI,OAAO,IAAI,CAAC,CAAC,KAAK,KAAK,IAAI,EAAE,CAAC;gBAChC,IAAI,CAAC,CAAC,UAAU,KAAK,IAAI,IAAI,CAAC,gBAAgB,CAAC,CAAC,CAAC,UAAU,CAAC;oBAAE,MAAM,CAAC,IAAI,CAAC,GAAG,KAAK,2CAA2C,CAAC,CAAC;gBAC/H,IAAI,CAAC,CAAC,UAAU,KAAK,IAAI,IAAI,CAAC,CAAC,OAAO,CAAC,CAAC,UAAU,KAAK,QAAQ,IAAI,iBAAiB,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,EAAE,CAAC;oBACpG,MAAM,CAAC,IAAI,CAAC,GAAG,KAAK,2CAA2C,CAAC,CAAC;gBACnE,CAAC;YACH,CAAC;YACD,QAAQ,CAAC,IAAI,CAAC;gBACZ,EAAE,EAAE,OAAO,EAAE,KAAK,QAAQ,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG;gBACrC,OAAO,EAAE,CAAC,OAAO,CAAC,CAAC,OAAO,KAAK,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,OAAO,CAAmB;gBAChF,KAAK,EAAE,OAAO,CAAC,CAAC,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE;gBACjD,KAAK,EAAG,CAAC,CAAC,KAAuB,IAAI,IAAI;gBACzC,QAAQ,EAAE,OAAO,CAAC,CAAC,QAAQ,KAAK,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE;gBAC1D,KAAK,EAAG,CAAC,CAAC,KAA6B,IAAI,IAAI;gBAC/C,UAAU,EAAG,CAAC,CAAC,UAA4B,IAAI,IAAI;gBACnD,UAAU,EAAG,CAAC,CAAC,UAA4B,IAAI,IAAI;aACpD,CAAC,CAAC;QACL,CAAC,CAAC,CAAC;IACL,CAAC;IAED,IAAI,MAAM,CAAC,MAAM,GAAG,CAAC;QAAE,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,CAAC;IACpD,OAAO;QACL,EAAE,EAAE,IAAI;QACR,GAAG,EAAE;YACH,qBAAqB,EAAE,GAAG,CAAC,qBAA+B;YAC1D,KAAK,EAAE,GAAG,CAAC,KAAe;YAC1B,MAAM,EAAE,GAAG,CAAC,MAAgB;YAC5B,QAAQ,EAAE,GAAG,CAAC,QAAkB;YAChC,QAAQ;SACT;KACF,CAAC;AACJ,CAAC;AAED,MAAM,gBAAgB,GAAG,cAAc,CAAC;AAExC;;;8FAG8F;AAC9F,MAAM,UAAU,qBAAqB,CAAC,UAAkB;IACtD,OAAO,gBAAgB,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC,OAAO,CAAC,gBAAgB,EAAE,gBAAgB,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;AAC3G,CAAC;AAOD;;;;;;;;GAQG;AACH,MAAM,UAAU,0BAA0B,CAAC,OAAe,EAAE,UAAkB;IAC5E,MAAM,WAAW,GAAG,qBAAqB,CAAC,UAAU,CAAC,CAAC;IACtD,IAAI,WAAW,KAAK,IAAI;QAAE,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAC;IACpD,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,EAAE,WAAW,CAAC,CAAC;IAC3C,IAAI,CAAC,UAAU,CAAC,OAAO,CAAC;QAAE,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAC;IACpD,IAAI,GAAW,CAAC;IAChB,IAAI,CAAC;QACH,GAAG,GAAG,YAAY,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;IACtC,CAAC;IAAC,OAAO,CAAC,EAAE,CAAC;QACX,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,IAAI,EAAE,WAAW,EAAE,OAAO,EAAE,kBAAkB,WAAW,KAAM,CAAW,CAAC,OAAO,EAAE,EAAE,CAAC;IACnH,CAAC;IACD,IAAI,MAAe,CAAC;IACpB,IAAI,CAAC;QACH,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IAC3B,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,IAAI,EAAE,WAAW,EAAE,OAAO,EAAE,GAAG,WAAW,oBAAoB,EAAE,CAAC;IAC7F,CAAC;IACD,MAAM,KAAK,GAAG,qBAAqB,CAAC,MAAM,CAAC,CAAC;IAC5C,IAAI,CAAC,KAAK,CAAC,EAAE;QAAE,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,IAAI,EAAE,WAAW,EAAE,OAAO,EAAE,GAAG,WAAW,KAAK,KAAK,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,EAAE,CAAC;IACpH,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,WAAW,EAAE,GAAG,EAAE,KAAK,CAAC,GAAG,EAAE,CAAC;AAC9D,CAAC;AAED;;;;;sDAKsD;AACtD,MAAM,UAAU,oBAAoB,CAAC,OAAe;IAClD,MAAM,GAAG,GAAG,IAAI,CAAC,OAAO,EAAE,aAAa,CAAC,CAAC;IACzC,IAAI,OAAiB,CAAC;IACtB,IAAI,CAAC;QACH,OAAO,GAAG,WAAW,CAAC,GAAG,CAAC,CAAC;IAC7B,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,CAAC;IACZ,CAAC;IACD,MAAM,KAAK,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,oCAAoC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,OAAO,EAAE,CAAC;IACnG,MAAM,OAAO,GAAyC,EAAE,CAAC;IACzD,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,MAAM,OAAO,GAAG,eAAe,IAAI,EAAE,CAAC;QACtC,IAAI,GAAW,CAAC;QAChB,IAAI,CAAC;YACH,GAAG,GAAG,YAAY,CAAC,IAAI,CAAC,GAAG,EAAE,IAAI,CAAC,EAAE,MAAM,CAAC,CAAC;QAC9C,CAAC;QAAC,MAAM,CAAC;YACP,SAAS;QACX,CAAC;QACD,IAAI,MAAe,CAAC;QACpB,IAAI,CAAC;YACH,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;QAC3B,CAAC;QAAC,MAAM,CAAC;YACP,SAAS;QACX,CAAC;QACD,MAAM,KAAK,GAAG,qBAAqB,CAAC,MAAM,CAAC,CAAC;QAC5C,IAAI,KAAK,CAAC,EAAE;YAAE,OAAO,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,GAAG,EAAE,KAAK,CAAC,GAAG,EAAE,CAAC,CAAC;IAChE,CAAC;IACD,OAAO,OAAO,CAAC;AACjB,CAAC;AAED;;;;qDAIqD;AACrD,MAAM,UAAU,sBAAsB,CAAC,QAAmB,EAAE,OAA4B;IACtF,OAAO,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,KAAK,IAAI,IAAI,CAAC,CAAC,CAAC,KAAK,KAAK,IAAI,IAAI,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;AAChG,CAAC;AAED;;;;;0DAK0D;AAC1D,MAAM,UAAU,iBAAiB,CAAC,IAAY;IAC5C,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,EAAE,CAAC;IAC5B,IAAI,CAAC,OAAO;QAAE,OAAO,IAAI,CAAC;IAC1B,MAAM,GAAG,GAAG,IAAI,GAAG,EAAU,CAAC;IAC9B,KAAK,MAAM,IAAI,IAAI,OAAO,CAAC,KAAK,CAAC,GAAG,CAAC,EAAE,CAAC;QACtC,MAAM,CAAC,GAAG,IAAI,CAAC,IAAI,EAAE,CAAC;QACtB,IAAI,CAAC,CAAC;YAAE,SAAS;QACjB,MAAM,KAAK,GAAG,yBAAyB,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAChD,IAAI,KAAK,EAAE,CAAC;YACV,MAAM,EAAE,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;YAC5B,MAAM,EAAE,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;YAC5B,IAAI,EAAE,GAAG,EAAE;gBAAE,OAAO,IAAI,CAAC;YACzB,KAAK,IAAI,CAAC,GAAG,EAAE,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,EAAE;gBAAE,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;YAC1C,SAAS;QACX,CAAC;QACD,MAAM,MAAM,GAAG,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QACjC,IAAI,CAAC,MAAM;YAAE,OAAO,IAAI,CAAC;QACzB,GAAG,CAAC,GAAG,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IAC7B,CAAC;IACD,OAAO,GAAG,CAAC,IAAI,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;AAC9D,CAAC;AAID;;;;;;;;GAQG;AACH,MAAM,UAAU,mBAAmB,CACjC,GAAgB,EAChB,GAAa,EACb,KAAmB,EACnB,UAAkB,EAClB,UAAkB;IAElB,MAAM,SAAS,GAAG,UAAU,CAAC,IAAI,EAAE,CAAC;IACpC,IAAI,CAAC,SAAS;QAAE,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,OAAO,EAAE,oCAAoC,EAAE,CAAC;IACpF,IAAI,CAAC,iBAAiB,CAAC,UAAU,CAAC;QAAE,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,OAAO,EAAE,iBAAiB,UAAU,yBAAyB,EAAE,CAAC;IAExH,MAAM,IAAI,GAAG,IAAI,GAAG,CAAC,GAAG,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC;IACzD,MAAM,OAAO,GAAG,GAAG,CAAC,MAAM,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC;IAClD,IAAI,OAAO,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACvB,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,OAAO,EAAE,yBAAyB,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,yBAAyB,EAAE,CAAC;IACtG,CAAC;IACD,KAAK,MAAM,EAAE,IAAI,GAAG,EAAE,CAAC;QACrB,MAAM,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,EAAE,CAAE,CAAC;QACxB,IAAI,CAAC,CAAC,KAAK,KAAK,IAAI,IAAI,CAAC,CAAC,KAAK,KAAK,KAAK,EAAE,CAAC;YAC1C,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,OAAO,EAAE,WAAW,EAAE,uBAAuB,CAAC,CAAC,KAAK,mCAAmC,KAAK,GAAG,EAAE,CAAC;QACxH,CAAC;IACH,CAAC;IAED,IAAI,OAAO,GAAG,KAAK,CAAC;IACpB,MAAM,KAAK,GAAG,IAAI,GAAG,CAAC,GAAG,CAAC,CAAC;IAC3B,MAAM,QAAQ,GAAG,GAAG,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE;QACtC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC;YAAE,OAAO,CAAC,CAAC;QAC/B,IAAI,CAAC,CAAC,KAAK,KAAK,KAAK,IAAI,CAAC,CAAC,UAAU,KAAK,SAAS,IAAI,CAAC,CAAC,UAAU,KAAK,UAAU;YAAE,OAAO,CAAC,CAAC,CAAC,QAAQ;QACtG,OAAO,GAAG,IAAI,CAAC;QACf,OAAO,EAAE,GAAG,CAAC,EAAE,KAAK,EAAE,UAAU,EAAE,SAAS,EAAE,UAAU,EAAE,CAAC;IAC5D,CAAC,CAAC,CAAC;IACH,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,GAAG,EAAE,EAAE,GAAG,GAAG,EAAE,QAAQ,EAAE,EAAE,OAAO,EAAE,CAAC;AAC1D,CAAC;AAID;6EAC6E;AAC7E,MAAM,UAAU,iBAAiB,CAAC,IAAY;IAC5C,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC;QAAE,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC,iBAAiB,IAAI,EAAE,CAAC,EAAE,CAAC;IACrF,IAAI,GAAW,CAAC;IAChB,IAAI,CAAC;QACH,GAAG,GAAG,YAAY,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;IACnC,CAAC;IAAC,OAAO,CAAC,EAAE,CAAC;QACX,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC,kBAAkB,IAAI,KAAM,CAAW,CAAC,OAAO,EAAE,CAAC,EAAE,CAAC;IAC1F,CAAC;IACD,IAAI,MAAe,CAAC;IACpB,IAAI,CAAC;QACH,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IAC3B,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC,GAAG,IAAI,oBAAoB,CAAC,EAAE,CAAC;IACpE,CAAC;IACD,MAAM,KAAK,GAAG,qBAAqB,CAAC,MAAM,CAAC,CAAC;IAC5C,IAAI,CAAC,KAAK,CAAC,EAAE;QAAE,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,CAAC,MAAM,EAAE,CAAC;IAChE,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,aAAa,EAAE,KAAK,CAAC,GAAG,CAAC,QAAQ,CAAC,MAAM,EAAE,CAAC;AACtE,CAAC;AAED;;wEAEwE;AACxE,MAAM,UAAU,sBAAsB,CAAC,MAA2B;IAChE,MAAM,GAAG,GAAG,MAAM,CAAC,EAAE;QACnB,CAAC,CAAC,EAAE,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,CAAC,IAAI,EAAE,aAAa,EAAE,MAAM,CAAC,aAAa,EAAE,MAAM,EAAE,EAAc,EAAE;QAC9F,CAAC,CAAC,EAAE,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,MAAM,CAAC,IAAI,EAAE,aAAa,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,CAAC;IACjF,OAAO,IAAI,CAAC,SAAS,CAAC,GAAG,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC;AACtC,CAAC"}
|
|
@@ -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;
|