@shrkcrft/cli 0.1.0-alpha.26 → 0.1.0-alpha.28
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/dist/command-registry.d.ts +12 -0
- package/dist/command-registry.d.ts.map +1 -1
- package/dist/command-registry.js +25 -0
- package/dist/commands/baseline.command.d.ts +8 -0
- package/dist/commands/baseline.command.d.ts.map +1 -0
- package/dist/commands/baseline.command.js +511 -0
- package/dist/commands/changelog-data.d.ts.map +1 -1
- package/dist/commands/changelog-data.js +43 -0
- package/dist/commands/check.command.d.ts.map +1 -1
- package/dist/commands/check.command.js +28 -1
- package/dist/commands/command-catalog.d.ts.map +1 -1
- package/dist/commands/command-catalog.js +112 -0
- package/dist/commands/delegate.command.d.ts +76 -1
- package/dist/commands/delegate.command.d.ts.map +1 -1
- package/dist/commands/delegate.command.js +585 -25
- package/dist/commands/finish.command.js +4 -4
- package/dist/commands/gates.command.d.ts +6 -0
- package/dist/commands/gates.command.d.ts.map +1 -0
- package/dist/commands/gates.command.js +334 -0
- package/dist/commands/generated.command.d.ts +6 -0
- package/dist/commands/generated.command.d.ts.map +1 -0
- package/dist/commands/generated.command.js +514 -0
- package/dist/commands/help.command.d.ts.map +1 -1
- package/dist/commands/help.command.js +73 -0
- package/dist/commands/ingest.command.d.ts +11 -0
- package/dist/commands/ingest.command.d.ts.map +1 -1
- package/dist/commands/ingest.command.js +49 -23
- package/dist/commands/policy-lint.command.d.ts +37 -0
- package/dist/commands/policy-lint.command.d.ts.map +1 -1
- package/dist/commands/policy-lint.command.js +119 -2
- package/dist/commands/registry-resolve.d.ts +11 -4
- package/dist/commands/registry-resolve.d.ts.map +1 -1
- package/dist/commands/registry-resolve.js +50 -24
- package/dist/commands/registry.command.d.ts.map +1 -1
- package/dist/commands/registry.command.js +36 -4
- package/dist/commands/trace.command.d.ts.map +1 -1
- package/dist/commands/trace.command.js +7 -1
- package/dist/commands/wiring.command.d.ts.map +1 -1
- package/dist/commands/wiring.command.js +113 -14
- package/dist/exit-codes.d.ts +41 -0
- package/dist/exit-codes.d.ts.map +1 -1
- package/dist/exit-codes.js +85 -0
- package/dist/finish/run-finish.d.ts +22 -3
- package/dist/finish/run-finish.d.ts.map +1 -1
- package/dist/finish/run-finish.js +194 -18
- package/dist/gates/gate-rule-view.d.ts +35 -0
- package/dist/gates/gate-rule-view.d.ts.map +1 -0
- package/dist/gates/gate-rule-view.js +80 -0
- package/dist/gates/rule-coverage.d.ts +53 -0
- package/dist/gates/rule-coverage.d.ts.map +1 -0
- package/dist/gates/rule-coverage.js +165 -0
- package/dist/main.d.ts.map +1 -1
- package/dist/main.js +46 -6
- package/dist/output/output-compression.d.ts.map +1 -1
- package/dist/output/output-compression.js +4 -1
- package/package.json +33 -33
|
@@ -1,9 +1,23 @@
|
|
|
1
|
-
import { buildImportHygieneReport, filterViolationsToChangedScope, inspectSharkcraft, resolveChangedFiles, resolveProjectConfig, } from '@shrkcrft/inspector';
|
|
1
|
+
import { buildImportHygieneReport, filterViolationsToChangedScope, gitShowFile, inspectSharkcraft, resolveChangedFiles, resolveProjectConfig, } from '@shrkcrft/inspector';
|
|
2
2
|
import { existsSync } from 'node:fs';
|
|
3
3
|
import * as nodePath from 'node:path';
|
|
4
|
-
import { evaluateBoundaries, loadTsconfigPaths, runPolicyLint, runWiring, scanImports, } from '@shrkcrft/boundaries';
|
|
4
|
+
import { buildRegistrationGraph, evaluateBoundaries, loadTsconfigPaths, providedTokensFromEntries, registrationTouchesChanged, registrationUnprovided, runPolicyLint, runWiring, scanImports, } from '@shrkcrft/boundaries';
|
|
5
5
|
import { computeDeletedOrphans } from "../diff/deleted-orphans.js";
|
|
6
|
+
import { ExitCode } from "../exit-codes.js";
|
|
6
7
|
export const FINISH_SCHEMA = 'sharkcraft.finish/v1';
|
|
8
|
+
/** Files in the boundary/import/wiring domain — a change outside it evaluates nothing there. */
|
|
9
|
+
const CODE_FILE = /\.(?:m|c)?[jt]sx?$/;
|
|
10
|
+
function codeFilesOf(files) {
|
|
11
|
+
return files.filter((f) => CODE_FILE.test(f));
|
|
12
|
+
}
|
|
13
|
+
/** The base ref the unprovided gate diffs against to spot removed providers. */
|
|
14
|
+
function baseRefFor(input) {
|
|
15
|
+
if (input.mode === 'files')
|
|
16
|
+
return undefined; // an explicit file list has no diff base
|
|
17
|
+
if (input.mode === 'since' && input.scope.since)
|
|
18
|
+
return input.scope.since;
|
|
19
|
+
return 'HEAD'; // worktree + staged both diff vs HEAD
|
|
20
|
+
}
|
|
7
21
|
/** Map the changed-scope onto the orphan check's diff inputs. */
|
|
8
22
|
function orphanOptsFor(input) {
|
|
9
23
|
if (input.mode === 'files')
|
|
@@ -29,6 +43,11 @@ export async function runFinishGates(input) {
|
|
|
29
43
|
const { cwd } = input;
|
|
30
44
|
const changed = resolveChangedFiles(input.scope);
|
|
31
45
|
const changedFiles = changed.files;
|
|
46
|
+
// The boundary/import/wiring engines only reason about code files. A change to
|
|
47
|
+
// non-code files (docs, JSON, config) evaluates NOTHING in those gates, so they
|
|
48
|
+
// must SKIP, not trivially pass — otherwise a markdown-only change paints green
|
|
49
|
+
// ("evaluated nothing" must read as not-verified, never a `0`).
|
|
50
|
+
const codeChanged = codeFilesOf(changedFiles);
|
|
32
51
|
const gates = [];
|
|
33
52
|
// ── boundaries (changed-only) ────────────────────────────────────────
|
|
34
53
|
const inspection = await inspectSharkcraft({ cwd });
|
|
@@ -36,8 +55,8 @@ export async function runFinishGates(input) {
|
|
|
36
55
|
if (boundaryRules.length === 0) {
|
|
37
56
|
gates.push(skip('boundaries', 'no boundary rules configured'));
|
|
38
57
|
}
|
|
39
|
-
else if (
|
|
40
|
-
gates.push(skip('boundaries', 'no files in changed scope'));
|
|
58
|
+
else if (codeChanged.length === 0) {
|
|
59
|
+
gates.push(skip('boundaries', 'no code files in changed scope'));
|
|
41
60
|
}
|
|
42
61
|
else {
|
|
43
62
|
const scan = scanImports({ projectRoot: cwd });
|
|
@@ -62,23 +81,29 @@ export async function runFinishGates(input) {
|
|
|
62
81
|
});
|
|
63
82
|
}
|
|
64
83
|
// ── import hygiene (changed-only) ────────────────────────────────────
|
|
65
|
-
if (
|
|
66
|
-
gates.push(skip('imports', 'no files in changed scope'));
|
|
84
|
+
if (codeChanged.length === 0) {
|
|
85
|
+
gates.push(skip('imports', 'no code files in changed scope'));
|
|
67
86
|
}
|
|
68
87
|
else {
|
|
69
|
-
const report = buildImportHygieneReport(cwd, { files:
|
|
88
|
+
const report = buildImportHygieneReport(cwd, { files: codeChanged });
|
|
70
89
|
const errors = report.counts?.['error'] ?? (report.verdict === 'errors' ? report.findings.length : 0);
|
|
71
90
|
const warnings = report.counts?.['warning'] ?? (report.verdict === 'warnings' ? report.findings.length : 0);
|
|
91
|
+
// Only the findings that actually DRIVE the verdict become "failing items".
|
|
92
|
+
// An allowlisted import is downgraded to `info` by design and is not what
|
|
93
|
+
// failed — listing it anyway pads the renderer's 15-item cap and can push a
|
|
94
|
+
// real error out of view, while its allowlist justification reads like a
|
|
95
|
+
// fix instruction. A fix-list that names non-failures is not a fix-list.
|
|
96
|
+
const driving = report.findings.filter((f) => report.verdict === 'errors' ? f.severity === 'error' : f.severity === 'warning');
|
|
72
97
|
gates.push({
|
|
73
98
|
name: 'imports',
|
|
74
99
|
status: report.verdict === 'errors' ? 'fail' : 'pass',
|
|
75
|
-
detail: `verdict=${report.verdict} (${report.findings.length} finding(s))`,
|
|
100
|
+
detail: `verdict=${report.verdict} (${driving.length} of ${report.findings.length} finding(s) drive it)`,
|
|
76
101
|
errors,
|
|
77
102
|
warnings,
|
|
78
|
-
items:
|
|
103
|
+
items: driving.map((f) => ({
|
|
79
104
|
file: f.file,
|
|
80
105
|
line: f.line,
|
|
81
|
-
message:
|
|
106
|
+
message: `[${f.severity}] ${f.kind}: ${f.suggestedFix || f.reason || f.snippet}`.trim(),
|
|
82
107
|
})),
|
|
83
108
|
});
|
|
84
109
|
}
|
|
@@ -96,34 +121,61 @@ export async function runFinishGates(input) {
|
|
|
96
121
|
? `config did not load: ${loaded.error.message}`
|
|
97
122
|
: 'no sharkcraft config (gate not applicable)';
|
|
98
123
|
gates.push(skip('wiring', detail));
|
|
124
|
+
gates.push(skip('unprovided', detail));
|
|
99
125
|
gates.push(skip('policy', detail));
|
|
100
126
|
}
|
|
101
127
|
else {
|
|
102
|
-
gates.push(wiringGate(cwd, loaded.value.config.wiringRules ?? [], changedFiles), policyGate(cwd, loaded.value.config.policyRules ?? [], changedFiles));
|
|
128
|
+
gates.push(wiringGate(cwd, loaded.value.config.wiringRules ?? [], changedFiles), unprovidedGate(cwd, loaded.value.config.registrationGraph ?? [], changedFiles, baseRefFor(input)), policyGate(cwd, loaded.value.config.policyRules ?? [], changedFiles));
|
|
103
129
|
}
|
|
104
130
|
// ── deleted-orphans (write-safety) ───────────────────────────────────
|
|
105
131
|
gates.push(await orphansGate(input));
|
|
132
|
+
// ── architecture (advisory): cycles the change participates in ───────
|
|
133
|
+
gates.push(await archGate(cwd, changedFiles));
|
|
106
134
|
// ── impact summary (informational, best-effort) ──────────────────────
|
|
107
135
|
const impact = await impactSummary(cwd, changedFiles);
|
|
108
|
-
// ── fold into one verdict
|
|
109
|
-
|
|
136
|
+
// ── fold into one honest 0/1/2 verdict ───────────────────────────────
|
|
137
|
+
// Only NON-advisory ("deciding") gates decide the verdict. A deciding fail (or
|
|
138
|
+
// a config that could not load) is `1`. Otherwise, if NO deciding gate actually
|
|
139
|
+
// evaluated anything (all skipped), the run verified nothing → `2` (never a
|
|
140
|
+
// green `0`). Only when at least one deciding gate ran over a real scope and
|
|
141
|
+
// none failed is it a true `0`.
|
|
142
|
+
const deciding = gates.filter((g) => !g.advisory);
|
|
143
|
+
const failed = deciding.filter((g) => g.status === 'fail');
|
|
144
|
+
const anyEvaluated = deciding.some((g) => g.status === 'pass');
|
|
110
145
|
const warnings = gates.reduce((n, g) => n + g.warnings, 0);
|
|
111
|
-
|
|
146
|
+
let verdict;
|
|
147
|
+
let exit;
|
|
148
|
+
if (failed.length > 0 || configError) {
|
|
149
|
+
verdict = 'fail';
|
|
150
|
+
exit = ExitCode.Failure;
|
|
151
|
+
}
|
|
152
|
+
else if (!anyEvaluated) {
|
|
153
|
+
verdict = 'not-verified';
|
|
154
|
+
exit = ExitCode.NotVerified;
|
|
155
|
+
}
|
|
156
|
+
else {
|
|
157
|
+
verdict = 'pass';
|
|
158
|
+
exit = ExitCode.VerifiedPass;
|
|
159
|
+
}
|
|
112
160
|
const skipped = gates.filter((g) => g.status === 'skipped').map((g) => g.name);
|
|
161
|
+
const advisoryWarned = gates.filter((g) => g.advisory && g.warnings > 0).map((g) => g.name);
|
|
113
162
|
const summary = verdict === 'fail'
|
|
114
163
|
? `Not safe to finish: ${configError ? 'config failed to load; ' : ''}${failed.map((g) => `${g.name} (${g.errors} error(s))`).join(', ') || 'see gates'}.`
|
|
115
|
-
:
|
|
116
|
-
?
|
|
117
|
-
: `Safe to finish: every applicable gate passed${warnings > 0 ? ` (${warnings} non-blocking warning(s))` : ''}${skipped.length > 0 ? `; skipped: ${skipped.join(', ')}` : ''}.`;
|
|
164
|
+
: verdict === 'not-verified'
|
|
165
|
+
? `Not verified: no gate evaluated the changed scope${changedFiles.length === 0 ? ' (nothing changed)' : ' (nothing in it is gate-relevant)'} — this is NOT a pass. Re-run over a scope with code changes, or gate explicitly.`
|
|
166
|
+
: `Safe to finish: every applicable gate passed${warnings > 0 ? ` (${warnings} non-blocking warning(s)${advisoryWarned.length > 0 ? ` — see ${advisoryWarned.join(', ')}` : ''})` : ''}${skipped.length > 0 ? `; skipped: ${skipped.join(', ')}` : ''}.`;
|
|
118
167
|
const nextAction = verdict === 'fail'
|
|
119
168
|
? 'Fix every failing gate item (each carries file:line), then re-run `shrk finish`.'
|
|
120
|
-
:
|
|
169
|
+
: verdict === 'not-verified'
|
|
170
|
+
? 'Nothing was verified — do not treat this as done.'
|
|
171
|
+
: 'Safe to declare done.';
|
|
121
172
|
return {
|
|
122
173
|
schema: FINISH_SCHEMA,
|
|
123
174
|
scope: { mode: input.mode, files: changedFiles, fileCount: changedFiles.length },
|
|
124
175
|
gates,
|
|
125
176
|
impact,
|
|
126
177
|
verdict,
|
|
178
|
+
exit,
|
|
127
179
|
warnings,
|
|
128
180
|
...(configError ? { configError } : {}),
|
|
129
181
|
summary,
|
|
@@ -133,6 +185,10 @@ export async function runFinishGates(input) {
|
|
|
133
185
|
function skip(name, detail) {
|
|
134
186
|
return { name, status: 'skipped', detail, errors: 0, warnings: 0, items: [] };
|
|
135
187
|
}
|
|
188
|
+
/** A skipped ADVISORY gate — reports its detail but never decides the verdict. */
|
|
189
|
+
function advisorySkip(name, detail) {
|
|
190
|
+
return { name, status: 'skipped', detail, errors: 0, warnings: 0, items: [], advisory: true };
|
|
191
|
+
}
|
|
136
192
|
function wiringGate(cwd, rules, changedFiles) {
|
|
137
193
|
if (rules.length === 0)
|
|
138
194
|
return skip('wiring', 'no wiring rules configured');
|
|
@@ -188,6 +244,126 @@ function policyGate(cwd, rules, changedFiles) {
|
|
|
188
244
|
],
|
|
189
245
|
};
|
|
190
246
|
}
|
|
247
|
+
/**
|
|
248
|
+
* The runtime-wiring `unprovided` gate — the silent-at-runtime class imports
|
|
249
|
+
* can't see: a token DECLARED or INJECTED but never PROVIDED (typecheck-green,
|
|
250
|
+
* absent at runtime). Backed by the registration/DI graph, scoped to the
|
|
251
|
+
* changeset so only tokens THIS change touches decide the verdict. Skips (never
|
|
252
|
+
* fails) when no idioms are configured or the change touched no wiring — so a
|
|
253
|
+
* repo that never modeled its DI is not penalized.
|
|
254
|
+
*/
|
|
255
|
+
function unprovidedGate(cwd, idioms, changedFiles, baseRef) {
|
|
256
|
+
if (idioms.length === 0)
|
|
257
|
+
return skip('unprovided', 'no registration idioms configured');
|
|
258
|
+
const graph = buildRegistrationGraph(cwd, idioms);
|
|
259
|
+
const diag = graph.diagnostics;
|
|
260
|
+
// Two ways THIS change can leave a token unprovided:
|
|
261
|
+
// (1) a declared/injected site added in a changed file with no provider —
|
|
262
|
+
// caught by scoping the graph query to the changed files;
|
|
263
|
+
// (2) the last PROVIDER removed from a changed file — which leaves NO site in
|
|
264
|
+
// the changed file, so (1) structurally can't see it. Recover it by
|
|
265
|
+
// diffing the base content of the changed files (providerRegressions).
|
|
266
|
+
const scoped = registrationUnprovided(graph, changedFiles);
|
|
267
|
+
const regressions = baseRef ? providerRegressions(cwd, idioms, graph, changedFiles, baseRef) : [];
|
|
268
|
+
const byToken = new Map();
|
|
269
|
+
for (const u of [...scoped, ...regressions])
|
|
270
|
+
if (!byToken.has(u.token))
|
|
271
|
+
byToken.set(u.token, u);
|
|
272
|
+
const unprovided = [...byToken.values()].sort((a, b) => a.token.localeCompare(b.token));
|
|
273
|
+
// Skip (evaluated nothing) only when the change touched no registration site
|
|
274
|
+
// AND removed no provider AND has no misconfigured idiom — never a silent pass.
|
|
275
|
+
if (!registrationTouchesChanged(graph, changedFiles) && unprovided.length === 0 && diag.length === 0) {
|
|
276
|
+
return skip('unprovided', 'no registration sites in the changed scope');
|
|
277
|
+
}
|
|
278
|
+
return {
|
|
279
|
+
name: 'unprovided',
|
|
280
|
+
status: unprovided.length > 0 || diag.length > 0 ? 'fail' : 'pass',
|
|
281
|
+
detail: `${unprovided.length} unprovided token(s) attributable to the change${diag.length > 0 ? `, ${diag.length} misconfigured idiom(s)` : ''}`,
|
|
282
|
+
errors: unprovided.length + diag.length,
|
|
283
|
+
warnings: 0,
|
|
284
|
+
items: [
|
|
285
|
+
...unprovided.map((u) => {
|
|
286
|
+
const site = u.declared[0] ?? u.consumed[0];
|
|
287
|
+
return {
|
|
288
|
+
...(site ? { file: site.file, line: site.line } : {}),
|
|
289
|
+
message: `"${u.token}" declared/injected but never provided (silent at runtime)`,
|
|
290
|
+
};
|
|
291
|
+
}),
|
|
292
|
+
...diag.map((d) => ({ message: `misconfigured idiom: ${d}` })),
|
|
293
|
+
],
|
|
294
|
+
};
|
|
295
|
+
}
|
|
296
|
+
/**
|
|
297
|
+
* Tokens whose LAST provider this change removed. Reads the base content of each
|
|
298
|
+
* changed code file, extracts what it USED to provide, and keeps any token now
|
|
299
|
+
* provided nowhere in the worktree but still declared/consumed — the runtime
|
|
300
|
+
* break (`inject(T)` → undefined) that deleting a provider registration causes,
|
|
301
|
+
* which leaves no site in the changed file for post-change scoping to catch.
|
|
302
|
+
*/
|
|
303
|
+
function providerRegressions(cwd, idioms, graph, changedFiles, baseRef) {
|
|
304
|
+
const codeChanged = codeFilesOf(changedFiles);
|
|
305
|
+
if (codeChanged.length === 0)
|
|
306
|
+
return [];
|
|
307
|
+
const baseEntries = [];
|
|
308
|
+
for (const f of codeChanged) {
|
|
309
|
+
const content = gitShowFile(cwd, baseRef, f);
|
|
310
|
+
if (content !== null)
|
|
311
|
+
baseEntries.push({ path: f, content });
|
|
312
|
+
}
|
|
313
|
+
if (baseEntries.length === 0)
|
|
314
|
+
return [];
|
|
315
|
+
const wasProvided = providedTokensFromEntries(idioms, baseEntries);
|
|
316
|
+
const out = [];
|
|
317
|
+
for (const token of wasProvided) {
|
|
318
|
+
const node = graph.tokens.find((t) => t.token === token);
|
|
319
|
+
if (node && node.provided.length === 0 && (node.declared.length > 0 || node.consumed.length > 0)) {
|
|
320
|
+
out.push({ token, declared: node.declared, consumed: node.consumed });
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
return out;
|
|
324
|
+
}
|
|
325
|
+
/**
|
|
326
|
+
* Advisory architecture gate: does any changed file participate in a runtime
|
|
327
|
+
* import cycle? Distinct from the `boundaries` gate (which sees layer violations,
|
|
328
|
+
* not cycles). Deliberately ADVISORY — a pre-existing cycle a change merely
|
|
329
|
+
* touches must not be attributed to this changeset, so it reports as a
|
|
330
|
+
* non-blocking warning and never fails the composite. Type-only import edges are
|
|
331
|
+
* excluded (they erase at emit and can't cause a runtime cycle). Best-effort:
|
|
332
|
+
* a missing graph index degrades to skip, never fail.
|
|
333
|
+
*/
|
|
334
|
+
async function archGate(cwd, changedFiles) {
|
|
335
|
+
const codeChanged = codeFilesOf(changedFiles);
|
|
336
|
+
if (codeChanged.length === 0)
|
|
337
|
+
return advisorySkip('arch', 'no code files in changed scope');
|
|
338
|
+
try {
|
|
339
|
+
const { GraphStore, GraphQueryApi } = await import('@shrkcrft/graph');
|
|
340
|
+
if (!new GraphStore(cwd).exists()) {
|
|
341
|
+
return advisorySkip('arch', 'code-graph index missing — run `shrk graph index`');
|
|
342
|
+
}
|
|
343
|
+
const api = GraphQueryApi.fromStore(cwd);
|
|
344
|
+
const cycles = api.cycles(); // runtime cycles only (type-only edges excluded)
|
|
345
|
+
const scope = new Set(codeChanged.map((f) => f.replace(/\\/g, '/').replace(/^\.\//, '')));
|
|
346
|
+
const touching = cycles.filter((c) => (c.paths ?? c.nodeIds.map((id) => id.replace(/^file:/, ''))).some((p) => scope.has(p)));
|
|
347
|
+
if (touching.length === 0) {
|
|
348
|
+
return advisorySkip('arch', `no changed file participates in an import cycle (${cycles.length} runtime cycle(s) in repo)`);
|
|
349
|
+
}
|
|
350
|
+
return {
|
|
351
|
+
name: 'arch',
|
|
352
|
+
status: 'pass', // advisory: reports cycles as warnings, never fails the composite
|
|
353
|
+
advisory: true,
|
|
354
|
+
detail: `${touching.length} changed file(s) participate in a runtime import cycle (advisory — not attributed to this change)`,
|
|
355
|
+
errors: 0,
|
|
356
|
+
warnings: touching.length,
|
|
357
|
+
items: touching.slice(0, 15).map((c) => {
|
|
358
|
+
const paths = c.paths ?? c.nodeIds.map((id) => id.replace(/^file:/, ''));
|
|
359
|
+
return { message: `import cycle (size ${c.size}): ${paths.join(' → ')}` };
|
|
360
|
+
}),
|
|
361
|
+
};
|
|
362
|
+
}
|
|
363
|
+
catch (e) {
|
|
364
|
+
return advisorySkip('arch', `arch check unavailable: ${e instanceof Error ? e.message : String(e)}`);
|
|
365
|
+
}
|
|
366
|
+
}
|
|
191
367
|
async function orphansGate(input) {
|
|
192
368
|
const opts = orphanOptsFor(input);
|
|
193
369
|
if (!opts)
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import type { IBaselineRule, IGeneratedArtifactRule, IPolicyRule, IRegistrationIdiom, IRegistryDeclaration, IRuleSelfTest, IWiringRule } from '@shrkcrft/core';
|
|
2
|
+
/** Which data-defined plane a rule belongs to. */
|
|
3
|
+
export type GatePlane = 'wiring' | 'policy' | 'registry' | 'registration' | 'baseline' | 'generated';
|
|
4
|
+
/** Every plane, in the order `shrk gates list` prints them. */
|
|
5
|
+
export declare const GATE_PLANES: readonly GatePlane[];
|
|
6
|
+
/**
|
|
7
|
+
* One data-defined rule, normalized across planes.
|
|
8
|
+
*
|
|
9
|
+
* The trust layer's whole job is to answer "what did this rule actually match?"
|
|
10
|
+
* for ANY rule, so it needs one shape to iterate. The plane-specific rule object
|
|
11
|
+
* rides along in `raw` for the explain dispatch.
|
|
12
|
+
*/
|
|
13
|
+
export interface IGateRuleView {
|
|
14
|
+
readonly id: string;
|
|
15
|
+
readonly plane: GatePlane;
|
|
16
|
+
readonly description?: string;
|
|
17
|
+
readonly severity: 'error' | 'warning';
|
|
18
|
+
/** True when a zero-match is a hard failure rather than a loud skip. */
|
|
19
|
+
readonly failOnEmpty: boolean;
|
|
20
|
+
readonly selfTest?: IRuleSelfTest;
|
|
21
|
+
/** The underlying rule, for the plane-specific explainer. */
|
|
22
|
+
readonly raw: IWiringRule | IPolicyRule | IRegistryDeclaration | IRegistrationIdiom | IBaselineRule | IGeneratedArtifactRule;
|
|
23
|
+
}
|
|
24
|
+
/** The config planes this view is built from. */
|
|
25
|
+
export interface IGatePlanes {
|
|
26
|
+
readonly wiringRules?: readonly IWiringRule[];
|
|
27
|
+
readonly policyRules?: readonly IPolicyRule[];
|
|
28
|
+
readonly registries?: readonly IRegistryDeclaration[];
|
|
29
|
+
readonly registrationGraph?: readonly IRegistrationIdiom[];
|
|
30
|
+
readonly baselines?: readonly IBaselineRule[];
|
|
31
|
+
readonly generatedArtifacts?: readonly IGeneratedArtifactRule[];
|
|
32
|
+
}
|
|
33
|
+
/** Flatten every declared rule across every plane into one iterable list. */
|
|
34
|
+
export declare function collectGateRules(planes: IGatePlanes): IGateRuleView[];
|
|
35
|
+
//# sourceMappingURL=gate-rule-view.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"gate-rule-view.d.ts","sourceRoot":"","sources":["../../src/gates/gate-rule-view.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,aAAa,EACb,sBAAsB,EACtB,WAAW,EACX,kBAAkB,EAClB,oBAAoB,EACpB,aAAa,EACb,WAAW,EACZ,MAAM,gBAAgB,CAAC;AAExB,kDAAkD;AAClD,MAAM,MAAM,SAAS,GAAG,QAAQ,GAAG,QAAQ,GAAG,UAAU,GAAG,cAAc,GAAG,UAAU,GAAG,WAAW,CAAC;AAErG,+DAA+D;AAC/D,eAAO,MAAM,WAAW,EAAE,SAAS,SAAS,EAO3C,CAAC;AAEF;;;;;;GAMG;AACH,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,KAAK,EAAE,SAAS,CAAC;IAC1B,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,QAAQ,EAAE,OAAO,GAAG,SAAS,CAAC;IACvC,wEAAwE;IACxE,QAAQ,CAAC,WAAW,EAAE,OAAO,CAAC;IAC9B,QAAQ,CAAC,QAAQ,CAAC,EAAE,aAAa,CAAC;IAClC,6DAA6D;IAC7D,QAAQ,CAAC,GAAG,EACR,WAAW,GACX,WAAW,GACX,oBAAoB,GACpB,kBAAkB,GAClB,aAAa,GACb,sBAAsB,CAAC;CAC5B;AAED,iDAAiD;AACjD,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,WAAW,CAAC,EAAE,SAAS,WAAW,EAAE,CAAC;IAC9C,QAAQ,CAAC,WAAW,CAAC,EAAE,SAAS,WAAW,EAAE,CAAC;IAC9C,QAAQ,CAAC,UAAU,CAAC,EAAE,SAAS,oBAAoB,EAAE,CAAC;IACtD,QAAQ,CAAC,iBAAiB,CAAC,EAAE,SAAS,kBAAkB,EAAE,CAAC;IAC3D,QAAQ,CAAC,SAAS,CAAC,EAAE,SAAS,aAAa,EAAE,CAAC;IAC9C,QAAQ,CAAC,kBAAkB,CAAC,EAAE,SAAS,sBAAsB,EAAE,CAAC;CACjE;AAED,6EAA6E;AAC7E,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,WAAW,GAAG,aAAa,EAAE,CAqErE"}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/** Every plane, in the order `shrk gates list` prints them. */
|
|
2
|
+
export const GATE_PLANES = [
|
|
3
|
+
'wiring',
|
|
4
|
+
'policy',
|
|
5
|
+
'registry',
|
|
6
|
+
'registration',
|
|
7
|
+
'baseline',
|
|
8
|
+
'generated',
|
|
9
|
+
];
|
|
10
|
+
/** Flatten every declared rule across every plane into one iterable list. */
|
|
11
|
+
export function collectGateRules(planes) {
|
|
12
|
+
const out = [];
|
|
13
|
+
for (const r of planes.wiringRules ?? []) {
|
|
14
|
+
out.push({
|
|
15
|
+
id: r.id,
|
|
16
|
+
plane: 'wiring',
|
|
17
|
+
...(r.description ? { description: r.description } : {}),
|
|
18
|
+
severity: r.severity ?? 'error',
|
|
19
|
+
failOnEmpty: r.failOnEmpty === true,
|
|
20
|
+
...(r.selfTest ? { selfTest: r.selfTest } : {}),
|
|
21
|
+
raw: r,
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
for (const r of planes.policyRules ?? []) {
|
|
25
|
+
out.push({
|
|
26
|
+
id: r.id,
|
|
27
|
+
plane: 'policy',
|
|
28
|
+
...(r.description ? { description: r.description } : {}),
|
|
29
|
+
severity: r.severity ?? 'error',
|
|
30
|
+
failOnEmpty: r.failOnEmpty === true,
|
|
31
|
+
...(r.selfTest ? { selfTest: r.selfTest } : {}),
|
|
32
|
+
raw: r,
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
for (const r of planes.registries ?? []) {
|
|
36
|
+
out.push({
|
|
37
|
+
id: r.name,
|
|
38
|
+
plane: 'registry',
|
|
39
|
+
...(r.description ? { description: r.description } : {}),
|
|
40
|
+
// A registry is an inventory, not a gate — it never fails a build on its
|
|
41
|
+
// own, so it carries no severity of its own.
|
|
42
|
+
severity: 'warning',
|
|
43
|
+
failOnEmpty: false,
|
|
44
|
+
raw: r,
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
for (const r of planes.registrationGraph ?? []) {
|
|
48
|
+
out.push({
|
|
49
|
+
id: r.name,
|
|
50
|
+
plane: 'registration',
|
|
51
|
+
...(r.description ? { description: r.description } : {}),
|
|
52
|
+
severity: 'warning',
|
|
53
|
+
failOnEmpty: false,
|
|
54
|
+
raw: r,
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
for (const r of planes.baselines ?? []) {
|
|
58
|
+
out.push({
|
|
59
|
+
id: r.id,
|
|
60
|
+
plane: 'baseline',
|
|
61
|
+
...(r.description ? { description: r.description } : {}),
|
|
62
|
+
severity: r.severity ?? 'error',
|
|
63
|
+
failOnEmpty: r.failOnEmpty === true,
|
|
64
|
+
...(r.selfTest ? { selfTest: r.selfTest } : {}),
|
|
65
|
+
raw: r,
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
for (const r of planes.generatedArtifacts ?? []) {
|
|
69
|
+
out.push({
|
|
70
|
+
id: r.id,
|
|
71
|
+
plane: 'generated',
|
|
72
|
+
...(r.description ? { description: r.description } : {}),
|
|
73
|
+
severity: r.severity ?? 'error',
|
|
74
|
+
failOnEmpty: r.failOnEmpty === true,
|
|
75
|
+
...(r.selfTest ? { selfTest: r.selfTest } : {}),
|
|
76
|
+
raw: r,
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
return out;
|
|
80
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import type { IGateRuleView } from './gate-rule-view.js';
|
|
2
|
+
export declare const GATE_COVERAGE_SCHEMA: "sharkcraft.gate-coverage/v1";
|
|
3
|
+
/**
|
|
4
|
+
* Whether a rule is connected to anything at all.
|
|
5
|
+
*
|
|
6
|
+
* `empty` is the finding this whole surface exists for: a stale selector that
|
|
7
|
+
* matches nothing passes every gate forever, so the ONLY way to notice is to
|
|
8
|
+
* report the match count itself and flag zero. `failed-expectation` is the
|
|
9
|
+
* stronger form — the author wrote down what the rule should match, and it
|
|
10
|
+
* doesn't.
|
|
11
|
+
*/
|
|
12
|
+
export type GateCoverageStatus = 'ok' | 'empty' | 'error' | 'failed-expectation';
|
|
13
|
+
export interface IGateCoverage {
|
|
14
|
+
readonly id: string;
|
|
15
|
+
readonly plane: IGateRuleView['plane'];
|
|
16
|
+
readonly description?: string;
|
|
17
|
+
readonly status: GateCoverageStatus;
|
|
18
|
+
/** Files the rule's primary selector matched. */
|
|
19
|
+
readonly filesMatched: number;
|
|
20
|
+
/** Ids/units the rule extracted (findings scanned, for the policy plane). */
|
|
21
|
+
readonly unitsMatched: number;
|
|
22
|
+
/** What "units" means for this plane, for honest reporting. */
|
|
23
|
+
readonly unitLabel: string;
|
|
24
|
+
/** A few of the extracted ids, so the author can eyeball correctness. */
|
|
25
|
+
readonly sampleIds: readonly string[];
|
|
26
|
+
/** True when a zero match is a hard failure for this rule. */
|
|
27
|
+
readonly failOnEmpty: boolean;
|
|
28
|
+
readonly error?: string;
|
|
29
|
+
/** Unmet `selfTest` expectations, each a human-readable sentence. */
|
|
30
|
+
readonly expectationFailures: readonly string[];
|
|
31
|
+
}
|
|
32
|
+
export interface IGateCoverageReport {
|
|
33
|
+
readonly schema: typeof GATE_COVERAGE_SCHEMA;
|
|
34
|
+
readonly rules: readonly IGateCoverage[];
|
|
35
|
+
readonly total: number;
|
|
36
|
+
readonly empty: number;
|
|
37
|
+
readonly errored: number;
|
|
38
|
+
readonly expectationFailures: number;
|
|
39
|
+
/**
|
|
40
|
+
* `pass` — every rule matched something and met its expectations.
|
|
41
|
+
* `stale` — at least one rule matched nothing (or broke an expectation).
|
|
42
|
+
*/
|
|
43
|
+
readonly verdict: 'pass' | 'stale';
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Resolve every declared rule against the live tree and report what it matched.
|
|
47
|
+
*
|
|
48
|
+
* The `command`-compute baseline is the one rule kind that cannot be inspected
|
|
49
|
+
* without side effects; it is reported as un-inspected (never as `empty`), so
|
|
50
|
+
* the report never claims a fact it did not check.
|
|
51
|
+
*/
|
|
52
|
+
export declare function buildGateCoverage(cwd: string, rules: readonly IGateRuleView[], excludeDirs?: readonly string[]): IGateCoverageReport;
|
|
53
|
+
//# sourceMappingURL=rule-coverage.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rule-coverage.d.ts","sourceRoot":"","sources":["../../src/gates/rule-coverage.ts"],"names":[],"mappings":"AAeA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAEzD,eAAO,MAAM,oBAAoB,EAAG,6BAAsC,CAAC;AAE3E;;;;;;;;GAQG;AACH,MAAM,MAAM,kBAAkB,GAAG,IAAI,GAAG,OAAO,GAAG,OAAO,GAAG,oBAAoB,CAAC;AAEjF,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,KAAK,EAAE,aAAa,CAAC,OAAO,CAAC,CAAC;IACvC,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,MAAM,EAAE,kBAAkB,CAAC;IACpC,iDAAiD;IACjD,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,6EAA6E;IAC7E,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,+DAA+D;IAC/D,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,yEAAyE;IACzE,QAAQ,CAAC,SAAS,EAAE,SAAS,MAAM,EAAE,CAAC;IACtC,8DAA8D;IAC9D,QAAQ,CAAC,WAAW,EAAE,OAAO,CAAC;IAC9B,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IACxB,qEAAqE;IACrE,QAAQ,CAAC,mBAAmB,EAAE,SAAS,MAAM,EAAE,CAAC;CACjD;AAED,MAAM,WAAW,mBAAmB;IAClC,QAAQ,CAAC,MAAM,EAAE,OAAO,oBAAoB,CAAC;IAC7C,QAAQ,CAAC,KAAK,EAAE,SAAS,aAAa,EAAE,CAAC;IACzC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,mBAAmB,EAAE,MAAM,CAAC;IACrC;;;OAGG;IACH,QAAQ,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC;CACpC;AAsHD;;;;;;GAMG;AACH,wBAAgB,iBAAiB,CAC/B,GAAG,EAAE,MAAM,EACX,KAAK,EAAE,SAAS,aAAa,EAAE,EAC/B,WAAW,GAAE,SAAS,MAAM,EAAO,GAClC,mBAAmB,CA4DrB"}
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
import { inspectSource, runPolicyLint, scanGeneratedFiles, wiringSourceSide, } from '@shrkcrft/boundaries';
|
|
2
|
+
export const GATE_COVERAGE_SCHEMA = 'sharkcraft.gate-coverage/v1';
|
|
3
|
+
/** Evaluate the author's declared expectations against what the rule extracted. */
|
|
4
|
+
function checkExpectations(selfTest, ids, unitsMatched) {
|
|
5
|
+
if (!selfTest)
|
|
6
|
+
return [];
|
|
7
|
+
const out = [];
|
|
8
|
+
if (selfTest.expectMatchesAtLeast !== undefined &&
|
|
9
|
+
unitsMatched < selfTest.expectMatchesAtLeast) {
|
|
10
|
+
out.push(`expected at least ${selfTest.expectMatchesAtLeast} match(es), got ${unitsMatched}`);
|
|
11
|
+
}
|
|
12
|
+
const present = new Set(ids);
|
|
13
|
+
for (const id of selfTest.expectIds ?? []) {
|
|
14
|
+
if (!present.has(id))
|
|
15
|
+
out.push(`expected id "${id}" was NOT extracted`);
|
|
16
|
+
}
|
|
17
|
+
for (const id of selfTest.expectNotIds ?? []) {
|
|
18
|
+
if (present.has(id))
|
|
19
|
+
out.push(`id "${id}" was extracted but is listed in expectNotIds`);
|
|
20
|
+
}
|
|
21
|
+
return out;
|
|
22
|
+
}
|
|
23
|
+
function matchWiring(cwd, rule, excludeDirs) {
|
|
24
|
+
const side = wiringSourceSide(rule);
|
|
25
|
+
if (!side)
|
|
26
|
+
return { filesMatched: 0, unitsMatched: 0, unitLabel: 'ids', ids: [], error: 'rule sets no source side' };
|
|
27
|
+
const insp = inspectSource(cwd, side, excludeDirs);
|
|
28
|
+
return {
|
|
29
|
+
filesMatched: insp.filesScanned,
|
|
30
|
+
unitsMatched: insp.ids.length,
|
|
31
|
+
unitLabel: 'ids',
|
|
32
|
+
ids: insp.ids,
|
|
33
|
+
...(insp.error ? { error: insp.error } : {}),
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
function matchPolicy(cwd, rule, excludeDirs) {
|
|
37
|
+
const report = runPolicyLint(cwd, [rule], { excludeDirs });
|
|
38
|
+
const result = report.rules[0];
|
|
39
|
+
return {
|
|
40
|
+
// A policy rule's "files" and "units" differ only for inline templates.
|
|
41
|
+
filesMatched: result?.unitsScanned ?? 0,
|
|
42
|
+
unitsMatched: result?.unitsScanned ?? 0,
|
|
43
|
+
unitLabel: 'content units',
|
|
44
|
+
ids: [],
|
|
45
|
+
...(result?.error ? { error: result.error } : {}),
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
function matchRegistry(cwd, decl, excludeDirs) {
|
|
49
|
+
const insp = inspectSource(cwd, decl.source, excludeDirs);
|
|
50
|
+
return {
|
|
51
|
+
filesMatched: insp.filesScanned,
|
|
52
|
+
unitsMatched: insp.ids.length,
|
|
53
|
+
unitLabel: 'ids',
|
|
54
|
+
ids: insp.ids,
|
|
55
|
+
...(insp.error ? { error: insp.error } : {}),
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
function matchRegistration(cwd, idiom, excludeDirs) {
|
|
59
|
+
const insp = inspectSource(cwd, idiom.declared, excludeDirs);
|
|
60
|
+
return {
|
|
61
|
+
filesMatched: insp.filesScanned,
|
|
62
|
+
unitsMatched: insp.ids.length,
|
|
63
|
+
unitLabel: 'declared tokens',
|
|
64
|
+
ids: insp.ids,
|
|
65
|
+
...(insp.error ? { error: insp.error } : {}),
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
function matchBaseline(cwd, rule, excludeDirs) {
|
|
69
|
+
// Only the EXTRACTOR half can be inspected without spawning. A command
|
|
70
|
+
// compute is reported honestly as un-inspectable rather than guessed at —
|
|
71
|
+
// `shrk baseline explain --id X` runs it on purpose.
|
|
72
|
+
if (rule.compute.kind !== 'extractor' || !rule.compute.source) {
|
|
73
|
+
return {
|
|
74
|
+
filesMatched: 0,
|
|
75
|
+
unitsMatched: 0,
|
|
76
|
+
unitLabel: 'entries (command compute — not inspected)',
|
|
77
|
+
ids: [],
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
const insp = inspectSource(cwd, rule.compute.source, excludeDirs);
|
|
81
|
+
return {
|
|
82
|
+
filesMatched: insp.filesScanned,
|
|
83
|
+
unitsMatched: insp.ids.length,
|
|
84
|
+
unitLabel: 'entries',
|
|
85
|
+
ids: insp.ids,
|
|
86
|
+
...(insp.error ? { error: insp.error } : {}),
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
function matchGenerated(cwd, rule, excludeDirs) {
|
|
90
|
+
const scan = scanGeneratedFiles(cwd, rule, excludeDirs);
|
|
91
|
+
const files = [...scan.generated.keys()];
|
|
92
|
+
return {
|
|
93
|
+
filesMatched: files.length,
|
|
94
|
+
unitsMatched: files.length,
|
|
95
|
+
unitLabel: 'generated files',
|
|
96
|
+
ids: files,
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Resolve every declared rule against the live tree and report what it matched.
|
|
101
|
+
*
|
|
102
|
+
* The `command`-compute baseline is the one rule kind that cannot be inspected
|
|
103
|
+
* without side effects; it is reported as un-inspected (never as `empty`), so
|
|
104
|
+
* the report never claims a fact it did not check.
|
|
105
|
+
*/
|
|
106
|
+
export function buildGateCoverage(cwd, rules, excludeDirs = []) {
|
|
107
|
+
const out = [];
|
|
108
|
+
for (const view of rules) {
|
|
109
|
+
let match;
|
|
110
|
+
switch (view.plane) {
|
|
111
|
+
case 'wiring':
|
|
112
|
+
match = matchWiring(cwd, view.raw, excludeDirs);
|
|
113
|
+
break;
|
|
114
|
+
case 'policy':
|
|
115
|
+
match = matchPolicy(cwd, view.raw, excludeDirs);
|
|
116
|
+
break;
|
|
117
|
+
case 'registry':
|
|
118
|
+
match = matchRegistry(cwd, view.raw, excludeDirs);
|
|
119
|
+
break;
|
|
120
|
+
case 'registration':
|
|
121
|
+
match = matchRegistration(cwd, view.raw, excludeDirs);
|
|
122
|
+
break;
|
|
123
|
+
case 'baseline':
|
|
124
|
+
match = matchBaseline(cwd, view.raw, excludeDirs);
|
|
125
|
+
break;
|
|
126
|
+
default:
|
|
127
|
+
match = matchGenerated(cwd, view.raw, excludeDirs);
|
|
128
|
+
break;
|
|
129
|
+
}
|
|
130
|
+
const expectationFailures = checkExpectations(view.selfTest, match.ids, match.unitsMatched);
|
|
131
|
+
const uninspected = match.unitLabel.includes('not inspected');
|
|
132
|
+
const status = match.error !== undefined
|
|
133
|
+
? 'error'
|
|
134
|
+
: expectationFailures.length > 0
|
|
135
|
+
? 'failed-expectation'
|
|
136
|
+
: match.unitsMatched === 0 && !uninspected
|
|
137
|
+
? 'empty'
|
|
138
|
+
: 'ok';
|
|
139
|
+
out.push({
|
|
140
|
+
id: view.id,
|
|
141
|
+
plane: view.plane,
|
|
142
|
+
...(view.description ? { description: view.description } : {}),
|
|
143
|
+
status,
|
|
144
|
+
filesMatched: match.filesMatched,
|
|
145
|
+
unitsMatched: match.unitsMatched,
|
|
146
|
+
unitLabel: match.unitLabel,
|
|
147
|
+
sampleIds: match.ids.slice(0, 5),
|
|
148
|
+
failOnEmpty: view.failOnEmpty,
|
|
149
|
+
...(match.error ? { error: match.error } : {}),
|
|
150
|
+
expectationFailures,
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
const empty = out.filter((r) => r.status === 'empty').length;
|
|
154
|
+
const errored = out.filter((r) => r.status === 'error').length;
|
|
155
|
+
const expectationFailures = out.filter((r) => r.status === 'failed-expectation').length;
|
|
156
|
+
return {
|
|
157
|
+
schema: GATE_COVERAGE_SCHEMA,
|
|
158
|
+
rules: out,
|
|
159
|
+
total: out.length,
|
|
160
|
+
empty,
|
|
161
|
+
errored,
|
|
162
|
+
expectationFailures,
|
|
163
|
+
verdict: empty + errored + expectationFailures > 0 ? 'stale' : 'pass',
|
|
164
|
+
};
|
|
165
|
+
}
|
package/dist/main.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"main.d.ts","sourceRoot":"","sources":["../src/main.ts"],"names":[],"mappings":";AAEA,OAAO,EACL,eAAe,
|
|
1
|
+
{"version":3,"file":"main.d.ts","sourceRoot":"","sources":["../src/main.ts"],"names":[],"mappings":";AAEA,OAAO,EACL,eAAe,EAMhB,MAAM,uBAAuB,CAAC;AAoZ/B,wBAAgB,aAAa,IAAI,eAAe,CAqZ/C;AAED,wBAAsB,MAAM,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC,CA+CrE;AAwID;;;;;;;;;;;GAWG;AACH,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,GAAG,OAAO,CAoDxE"}
|