@shrkcrft/cli 0.1.0-alpha.29 → 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.
Files changed (41) hide show
  1. package/dist/commands/baseline.command.d.ts +25 -0
  2. package/dist/commands/baseline.command.d.ts.map +1 -1
  3. package/dist/commands/baseline.command.js +23 -7
  4. package/dist/commands/changelog-data.d.ts.map +1 -1
  5. package/dist/commands/changelog-data.js +26 -0
  6. package/dist/commands/check.command.d.ts.map +1 -1
  7. package/dist/commands/check.command.js +19 -2
  8. package/dist/commands/code-intel.command.d.ts.map +1 -1
  9. package/dist/commands/code-intel.command.js +5 -1
  10. package/dist/commands/docs-references.command.d.ts +7 -0
  11. package/dist/commands/docs-references.command.d.ts.map +1 -0
  12. package/dist/commands/docs-references.command.js +323 -0
  13. package/dist/commands/doctor.command.d.ts.map +1 -1
  14. package/dist/commands/doctor.command.js +3 -2
  15. package/dist/commands/gates.command.d.ts +2 -0
  16. package/dist/commands/gates.command.d.ts.map +1 -1
  17. package/dist/commands/gates.command.js +640 -16
  18. package/dist/commands/generated.command.d.ts +32 -0
  19. package/dist/commands/generated.command.d.ts.map +1 -1
  20. package/dist/commands/generated.command.js +180 -49
  21. package/dist/commands/graph-code-subverbs.d.ts.map +1 -1
  22. package/dist/commands/graph-code-subverbs.js +5 -1
  23. package/dist/commands/wiring.command.d.ts.map +1 -1
  24. package/dist/commands/wiring.command.js +4 -3
  25. package/dist/gates/gate-envelope.d.ts +1 -1
  26. package/dist/gates/gate-envelope.d.ts.map +1 -1
  27. package/dist/gates/gate-rule-globs.d.ts +33 -0
  28. package/dist/gates/gate-rule-globs.d.ts.map +1 -0
  29. package/dist/gates/gate-rule-globs.js +101 -0
  30. package/dist/gates/gate-rule-view.d.ts +9 -3
  31. package/dist/gates/gate-rule-view.d.ts.map +1 -1
  32. package/dist/gates/gate-rule-view.js +13 -0
  33. package/dist/gates/rule-coverage.d.ts +39 -1
  34. package/dist/gates/rule-coverage.d.ts.map +1 -1
  35. package/dist/gates/rule-coverage.js +123 -8
  36. package/dist/gates/run-gate-planes.d.ts +44 -0
  37. package/dist/gates/run-gate-planes.d.ts.map +1 -0
  38. package/dist/gates/run-gate-planes.js +261 -0
  39. package/dist/main.d.ts.map +1 -1
  40. package/dist/main.js +5 -1
  41. package/package.json +33 -33
@@ -1,6 +1,6 @@
1
- import { type IBaselineRule, type IGeneratedArtifactRule, type IPolicyRule, type IRegistrationIdiom, type IRegistryDeclaration, type IRuleSelfTest, type IWiringRule } from '@shrkcrft/core';
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,EAEL,KAAK,aAAa,EAClB,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,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"}
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"}
@@ -7,6 +7,7 @@ export const GATE_PLANES = [
7
7
  'registration',
8
8
  'baseline',
9
9
  'generated',
10
+ 'doc-reference',
10
11
  ];
11
12
  /** Flatten every declared rule across every plane into one iterable list. */
12
13
  export function collectGateRules(planes) {
@@ -62,6 +63,18 @@ export function collectGateRules(planes) {
62
63
  ...(r.description ? { description: r.description } : {}),
63
64
  severity: r.severity ?? 'error',
64
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),
65
78
  ...(r.selfTest ? { selfTest: r.selfTest } : {}),
66
79
  raw: r,
67
80
  });
@@ -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":"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"}
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 { inspectSource, runPolicyLint, scanGeneratedFiles, wiringSourceSide, } from '@shrkcrft/boundaries';
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: 0,
75
- unitsMatched: 0,
76
- unitLabel: 'entries (command compute not inspected)',
77
- ids: [],
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"}