@shrkcrft/cli 0.1.0-alpha.28 → 0.1.0-alpha.30
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/commands/baseline.command.d.ts +25 -0
- package/dist/commands/baseline.command.d.ts.map +1 -1
- package/dist/commands/baseline.command.js +64 -17
- package/dist/commands/changelog-data.d.ts.map +1 -1
- package/dist/commands/changelog-data.js +46 -0
- package/dist/commands/check.command.d.ts.map +1 -1
- package/dist/commands/check.command.js +137 -12
- package/dist/commands/code-intel.command.d.ts.map +1 -1
- package/dist/commands/code-intel.command.js +5 -1
- package/dist/commands/command-catalog.d.ts.map +1 -1
- package/dist/commands/command-catalog.js +8 -0
- package/dist/commands/daily.commands.d.ts.map +1 -1
- package/dist/commands/daily.commands.js +11 -1
- package/dist/commands/docs-references.command.d.ts +7 -0
- package/dist/commands/docs-references.command.d.ts.map +1 -0
- package/dist/commands/docs-references.command.js +323 -0
- package/dist/commands/doctor.command.d.ts.map +1 -1
- package/dist/commands/doctor.command.js +3 -2
- package/dist/commands/gates.command.d.ts +13 -1
- package/dist/commands/gates.command.d.ts.map +1 -1
- package/dist/commands/gates.command.js +693 -26
- package/dist/commands/generated.command.d.ts +32 -0
- package/dist/commands/generated.command.d.ts.map +1 -1
- package/dist/commands/generated.command.js +214 -53
- package/dist/commands/graph-code-subverbs.d.ts.map +1 -1
- package/dist/commands/graph-code-subverbs.js +5 -1
- package/dist/commands/help.command.d.ts.map +1 -1
- package/dist/commands/help.command.js +64 -2
- package/dist/commands/policy-lint.command.d.ts.map +1 -1
- package/dist/commands/policy-lint.command.js +52 -10
- package/dist/commands/registry.command.d.ts.map +1 -1
- package/dist/commands/registry.command.js +34 -9
- package/dist/commands/wiring.command.d.ts.map +1 -1
- package/dist/commands/wiring.command.js +4 -3
- package/dist/exit-codes.d.ts +27 -7
- package/dist/exit-codes.d.ts.map +1 -1
- package/dist/exit-codes.js +47 -8
- package/dist/gates/gate-envelope.d.ts +64 -0
- package/dist/gates/gate-envelope.d.ts.map +1 -0
- package/dist/gates/gate-envelope.js +26 -0
- package/dist/gates/gate-rule-globs.d.ts +33 -0
- package/dist/gates/gate-rule-globs.d.ts.map +1 -0
- package/dist/gates/gate-rule-globs.js +101 -0
- package/dist/gates/gate-rule-view.d.ts +9 -3
- package/dist/gates/gate-rule-view.d.ts.map +1 -1
- package/dist/gates/gate-rule-view.js +18 -4
- package/dist/gates/rule-coverage.d.ts +39 -1
- package/dist/gates/rule-coverage.d.ts.map +1 -1
- package/dist/gates/rule-coverage.js +123 -8
- package/dist/gates/run-gate-planes.d.ts +44 -0
- package/dist/gates/run-gate-planes.d.ts.map +1 -0
- package/dist/gates/run-gate-planes.js +261 -0
- package/dist/main.d.ts.map +1 -1
- package/dist/main.js +7 -2
- package/package.json +33 -33
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { type IBaselineRule, type IDocReferenceRule, type IGeneratedArtifactRule, type IPolicyRule, type IRegistrationIdiom, type IRegistryDeclaration, type IRuleSelfTest, type IWiringRule } from '@shrkcrft/core';
|
|
2
2
|
/** Which data-defined plane a rule belongs to. */
|
|
3
|
-
export type GatePlane = 'wiring' | 'policy' | 'registry' | 'registration' | 'baseline' | 'generated';
|
|
3
|
+
export type GatePlane = 'wiring' | 'policy' | 'registry' | 'registration' | 'baseline' | 'generated' | 'doc-reference';
|
|
4
4
|
/** Every plane, in the order `shrk gates list` prints them. */
|
|
5
5
|
export declare const GATE_PLANES: readonly GatePlane[];
|
|
6
6
|
/**
|
|
@@ -17,9 +17,14 @@ export interface IGateRuleView {
|
|
|
17
17
|
readonly severity: 'error' | 'warning';
|
|
18
18
|
/** True when a zero-match is a hard failure rather than a loud skip. */
|
|
19
19
|
readonly failOnEmpty: boolean;
|
|
20
|
+
/**
|
|
21
|
+
* True when an EMPTY result is the rule's passing state (a fence). The
|
|
22
|
+
* loud-skip contract is inverted for these, and only for these.
|
|
23
|
+
*/
|
|
24
|
+
readonly expectEmpty?: boolean;
|
|
20
25
|
readonly selfTest?: IRuleSelfTest;
|
|
21
26
|
/** The underlying rule, for the plane-specific explainer. */
|
|
22
|
-
readonly raw: IWiringRule | IPolicyRule | IRegistryDeclaration | IRegistrationIdiom | IBaselineRule | IGeneratedArtifactRule;
|
|
27
|
+
readonly raw: IWiringRule | IPolicyRule | IRegistryDeclaration | IRegistrationIdiom | IBaselineRule | IGeneratedArtifactRule | IDocReferenceRule;
|
|
23
28
|
}
|
|
24
29
|
/** The config planes this view is built from. */
|
|
25
30
|
export interface IGatePlanes {
|
|
@@ -29,6 +34,7 @@ export interface IGatePlanes {
|
|
|
29
34
|
readonly registrationGraph?: readonly IRegistrationIdiom[];
|
|
30
35
|
readonly baselines?: readonly IBaselineRule[];
|
|
31
36
|
readonly generatedArtifacts?: readonly IGeneratedArtifactRule[];
|
|
37
|
+
readonly docReferences?: readonly IDocReferenceRule[];
|
|
32
38
|
}
|
|
33
39
|
/** Flatten every declared rule across every plane into one iterable list. */
|
|
34
40
|
export declare function collectGateRules(planes: IGatePlanes): IGateRuleView[];
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"gate-rule-view.d.ts","sourceRoot":"","sources":["../../src/gates/gate-rule-view.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,
|
|
1
|
+
{"version":3,"file":"gate-rule-view.d.ts","sourceRoot":"","sources":["../../src/gates/gate-rule-view.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,aAAa,EAClB,KAAK,iBAAiB,EACtB,KAAK,sBAAsB,EAC3B,KAAK,WAAW,EAChB,KAAK,kBAAkB,EACvB,KAAK,oBAAoB,EACzB,KAAK,aAAa,EAClB,KAAK,WAAW,EACjB,MAAM,gBAAgB,CAAC;AAExB,kDAAkD;AAClD,MAAM,MAAM,SAAS,GACjB,QAAQ,GACR,QAAQ,GACR,UAAU,GACV,cAAc,GACd,UAAU,GACV,WAAW,GACX,eAAe,CAAC;AAEpB,+DAA+D;AAC/D,eAAO,MAAM,WAAW,EAAE,SAAS,SAAS,EAQ3C,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;;;OAGG;IACH,QAAQ,CAAC,WAAW,CAAC,EAAE,OAAO,CAAC;IAC/B,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,GACtB,iBAAiB,CAAC;CACvB;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;IAChE,QAAQ,CAAC,aAAa,CAAC,EAAE,SAAS,iBAAiB,EAAE,CAAC;CACvD;AAED,6EAA6E;AAC7E,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,WAAW,GAAG,aAAa,EAAE,CAiFrE"}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { failsWhenEmpty, } from '@shrkcrft/core';
|
|
1
2
|
/** Every plane, in the order `shrk gates list` prints them. */
|
|
2
3
|
export const GATE_PLANES = [
|
|
3
4
|
'wiring',
|
|
@@ -6,6 +7,7 @@ export const GATE_PLANES = [
|
|
|
6
7
|
'registration',
|
|
7
8
|
'baseline',
|
|
8
9
|
'generated',
|
|
10
|
+
'doc-reference',
|
|
9
11
|
];
|
|
10
12
|
/** Flatten every declared rule across every plane into one iterable list. */
|
|
11
13
|
export function collectGateRules(planes) {
|
|
@@ -16,7 +18,7 @@ export function collectGateRules(planes) {
|
|
|
16
18
|
plane: 'wiring',
|
|
17
19
|
...(r.description ? { description: r.description } : {}),
|
|
18
20
|
severity: r.severity ?? 'error',
|
|
19
|
-
failOnEmpty: r
|
|
21
|
+
failOnEmpty: failsWhenEmpty(r),
|
|
20
22
|
...(r.selfTest ? { selfTest: r.selfTest } : {}),
|
|
21
23
|
raw: r,
|
|
22
24
|
});
|
|
@@ -27,7 +29,7 @@ export function collectGateRules(planes) {
|
|
|
27
29
|
plane: 'policy',
|
|
28
30
|
...(r.description ? { description: r.description } : {}),
|
|
29
31
|
severity: r.severity ?? 'error',
|
|
30
|
-
failOnEmpty: r
|
|
32
|
+
failOnEmpty: failsWhenEmpty(r),
|
|
31
33
|
...(r.selfTest ? { selfTest: r.selfTest } : {}),
|
|
32
34
|
raw: r,
|
|
33
35
|
});
|
|
@@ -60,7 +62,19 @@ export function collectGateRules(planes) {
|
|
|
60
62
|
plane: 'baseline',
|
|
61
63
|
...(r.description ? { description: r.description } : {}),
|
|
62
64
|
severity: r.severity ?? 'error',
|
|
63
|
-
failOnEmpty: r
|
|
65
|
+
failOnEmpty: failsWhenEmpty(r),
|
|
66
|
+
...(r.expectEmpty === true ? { expectEmpty: true } : {}),
|
|
67
|
+
...(r.selfTest ? { selfTest: r.selfTest } : {}),
|
|
68
|
+
raw: r,
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
for (const r of planes.docReferences ?? []) {
|
|
72
|
+
out.push({
|
|
73
|
+
id: r.id,
|
|
74
|
+
plane: 'doc-reference',
|
|
75
|
+
...(r.description ? { description: r.description } : {}),
|
|
76
|
+
severity: r.severity ?? 'error',
|
|
77
|
+
failOnEmpty: failsWhenEmpty(r),
|
|
64
78
|
...(r.selfTest ? { selfTest: r.selfTest } : {}),
|
|
65
79
|
raw: r,
|
|
66
80
|
});
|
|
@@ -71,7 +85,7 @@ export function collectGateRules(planes) {
|
|
|
71
85
|
plane: 'generated',
|
|
72
86
|
...(r.description ? { description: r.description } : {}),
|
|
73
87
|
severity: r.severity ?? 'error',
|
|
74
|
-
failOnEmpty: r
|
|
88
|
+
failOnEmpty: failsWhenEmpty(r),
|
|
75
89
|
...(r.selfTest ? { selfTest: r.selfTest } : {}),
|
|
76
90
|
raw: r,
|
|
77
91
|
});
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import type { IWiringSource } from '@shrkcrft/core';
|
|
2
|
+
import { type ISharkcraftInspection } from '@shrkcrft/inspector';
|
|
1
3
|
import type { IGateRuleView } from './gate-rule-view.js';
|
|
2
4
|
export declare const GATE_COVERAGE_SCHEMA: "sharkcraft.gate-coverage/v1";
|
|
3
5
|
/**
|
|
@@ -23,11 +25,45 @@ export interface IGateCoverage {
|
|
|
23
25
|
readonly unitLabel: string;
|
|
24
26
|
/** A few of the extracted ids, so the author can eyeball correctness. */
|
|
25
27
|
readonly sampleIds: readonly string[];
|
|
28
|
+
/**
|
|
29
|
+
* Every extracted id. Populated only when the caller asks (`includeAllIds`),
|
|
30
|
+
* because a full set on every rule would bloat `gates coverage --json` for a
|
|
31
|
+
* fact almost no consumer of it needs — `gates try --full` does.
|
|
32
|
+
*/
|
|
33
|
+
readonly allIds?: readonly string[];
|
|
26
34
|
/** True when a zero match is a hard failure for this rule. */
|
|
27
35
|
readonly failOnEmpty: boolean;
|
|
28
36
|
readonly error?: string;
|
|
37
|
+
/**
|
|
38
|
+
* A diagnosis for a zero-match that is correct but probably not intended —
|
|
39
|
+
* e.g. an `import-edges` rule targeting `to.files` against barrel imports.
|
|
40
|
+
*/
|
|
41
|
+
readonly hint?: string;
|
|
29
42
|
/** Unmet `selfTest` expectations, each a human-readable sentence. */
|
|
30
43
|
readonly expectationFailures: readonly string[];
|
|
44
|
+
/**
|
|
45
|
+
* Named extractor this rule's primary selector came from. Present only for a
|
|
46
|
+
* `$use` consumer — the answer to "is this rule looking at the shared set, or
|
|
47
|
+
* a copy of it that has since drifted?".
|
|
48
|
+
*/
|
|
49
|
+
readonly viaExtractor?: string;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* One named extractor, reported ONCE with the rules that share it.
|
|
53
|
+
*
|
|
54
|
+
* The point of a shared extractor is that its consumers cannot disagree about
|
|
55
|
+
* which set they check. Reporting it once — with its match count and its
|
|
56
|
+
* consumer list — makes that guarantee visible: one line proves N rules are
|
|
57
|
+
* pointed at the same, live, non-empty set.
|
|
58
|
+
*/
|
|
59
|
+
export interface ISharedExtractorCoverage {
|
|
60
|
+
readonly id: string;
|
|
61
|
+
/** Rule ids (plane-qualified) that resolve their primary selector from it. */
|
|
62
|
+
readonly consumers: readonly string[];
|
|
63
|
+
readonly filesMatched: number;
|
|
64
|
+
readonly idsMatched: number;
|
|
65
|
+
readonly sampleIds: readonly string[];
|
|
66
|
+
readonly error?: string;
|
|
31
67
|
}
|
|
32
68
|
export interface IGateCoverageReport {
|
|
33
69
|
readonly schema: typeof GATE_COVERAGE_SCHEMA;
|
|
@@ -36,6 +72,8 @@ export interface IGateCoverageReport {
|
|
|
36
72
|
readonly empty: number;
|
|
37
73
|
readonly errored: number;
|
|
38
74
|
readonly expectationFailures: number;
|
|
75
|
+
/** Named extractors referenced by at least one rule, each reported once. */
|
|
76
|
+
readonly extractors: readonly ISharedExtractorCoverage[];
|
|
39
77
|
/**
|
|
40
78
|
* `pass` — every rule matched something and met its expectations.
|
|
41
79
|
* `stale` — at least one rule matched nothing (or broke an expectation).
|
|
@@ -49,5 +87,5 @@ export interface IGateCoverageReport {
|
|
|
49
87
|
* without side effects; it is reported as un-inspected (never as `empty`), so
|
|
50
88
|
* the report never claims a fact it did not check.
|
|
51
89
|
*/
|
|
52
|
-
export declare function buildGateCoverage(cwd: string, rules: readonly IGateRuleView[], excludeDirs?: readonly string[]): IGateCoverageReport;
|
|
90
|
+
export declare function buildGateCoverage(cwd: string, rules: readonly IGateRuleView[], excludeDirs?: readonly string[], extractors?: Readonly<Record<string, IWiringSource>>, includeAllIds?: boolean, inspection?: ISharkcraftInspection): IGateCoverageReport;
|
|
53
91
|
//# sourceMappingURL=rule-coverage.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"rule-coverage.d.ts","sourceRoot":"","sources":["../../src/gates/rule-coverage.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"rule-coverage.d.ts","sourceRoot":"","sources":["../../src/gates/rule-coverage.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAQV,aAAa,EAEd,MAAM,gBAAgB,CAAC;AAWxB,OAAO,EAAsB,KAAK,qBAAqB,EAAE,MAAM,qBAAqB,CAAC;AACrF,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;;;;OAIG;IACH,QAAQ,CAAC,MAAM,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IACpC,8DAA8D;IAC9D,QAAQ,CAAC,WAAW,EAAE,OAAO,CAAC;IAC9B,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IACxB;;;OAGG;IACH,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IACvB,qEAAqE;IACrE,QAAQ,CAAC,mBAAmB,EAAE,SAAS,MAAM,EAAE,CAAC;IAChD;;;;OAIG;IACH,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAC;CAChC;AAED;;;;;;;GAOG;AACH,MAAM,WAAW,wBAAwB;IACvC,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,8EAA8E;IAC9E,QAAQ,CAAC,SAAS,EAAE,SAAS,MAAM,EAAE,CAAC;IACtC,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,SAAS,EAAE,SAAS,MAAM,EAAE,CAAC;IACtC,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;CACzB;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,4EAA4E;IAC5E,QAAQ,CAAC,UAAU,EAAE,SAAS,wBAAwB,EAAE,CAAC;IACzD;;;OAGG;IACH,QAAQ,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC;CACpC;AAyKD;;;;;;GAMG;AACH,wBAAgB,iBAAiB,CAC/B,GAAG,EAAE,MAAM,EACX,KAAK,EAAE,SAAS,aAAa,EAAE,EAC/B,WAAW,GAAE,SAAS,MAAM,EAAO,EACnC,UAAU,GAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,aAAa,CAAC,CAAM,EACxD,aAAa,UAAQ,EACrB,UAAU,CAAC,EAAE,qBAAqB,GACjC,mBAAmB,CAOrB"}
|
|
@@ -1,4 +1,7 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { referencedExtractorIds } from '@shrkcrft/core';
|
|
2
|
+
import { inspectSource, runPolicyLint, scanGeneratedFiles, walkMatching, withFileReadCache, wiringSourceSide, } from '@shrkcrft/boundaries';
|
|
3
|
+
import { gateRuleSources } from "./gate-rule-globs.js";
|
|
4
|
+
import { checkDocReferences } from '@shrkcrft/inspector';
|
|
2
5
|
export const GATE_COVERAGE_SCHEMA = 'sharkcraft.gate-coverage/v1';
|
|
3
6
|
/** Evaluate the author's declared expectations against what the rule extracted. */
|
|
4
7
|
function checkExpectations(selfTest, ids, unitsMatched) {
|
|
@@ -30,7 +33,9 @@ function matchWiring(cwd, rule, excludeDirs) {
|
|
|
30
33
|
unitsMatched: insp.ids.length,
|
|
31
34
|
unitLabel: 'ids',
|
|
32
35
|
ids: insp.ids,
|
|
36
|
+
...(side.$use ? { viaExtractor: side.$use } : {}),
|
|
33
37
|
...(insp.error ? { error: insp.error } : {}),
|
|
38
|
+
...(insp.hint ? { hint: insp.hint } : {}),
|
|
34
39
|
};
|
|
35
40
|
}
|
|
36
41
|
function matchPolicy(cwd, rule, excludeDirs) {
|
|
@@ -52,7 +57,9 @@ function matchRegistry(cwd, decl, excludeDirs) {
|
|
|
52
57
|
unitsMatched: insp.ids.length,
|
|
53
58
|
unitLabel: 'ids',
|
|
54
59
|
ids: insp.ids,
|
|
60
|
+
...(decl.source.$use ? { viaExtractor: decl.source.$use } : {}),
|
|
55
61
|
...(insp.error ? { error: insp.error } : {}),
|
|
62
|
+
...(insp.hint ? { hint: insp.hint } : {}),
|
|
56
63
|
};
|
|
57
64
|
}
|
|
58
65
|
function matchRegistration(cwd, idiom, excludeDirs) {
|
|
@@ -62,7 +69,9 @@ function matchRegistration(cwd, idiom, excludeDirs) {
|
|
|
62
69
|
unitsMatched: insp.ids.length,
|
|
63
70
|
unitLabel: 'declared tokens',
|
|
64
71
|
ids: insp.ids,
|
|
72
|
+
...(idiom.declared.$use ? { viaExtractor: idiom.declared.$use } : {}),
|
|
65
73
|
...(insp.error ? { error: insp.error } : {}),
|
|
74
|
+
...(insp.hint ? { hint: insp.hint } : {}),
|
|
66
75
|
};
|
|
67
76
|
}
|
|
68
77
|
function matchBaseline(cwd, rule, excludeDirs) {
|
|
@@ -70,11 +79,28 @@ function matchBaseline(cwd, rule, excludeDirs) {
|
|
|
70
79
|
// compute is reported honestly as un-inspectable rather than guessed at —
|
|
71
80
|
// `shrk baseline explain --id X` runs it on purpose.
|
|
72
81
|
if (rule.compute.kind !== 'extractor' || !rule.compute.source) {
|
|
82
|
+
// ...but its INPUTS can still be probed. `watchFiles` names the files that
|
|
83
|
+
// feed the command, so globbing them (never spawning) answers the one
|
|
84
|
+
// question the trust layer otherwise cannot: is this rule still connected
|
|
85
|
+
// to the repo at all? A command baseline whose watchFiles point at a moved
|
|
86
|
+
// directory is indistinguishable from a healthy one without this.
|
|
87
|
+
const watch = rule.watchFiles ?? [];
|
|
88
|
+
if (watch.length === 0) {
|
|
89
|
+
return {
|
|
90
|
+
filesMatched: 0,
|
|
91
|
+
unitsMatched: 0,
|
|
92
|
+
unitLabel: 'entries (command compute — not inspected; add `watchFiles` to enable the probe)',
|
|
93
|
+
ids: [],
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
const probed = walkMatching(cwd, watch, new Set(excludeDirs));
|
|
73
97
|
return {
|
|
74
|
-
filesMatched:
|
|
75
|
-
unitsMatched:
|
|
76
|
-
|
|
77
|
-
|
|
98
|
+
filesMatched: probed.length,
|
|
99
|
+
unitsMatched: probed.length,
|
|
100
|
+
// A zero here is a REAL empty (stale watchFiles), so the label must not
|
|
101
|
+
// carry the "not inspected" marker that suppresses the empty verdict.
|
|
102
|
+
unitLabel: 'watchFiles input(s) (compute unverified — command never run)',
|
|
103
|
+
ids: probed.slice(0, 5),
|
|
78
104
|
};
|
|
79
105
|
}
|
|
80
106
|
const insp = inspectSource(cwd, rule.compute.source, excludeDirs);
|
|
@@ -83,7 +109,26 @@ function matchBaseline(cwd, rule, excludeDirs) {
|
|
|
83
109
|
unitsMatched: insp.ids.length,
|
|
84
110
|
unitLabel: 'entries',
|
|
85
111
|
ids: insp.ids,
|
|
112
|
+
...(rule.compute.source.$use ? { viaExtractor: rule.compute.source.$use } : {}),
|
|
86
113
|
...(insp.error ? { error: insp.error } : {}),
|
|
114
|
+
...(insp.hint ? { hint: insp.hint } : {}),
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* A doc-reference rule's "match" is how many id-shaped TOKENS it validated.
|
|
119
|
+
*
|
|
120
|
+
* Files alone would lie: a rule whose globs match 40 docs but whose
|
|
121
|
+
* `requireContext` gate rejects every token is enforcing nothing, and coverage
|
|
122
|
+
* exists precisely to make that visible.
|
|
123
|
+
*/
|
|
124
|
+
function matchDocReference(cwd, rule, inspection, excludeDirs) {
|
|
125
|
+
const res = checkDocReferences(cwd, rule, inspection, excludeDirs);
|
|
126
|
+
return {
|
|
127
|
+
filesMatched: res.filesScanned,
|
|
128
|
+
unitsMatched: res.tokensChecked,
|
|
129
|
+
unitLabel: 'doc reference(s)',
|
|
130
|
+
ids: [...new Set(res.tokens.filter((t) => t.skipped === undefined).map((t) => t.token))].sort(),
|
|
131
|
+
...(res.error ? { error: res.error } : {}),
|
|
87
132
|
};
|
|
88
133
|
}
|
|
89
134
|
function matchGenerated(cwd, rule, excludeDirs) {
|
|
@@ -103,7 +148,13 @@ function matchGenerated(cwd, rule, excludeDirs) {
|
|
|
103
148
|
* without side effects; it is reported as un-inspected (never as `empty`), so
|
|
104
149
|
* the report never claims a fact it did not check.
|
|
105
150
|
*/
|
|
106
|
-
export function buildGateCoverage(cwd, rules, excludeDirs = []) {
|
|
151
|
+
export function buildGateCoverage(cwd, rules, excludeDirs = [], extractors = {}, includeAllIds = false, inspection) {
|
|
152
|
+
// Safe to memo the tree reads for exactly this call: coverage NEVER spawns
|
|
153
|
+
// (the `command` baseline is probed, not run) and never writes, so nothing
|
|
154
|
+
// can change the tree between the first rule's scan and the last one's.
|
|
155
|
+
return withFileReadCache(() => buildGateCoverageUncached(cwd, rules, excludeDirs, extractors, includeAllIds, inspection));
|
|
156
|
+
}
|
|
157
|
+
function buildGateCoverageUncached(cwd, rules, excludeDirs, extractors, includeAllIds, inspection) {
|
|
107
158
|
const out = [];
|
|
108
159
|
for (const view of rules) {
|
|
109
160
|
let match;
|
|
@@ -123,17 +174,33 @@ export function buildGateCoverage(cwd, rules, excludeDirs = []) {
|
|
|
123
174
|
case 'baseline':
|
|
124
175
|
match = matchBaseline(cwd, view.raw, excludeDirs);
|
|
125
176
|
break;
|
|
177
|
+
case 'doc-reference':
|
|
178
|
+
// The inspection is only built when this plane is actually present, so
|
|
179
|
+
// a repo without it never pays for the registries.
|
|
180
|
+
match = inspection
|
|
181
|
+
? matchDocReference(cwd, view.raw, inspection, excludeDirs)
|
|
182
|
+
: {
|
|
183
|
+
filesMatched: 0,
|
|
184
|
+
unitsMatched: 0,
|
|
185
|
+
unitLabel: 'doc reference(s) (registries not loaded — not inspected)',
|
|
186
|
+
ids: [],
|
|
187
|
+
};
|
|
188
|
+
break;
|
|
126
189
|
default:
|
|
127
190
|
match = matchGenerated(cwd, view.raw, excludeDirs);
|
|
128
191
|
break;
|
|
129
192
|
}
|
|
130
193
|
const expectationFailures = checkExpectations(view.selfTest, match.ids, match.unitsMatched);
|
|
131
194
|
const uninspected = match.unitLabel.includes('not inspected');
|
|
195
|
+
// A FENCE asserts emptiness, so for it a zero match is the verified state,
|
|
196
|
+
// not a stale-selector suspect. Without this the rule is permanently red in
|
|
197
|
+
// `gates coverage` and could never be part of a green CI.
|
|
198
|
+
const emptyIsExpected = view.expectEmpty === true && match.unitsMatched === 0;
|
|
132
199
|
const status = match.error !== undefined
|
|
133
200
|
? 'error'
|
|
134
201
|
: expectationFailures.length > 0
|
|
135
202
|
? 'failed-expectation'
|
|
136
|
-
: match.unitsMatched === 0 && !uninspected
|
|
203
|
+
: match.unitsMatched === 0 && !uninspected && !emptyIsExpected
|
|
137
204
|
? 'empty'
|
|
138
205
|
: 'ok';
|
|
139
206
|
out.push({
|
|
@@ -143,13 +210,60 @@ export function buildGateCoverage(cwd, rules, excludeDirs = []) {
|
|
|
143
210
|
status,
|
|
144
211
|
filesMatched: match.filesMatched,
|
|
145
212
|
unitsMatched: match.unitsMatched,
|
|
146
|
-
unitLabel: match.unitLabel,
|
|
213
|
+
unitLabel: emptyIsExpected ? `${match.unitLabel} (empty — the asserted state)` : match.unitLabel,
|
|
147
214
|
sampleIds: match.ids.slice(0, 5),
|
|
215
|
+
...(includeAllIds ? { allIds: match.ids } : {}),
|
|
148
216
|
failOnEmpty: view.failOnEmpty,
|
|
149
217
|
...(match.error ? { error: match.error } : {}),
|
|
218
|
+
...(match.hint ? { hint: match.hint } : {}),
|
|
219
|
+
...(match.viaExtractor ? { viaExtractor: match.viaExtractor } : {}),
|
|
150
220
|
expectationFailures,
|
|
151
221
|
});
|
|
152
222
|
}
|
|
223
|
+
// Report each SHARED extractor once, with the rules that consume it. One
|
|
224
|
+
// resolve per extractor, not per consumer — the shared definition is a single
|
|
225
|
+
// fact about the tree, and stating it N times would invite the reader to
|
|
226
|
+
// treat N agreeing consumers as N independent confirmations.
|
|
227
|
+
//
|
|
228
|
+
// Consumers are collected from EVERY side of every rule, not just the side
|
|
229
|
+
// coverage happens to inspect: a rule bound to the extractor through its
|
|
230
|
+
// `registered` sink is every bit as bound as one using it for `declared`, and
|
|
231
|
+
// listing half of them would understate the guarantee.
|
|
232
|
+
const consumersByExtractor = new Map();
|
|
233
|
+
for (const view of rules) {
|
|
234
|
+
const label = `${view.plane}:${view.id}`;
|
|
235
|
+
for (const id of referencedExtractorIds(gateRuleSources(view))) {
|
|
236
|
+
const list = consumersByExtractor.get(id) ?? [];
|
|
237
|
+
if (!list.includes(label))
|
|
238
|
+
list.push(label);
|
|
239
|
+
consumersByExtractor.set(id, list);
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
const extractorCoverage = [...consumersByExtractor.keys()]
|
|
243
|
+
.sort()
|
|
244
|
+
.map((id) => {
|
|
245
|
+
const definition = extractors[id];
|
|
246
|
+
if (!definition) {
|
|
247
|
+
return {
|
|
248
|
+
id,
|
|
249
|
+
consumers: consumersByExtractor.get(id),
|
|
250
|
+
filesMatched: 0,
|
|
251
|
+
idsMatched: 0,
|
|
252
|
+
sampleIds: [],
|
|
253
|
+
error: 'referenced but not declared in `extractors`',
|
|
254
|
+
};
|
|
255
|
+
}
|
|
256
|
+
const insp = inspectSource(cwd, definition, excludeDirs);
|
|
257
|
+
return {
|
|
258
|
+
id,
|
|
259
|
+
consumers: consumersByExtractor.get(id),
|
|
260
|
+
filesMatched: insp.filesScanned,
|
|
261
|
+
idsMatched: insp.ids.length,
|
|
262
|
+
sampleIds: insp.ids.slice(0, 5),
|
|
263
|
+
...(insp.error ? { error: insp.error } : {}),
|
|
264
|
+
...(insp.hint ? { hint: insp.hint } : {}),
|
|
265
|
+
};
|
|
266
|
+
});
|
|
153
267
|
const empty = out.filter((r) => r.status === 'empty').length;
|
|
154
268
|
const errored = out.filter((r) => r.status === 'error').length;
|
|
155
269
|
const expectationFailures = out.filter((r) => r.status === 'failed-expectation').length;
|
|
@@ -160,6 +274,7 @@ export function buildGateCoverage(cwd, rules, excludeDirs = []) {
|
|
|
160
274
|
empty,
|
|
161
275
|
errored,
|
|
162
276
|
expectationFailures,
|
|
277
|
+
extractors: extractorCoverage,
|
|
163
278
|
verdict: empty + errored + expectationFailures > 0 ? 'stale' : 'pass',
|
|
164
279
|
};
|
|
165
280
|
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { type ISharkcraftInspection } from '@shrkcrft/inspector';
|
|
2
|
+
import type { IGateRuleResult } from './gate-envelope.js';
|
|
3
|
+
import type { IGateRuleView } from './gate-rule-view.js';
|
|
4
|
+
/**
|
|
5
|
+
* Run every data-defined plane's VIOLATION check and normalize the results.
|
|
6
|
+
*
|
|
7
|
+
* Each plane already has a verb, but gating a change meant running five of them
|
|
8
|
+
* and `&&`-chaining five exit codes — so in practice people ran one or two. The
|
|
9
|
+
* value here is that one call answers "does this change violate any declared
|
|
10
|
+
* rule?" with one exit code, and it does so by calling the SAME evaluators the
|
|
11
|
+
* per-plane verbs call. A second implementation of any plane's check would
|
|
12
|
+
* eventually disagree with the verb, and the aggregate — the one wired into CI
|
|
13
|
+
* — would be the copy nobody notices is wrong.
|
|
14
|
+
*/
|
|
15
|
+
export interface IRunGatePlanesOptions {
|
|
16
|
+
readonly cwd: string;
|
|
17
|
+
readonly excludeDirs: readonly string[];
|
|
18
|
+
/**
|
|
19
|
+
* Restrict to rules whose footprint intersects these changed files. Scoping
|
|
20
|
+
* is applied by the CALLER (it owns the git diff); this is passed through to
|
|
21
|
+
* the plane engines that scope internally.
|
|
22
|
+
*/
|
|
23
|
+
readonly changedFiles?: readonly string[];
|
|
24
|
+
/**
|
|
25
|
+
* Skip every check that would SPAWN a shell (a `command` baseline compute, a
|
|
26
|
+
* generated `regen`). The header/classification halves still run, so the
|
|
27
|
+
* result stays honest — a skipped drift check is reported as skipped, never
|
|
28
|
+
* folded into a pass.
|
|
29
|
+
*/
|
|
30
|
+
readonly noSpawn?: boolean;
|
|
31
|
+
/**
|
|
32
|
+
* Registries for the doc-reference plane. Built lazily by the caller — a
|
|
33
|
+
* repo without that plane never loads them.
|
|
34
|
+
*/
|
|
35
|
+
readonly inspection?: ISharkcraftInspection;
|
|
36
|
+
}
|
|
37
|
+
/** One plane's contribution, plus any engine diagnostics it produced. */
|
|
38
|
+
export interface IGatePlaneRun {
|
|
39
|
+
readonly results: readonly IGateRuleResult[];
|
|
40
|
+
readonly diagnostics: readonly string[];
|
|
41
|
+
}
|
|
42
|
+
/** Evaluate the given rules, dispatching each to its plane's real engine. */
|
|
43
|
+
export declare function runGatePlanes(rules: readonly IGateRuleView[], options: IRunGatePlanesOptions): IGatePlaneRun;
|
|
44
|
+
//# sourceMappingURL=run-gate-planes.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"run-gate-planes.d.ts","sourceRoot":"","sources":["../../src/gates/run-gate-planes.ts"],"names":[],"mappings":"AACA,OAAO,EAAsB,KAAK,qBAAqB,EAAE,MAAM,qBAAqB,CAAC;AAsBrF,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC;AAC1D,OAAO,KAAK,EAAa,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAEpE;;;;;;;;;;GAUG;AAEH,MAAM,WAAW,qBAAqB;IACpC,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,WAAW,EAAE,SAAS,MAAM,EAAE,CAAC;IACxC;;;;OAIG;IACH,QAAQ,CAAC,YAAY,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAC1C;;;;;OAKG;IACH,QAAQ,CAAC,OAAO,CAAC,EAAE,OAAO,CAAC;IAC3B;;;OAGG;IACH,QAAQ,CAAC,UAAU,CAAC,EAAE,qBAAqB,CAAC;CAC7C;AAED,yEAAyE;AACzE,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,OAAO,EAAE,SAAS,eAAe,EAAE,CAAC;IAC7C,QAAQ,CAAC,WAAW,EAAE,SAAS,MAAM,EAAE,CAAC;CACzC;AAED,6EAA6E;AAC7E,wBAAgB,aAAa,CAC3B,KAAK,EAAE,SAAS,aAAa,EAAE,EAC/B,OAAO,EAAE,qBAAqB,GAC7B,aAAa,CAef"}
|