@milehimikey/em 1.8.1 → 1.9.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 +58 -404
- package/.claude/skills/event-modeling-conform/SKILL.md +77 -0
- package/.claude/skills/{event-modeling → event-modeling-conform}/reference/conform.md +39 -13
- package/.claude/skills/event-modeling-design/SKILL.md +139 -0
- package/.claude/skills/event-modeling-discover/SKILL.md +89 -0
- package/.claude/skills/{event-modeling → event-modeling-discover}/reference/extract.md +3 -2
- package/.claude/skills/event-modeling-implement/SKILL.md +40 -0
- package/.claude/skills/{event-modeling → event-modeling-implement}/reference/implement.md +3 -2
- package/.claude/skills/event-modeling-review/SKILL.md +58 -0
- package/.claude/skills/{event-modeling → event-modeling-shared}/reference/em-dsl.md +115 -83
- package/.claude/skills/{event-modeling → event-modeling-shared}/reference/methodology.md +6 -5
- package/.claude/skills/event-modeling-shared/reference/operating-principles.md +147 -0
- package/.claude/skills/{event-modeling → event-modeling-shared}/templates/model-readme.md +2 -2
- package/.claude/skills/{event-modeling → event-modeling-shared}/templates/slice.md +51 -8
- package/README.md +24 -4
- package/dist/catalog/build.js +9 -0
- package/dist/catalog/build.js.map +1 -1
- package/dist/catalog/docJoin.d.ts +15 -0
- package/dist/catalog/docJoin.js +8 -0
- package/dist/catalog/docJoin.js.map +1 -1
- package/dist/catalog/modelCollisionValidate.d.ts +22 -0
- package/dist/catalog/modelCollisionValidate.js +67 -0
- package/dist/catalog/modelCollisionValidate.js.map +1 -0
- package/dist/catalog/orphanedSliceDocValidate.d.ts +11 -0
- package/dist/catalog/orphanedSliceDocValidate.js +98 -0
- package/dist/catalog/orphanedSliceDocValidate.js.map +1 -0
- package/dist/catalog/sliceDoc.d.ts +15 -0
- package/dist/catalog/sliceDoc.js +18 -0
- package/dist/catalog/sliceDoc.js.map +1 -1
- package/dist/cli/changelogBuild.d.ts +12 -0
- package/dist/cli/changelogBuild.js +89 -0
- package/dist/cli/changelogBuild.js.map +1 -0
- package/dist/cli/ciInit.d.ts +42 -0
- package/dist/cli/ciInit.js +298 -0
- package/dist/cli/ciInit.js.map +1 -0
- package/dist/cli/conformSupersede.d.ts +32 -0
- package/dist/cli/conformSupersede.js +102 -0
- package/dist/cli/conformSupersede.js.map +1 -0
- package/dist/cli/coverage.d.ts +6 -1
- package/dist/cli/coverage.js +28 -3
- package/dist/cli/coverage.js.map +1 -1
- package/dist/cli/frontmatterSurgery.d.ts +28 -0
- package/dist/cli/frontmatterSurgery.js +66 -0
- package/dist/cli/frontmatterSurgery.js.map +1 -0
- package/dist/cli/markImplemented.js +6 -45
- package/dist/cli/markImplemented.js.map +1 -1
- package/dist/cli/ratify.d.ts +41 -0
- package/dist/cli/ratify.js +151 -0
- package/dist/cli/ratify.js.map +1 -0
- package/dist/cli/reratify.d.ts +33 -0
- package/dist/cli/reratify.js +137 -0
- package/dist/cli/reratify.js.map +1 -0
- package/dist/cli/skillCheck.d.ts +6 -0
- package/dist/cli/skillCheck.js +46 -0
- package/dist/cli/skillCheck.js.map +1 -1
- package/dist/cli/skillDirs.d.ts +15 -0
- package/dist/cli/skillDirs.js +34 -0
- package/dist/cli/skillDirs.js.map +1 -0
- package/dist/cli/skillSync.d.ts +10 -0
- package/dist/cli/skillSync.js +15 -0
- package/dist/cli/skillSync.js.map +1 -1
- package/dist/cli/sliceIndex.d.ts +3 -0
- package/dist/cli/sliceIndex.js +6 -2
- package/dist/cli/sliceIndex.js.map +1 -1
- package/dist/cli/sliceLink.d.ts +52 -0
- package/dist/cli/sliceLink.js +152 -0
- package/dist/cli/sliceLink.js.map +1 -0
- package/dist/cli/status.d.ts +261 -0
- package/dist/cli/status.js +536 -0
- package/dist/cli/status.js.map +1 -0
- package/dist/cli/usageLog.d.ts +75 -0
- package/dist/cli/usageLog.js +193 -0
- package/dist/cli/usageLog.js.map +1 -0
- package/dist/cli.js +501 -130
- package/dist/cli.js.map +1 -1
- package/dist/emit/freshnessJson.d.ts +6 -0
- package/dist/emit/freshnessJson.js +24 -0
- package/dist/emit/freshnessJson.js.map +1 -0
- package/dist/emit/json.d.ts +62 -8
- package/dist/emit/json.js +14 -3
- package/dist/emit/json.js.map +1 -1
- package/dist/emit/statusJson.d.ts +7 -0
- package/dist/emit/statusJson.js +39 -0
- package/dist/emit/statusJson.js.map +1 -0
- package/dist/emit/typespec.d.ts +26 -0
- package/dist/emit/typespec.js +356 -0
- package/dist/emit/typespec.js.map +1 -0
- package/dist/emit/usageReportJson.d.ts +3 -0
- package/dist/emit/usageReportJson.js +23 -0
- package/dist/emit/usageReportJson.js.map +1 -0
- package/dist/mcp/server.d.ts +1 -1
- package/dist/mcp/server.js +301 -11
- package/dist/mcp/server.js.map +1 -1
- package/dist/model/rules.d.ts +13 -0
- package/dist/model/rules.js +20 -0
- package/dist/model/rules.js.map +1 -1
- package/dist/templates.d.ts +1 -1
- package/dist/templates.js +2 -2
- package/dist/util/markers.d.ts +16 -4
- package/dist/util/markers.js +23 -10
- package/dist/util/markers.js.map +1 -1
- package/package.json +2 -1
- /package/.claude/skills/{event-modeling → event-modeling-shared}/templates/conformance-report.md +0 -0
- /package/.claude/skills/{event-modeling → event-modeling-shared}/templates/state.md +0 -0
|
@@ -0,0 +1,536 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
// `em status` (MIL-163): one deterministic command that answers "what state is the system in"
|
|
3
|
+
// over one or more models — the surface a reader currently has to assemble by hand from the
|
|
4
|
+
// README's Slices table, a conformance report, and `.event-modeling.md`. Pure aggregation over
|
|
5
|
+
// exports the rest of `em` already computes; nothing here re-derives a fact another module owns:
|
|
6
|
+
//
|
|
7
|
+
// - lifecycle status / driftSignal come from the same doc join `em export`/`em coverage` use
|
|
8
|
+
// (catalog/docJoin.ts's resolveSliceDocJoin, catalog/driftSignal.ts's classification).
|
|
9
|
+
// - open Open Questions counts come from the same slice-doc parse `em coverage`'s scope check
|
|
10
|
+
// already reads (catalog/sliceDoc.ts's openQuestionsTotal/openQuestionsUnchecked).
|
|
11
|
+
// - invariant coverage totals come from `em coverage`'s own report builder (./coverage.ts),
|
|
12
|
+
// summed across every input model rather than re-implemented.
|
|
13
|
+
// - last-conformance + commits-behind-HEAD reuses stateFile.ts's parser and the same injectable
|
|
14
|
+
// GitRunner convention conform-scope.ts/diff-inputs.ts use, so every git failure branch stays
|
|
15
|
+
// unit-testable without a real repository.
|
|
16
|
+
// - slice-PRs-behind-HEAD (MIL-164) reuses conform-scope.ts's own machinery outright —
|
|
17
|
+
// `changedPathsSince` + `buildConformScope` — rather than re-deriving the
|
|
18
|
+
// implementedIn-to-changed-path match rule a second time. The standalone surface for this
|
|
19
|
+
// freshness clause alone (no full model rollup needed) is `em freshness` (freshness.ts).
|
|
20
|
+
//
|
|
21
|
+
// Deterministic core: no LLM calls, no wall-clock timestamps in the output, byte-stable for the
|
|
22
|
+
// same inputs. Text/markdown/badge are formatting layers over one aggregated StatusReport; the
|
|
23
|
+
// JSON document (emit/statusJson.ts) is a versioned envelope around the exact same object.
|
|
24
|
+
import { basename, dirname, resolve } from "node:path";
|
|
25
|
+
import { resolveSliceDocJoin } from "../catalog/docJoin.js";
|
|
26
|
+
import { readSliceDoc } from "../catalog/readSliceDoc.js";
|
|
27
|
+
import { realGit } from "./diff-inputs.js";
|
|
28
|
+
import { loadStateFile, parseState } from "./stateFile.js";
|
|
29
|
+
import { changedPathsSince, buildConformScope } from "./conformScope.js";
|
|
30
|
+
/** The 4 canonical slice-doc lifecycle statuses (docs/slice-doc-schema.md) — the same enum
|
|
31
|
+
* `em catalog`'s header coloring and `em render`'s status legend already recognize. */
|
|
32
|
+
export const CANONICAL_STATUSES = ["draft", "reviewed", "ready-to-implement", "implemented"];
|
|
33
|
+
export function classifyStatusBucket(found, reason, status) {
|
|
34
|
+
if (!found)
|
|
35
|
+
return "no-doc";
|
|
36
|
+
if (reason === "frontmatter-invalid")
|
|
37
|
+
return "frontmatter-invalid";
|
|
38
|
+
if (status !== null && CANONICAL_STATUSES.includes(status))
|
|
39
|
+
return status;
|
|
40
|
+
return "unknown";
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Resolve every slice's status-rollup facts for one already-compiled model. `baseDir` is the
|
|
44
|
+
* `.em` file's directory, same doc-resolution convention every other doc-aware command uses.
|
|
45
|
+
* Open Questions counts are read from the slice's OWN bound doc (re-deriving the key from
|
|
46
|
+
* `doc.path`, not assuming it's this slice's own — MIL-121 cross-binding can resolve to a
|
|
47
|
+
* different slice's doc, same re-derivation `em coverage`/`--slice-ready` use) rather than a
|
|
48
|
+
* second independent parse.
|
|
49
|
+
*/
|
|
50
|
+
export function resolveSliceStatusFacts(file, model, refs, baseDir) {
|
|
51
|
+
const diagnostics = [];
|
|
52
|
+
const facts = model.slices.map((slice, i) => {
|
|
53
|
+
const key = refs.sliceKeys[i];
|
|
54
|
+
const { doc, diagnostics: docDiags } = resolveSliceDocJoin(slice, key, baseDir, (id) => refs.refById.get(id));
|
|
55
|
+
diagnostics.push(...docDiags);
|
|
56
|
+
let docPath = null;
|
|
57
|
+
let openQuestionsTotal = 0;
|
|
58
|
+
let openQuestionsUnchecked = 0;
|
|
59
|
+
if (doc.found) {
|
|
60
|
+
const boundKey = doc.path.replace(/^slices\//, "").replace(/\.md$/, "");
|
|
61
|
+
docPath = resolve(baseDir, doc.path);
|
|
62
|
+
const parsed = readSliceDoc(baseDir, boundKey);
|
|
63
|
+
if (parsed) {
|
|
64
|
+
openQuestionsTotal = parsed.openQuestionsTotal;
|
|
65
|
+
openQuestionsUnchecked = parsed.openQuestionsUnchecked;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
return {
|
|
69
|
+
file,
|
|
70
|
+
key,
|
|
71
|
+
docFound: doc.found,
|
|
72
|
+
docReason: doc.reason,
|
|
73
|
+
docPath,
|
|
74
|
+
rawStatus: doc.status,
|
|
75
|
+
implementedIn: doc.implementedIn,
|
|
76
|
+
owner: doc.owner,
|
|
77
|
+
bucket: classifyStatusBucket(doc.found, doc.reason, doc.status),
|
|
78
|
+
driftSignal: doc.driftSignal,
|
|
79
|
+
openQuestionsTotal,
|
|
80
|
+
openQuestionsUnchecked,
|
|
81
|
+
};
|
|
82
|
+
});
|
|
83
|
+
return { facts, diagnostics };
|
|
84
|
+
}
|
|
85
|
+
/** Open `issue "text"` markers in a model — the same predicate `em validate --list-issues`
|
|
86
|
+
* counts, here reduced to a bare count for the rollup. */
|
|
87
|
+
export function countOpenIssues(model) {
|
|
88
|
+
return model.elements.filter((el) => el.issue).length;
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* `git rev-list --count <revision>..HEAD` in `repo` (any directory inside it, not necessarily
|
|
92
|
+
* its root — same "anchor dir, resolve toplevel" convention `conformScope.ts`'s
|
|
93
|
+
* `changedPathsSince` uses). The git runner is injectable so every failure branch is
|
|
94
|
+
* unit-testable without a real repository.
|
|
95
|
+
*/
|
|
96
|
+
export function commitsBehindHead(repo, revision, runGit = realGit) {
|
|
97
|
+
const toplevel = runGit(["-C", repo, "rev-parse", "--show-toplevel"]);
|
|
98
|
+
if (toplevel.status !== 0) {
|
|
99
|
+
return { ok: false, message: `em status: ${repo} is not a git repository` };
|
|
100
|
+
}
|
|
101
|
+
const repoRoot = toplevel.stdout.trim();
|
|
102
|
+
const result = runGit(["-C", repoRoot, "rev-list", "--count", `${revision}..HEAD`]);
|
|
103
|
+
if (result.status !== 0) {
|
|
104
|
+
return {
|
|
105
|
+
ok: false,
|
|
106
|
+
message: `em status: git rev-list failed: ${(result.stderr || "").trim() || `unknown revision "${revision}"`}`,
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
const count = parseInt(result.stdout.trim(), 10);
|
|
110
|
+
if (Number.isNaN(count)) {
|
|
111
|
+
return { ok: false, message: `em status: unexpected git rev-list output for revision "${revision}"` };
|
|
112
|
+
}
|
|
113
|
+
return { ok: true, count };
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* `M` in "N commits and M slice-PRs behind HEAD" (MIL-164) — how many slices whose doc's
|
|
117
|
+
* `implementedIn:` names a path this model's target repo changed since `revision`. Reuses the
|
|
118
|
+
* exact `em conform-scope` machinery for one model at a time: `changedPathsSince` (the same
|
|
119
|
+
* `git diff --name-only <revision>..HEAD` walk, same injectable `GitRunner`) to get the changed
|
|
120
|
+
* paths, then `buildConformScope`'s own `implementedIn`-prefix matching (never fuzzy, never a
|
|
121
|
+
* second guess) to turn them into a candidate-slice count. `full` is always `false` here — this
|
|
122
|
+
* is only ever called once `lastConformance` is known non-null (see `resolveConformanceEntry`
|
|
123
|
+
* below), so there is always a revision to diff against.
|
|
124
|
+
*/
|
|
125
|
+
export function resolveSlicePRsBehindHead(repo, revision, slices, runGit = realGit) {
|
|
126
|
+
const changed = changedPathsSince(repo, revision, runGit);
|
|
127
|
+
if (!changed.ok)
|
|
128
|
+
return { ok: false, message: changed.message };
|
|
129
|
+
// `date`/`report` are irrelevant to matching (buildConformScope only echoes them back in its
|
|
130
|
+
// own `lastConformance` field, which this function discards) — only `revision` drives the
|
|
131
|
+
// implementedIn-to-changed-path match below.
|
|
132
|
+
const scope = buildConformScope(slices, { date: "", revision, report: "" }, changed.paths, false);
|
|
133
|
+
return { ok: true, count: scope.candidateSlices.length };
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* Resolve one model's conformance entry: read its sibling state file (if any — not every model
|
|
137
|
+
* has reached the `conform` phase, so a missing state file is routine, not an error), then
|
|
138
|
+
* compute commits-behind-HEAD and slice-PRs-behind-HEAD (MIL-164) when `Last conformance:` is
|
|
139
|
+
* set. `repoOverride` is `--repo`; omitted, each model's own directory is used as the repo to
|
|
140
|
+
* walk (the common single-repo project case — see `repo` field doc above). `sliceDocFacts` is
|
|
141
|
+
* THIS model's own slice facts (`key`/`status`/`implementedIn`) — the caller's responsibility to
|
|
142
|
+
* scope correctly per file, since `resolveSlicePRsBehindHead` matches against exactly this
|
|
143
|
+
* model's slices, not some other input model's.
|
|
144
|
+
*
|
|
145
|
+
* A state file is shared by every `.em` file in its directory (`.event-modeling.md` has no
|
|
146
|
+
* per-model namespacing), but its `Model file:` bullet names exactly ONE of them — so a sibling
|
|
147
|
+
* file it does NOT describe (the common case: a `conform-scope --seed-asis` scratch copy like
|
|
148
|
+
* `checkout-asis.em` sitting next to `checkout.em`) must not inherit `checkout.em`'s conformance
|
|
149
|
+
* record just because it lives in the same directory. When `Model file:` is set and doesn't match
|
|
150
|
+
* `basename(file)`, the record is reported as unattributed (via `error`, the same non-fatal
|
|
151
|
+
* channel a git failure uses) rather than silently misattributed.
|
|
152
|
+
*/
|
|
153
|
+
export function resolveConformanceEntry(file, repoOverride, sliceDocFacts, runGit = realGit) {
|
|
154
|
+
const modelDir = dirname(file);
|
|
155
|
+
const repo = repoOverride ?? modelDir;
|
|
156
|
+
const loaded = loadStateFile(modelDir);
|
|
157
|
+
if (!loaded.ok) {
|
|
158
|
+
return { file, modelDir, hasStateFile: false, lastConformance: null, repo, commitsBehindHead: null, slicePRsBehindHead: null, error: null };
|
|
159
|
+
}
|
|
160
|
+
const parsed = parseState(loaded.text);
|
|
161
|
+
if (!parsed.ok) {
|
|
162
|
+
return {
|
|
163
|
+
file,
|
|
164
|
+
modelDir,
|
|
165
|
+
hasStateFile: true,
|
|
166
|
+
lastConformance: null,
|
|
167
|
+
repo,
|
|
168
|
+
commitsBehindHead: null,
|
|
169
|
+
slicePRsBehindHead: null,
|
|
170
|
+
error: `state file: ${parsed.message}`,
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
if (parsed.state.modelPath && parsed.state.modelPath !== basename(file)) {
|
|
174
|
+
return {
|
|
175
|
+
file,
|
|
176
|
+
modelDir,
|
|
177
|
+
hasStateFile: true,
|
|
178
|
+
lastConformance: null,
|
|
179
|
+
repo,
|
|
180
|
+
commitsBehindHead: null,
|
|
181
|
+
slicePRsBehindHead: null,
|
|
182
|
+
error: `state file describes "${parsed.state.modelPath}", not "${basename(file)}" — not attributing its conformance record`,
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
if (!parsed.state.lastConformance) {
|
|
186
|
+
return { file, modelDir, hasStateFile: true, lastConformance: null, repo, commitsBehindHead: null, slicePRsBehindHead: null, error: null };
|
|
187
|
+
}
|
|
188
|
+
const { date, revision } = parsed.state.lastConformance;
|
|
189
|
+
const commitsResult = commitsBehindHead(repo, revision, runGit);
|
|
190
|
+
if (!commitsResult.ok) {
|
|
191
|
+
return {
|
|
192
|
+
file,
|
|
193
|
+
modelDir,
|
|
194
|
+
hasStateFile: true,
|
|
195
|
+
lastConformance: { date, revision },
|
|
196
|
+
repo,
|
|
197
|
+
commitsBehindHead: null,
|
|
198
|
+
slicePRsBehindHead: null,
|
|
199
|
+
error: commitsResult.message,
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
const slicePRsResult = resolveSlicePRsBehindHead(repo, revision, sliceDocFacts, runGit);
|
|
203
|
+
if (!slicePRsResult.ok) {
|
|
204
|
+
return {
|
|
205
|
+
file,
|
|
206
|
+
modelDir,
|
|
207
|
+
hasStateFile: true,
|
|
208
|
+
lastConformance: { date, revision },
|
|
209
|
+
repo,
|
|
210
|
+
commitsBehindHead: commitsResult.count,
|
|
211
|
+
slicePRsBehindHead: null,
|
|
212
|
+
error: slicePRsResult.message,
|
|
213
|
+
};
|
|
214
|
+
}
|
|
215
|
+
return {
|
|
216
|
+
file,
|
|
217
|
+
modelDir,
|
|
218
|
+
hasStateFile: true,
|
|
219
|
+
lastConformance: { date, revision },
|
|
220
|
+
repo,
|
|
221
|
+
commitsBehindHead: commitsResult.count,
|
|
222
|
+
slicePRsBehindHead: slicePRsResult.count,
|
|
223
|
+
error: null,
|
|
224
|
+
};
|
|
225
|
+
}
|
|
226
|
+
/** Aggregate everything `em status` reports into one `StatusReport` — pure, no I/O. Callers
|
|
227
|
+
* (the CLI action, the MCP tool) resolve every input first (compile each model, join every
|
|
228
|
+
* slice's doc, scan the test tree once, resolve conformance) and hand the results here. */
|
|
229
|
+
export function buildStatusReport(files, sliceFacts, openIssuesCount, invariants, conformance, diagnostics) {
|
|
230
|
+
const byStatus = { draft: 0, reviewed: 0, readyToImplement: 0, implemented: 0, noDoc: 0, frontmatterInvalid: 0, unknown: 0 };
|
|
231
|
+
const drift = {
|
|
232
|
+
inSync: 0,
|
|
233
|
+
neverImplemented: 0,
|
|
234
|
+
unpropagatedDelta: 0,
|
|
235
|
+
implementedWithoutLink: 0,
|
|
236
|
+
notApplicable: 0,
|
|
237
|
+
frontmatterInvalid: 0,
|
|
238
|
+
};
|
|
239
|
+
let openQuestionsTotal = 0;
|
|
240
|
+
let openQuestionsUnchecked = 0;
|
|
241
|
+
// MIL-121 `covers:` cross-binding lets several slices resolve to the SAME doc file — a doc's
|
|
242
|
+
// own Open Questions belong to the doc, not to each slice covered by it, so a shared doc is
|
|
243
|
+
// only counted once regardless of how many slices are bound to it (keyed by resolved absolute
|
|
244
|
+
// path, since two different input models' `slices/` dirs could otherwise collide on the same
|
|
245
|
+
// relative path string without actually being the same file).
|
|
246
|
+
const countedDocPaths = new Set();
|
|
247
|
+
for (const f of sliceFacts) {
|
|
248
|
+
switch (f.bucket) {
|
|
249
|
+
case "draft":
|
|
250
|
+
byStatus.draft++;
|
|
251
|
+
break;
|
|
252
|
+
case "reviewed":
|
|
253
|
+
byStatus.reviewed++;
|
|
254
|
+
break;
|
|
255
|
+
case "ready-to-implement":
|
|
256
|
+
byStatus.readyToImplement++;
|
|
257
|
+
break;
|
|
258
|
+
case "implemented":
|
|
259
|
+
byStatus.implemented++;
|
|
260
|
+
break;
|
|
261
|
+
case "no-doc":
|
|
262
|
+
byStatus.noDoc++;
|
|
263
|
+
break;
|
|
264
|
+
case "frontmatter-invalid":
|
|
265
|
+
byStatus.frontmatterInvalid++;
|
|
266
|
+
break;
|
|
267
|
+
case "unknown":
|
|
268
|
+
byStatus.unknown++;
|
|
269
|
+
break;
|
|
270
|
+
}
|
|
271
|
+
switch (f.driftSignal) {
|
|
272
|
+
case "in-sync":
|
|
273
|
+
drift.inSync++;
|
|
274
|
+
break;
|
|
275
|
+
case "never-implemented":
|
|
276
|
+
drift.neverImplemented++;
|
|
277
|
+
break;
|
|
278
|
+
case "unpropagated-delta":
|
|
279
|
+
drift.unpropagatedDelta++;
|
|
280
|
+
break;
|
|
281
|
+
case "implemented-without-link":
|
|
282
|
+
drift.implementedWithoutLink++;
|
|
283
|
+
break;
|
|
284
|
+
case null:
|
|
285
|
+
if (f.docReason === "frontmatter-invalid")
|
|
286
|
+
drift.frontmatterInvalid++;
|
|
287
|
+
else
|
|
288
|
+
drift.notApplicable++;
|
|
289
|
+
break;
|
|
290
|
+
}
|
|
291
|
+
const alreadyCounted = f.docPath !== null && countedDocPaths.has(f.docPath);
|
|
292
|
+
if (f.docPath !== null)
|
|
293
|
+
countedDocPaths.add(f.docPath);
|
|
294
|
+
if (!alreadyCounted) {
|
|
295
|
+
openQuestionsTotal += f.openQuestionsTotal;
|
|
296
|
+
openQuestionsUnchecked += f.openQuestionsUnchecked;
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
return {
|
|
300
|
+
files,
|
|
301
|
+
slices: { total: sliceFacts.length, byStatus },
|
|
302
|
+
driftSignal: drift,
|
|
303
|
+
invariants,
|
|
304
|
+
issues: { openIssues: openIssuesCount, openQuestionsTotal, openQuestionsUnchecked },
|
|
305
|
+
conformance,
|
|
306
|
+
diagnostics,
|
|
307
|
+
owners: sliceFacts.map((f) => ({ file: f.file, key: f.key, owner: f.owner })),
|
|
308
|
+
};
|
|
309
|
+
}
|
|
310
|
+
/** Sum per-model `CoverageReport`s (see `./coverage.ts`) into one `StatusInvariantTotals` —
|
|
311
|
+
* `em status` doesn't need the per-slice/per-ID detail `em coverage --json` carries, just the
|
|
312
|
+
* totals, across however many models `--tests <dir>` was scanned against. */
|
|
313
|
+
export function aggregateInvariantTotals(testsDir, reports) {
|
|
314
|
+
let total = 0;
|
|
315
|
+
let uncovered = 0;
|
|
316
|
+
for (const r of reports) {
|
|
317
|
+
total += r.totalInvariants;
|
|
318
|
+
uncovered += r.uncoveredCount;
|
|
319
|
+
}
|
|
320
|
+
return { testsDir, total, cited: total - uncovered, uncovered };
|
|
321
|
+
}
|
|
322
|
+
// ---- Text / markdown / badge formatting ----
|
|
323
|
+
function pluralize(n, word) {
|
|
324
|
+
return `${n} ${word}${n === 1 ? "" : "s"}`;
|
|
325
|
+
}
|
|
326
|
+
/** "N commits and M slice-PRs behind HEAD" (MIL-164's freshness clause), handling either count
|
|
327
|
+
* being independently unknown — defensive against a hand-built `ConformanceEntry` (tests) even
|
|
328
|
+
* though on the real `resolveConformanceEntry` path the two counts are only ever null together
|
|
329
|
+
* (both fail whenever `error` is set, which callers check first — see that function's doc). */
|
|
330
|
+
function formatBehindHead(entry) {
|
|
331
|
+
const commits = entry.commitsBehindHead;
|
|
332
|
+
const slicePRs = entry.slicePRsBehindHead;
|
|
333
|
+
if (commits === null && slicePRs === null)
|
|
334
|
+
return "commits and slice-PRs behind HEAD unknown";
|
|
335
|
+
if (commits === null)
|
|
336
|
+
return `commits behind HEAD unknown, ${pluralize(slicePRs, "slice-PR")} behind HEAD`;
|
|
337
|
+
if (slicePRs === null)
|
|
338
|
+
return `${pluralize(commits, "commit")} behind HEAD, slice-PRs behind HEAD unknown`;
|
|
339
|
+
return `${pluralize(commits, "commit")} and ${pluralize(slicePRs, "slice-PR")} behind HEAD`;
|
|
340
|
+
}
|
|
341
|
+
/** Exported for `em freshness` (freshness.ts, MIL-164) — the standalone surface for exactly
|
|
342
|
+
* this one clause, without pulling in the rest of `em status`'s rollup. */
|
|
343
|
+
export function formatConformancePart(entry) {
|
|
344
|
+
if (entry.error)
|
|
345
|
+
return `conformance unknown (${entry.error})`;
|
|
346
|
+
if (!entry.hasStateFile)
|
|
347
|
+
return "no state file";
|
|
348
|
+
if (!entry.lastConformance)
|
|
349
|
+
return "never conformed";
|
|
350
|
+
return `last conformed ${entry.lastConformance.revision} — ${formatBehindHead(entry)}`;
|
|
351
|
+
}
|
|
352
|
+
/** Same facts as `formatConformancePart`, without the leading "last conformed"/"conformance
|
|
353
|
+
* unknown" prose — for a markdown table cell that already carries a "Last conformed" row
|
|
354
|
+
* label, so the value itself shouldn't repeat it. */
|
|
355
|
+
function formatConformanceValue(entry) {
|
|
356
|
+
if (entry.error)
|
|
357
|
+
return `unknown (${entry.error})`;
|
|
358
|
+
if (!entry.hasStateFile)
|
|
359
|
+
return "no state file";
|
|
360
|
+
if (!entry.lastConformance)
|
|
361
|
+
return "never conformed";
|
|
362
|
+
return `\`${entry.lastConformance.revision}\` — ${formatBehindHead(entry)}`;
|
|
363
|
+
}
|
|
364
|
+
/** The one-line rollup: `"8/8 implemented · 20/20 invariants covered · 0 open issues · last
|
|
365
|
+
* conformed <rev>, N commits behind HEAD"` (MIL-163's acceptance line). For multiple input
|
|
366
|
+
* models, the conformance clause reports the FIRST file's entry — a single-file invocation is
|
|
367
|
+
* the common case this line is written for; the full per-model breakdown is always in
|
|
368
|
+
* `formatStatusDetail`/the JSON document regardless of file count. */
|
|
369
|
+
export function formatStatusSummary(report) {
|
|
370
|
+
const { implemented } = report.slices.byStatus;
|
|
371
|
+
const total = report.slices.total;
|
|
372
|
+
const invPart = report.invariants
|
|
373
|
+
? `${report.invariants.cited}/${report.invariants.total} invariants covered`
|
|
374
|
+
: "invariants not checked (pass --tests <dir>)";
|
|
375
|
+
const issuesPart = pluralize(report.issues.openIssues, "open issue");
|
|
376
|
+
const oqPart = report.issues.openQuestionsUnchecked > 0 ? `, ${pluralize(report.issues.openQuestionsUnchecked, "unchecked open question")}` : "";
|
|
377
|
+
const conformancePart = formatConformancePart(report.conformance[0]);
|
|
378
|
+
return `${implemented}/${total} implemented · ${invPart} · ${issuesPart}${oqPart} · ${conformancePart}`;
|
|
379
|
+
}
|
|
380
|
+
/** The detail block below the summary line: one line per rollup dimension, plus one line per
|
|
381
|
+
* model's conformance entry. */
|
|
382
|
+
export function formatStatusDetail(report) {
|
|
383
|
+
const { byStatus } = report.slices;
|
|
384
|
+
const lines = [];
|
|
385
|
+
lines.push(`slices: ${report.slices.total} total — ${byStatus.implemented} implemented, ${byStatus.readyToImplement} ready-to-implement, ` +
|
|
386
|
+
`${byStatus.reviewed} reviewed, ${byStatus.draft} draft, ${byStatus.noDoc} no doc, ${byStatus.frontmatterInvalid} frontmatter invalid, ` +
|
|
387
|
+
`${byStatus.unknown} unknown status`);
|
|
388
|
+
const d = report.driftSignal;
|
|
389
|
+
lines.push(`driftSignal: ${d.inSync} in-sync, ${d.neverImplemented} never-implemented, ${d.unpropagatedDelta} unpropagated-delta, ` +
|
|
390
|
+
`${d.implementedWithoutLink} implemented-without-link, ${d.notApplicable} n/a (no doc), ${d.frontmatterInvalid} n/a (frontmatter invalid)`);
|
|
391
|
+
lines.push(report.invariants
|
|
392
|
+
? `invariants: ${report.invariants.cited}/${report.invariants.total} covered (${report.invariants.uncovered} uncovered) — ${report.invariants.testsDir}`
|
|
393
|
+
: "invariants: not checked — pass --tests <dir> to enable");
|
|
394
|
+
lines.push(`issues: ${pluralize(report.issues.openIssues, "open issue")}, ` +
|
|
395
|
+
`${report.issues.openQuestionsUnchecked}/${report.issues.openQuestionsTotal} open question(s) unchecked`);
|
|
396
|
+
const multi = report.conformance.length > 1;
|
|
397
|
+
for (const entry of report.conformance) {
|
|
398
|
+
const label = multi ? `conformance (${entry.file}): ` : "conformance: ";
|
|
399
|
+
lines.push(`${label}${formatConformancePart(entry)}`);
|
|
400
|
+
}
|
|
401
|
+
if (report.diagnostics.length > 0) {
|
|
402
|
+
lines.push(`doc issues: ${pluralize(report.diagnostics.length, "warning")} — see diagnostics (${report.diagnostics.map((d) => d.code).join(", ")})`);
|
|
403
|
+
}
|
|
404
|
+
return lines.join("\n");
|
|
405
|
+
}
|
|
406
|
+
/** Full text report: the summary line, a blank line, then the detail block — same
|
|
407
|
+
* "rollup line, then detail" shape `em diff`'s text report uses. */
|
|
408
|
+
export function formatStatusText(report) {
|
|
409
|
+
return `${formatStatusSummary(report)}\n\n${formatStatusDetail(report)}`;
|
|
410
|
+
}
|
|
411
|
+
/** Escape characters that would break a markdown table cell: `|` (the column separator) and
|
|
412
|
+
* newlines — plus backslashes, escaped FIRST. Escaping `|` alone would let a pre-existing `\`
|
|
413
|
+
* in the source text (a Windows path in `entry.file`, or free text inside a conformance
|
|
414
|
+
* `error` string) combine with the newly-inserted `\` (e.g. a literal `\|`) and un-escape the
|
|
415
|
+
* pipe again once Markdown unescapes the backslash sequence — a CodeQL
|
|
416
|
+
* `js/incomplete-sanitization` finding (PR #116 review). Same convention `em slice index`'s
|
|
417
|
+
* own `escapeCell` (sliceIndex.ts) already uses for exactly this reason. Applied to both the
|
|
418
|
+
* key and value columns: the multi-model `Last conformed (${entry.file})` row embeds
|
|
419
|
+
* `entry.file`, which is CLI-argument/glob-controlled, not just the value column. */
|
|
420
|
+
function escapeCell(s) {
|
|
421
|
+
return s.replace(/\\/g, "\\\\").replace(/\|/g, "\\|").replace(/\r?\n/g, " ");
|
|
422
|
+
}
|
|
423
|
+
/** A markdown block suited for pasting into a README (MIL-163's `--md`) — a small table,
|
|
424
|
+
* same spirit as `em slice index`'s GENERATED Slices table, but not marker-managed: `--md`
|
|
425
|
+
* just prints the block, leaving where/whether to embed it to the caller. */
|
|
426
|
+
export function formatStatusMarkdown(report) {
|
|
427
|
+
const { byStatus } = report.slices;
|
|
428
|
+
const rows = [
|
|
429
|
+
["Slices", `${byStatus.implemented}/${report.slices.total} implemented (${byStatus.readyToImplement} ready-to-implement, ${byStatus.reviewed} reviewed, ${byStatus.draft} draft)`],
|
|
430
|
+
["Invariants", report.invariants ? `${report.invariants.cited}/${report.invariants.total} covered` : "not checked"],
|
|
431
|
+
["Open issues", `${report.issues.openIssues}`],
|
|
432
|
+
["Open questions", `${report.issues.openQuestionsUnchecked} unchecked`],
|
|
433
|
+
];
|
|
434
|
+
if (report.conformance.length <= 1) {
|
|
435
|
+
const entry = report.conformance[0];
|
|
436
|
+
rows.push(["Last conformed", formatConformanceValue(entry)]);
|
|
437
|
+
}
|
|
438
|
+
else {
|
|
439
|
+
for (const entry of report.conformance) {
|
|
440
|
+
rows.push([`Last conformed (${entry.file})`, formatConformanceValue(entry)]);
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
const header = "| Metric | Value |\n|---|---|";
|
|
444
|
+
const body = rows.map(([k, v]) => `| ${escapeCell(k)} | ${escapeCell(v)} |`).join("\n");
|
|
445
|
+
return `${header}\n${body}`;
|
|
446
|
+
}
|
|
447
|
+
// ---- SVG badge ----
|
|
448
|
+
function escapeXml(s) {
|
|
449
|
+
return s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
450
|
+
}
|
|
451
|
+
/** Deterministic, dependency-free text-width estimate for a shields.io-style flat badge: a
|
|
452
|
+
* fixed average character advance (no font metrics table, no rendering) so the same message
|
|
453
|
+
* always measures to the same width — good enough for a legible badge, not a claim of
|
|
454
|
+
* pixel-perfect parity with shields.io's own Verdana metrics. */
|
|
455
|
+
function textWidth(s) {
|
|
456
|
+
return Math.ceil(s.length * 6.5) + 10;
|
|
457
|
+
}
|
|
458
|
+
/** Render one flat-style SVG badge — two colored rects (label, message) plus centered text,
|
|
459
|
+
* no external fonts/images/scripts, so it renders identically anywhere an SVG does. */
|
|
460
|
+
export function renderBadgeSvg(label, message, color) {
|
|
461
|
+
const labelWidth = textWidth(label);
|
|
462
|
+
const messageWidth = textWidth(message);
|
|
463
|
+
const totalWidth = labelWidth + messageWidth;
|
|
464
|
+
const labelX = labelWidth / 2;
|
|
465
|
+
const messageX = labelWidth + messageWidth / 2;
|
|
466
|
+
const ariaLabel = escapeXml(`${label}: ${message}`);
|
|
467
|
+
return (`<svg xmlns="http://www.w3.org/2000/svg" width="${totalWidth}" height="20" role="img" aria-label="${ariaLabel}">\n` +
|
|
468
|
+
` <title>${ariaLabel}</title>\n` +
|
|
469
|
+
` <linearGradient id="s" x2="0" y2="100%">\n` +
|
|
470
|
+
` <stop offset="0" stop-color="#bbb" stop-opacity=".1"/>\n` +
|
|
471
|
+
` <stop offset="1" stop-opacity=".1"/>\n` +
|
|
472
|
+
` </linearGradient>\n` +
|
|
473
|
+
` <clipPath id="r">\n` +
|
|
474
|
+
` <rect width="${totalWidth}" height="20" rx="3" fill="#fff"/>\n` +
|
|
475
|
+
` </clipPath>\n` +
|
|
476
|
+
` <g clip-path="url(#r)">\n` +
|
|
477
|
+
` <rect width="${labelWidth}" height="20" fill="#555"/>\n` +
|
|
478
|
+
` <rect x="${labelWidth}" width="${messageWidth}" height="20" fill="${color}"/>\n` +
|
|
479
|
+
` <rect width="${totalWidth}" height="20" fill="url(#s)"/>\n` +
|
|
480
|
+
` </g>\n` +
|
|
481
|
+
` <g fill="#fff" text-anchor="middle" font-family="Verdana,Geneva,DejaVu Sans,sans-serif" font-size="11">\n` +
|
|
482
|
+
` <text x="${labelX}" y="14">${escapeXml(label)}</text>\n` +
|
|
483
|
+
` <text x="${messageX}" y="14">${escapeXml(message)}</text>\n` +
|
|
484
|
+
` </g>\n` +
|
|
485
|
+
`</svg>`);
|
|
486
|
+
}
|
|
487
|
+
/** Badge message: a compact version of the summary line's first two/three clauses — a
|
|
488
|
+
* badge has no room for the full conformance clause. */
|
|
489
|
+
function badgeMessage(report) {
|
|
490
|
+
const { implemented } = report.slices.byStatus;
|
|
491
|
+
const total = report.slices.total;
|
|
492
|
+
const parts = [`${implemented}/${total} implemented`];
|
|
493
|
+
if (report.invariants)
|
|
494
|
+
parts.push(`${report.invariants.cited}/${report.invariants.total} invariants`);
|
|
495
|
+
parts.push(pluralize(report.issues.openIssues, "issue"));
|
|
496
|
+
return parts.join(" · ");
|
|
497
|
+
}
|
|
498
|
+
/** Badge color contract (documented here AND in docs/cli.md — keep both in sync):
|
|
499
|
+
*
|
|
500
|
+
* - **red** `#e05d44` — a genuine problem: an open issue, an uncovered invariant, a
|
|
501
|
+
* `frontmatter-invalid` doc, or `implemented-without-link` drift (a doc claiming
|
|
502
|
+
* `implemented` with nothing linking to it).
|
|
503
|
+
* - **yellow** `#dfb317` — things are merely in flight, OR conformance couldn't be verified:
|
|
504
|
+
* not every slice implemented yet, an unchecked Open Question, any model with
|
|
505
|
+
* `commitsBehindHead > 0` or `slicePRsBehindHead > 0` (MIL-164), or — MIL-163 review — any
|
|
506
|
+
* conformance entry carrying a non-null `error` (an unresolvable revision, an unparseable
|
|
507
|
+
* state file, a `--repo` that isn't a git repo, or a state file describing a different
|
|
508
|
+
* model). An unverifiable conformance state must never be indistinguishable from a verified,
|
|
509
|
+
* current one — `commitsBehindHead: null`/`slicePRsBehindHead: null` from an `error` is NOT
|
|
510
|
+
* the same fact as "0 behind", so neither can be allowed to coalesce to 0 and read as healthy.
|
|
511
|
+
* - **green** `#4c1` — otherwise. Deliberately NOT keyed on "every slice implemented" alone —
|
|
512
|
+
* most projects spend most of their life not fully implemented, and that's not a problem a
|
|
513
|
+
* badge should flag. A model with NO conformance history yet (`hasStateFile: false`, or
|
|
514
|
+
* `Last conformance: never` — both `error: null`) is a legitimate, unremarkable state too
|
|
515
|
+
* ("hasn't reached the conform phase", not "broken") and stays eligible for green — only an
|
|
516
|
+
* actual `error` demotes a conformance entry out of green.
|
|
517
|
+
*/
|
|
518
|
+
function badgeColor(report) {
|
|
519
|
+
const hasProblem = report.issues.openIssues > 0 ||
|
|
520
|
+
(report.invariants !== null && report.invariants.uncovered > 0) ||
|
|
521
|
+
report.driftSignal.implementedWithoutLink > 0 ||
|
|
522
|
+
report.driftSignal.frontmatterInvalid > 0;
|
|
523
|
+
if (hasProblem)
|
|
524
|
+
return "#e05d44"; // red
|
|
525
|
+
const inFlight = report.slices.byStatus.implemented < report.slices.total ||
|
|
526
|
+
report.issues.openQuestionsUnchecked > 0 ||
|
|
527
|
+
report.conformance.some((c) => (c.commitsBehindHead ?? 0) > 0 || (c.slicePRsBehindHead ?? 0) > 0 || c.error !== null);
|
|
528
|
+
if (inFlight)
|
|
529
|
+
return "#dfb317"; // yellow
|
|
530
|
+
return "#4c1"; // green
|
|
531
|
+
}
|
|
532
|
+
/** Build the `em status --badge` SVG document. */
|
|
533
|
+
export function buildStatusBadge(report) {
|
|
534
|
+
return renderBadgeSvg("em status", badgeMessage(report), badgeColor(report));
|
|
535
|
+
}
|
|
536
|
+
//# sourceMappingURL=status.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"status.js","sourceRoot":"","sources":["../../src/cli/status.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { PatchResult } from "./stateFile.js";
|
|
2
|
+
/** The Usage log's phase vocabulary (docs/usage-data.md#what-s-captured-and-where): the state
|
|
3
|
+
* file's own `Current phase:` enum (`stateFile.ts`'s `PHASES`) plus `watch` — a live-viewing
|
|
4
|
+
* activity a session can touch without ever being a `Current phase:` value itself (see
|
|
5
|
+
* `stateFile.ts`'s own comment on why `watch` is deliberately excluded there). Order here is
|
|
6
|
+
* the canonical sort order `log-usage` writes phases in, matching docs/usage-data.md's own
|
|
7
|
+
* listing order. */
|
|
8
|
+
export declare const USAGE_PHASES: readonly ["discover", "extract", "model", "slice", "implement", "conform", "review", "validate", "watch"];
|
|
9
|
+
export type UsagePhase = (typeof USAGE_PHASES)[number];
|
|
10
|
+
export declare function isUsagePhase(value: string): value is UsagePhase;
|
|
11
|
+
/** Dedupe and sort a `--phases` list into `USAGE_PHASES`' own canonical order (matching
|
|
12
|
+
* docs/usage-data.md's own listing order) rather than input order or alphabetical — so two
|
|
13
|
+
* sessions naming the same phases in a different order always log an identical line. */
|
|
14
|
+
export declare function sortUsagePhases(phases: readonly string[]): string[];
|
|
15
|
+
/** Format one Usage log bullet line — the ONE place this exact shape is generated, so
|
|
16
|
+
* `log-usage`'s writer and any future caller never drift from `usage-report`'s parser. */
|
|
17
|
+
export declare function formatUsageLogLine(date: string, phases: readonly string[], categories: readonly string[]): string;
|
|
18
|
+
/**
|
|
19
|
+
* Append one Usage log bullet to `text`'s "## Usage log" section — after whatever's already
|
|
20
|
+
* there (the guidance comment, and/or prior bullets), never editing or reordering existing
|
|
21
|
+
* content. `phases`/`categories` are trusted already-deduped, already-sorted, non-empty arrays
|
|
22
|
+
* (the CLI layer owns that — see `sortByCanonicalOrder`/dedup below); `categories` is `["none"]`
|
|
23
|
+
* when nothing fired. Fails (without writing anything) if the file has no "## Usage log"
|
|
24
|
+
* heading at all — same "refuse rather than guess where it goes" discipline
|
|
25
|
+
* `stateFile.ts`'s `applyBulletUpdates` uses for a missing mechanical bullet.
|
|
26
|
+
*/
|
|
27
|
+
export declare function appendUsageLogEntry(text: string, date: string, phases: readonly string[], categories: readonly string[]): PatchResult;
|
|
28
|
+
/** One canonically-formatted Usage log entry, parsed back into its two fields. */
|
|
29
|
+
export interface UsageLogEntry {
|
|
30
|
+
date: string;
|
|
31
|
+
phases: string[];
|
|
32
|
+
categories: string[];
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Parse every `- YYYY-MM-DD: phases: ... — validate: ...` bullet out of `text`'s "## Usage log"
|
|
36
|
+
* section. A line that starts with `- ` inside the section but doesn't match the canonical shape
|
|
37
|
+
* (hand-edited, or written before `log-usage` existed) is returned in `malformed` verbatim
|
|
38
|
+
* rather than silently skipped or force-fit — `usage-report` surfaces these so a stale/foreign
|
|
39
|
+
* format is visible, not invisibly under-counted. Lines that are just the guidance comment (or
|
|
40
|
+
* blank) are ignored entirely — only an actual `- ` bullet attempt counts as malformed.
|
|
41
|
+
*/
|
|
42
|
+
export declare function parseUsageLogSection(text: string): {
|
|
43
|
+
entries: UsageLogEntry[];
|
|
44
|
+
malformed: string[];
|
|
45
|
+
};
|
|
46
|
+
/** Every `.event-modeling.md` under `root`, recursively, as paths relative to `root` — sorted,
|
|
47
|
+
* POSIX forward slashes (see `walkDir`). Prunes VCS/dependency directories a repo-wide sweep
|
|
48
|
+
* should never descend into. */
|
|
49
|
+
export declare function findStateFiles(root: string): string[];
|
|
50
|
+
export interface CountEntry {
|
|
51
|
+
key: string;
|
|
52
|
+
count: number;
|
|
53
|
+
}
|
|
54
|
+
export interface UsageReport {
|
|
55
|
+
root: string;
|
|
56
|
+
files: string[];
|
|
57
|
+
sessions: number;
|
|
58
|
+
phaseCounts: CountEntry[];
|
|
59
|
+
/** Excludes `"none"` — same as docs/usage-data.md's own aggregation recipe (`grep -v
|
|
60
|
+
* '^none$'`): a session with nothing to report isn't a category worth tallying. */
|
|
61
|
+
categoryCounts: CountEntry[];
|
|
62
|
+
unparseableLines: Array<{
|
|
63
|
+
file: string;
|
|
64
|
+
line: string;
|
|
65
|
+
}>;
|
|
66
|
+
}
|
|
67
|
+
/** Aggregate every input file's Usage log into one report — pure, no I/O; the CLI layer resolves
|
|
68
|
+
* `entries` first (`findStateFiles` + a plain read per file). `root` is carried through only
|
|
69
|
+
* for the report's own labeling, never used to re-derive `files`. */
|
|
70
|
+
export declare function aggregateUsageReport(root: string, entries: Array<{
|
|
71
|
+
file: string;
|
|
72
|
+
text: string;
|
|
73
|
+
}>): UsageReport;
|
|
74
|
+
/** Text report: a one-line summary, then a phase tally and a category tally, each empty-aware. */
|
|
75
|
+
export declare function formatUsageReportText(report: UsageReport): string;
|