@shrkcrft/boundaries 0.1.0-alpha.29 → 0.1.0-alpha.31
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/baseline/compute-baseline.d.ts +8 -0
- package/dist/baseline/compute-baseline.d.ts.map +1 -1
- package/dist/baseline/compute-baseline.js +10 -8
- package/dist/baseline/diff-baseline.d.ts +28 -0
- package/dist/baseline/diff-baseline.d.ts.map +1 -1
- package/dist/baseline/diff-baseline.js +29 -0
- package/dist/evaluate/boundary-unit-finding.d.ts +10 -0
- package/dist/evaluate/boundary-unit-finding.d.ts.map +1 -0
- package/dist/evaluate/boundary-unit-finding.js +21 -0
- package/dist/evaluate/boundary-unit-kind.d.ts +7 -0
- package/dist/evaluate/boundary-unit-kind.d.ts.map +1 -0
- package/dist/evaluate/boundary-unit-kind.js +14 -0
- package/dist/evaluate/evaluate-boundaries.d.ts +230 -3
- package/dist/evaluate/evaluate-boundaries.d.ts.map +1 -1
- package/dist/evaluate/evaluate-boundaries.js +481 -53
- package/dist/evaluate/i-boundary-rule-settle-input.d.ts +27 -0
- package/dist/evaluate/i-boundary-rule-settle-input.d.ts.map +1 -0
- package/dist/evaluate/i-boundary-rule-settle-input.js +1 -0
- package/dist/evaluate/i-boundary-rule-settlement.d.ts +23 -0
- package/dist/evaluate/i-boundary-rule-settlement.d.ts.map +1 -0
- package/dist/evaluate/i-boundary-rule-settlement.js +1 -0
- package/dist/evaluate/i-boundary-unit-finding.d.ts +22 -0
- package/dist/evaluate/i-boundary-unit-finding.d.ts.map +1 -0
- package/dist/evaluate/i-boundary-unit-finding.js +1 -0
- package/dist/evaluate/settle-boundary-rule.d.ts +23 -0
- package/dist/evaluate/settle-boundary-rule.d.ts.map +1 -0
- package/dist/evaluate/settle-boundary-rule.js +73 -0
- package/dist/evaluate/with-boundary-rule-settlement.d.ts +11 -0
- package/dist/evaluate/with-boundary-rule-settlement.d.ts.map +1 -0
- package/dist/evaluate/with-boundary-rule-settlement.js +44 -0
- package/dist/extract/code-zones.d.ts +95 -3
- package/dist/extract/code-zones.d.ts.map +1 -1
- package/dist/extract/code-zones.js +313 -4
- package/dist/extract/extract-tokens.d.ts +49 -1
- package/dist/extract/extract-tokens.d.ts.map +1 -1
- package/dist/extract/extract-tokens.js +283 -25
- package/dist/extract/i-labeled-source.d.ts +12 -0
- package/dist/extract/i-labeled-source.d.ts.map +1 -0
- package/dist/extract/i-labeled-source.js +1 -0
- package/dist/extract/import-edges.d.ts +50 -0
- package/dist/extract/import-edges.d.ts.map +1 -0
- package/dist/extract/import-edges.js +170 -0
- package/dist/extract/inspect-source.d.ts +38 -3
- package/dist/extract/inspect-source.d.ts.map +1 -1
- package/dist/extract/inspect-source.js +56 -8
- package/dist/extract/parse-imports.d.ts +80 -0
- package/dist/extract/parse-imports.d.ts.map +1 -0
- package/dist/extract/parse-imports.js +254 -0
- package/dist/extract/scan-literals.d.ts +12 -1
- package/dist/extract/scan-literals.d.ts.map +1 -1
- package/dist/extract/scan-literals.js +15 -1
- package/dist/extract/source-liveness-request.d.ts +24 -0
- package/dist/extract/source-liveness-request.d.ts.map +1 -0
- package/dist/extract/source-liveness-request.js +45 -0
- package/dist/generated/check-provenance.d.ts +20 -2
- package/dist/generated/check-provenance.d.ts.map +1 -1
- package/dist/generated/check-provenance.js +27 -6
- package/dist/generated/read-regen-tree.d.ts +16 -0
- package/dist/generated/read-regen-tree.d.ts.map +1 -0
- package/dist/generated/read-regen-tree.js +61 -0
- package/dist/generated/scan-generated.d.ts +57 -3
- package/dist/generated/scan-generated.d.ts.map +1 -1
- package/dist/generated/scan-generated.js +111 -11
- package/dist/index.d.ts +51 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +54 -0
- package/dist/model/boundary-intended-empty-line.d.ts +10 -0
- package/dist/model/boundary-intended-empty-line.d.ts.map +1 -0
- package/dist/model/boundary-intended-empty-line.js +11 -0
- package/dist/model/boundary-markable-list.d.ts +15 -0
- package/dist/model/boundary-markable-list.d.ts.map +1 -0
- package/dist/model/boundary-markable-list.js +15 -0
- package/dist/model/boundary-rule-input-keys.d.ts +11 -0
- package/dist/model/boundary-rule-input-keys.d.ts.map +1 -0
- package/dist/model/boundary-rule-input-keys.js +29 -0
- package/dist/model/boundary-rule-input.d.ts +23 -0
- package/dist/model/boundary-rule-input.d.ts.map +1 -0
- package/dist/model/boundary-rule-input.js +1 -0
- package/dist/model/boundary-rule-key-problems.d.ts +11 -0
- package/dist/model/boundary-rule-key-problems.d.ts.map +1 -0
- package/dist/model/boundary-rule-key-problems.js +36 -0
- package/dist/model/boundary-rule-marker-problems.d.ts +26 -0
- package/dist/model/boundary-rule-marker-problems.d.ts.map +1 -0
- package/dist/model/boundary-rule-marker-problems.js +94 -0
- package/dist/model/boundary-rule-scope.d.ts +88 -0
- package/dist/model/boundary-rule-scope.d.ts.map +1 -0
- package/dist/model/boundary-rule-scope.js +175 -0
- package/dist/model/boundary-rule.d.ts +120 -5
- package/dist/model/boundary-rule.d.ts.map +1 -1
- package/dist/model/boundary-rule.js +148 -1
- package/dist/model/boundary-unit-problem-issue.d.ts +9 -0
- package/dist/model/boundary-unit-problem-issue.d.ts.map +1 -0
- package/dist/model/boundary-unit-problem-issue.js +16 -0
- package/dist/model/normalize-boundary-rule.d.ts +21 -0
- package/dist/model/normalize-boundary-rule.d.ts.map +1 -0
- package/dist/model/normalize-boundary-rule.js +62 -0
- package/dist/policy/evaluate-policy.d.ts +80 -3
- package/dist/policy/evaluate-policy.d.ts.map +1 -1
- package/dist/policy/evaluate-policy.js +152 -19
- package/dist/policy/i-policy-rule-liveness.d.ts +15 -0
- package/dist/policy/i-policy-rule-liveness.d.ts.map +1 -0
- package/dist/policy/i-policy-rule-liveness.js +1 -0
- package/dist/policy/run-policy.d.ts +1 -1
- package/dist/policy/run-policy.d.ts.map +1 -1
- package/dist/policy/run-policy.js +171 -21
- package/dist/registry/load-boundary-rules.d.ts +29 -1
- package/dist/registry/load-boundary-rules.d.ts.map +1 -1
- package/dist/registry/load-boundary-rules.js +38 -8
- package/dist/scan/glob.d.ts +90 -0
- package/dist/scan/glob.d.ts.map +1 -1
- package/dist/scan/glob.js +205 -0
- package/dist/scan/i-glob-unit-measure.d.ts +22 -0
- package/dist/scan/i-glob-unit-measure.d.ts.map +1 -0
- package/dist/scan/i-glob-unit-measure.js +1 -0
- package/dist/scan/import-pattern.d.ts +109 -0
- package/dist/scan/import-pattern.d.ts.map +1 -0
- package/dist/scan/import-pattern.js +191 -0
- package/dist/scan/node-builtin-package-names.d.ts +13 -0
- package/dist/scan/node-builtin-package-names.d.ts.map +1 -0
- package/dist/scan/node-builtin-package-names.js +25 -0
- package/dist/scan/scan-imports.d.ts +36 -3
- package/dist/scan/scan-imports.d.ts.map +1 -1
- package/dist/scan/scan-imports.js +114 -45
- package/dist/util/blank-run-hazard-finding.d.ts +34 -0
- package/dist/util/blank-run-hazard-finding.d.ts.map +1 -0
- package/dist/util/blank-run-hazard-finding.js +1 -0
- package/dist/util/blank-run-hazard.d.ts +29 -0
- package/dist/util/blank-run-hazard.d.ts.map +1 -0
- package/dist/util/blank-run-hazard.js +450 -0
- package/dist/util/dead-glob-units.d.ts +46 -0
- package/dist/util/dead-glob-units.d.ts.map +1 -0
- package/dist/util/dead-glob-units.js +116 -0
- package/dist/util/glob-list-liveness-input.d.ts +31 -0
- package/dist/util/glob-list-liveness-input.d.ts.map +1 -0
- package/dist/util/glob-list-liveness-input.js +65 -0
- package/dist/util/i-dead-glob-unit.d.ts +24 -0
- package/dist/util/i-dead-glob-unit.d.ts.map +1 -0
- package/dist/util/i-dead-glob-unit.js +1 -0
- package/dist/util/i-glob-list-units.d.ts +25 -0
- package/dist/util/i-glob-list-units.d.ts.map +1 -0
- package/dist/util/i-glob-list-units.js +1 -0
- package/dist/util/i-glob-liveness-list.d.ts +23 -0
- package/dist/util/i-glob-liveness-list.d.ts.map +1 -0
- package/dist/util/i-glob-liveness-list.js +1 -0
- package/dist/util/i-glob-liveness-request.d.ts +11 -0
- package/dist/util/i-glob-liveness-request.d.ts.map +1 -0
- package/dist/util/i-glob-liveness-request.js +1 -0
- package/dist/util/i-glob-negation.d.ts +13 -0
- package/dist/util/i-glob-negation.d.ts.map +1 -0
- package/dist/util/i-glob-negation.js +1 -0
- package/dist/util/matched-files.d.ts +22 -0
- package/dist/util/matched-files.d.ts.map +1 -0
- package/dist/util/matched-files.js +1 -0
- package/dist/util/negation-cause.d.ts +15 -0
- package/dist/util/negation-cause.d.ts.map +1 -0
- package/dist/util/negation-cause.js +21 -0
- package/dist/util/plane-scan-exclude-dirs.d.ts +17 -0
- package/dist/util/plane-scan-exclude-dirs.d.ts.map +1 -0
- package/dist/util/plane-scan-exclude-dirs.js +22 -0
- package/dist/util/read-glob-list-liveness.d.ts +20 -0
- package/dist/util/read-glob-list-liveness.d.ts.map +1 -0
- package/dist/util/read-glob-list-liveness.js +20 -0
- package/dist/util/read-scope-coverage.d.ts +90 -0
- package/dist/util/read-scope-coverage.d.ts.map +1 -0
- package/dist/util/read-scope-coverage.js +173 -0
- package/dist/util/read-scope.d.ts +14 -0
- package/dist/util/read-scope.d.ts.map +1 -0
- package/dist/util/read-scope.js +1 -0
- package/dist/util/read-selected-files.d.ts +16 -0
- package/dist/util/read-selected-files.d.ts.map +1 -0
- package/dist/util/read-selected-files.js +25 -0
- package/dist/util/settle-glob-lists.d.ts +12 -0
- package/dist/util/settle-glob-lists.d.ts.map +1 -0
- package/dist/util/settle-glob-lists.js +13 -0
- package/dist/util/unread-file-reason.d.ts +26 -0
- package/dist/util/unread-file-reason.d.ts.map +1 -0
- package/dist/util/unread-file-reason.js +26 -0
- package/dist/util/unread-file.d.ts +10 -0
- package/dist/util/unread-file.d.ts.map +1 -0
- package/dist/util/unread-file.js +1 -0
- package/dist/util/walk-files.d.ts +90 -7
- package/dist/util/walk-files.d.ts.map +1 -1
- package/dist/util/walk-files.js +206 -19
- package/dist/wiring/evaluate-wiring.d.ts +88 -7
- package/dist/wiring/evaluate-wiring.d.ts.map +1 -1
- package/dist/wiring/evaluate-wiring.js +276 -16
- package/dist/wiring/explain-wiring.d.ts +56 -14
- package/dist/wiring/explain-wiring.d.ts.map +1 -1
- package/dist/wiring/explain-wiring.js +136 -10
- package/dist/wiring/i-idiom-role-coverage.d.ts +24 -0
- package/dist/wiring/i-idiom-role-coverage.d.ts.map +1 -0
- package/dist/wiring/i-idiom-role-coverage.js +1 -0
- package/dist/wiring/i-registration-query-verdict.d.ts +38 -0
- package/dist/wiring/i-registration-query-verdict.d.ts.map +1 -0
- package/dist/wiring/i-registration-query-verdict.js +1 -0
- package/dist/wiring/i-registration-roles.d.ts +71 -0
- package/dist/wiring/i-registration-roles.d.ts.map +1 -0
- package/dist/wiring/i-registration-roles.js +1 -0
- package/dist/wiring/measure-idiom-role-coverage.d.ts +12 -0
- package/dist/wiring/measure-idiom-role-coverage.d.ts.map +1 -0
- package/dist/wiring/measure-idiom-role-coverage.js +20 -0
- package/dist/wiring/measure-registration-roles.d.ts +25 -0
- package/dist/wiring/measure-registration-roles.d.ts.map +1 -0
- package/dist/wiring/measure-registration-roles.js +124 -0
- package/dist/wiring/plan-wiring-fix.d.ts +16 -1
- package/dist/wiring/plan-wiring-fix.d.ts.map +1 -1
- package/dist/wiring/plan-wiring-fix.js +177 -4
- package/dist/wiring/registration-graph.d.ts +13 -1
- package/dist/wiring/registration-graph.d.ts.map +1 -1
- package/dist/wiring/registration-graph.js +22 -6
- package/dist/wiring/registration-query-verdict.d.ts +23 -0
- package/dist/wiring/registration-query-verdict.d.ts.map +1 -0
- package/dist/wiring/registration-query-verdict.js +118 -0
- package/dist/wiring/registry-query.d.ts +9 -1
- package/dist/wiring/registry-query.d.ts.map +1 -1
- package/dist/wiring/registry-query.js +17 -6
- package/dist/wiring/scan-wiring-files.d.ts +5 -3
- package/dist/wiring/scan-wiring-files.d.ts.map +1 -1
- package/dist/wiring/scan-wiring-files.js +55 -14
- package/dist/wiring/sink-imports.d.ts +95 -0
- package/dist/wiring/sink-imports.d.ts.map +1 -0
- package/dist/wiring/sink-imports.js +124 -0
- package/dist/wiring/trace-literal.d.ts +6 -0
- package/dist/wiring/trace-literal.d.ts.map +1 -1
- package/dist/wiring/trace-literal.js +5 -2
- package/dist/wiring/wiring-labeled-sources.d.ts +12 -0
- package/dist/wiring/wiring-labeled-sources.d.ts.map +1 -0
- package/dist/wiring/wiring-labeled-sources.js +20 -0
- package/package.json +2 -2
|
@@ -1,5 +1,7 @@
|
|
|
1
|
-
import { type IWiringRule, type IWiringSource } from '@shrkcrft/core';
|
|
2
|
-
import { type IExtractFileEntry, type IExtractedSite } from '../extract/extract-tokens.js';
|
|
1
|
+
import { type ISettledUnitLiveness, type IUnitLiveness, type IUnitStateLists, type IVerdictCoverage, type IWiringRule, type IWiringSource } from '@shrkcrft/core';
|
|
2
|
+
import { type IExtractContext, type IExtractFileEntry, type IExtractedSite } from '../extract/extract-tokens.js';
|
|
3
|
+
import type { IUnreadFile } from '../util/unread-file.js';
|
|
4
|
+
import type { IGlobNegation } from '../util/i-glob-negation.js';
|
|
3
5
|
export declare const WIRING_SCHEMA: "sharkcraft.wiring/v1";
|
|
4
6
|
/** A file made available to the engine. */
|
|
5
7
|
export type IWiringFileEntry = IExtractFileEntry;
|
|
@@ -68,8 +70,52 @@ export interface IWiringRuleResult {
|
|
|
68
70
|
* silently reported as N unrelated violations.
|
|
69
71
|
*/
|
|
70
72
|
readonly emptySink?: boolean;
|
|
73
|
+
/**
|
|
74
|
+
* A diagnosis for a side that extracted nothing for a knowable reason — e.g.
|
|
75
|
+
* an `import-edges` sink targeting `to.files` against barrel imports. Carried
|
|
76
|
+
* so the empty-sink message can explain itself instead of sending the reader
|
|
77
|
+
* to check a glob that is fine.
|
|
78
|
+
*/
|
|
79
|
+
readonly sinkHint?: string;
|
|
71
80
|
/** Per-hop breakdown for a `chain` rule. */
|
|
72
81
|
readonly hops?: readonly IWiringHopResult[];
|
|
82
|
+
/**
|
|
83
|
+
* Registered sites whose membership key the declared side never produced —
|
|
84
|
+
* ONE derivation, two renderings. `parity` reports each as a
|
|
85
|
+
* `registered-missing` violation; `subset` (the default) reports them here,
|
|
86
|
+
* because a `declared ⊆ registered` rule never examined them: the declared
|
|
87
|
+
* selector may be narrower than reality, and the rule would pass by
|
|
88
|
+
* construction. Set for a classic (non-chain) subset/parity rule that ran,
|
|
89
|
+
* sorted by membership key. `explainWiring` reads this rather than
|
|
90
|
+
* recomputing it.
|
|
91
|
+
*/
|
|
92
|
+
readonly registeredOnly?: readonly IWiringTokenSite[];
|
|
93
|
+
/**
|
|
94
|
+
* What the rule examined against what it was asked to (see
|
|
95
|
+
* `IVerdictCoverage`). A subset rule whose registered side holds tokens the
|
|
96
|
+
* declared selector never produced reports them as unexamined `registered
|
|
97
|
+
* tokens`, so the gate envelope marks it `partial` and the verdict is not
|
|
98
|
+
* verified — unless the rule's `registeredExtras` accepts them explicitly.
|
|
99
|
+
* The engine fills it in; it never decides a verdict from it.
|
|
100
|
+
*/
|
|
101
|
+
readonly coverage: IVerdictCoverage;
|
|
102
|
+
/**
|
|
103
|
+
* Glob-matched files on any side of the rule that the one reader did not
|
|
104
|
+
* read (over the read cap, or unreadable). Set only when non-empty.
|
|
105
|
+
* `coverage` names them too: such a rule is never a pass.
|
|
106
|
+
*/
|
|
107
|
+
readonly unread?: readonly IUnreadFile[];
|
|
108
|
+
/**
|
|
109
|
+
* The acceptance of the rule's `expectEmpty` units — settle record B of
|
|
110
|
+
* every source's glob lists (round 13). Folded into the envelope's ONE
|
|
111
|
+
* settle beside {@link coverage}; when the rule is intended-empty it IS
|
|
112
|
+
* {@link coverage}.
|
|
113
|
+
*/
|
|
114
|
+
readonly unitAcceptance?: IVerdictCoverage;
|
|
115
|
+
/** The rule's dead / intended-empty / went-live glob units as printed lines (`unitStateLists`). */
|
|
116
|
+
readonly units?: IUnitStateLists;
|
|
117
|
+
/** The rule's non-live glob units, for `--fail-on-dead-units` (`selectorUnitFails`). */
|
|
118
|
+
readonly unitLiveness?: readonly IUnitLiveness[];
|
|
73
119
|
}
|
|
74
120
|
export interface IWiringReport {
|
|
75
121
|
readonly schema: typeof WIRING_SCHEMA;
|
|
@@ -80,11 +126,22 @@ export interface IWiringReport {
|
|
|
80
126
|
/** Rules that checked nothing, reported loudly instead of as a green pass. */
|
|
81
127
|
readonly skipped: readonly IWiringSkip[];
|
|
82
128
|
/**
|
|
83
|
-
* Count of rules that
|
|
84
|
-
*
|
|
85
|
-
*
|
|
129
|
+
* Count of rules that settled a verdict: ran a comparison, or were
|
|
130
|
+
* misconfigured (counted so their error is not swallowed), or were ACCEPTED
|
|
131
|
+
* as intended-empty (round 13 — every source inclusion glob marked
|
|
132
|
+
* `expectEmpty`, 0 files matched; counted so the verdict path's
|
|
133
|
+
* `evaluated === 0` NOT-VERIFIED guard never reads an accepted plan as
|
|
134
|
+
* "nothing ran"). A rule whose source side matched 0 files or extracted 0 ids
|
|
135
|
+
* WITHOUT that acceptance is NOT evaluated (see {@link skipped}). The printed
|
|
136
|
+
* "N evaluated" excludes the accepted ones: N = `evaluated − acceptedEmpty`.
|
|
86
137
|
*/
|
|
87
138
|
readonly evaluated: number;
|
|
139
|
+
/**
|
|
140
|
+
* Rules accepted as intended-empty (round 13, K6): they examined 0 files, so
|
|
141
|
+
* a renderer prints them apart — `N evaluated, M accepted as
|
|
142
|
+
* intended-empty` — never inside the evaluated count.
|
|
143
|
+
*/
|
|
144
|
+
readonly acceptedEmpty: number;
|
|
88
145
|
readonly verdict: 'pass' | 'errors' | 'warnings';
|
|
89
146
|
}
|
|
90
147
|
/** Resolves a rule-side's globs to the concrete files (path + content) to scan. */
|
|
@@ -107,9 +164,10 @@ export declare function wiringGlobsOf(rule: IWiringRule): string[];
|
|
|
107
164
|
* extractor-backed baseline compute, so all three honour identical semantics. A
|
|
108
165
|
* misconfigured source returns an `error` and no sites (never throws).
|
|
109
166
|
*/
|
|
110
|
-
export declare function collectSourceSites(source: IWiringSource, files: readonly IWiringFileEntry[]): {
|
|
167
|
+
export declare function collectSourceSites(source: IWiringSource, files: readonly IWiringFileEntry[], context?: IExtractContext): {
|
|
111
168
|
sites: readonly IWiringTokenSite[];
|
|
112
169
|
error?: string;
|
|
170
|
+
blankedChars?: number;
|
|
113
171
|
};
|
|
114
172
|
/** Structural validation of a whole rule (both forms), independent of the tree. */
|
|
115
173
|
export declare function validateWiringRule(rule: IWiringRule): string | undefined;
|
|
@@ -127,6 +185,29 @@ export declare function validateWiringRule(rule: IWiringRule): string | undefine
|
|
|
127
185
|
*
|
|
128
186
|
* The `resolve` callback supplies the files for a given rule-side (injected so
|
|
129
187
|
* the engine stays pure / testable — see `runWiring` for the fs-backed wiring).
|
|
188
|
+
*
|
|
189
|
+
* `unreadFor` supplies the files a side's globs matched that the one reader
|
|
190
|
+
* did NOT read. A rule with any in its scope has its coverage replaced by the
|
|
191
|
+
* file record (`readScopeCoverage`), so it is never a pass. When the unread
|
|
192
|
+
* file is on the source side and is why the rule compared nothing, the rule is
|
|
193
|
+
* PARTIAL rather than a (failOnEmpty) skip: it matched a file it could not read.
|
|
194
|
+
*
|
|
195
|
+
* `emptiedBy` names the negations that excluded EVERY file a source's
|
|
196
|
+
* inclusion globs matched (`undefined` when the list was not emptied that
|
|
197
|
+
* way). A source side emptied by its own `!` entries is skipped with THE
|
|
198
|
+
* negation-aware reason (`matched nothing after its own negations: …`), never
|
|
199
|
+
* "0 files matched the source globs" — a file matched, then was excluded.
|
|
200
|
+
*/
|
|
201
|
+
export declare function evaluateWiring(rules: readonly IWiringRule[], resolve: WiringFileResolver, context?: IExtractContext, unreadFor?: (source: IWiringSource) => readonly IUnreadFile[], emptiedBy?: (source: IWiringSource) => readonly IGlobNegation[] | undefined,
|
|
202
|
+
/**
|
|
203
|
+
* Every source's glob units settled with their `expectEmpty` markers
|
|
204
|
+
* (round 13) — `runWiring` supplies it (`sourceLivenessRequest` over the
|
|
205
|
+
* engine's side labels, `settleGlobLists`). A rule whose source side matched
|
|
206
|
+
* nothing is decided from it by THE rule-emptiness settle
|
|
207
|
+
* (`settleRuleEmptiness`): every inclusion glob of the source side
|
|
208
|
+
* intended-empty and no file matched → accepted, never a skip. Its
|
|
209
|
+
* acceptance and non-live units ride on the rule result. Absent: no unit is
|
|
210
|
+
* marked, so an empty rule is a loud skip exactly as before.
|
|
130
211
|
*/
|
|
131
|
-
|
|
212
|
+
ruleLivenessOf?: (rule: IWiringRule) => ISettledUnitLiveness): IWiringReport;
|
|
132
213
|
//# sourceMappingURL=evaluate-wiring.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"evaluate-wiring.d.ts","sourceRoot":"","sources":["../../src/wiring/evaluate-wiring.ts"],"names":[],"mappings":"AAAA,OAAO,
|
|
1
|
+
{"version":3,"file":"evaluate-wiring.d.ts","sourceRoot":"","sources":["../../src/wiring/evaluate-wiring.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,5 +1,8 @@
|
|
|
1
|
-
import { failsWhenEmpty, validateWiringSource, } from '@shrkcrft/core';
|
|
1
|
+
import { failsWhenEmpty, normalizeWiringRule, RuleEmptiness, settleRuleEmptiness, unitStateLists, UnitLivenessState, validateWiringSource, } from '@shrkcrft/core';
|
|
2
|
+
import { settleGlobLists } from "../util/settle-glob-lists.js";
|
|
2
3
|
import { extractTokens, } from "../extract/extract-tokens.js";
|
|
4
|
+
import { describeUnread, mergeReadScopes, readScopeCoverage, readScopeHasUnread, } from "../util/read-scope-coverage.js";
|
|
5
|
+
import { emptiedByNegationsReason } from "../util/negation-cause.js";
|
|
3
6
|
export const WIRING_SCHEMA = 'sharkcraft.wiring/v1';
|
|
4
7
|
/** Normalize the `registered` field (single source or union array) to an array. */
|
|
5
8
|
export function registeredSources(reg) {
|
|
@@ -54,9 +57,14 @@ function hopPairs(rule) {
|
|
|
54
57
|
* extractor-backed baseline compute, so all three honour identical semantics. A
|
|
55
58
|
* misconfigured source returns an `error` and no sites (never throws).
|
|
56
59
|
*/
|
|
57
|
-
export function collectSourceSites(source, files) {
|
|
58
|
-
const res = extractTokens(source, files);
|
|
59
|
-
|
|
60
|
+
export function collectSourceSites(source, files, context = {}) {
|
|
61
|
+
const res = extractTokens(source, files, context);
|
|
62
|
+
if (res.error)
|
|
63
|
+
return { sites: [], error: res.error };
|
|
64
|
+
return {
|
|
65
|
+
sites: res.sites,
|
|
66
|
+
...(res.blankedChars !== undefined ? { blankedChars: res.blankedChars } : {}),
|
|
67
|
+
};
|
|
60
68
|
}
|
|
61
69
|
/** Group key for a token site under `groupBy` (dir = dirname; package = first two segments). */
|
|
62
70
|
function groupKeyOf(path, groupBy) {
|
|
@@ -88,6 +96,67 @@ function renderMessage(template, rule, site) {
|
|
|
88
96
|
.replace(/\{line\}/g, String(site.line))
|
|
89
97
|
.replace(/\{rule\}/g, rule.id);
|
|
90
98
|
}
|
|
99
|
+
/**
|
|
100
|
+
* Registered sites whose membership key the declared side never produced,
|
|
101
|
+
* sorted by key. The ONE derivation behind both parity's `registered-missing`
|
|
102
|
+
* violations and a subset rule's coverage shortfall — two renderings of one set
|
|
103
|
+
* difference, so they cannot disagree.
|
|
104
|
+
*/
|
|
105
|
+
function registeredNotDeclared(fromKeys, unionKeys) {
|
|
106
|
+
return [...unionKeys.keys()]
|
|
107
|
+
.sort()
|
|
108
|
+
.filter((k) => !fromKeys.has(k))
|
|
109
|
+
.map((k) => unionKeys.get(k));
|
|
110
|
+
}
|
|
111
|
+
/** At most this many unexamined labels ride on one coverage record. */
|
|
112
|
+
const COVERAGE_LABEL_CAP = 20;
|
|
113
|
+
/**
|
|
114
|
+
* What one wiring rule examined, for the verdict's coverage guard.
|
|
115
|
+
*
|
|
116
|
+
* Only a classic SUBSET rule can pass over a scope it never examined: its
|
|
117
|
+
* relation is `declared ⊆ registered`, so a registered member the declared
|
|
118
|
+
* selector never produced is never looked at, and the rule weakens silently
|
|
119
|
+
* every time the tree grows past the selector. For it the unit is `registered
|
|
120
|
+
* tokens`; `registeredExtras` (literal ids, or `'allow'`) accepts known extras
|
|
121
|
+
* explicitly. Every other shape examines each declared token it extracted, so
|
|
122
|
+
* its unit is `declared tokens`. A skipped or misconfigured rule examined
|
|
123
|
+
* nothing.
|
|
124
|
+
*/
|
|
125
|
+
function wiringCoverage(rule, outcome) {
|
|
126
|
+
if (outcome.kind === 'error') {
|
|
127
|
+
return { unit: 'declared tokens', expected: 0, examined: 0, reason: 'the rule is misconfigured' };
|
|
128
|
+
}
|
|
129
|
+
if (outcome.kind === 'skipped') {
|
|
130
|
+
return { unit: 'declared tokens', expected: 0, examined: 0, reason: outcome.reason };
|
|
131
|
+
}
|
|
132
|
+
const only = outcome.registeredOnly;
|
|
133
|
+
const subset = rule.mode === undefined || rule.mode === 'subset';
|
|
134
|
+
if (only === undefined || !subset || outcome.registeredCount === 0) {
|
|
135
|
+
return { unit: 'declared tokens', expected: outcome.declaredCount, examined: outcome.declaredCount };
|
|
136
|
+
}
|
|
137
|
+
const extras = rule.registeredExtras;
|
|
138
|
+
const listed = new Set(extras === 'allow' ? [] : (extras ?? []));
|
|
139
|
+
const accepted = new Set(only.filter((s) => extras === 'allow' || listed.has(s.token)));
|
|
140
|
+
const unaccepted = only.filter((s) => !accepted.has(s));
|
|
141
|
+
const named = unaccepted.length > 0 ? unaccepted : [...accepted];
|
|
142
|
+
return {
|
|
143
|
+
unit: 'registered tokens',
|
|
144
|
+
expected: outcome.registeredCount,
|
|
145
|
+
examined: outcome.registeredCount - only.length,
|
|
146
|
+
...(named.length > 0
|
|
147
|
+
? {
|
|
148
|
+
unexamined: named.slice(0, COVERAGE_LABEL_CAP).map((s) => s.token),
|
|
149
|
+
unexaminedTotal: named.length,
|
|
150
|
+
reason: unaccepted.length > 0 && accepted.size > 0
|
|
151
|
+
? `registered with no declared site this selector produces (${accepted.size} more accepted by registeredExtras)`
|
|
152
|
+
: 'registered with no declared site this selector produces',
|
|
153
|
+
}
|
|
154
|
+
: {}),
|
|
155
|
+
...(unaccepted.length === 0 && accepted.size > 0
|
|
156
|
+
? { acceptedBy: extras === 'allow' ? "registeredExtras: 'allow'" : 'registeredExtras' }
|
|
157
|
+
: {}),
|
|
158
|
+
};
|
|
159
|
+
}
|
|
91
160
|
/** Structural validation of a whole rule (both forms), independent of the tree. */
|
|
92
161
|
export function validateWiringRule(rule) {
|
|
93
162
|
const hasChain = Array.isArray(rule.chain) && rule.chain.length > 0;
|
|
@@ -95,6 +164,10 @@ export function validateWiringRule(rule) {
|
|
|
95
164
|
if (hasChain && hasClassic) {
|
|
96
165
|
return '`chain` is mutually exclusive with `declared`/`registered`';
|
|
97
166
|
}
|
|
167
|
+
if (rule.registeredExtras !== undefined &&
|
|
168
|
+
(hasChain || (rule.mode !== undefined && rule.mode !== 'subset'))) {
|
|
169
|
+
return '`registeredExtras` applies only to a classic subset rule (parity reports registered-only tokens as violations; disjoint / chain rules never examine them)';
|
|
170
|
+
}
|
|
98
171
|
if (hasChain) {
|
|
99
172
|
if (rule.chain.length < 2)
|
|
100
173
|
return '`chain` needs at least 2 hops';
|
|
@@ -132,17 +205,69 @@ export function validateWiringRule(rule) {
|
|
|
132
205
|
*
|
|
133
206
|
* The `resolve` callback supplies the files for a given rule-side (injected so
|
|
134
207
|
* the engine stays pure / testable — see `runWiring` for the fs-backed wiring).
|
|
208
|
+
*
|
|
209
|
+
* `unreadFor` supplies the files a side's globs matched that the one reader
|
|
210
|
+
* did NOT read. A rule with any in its scope has its coverage replaced by the
|
|
211
|
+
* file record (`readScopeCoverage`), so it is never a pass. When the unread
|
|
212
|
+
* file is on the source side and is why the rule compared nothing, the rule is
|
|
213
|
+
* PARTIAL rather than a (failOnEmpty) skip: it matched a file it could not read.
|
|
214
|
+
*
|
|
215
|
+
* `emptiedBy` names the negations that excluded EVERY file a source's
|
|
216
|
+
* inclusion globs matched (`undefined` when the list was not emptied that
|
|
217
|
+
* way). A source side emptied by its own `!` entries is skipped with THE
|
|
218
|
+
* negation-aware reason (`matched nothing after its own negations: …`), never
|
|
219
|
+
* "0 files matched the source globs" — a file matched, then was excluded.
|
|
135
220
|
*/
|
|
136
|
-
export function evaluateWiring(rules, resolve
|
|
221
|
+
export function evaluateWiring(rules, resolve, context = {}, unreadFor, emptiedBy,
|
|
222
|
+
/**
|
|
223
|
+
* Every source's glob units settled with their `expectEmpty` markers
|
|
224
|
+
* (round 13) — `runWiring` supplies it (`sourceLivenessRequest` over the
|
|
225
|
+
* engine's side labels, `settleGlobLists`). A rule whose source side matched
|
|
226
|
+
* nothing is decided from it by THE rule-emptiness settle
|
|
227
|
+
* (`settleRuleEmptiness`): every inclusion glob of the source side
|
|
228
|
+
* intended-empty and no file matched → accepted, never a skip. Its
|
|
229
|
+
* acceptance and non-live units ride on the rule result. Absent: no unit is
|
|
230
|
+
* marked, so an empty rule is a loud skip exactly as before.
|
|
231
|
+
*/
|
|
232
|
+
ruleLivenessOf) {
|
|
137
233
|
const ruleResults = [];
|
|
138
234
|
const all = [];
|
|
139
235
|
const diagnostics = [];
|
|
140
236
|
const skipped = [];
|
|
141
237
|
let evaluated = 0;
|
|
238
|
+
let acceptedEmpty = 0;
|
|
142
239
|
let misconfigError = false;
|
|
143
240
|
let misconfigWarn = false;
|
|
144
|
-
for (const
|
|
145
|
-
const severity =
|
|
241
|
+
for (const authored of rules) {
|
|
242
|
+
const severity = authored.severity ?? 'error';
|
|
243
|
+
// The engine entry normalises idempotently (round 13): a loaded rule comes
|
|
244
|
+
// back equal; a hand-built `{ pattern, expectEmpty }` entry is a glob plus
|
|
245
|
+
// a marker; a malformed entry is a misconfigured rule — never a crash.
|
|
246
|
+
const normalized = normalizeWiringRule(authored);
|
|
247
|
+
if (!normalized.ok) {
|
|
248
|
+
const msg = `rule "${authored.id}": ${normalized.error.message}`;
|
|
249
|
+
diagnostics.push(msg);
|
|
250
|
+
if (severity === 'error')
|
|
251
|
+
misconfigError = true;
|
|
252
|
+
else
|
|
253
|
+
misconfigWarn = true;
|
|
254
|
+
ruleResults.push({
|
|
255
|
+
ruleId: authored.id,
|
|
256
|
+
...(authored.description ? { description: authored.description } : {}),
|
|
257
|
+
severity,
|
|
258
|
+
status: 'error',
|
|
259
|
+
declaredCount: 0,
|
|
260
|
+
registeredCount: 0,
|
|
261
|
+
declaredFiles: 0,
|
|
262
|
+
registeredFiles: 0,
|
|
263
|
+
violations: [],
|
|
264
|
+
error: msg,
|
|
265
|
+
coverage: wiringCoverage(authored, { kind: 'error' }),
|
|
266
|
+
});
|
|
267
|
+
evaluated += 1;
|
|
268
|
+
continue;
|
|
269
|
+
}
|
|
270
|
+
const rule = normalized.value;
|
|
146
271
|
const groupBy = rule.groupBy;
|
|
147
272
|
// Validate defensively — a misconfigured rule becomes a diagnostic, never a
|
|
148
273
|
// thrown exception that would crash the gate.
|
|
@@ -165,12 +290,26 @@ export function evaluateWiring(rules, resolve) {
|
|
|
165
290
|
registeredFiles: 0,
|
|
166
291
|
violations: [],
|
|
167
292
|
error: msg,
|
|
293
|
+
coverage: wiringCoverage(rule, { kind: 'error' }),
|
|
168
294
|
});
|
|
169
295
|
// A misconfigured rule attempted to run — count it so its error isn't
|
|
170
296
|
// swallowed by the gate's `evaluated === 0` skip path.
|
|
171
297
|
evaluated += 1;
|
|
172
298
|
continue;
|
|
173
299
|
}
|
|
300
|
+
// The rule's glob units and their `expectEmpty` markers (round 13): the
|
|
301
|
+
// acceptance, the printed unit lines and the non-live units ride on every
|
|
302
|
+
// result below, so `check wiring` accepts a planned glob the way `gates
|
|
303
|
+
// coverage` does.
|
|
304
|
+
const liveness = ruleLivenessOf?.(rule);
|
|
305
|
+
const nonLive = liveness?.units.filter((u) => u.state !== UnitLivenessState.Live) ?? [];
|
|
306
|
+
const unitFields = {
|
|
307
|
+
...(liveness?.acceptance !== undefined ? { unitAcceptance: liveness.acceptance } : {}),
|
|
308
|
+
...(liveness !== undefined && liveness.dead.length + liveness.intendedEmpty.length + liveness.wentLive.length > 0
|
|
309
|
+
? { units: unitStateLists(liveness) }
|
|
310
|
+
: {}),
|
|
311
|
+
...(nonLive.length > 0 ? { unitLiveness: nonLive } : {}),
|
|
312
|
+
};
|
|
174
313
|
const pairs = hopPairs(rule);
|
|
175
314
|
const violations = [];
|
|
176
315
|
const hops = [];
|
|
@@ -179,17 +318,32 @@ export function evaluateWiring(rules, resolve) {
|
|
|
179
318
|
let sinkFiles = 0;
|
|
180
319
|
let sinkCount = 0;
|
|
181
320
|
let emptySink = false;
|
|
321
|
+
let sinkHint;
|
|
322
|
+
// Registered sites no declared site produced — classic (one-hop) rules only.
|
|
323
|
+
let registeredOnly;
|
|
324
|
+
// Every file any side READ (for the read scope), and the last hop's sinks'
|
|
325
|
+
// unread files (to explain an empty sink that was simply never read).
|
|
326
|
+
const readPaths = new Set();
|
|
327
|
+
let sinkUnread = [];
|
|
182
328
|
for (const [hopIndex, pair] of pairs.entries()) {
|
|
183
329
|
const fromFiles = resolve(pair.from);
|
|
184
|
-
const
|
|
330
|
+
for (const f of fromFiles)
|
|
331
|
+
readPaths.add(f.path);
|
|
332
|
+
const fromSites = extractTokens(pair.from, fromFiles, context).sites;
|
|
185
333
|
const fromKeys = firstSites(fromSites, groupBy);
|
|
186
334
|
// Each sink kept separate so `intersection` can require membership in ALL.
|
|
187
335
|
const sinkKeySets = [];
|
|
188
336
|
let hopSinkFiles = 0;
|
|
337
|
+
sinkUnread = unreadFor ? pair.to.flatMap((s) => unreadFor(s)) : [];
|
|
189
338
|
for (const sink of pair.to) {
|
|
190
339
|
const files = resolve(sink);
|
|
340
|
+
for (const f of files)
|
|
341
|
+
readPaths.add(f.path);
|
|
191
342
|
hopSinkFiles += files.length;
|
|
192
|
-
|
|
343
|
+
const sinkRes = extractTokens(sink, files, context);
|
|
344
|
+
if (sinkRes.hint && sinkHint === undefined)
|
|
345
|
+
sinkHint = sinkRes.hint;
|
|
346
|
+
sinkKeySets.push(firstSites(sinkRes.sites, groupBy));
|
|
193
347
|
}
|
|
194
348
|
const unionKeys = new Map();
|
|
195
349
|
for (const set of sinkKeySets) {
|
|
@@ -248,12 +402,16 @@ export function evaluateWiring(rules, resolve) {
|
|
|
248
402
|
...(declaredHint ? { hint: declaredHint } : {}),
|
|
249
403
|
});
|
|
250
404
|
}
|
|
405
|
+
// ONE set difference, two renderings: parity FAILS on each registered
|
|
406
|
+
// token no declared site produced; subset records them as unexamined
|
|
407
|
+
// (its coverage shortfall). Only a classic one-hop rule carries the
|
|
408
|
+
// set — a chain hop keeps the declared-token view.
|
|
409
|
+
const regOnly = registeredNotDeclared(fromKeys, unionKeys);
|
|
410
|
+
if (pairs.length === 1)
|
|
411
|
+
registeredOnly = regOnly;
|
|
251
412
|
if (rule.mode === 'parity') {
|
|
252
413
|
const registeredHint = rule.hintRegisteredMissing ?? rule.hint;
|
|
253
|
-
for (const
|
|
254
|
-
if (fromKeys.has(k))
|
|
255
|
-
continue;
|
|
256
|
-
const site = unionKeys.get(k);
|
|
414
|
+
for (const site of regOnly) {
|
|
257
415
|
violations.push({
|
|
258
416
|
ruleId: rule.id,
|
|
259
417
|
token: site.token,
|
|
@@ -269,16 +427,104 @@ export function evaluateWiring(rules, resolve) {
|
|
|
269
427
|
}
|
|
270
428
|
hops.push({ index: hopIndex, fromCount: fromKeys.size, toCount: unionKeys.size, missing });
|
|
271
429
|
}
|
|
430
|
+
// What the rule's walk covered: every file any side read, and every file
|
|
431
|
+
// any side's globs matched that the reader did not read.
|
|
432
|
+
const scope = unreadFor
|
|
433
|
+
? mergeReadScopes(readPaths.size, wiringSourcesOf(rule).map((s) => unreadFor(s)))
|
|
434
|
+
: undefined;
|
|
435
|
+
const unreadField = scope !== undefined && scope.unread.length > 0 ? { unread: scope.unread } : {};
|
|
436
|
+
// An empty sink whose files were never READ is not a stale glob — say so,
|
|
437
|
+
// or the reader is sent to fix a selector that is fine.
|
|
438
|
+
if (emptySink && sinkUnread.length > 0) {
|
|
439
|
+
sinkHint = `the registered side ${describeUnread(sinkUnread)} was never read, so a token registered there cannot be seen`;
|
|
440
|
+
}
|
|
272
441
|
// Loud skip: the SOURCE side checked nothing. Deliberately NOT triggered by
|
|
273
442
|
// an empty sink — that is a real failure, flagged via `emptySink` instead.
|
|
443
|
+
// A source its OWN negations emptied is worded as such (round 12 review,
|
|
444
|
+
// R12-DOC-2), exactly as `gates coverage` and `policy-lint` word it.
|
|
445
|
+
const firstSource = pairs[0]?.from;
|
|
446
|
+
const emptiedByOwn = sourceFiles === 0 && firstSource ? emptiedBy?.(firstSource) : undefined;
|
|
274
447
|
const skipReason = sourceFiles === 0
|
|
275
|
-
?
|
|
448
|
+
? emptiedByOwn !== undefined && emptiedByOwn.length > 0
|
|
449
|
+
? emptiedByNegationsReason(emptiedByOwn)
|
|
450
|
+
: '0 files matched the source globs'
|
|
276
451
|
: sourceCount === 0
|
|
277
452
|
? '0 ids extracted from the source side'
|
|
278
453
|
: undefined;
|
|
454
|
+
const sourceSide = pairs[0]?.from;
|
|
455
|
+
const sourceUnread = sourceSide && unreadFor ? unreadFor(sourceSide) : [];
|
|
456
|
+
if (skipReason !== undefined && readScopeHasUnread({ read: sourceFiles, unread: sourceUnread })) {
|
|
457
|
+
// The source side's zero comes from a file the reader could not read,
|
|
458
|
+
// not from a stale selector: PARTIAL (its coverage names the file),
|
|
459
|
+
// never failOnEmpty's failure and never a pass. Nothing was compared,
|
|
460
|
+
// so no violation derived from the incomplete side is reported.
|
|
461
|
+
ruleResults.push({
|
|
462
|
+
ruleId: rule.id,
|
|
463
|
+
...(rule.description ? { description: rule.description } : {}),
|
|
464
|
+
severity,
|
|
465
|
+
status: 'passed',
|
|
466
|
+
declaredCount: sourceCount,
|
|
467
|
+
registeredCount: sinkCount,
|
|
468
|
+
declaredFiles: sourceFiles,
|
|
469
|
+
registeredFiles: sinkFiles,
|
|
470
|
+
violations: [],
|
|
471
|
+
...(pairs.length > 1 ? { hops } : {}),
|
|
472
|
+
...unreadField,
|
|
473
|
+
...unitFields,
|
|
474
|
+
coverage: readScopeCoverage(wiringCoverage(rule, { kind: 'skipped', reason: skipReason }), scope),
|
|
475
|
+
});
|
|
476
|
+
evaluated += 1;
|
|
477
|
+
continue;
|
|
478
|
+
}
|
|
279
479
|
if (skipReason !== undefined) {
|
|
280
|
-
|
|
281
|
-
|
|
480
|
+
// THE rule-emptiness settle (round 13, `settleRuleEmptiness`): every
|
|
481
|
+
// inclusion glob of the SOURCE side marked `expectEmpty` and no file
|
|
482
|
+
// matched → the empty result is the intended one, accepted and printed.
|
|
483
|
+
// Anything else is the loud skip it always was (failOnEmpty's 1, else 2):
|
|
484
|
+
// `0 ids extracted` from live files is never assertable.
|
|
485
|
+
const primaryLabel = (rule.chain?.length ?? 0) > 0 ? 'chain[0]' : 'declared';
|
|
486
|
+
const emptied = emptiedByOwn !== undefined && emptiedByOwn.length > 0;
|
|
487
|
+
const emptiness = settleRuleEmptiness({
|
|
488
|
+
subject: rule.id,
|
|
489
|
+
unitLabel: 'declared tokens',
|
|
490
|
+
filesMatched: sourceFiles,
|
|
491
|
+
unitsMatched: sourceCount,
|
|
492
|
+
unread: false,
|
|
493
|
+
emptiedByNegations: emptied,
|
|
494
|
+
...(emptied ? { emptiedReason: skipReason } : {}),
|
|
495
|
+
liveness: liveness ?? settleGlobLists({ subject: rule.id, lists: [], marks: [] }),
|
|
496
|
+
primaryLists: [`${primaryLabel}.files`],
|
|
497
|
+
failOnEmpty: failsWhenEmpty(rule),
|
|
498
|
+
noFilesReason: '0 files matched the source globs',
|
|
499
|
+
noUnitsReason: '0 ids extracted from the source side',
|
|
500
|
+
});
|
|
501
|
+
if (emptiness.state === RuleEmptiness.IntendedEmpty && emptiness.coverage !== undefined) {
|
|
502
|
+
ruleResults.push({
|
|
503
|
+
ruleId: rule.id,
|
|
504
|
+
...(rule.description ? { description: rule.description } : {}),
|
|
505
|
+
severity,
|
|
506
|
+
status: 'passed',
|
|
507
|
+
declaredCount: sourceCount,
|
|
508
|
+
registeredCount: sinkCount,
|
|
509
|
+
declaredFiles: sourceFiles,
|
|
510
|
+
registeredFiles: sinkFiles,
|
|
511
|
+
violations: [],
|
|
512
|
+
...(pairs.length > 1 ? { hops } : {}),
|
|
513
|
+
...unreadField,
|
|
514
|
+
...unitFields,
|
|
515
|
+
// The acceptance IS the rule's coverage (the same record, folded once).
|
|
516
|
+
coverage: emptiness.coverage,
|
|
517
|
+
});
|
|
518
|
+
// Counted in `evaluated` (the verdict path's "nothing ran" guard must
|
|
519
|
+
// never read an accepted plan as a skip) AND in `acceptedEmpty`, which
|
|
520
|
+
// a renderer subtracts: `N evaluated, M accepted as intended-empty`.
|
|
521
|
+
evaluated += 1;
|
|
522
|
+
acceptedEmpty += 1;
|
|
523
|
+
continue;
|
|
524
|
+
}
|
|
525
|
+
const reason = emptiness.skipReason ?? skipReason;
|
|
526
|
+
const failed = emptiness.fails;
|
|
527
|
+
skipped.push({ ruleId: rule.id, reason, failed, severity });
|
|
282
528
|
if (failed) {
|
|
283
529
|
if (severity === 'error')
|
|
284
530
|
misconfigError = true;
|
|
@@ -296,6 +542,9 @@ export function evaluateWiring(rules, resolve) {
|
|
|
296
542
|
registeredFiles: sinkFiles,
|
|
297
543
|
violations: [],
|
|
298
544
|
...(pairs.length > 1 ? { hops } : {}),
|
|
545
|
+
...unreadField,
|
|
546
|
+
...unitFields,
|
|
547
|
+
coverage: readScopeCoverage(wiringCoverage(rule, { kind: 'skipped', reason }), scope),
|
|
299
548
|
});
|
|
300
549
|
continue;
|
|
301
550
|
}
|
|
@@ -310,7 +559,17 @@ export function evaluateWiring(rules, resolve) {
|
|
|
310
559
|
registeredFiles: sinkFiles,
|
|
311
560
|
violations,
|
|
312
561
|
...(emptySink ? { emptySink: true } : {}),
|
|
562
|
+
...(emptySink && sinkHint !== undefined ? { sinkHint } : {}),
|
|
313
563
|
...(pairs.length > 1 ? { hops } : {}),
|
|
564
|
+
...(registeredOnly !== undefined ? { registeredOnly } : {}),
|
|
565
|
+
...unreadField,
|
|
566
|
+
...unitFields,
|
|
567
|
+
coverage: readScopeCoverage(wiringCoverage(rule, {
|
|
568
|
+
kind: 'ran',
|
|
569
|
+
declaredCount: sourceCount,
|
|
570
|
+
registeredCount: sinkCount,
|
|
571
|
+
...(registeredOnly !== undefined ? { registeredOnly } : {}),
|
|
572
|
+
}), scope),
|
|
314
573
|
});
|
|
315
574
|
all.push(...violations);
|
|
316
575
|
evaluated += 1;
|
|
@@ -326,6 +585,7 @@ export function evaluateWiring(rules, resolve) {
|
|
|
326
585
|
diagnostics,
|
|
327
586
|
skipped,
|
|
328
587
|
evaluated,
|
|
588
|
+
acceptedEmpty,
|
|
329
589
|
verdict: hasError ? 'errors' : hasWarn ? 'warnings' : 'pass',
|
|
330
590
|
};
|
|
331
591
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { type IUnitLiveness, type IUnitStateLists, type IVerdictCoverage, type IWiringRule, type ScanZone } from '@shrkcrft/core';
|
|
2
2
|
import { type IWiringHopResult, type IWiringTokenSite } from './evaluate-wiring.js';
|
|
3
3
|
export declare const WIRING_EXPLAIN_SCHEMA: "sharkcraft.wiring-explain/v1";
|
|
4
4
|
/** One side (declared or registered) of a wiring rule, as extracted from the tree. */
|
|
@@ -11,6 +11,24 @@ export interface IWiringSideExplain {
|
|
|
11
11
|
readonly filesScanned: number;
|
|
12
12
|
/** Misconfiguration (bad regex / no capture group / bad source), if any. */
|
|
13
13
|
readonly error?: string;
|
|
14
|
+
/**
|
|
15
|
+
* Named extractor this side resolved from, when it used one. Surfaced so an
|
|
16
|
+
* author reading an explain can tell a SHARED selector from a local copy of
|
|
17
|
+
* it — the two look identical in the extracted ids, and only one of them is
|
|
18
|
+
* guaranteed to stay in step with the other planes.
|
|
19
|
+
*/
|
|
20
|
+
readonly viaExtractor?: string;
|
|
21
|
+
/** The lexical zone this side extracted from, when it is not the default `all`. */
|
|
22
|
+
readonly scan?: ScanZone;
|
|
23
|
+
/**
|
|
24
|
+
* Characters a non-`all` `scan` blanked before extraction.
|
|
25
|
+
*
|
|
26
|
+
* A zone is the one setting that legitimately makes a rule match LESS while
|
|
27
|
+
* still reading green, so the amount removed is shown rather than assumed: a
|
|
28
|
+
* suspicious drop separates "the pattern was reading prose" from "the glob
|
|
29
|
+
* went stale".
|
|
30
|
+
*/
|
|
31
|
+
readonly blankedChars?: number;
|
|
14
32
|
}
|
|
15
33
|
/**
|
|
16
34
|
* The full intermediate output of evaluating ONE wiring rule against the live
|
|
@@ -36,11 +54,44 @@ export interface IWiringExplain {
|
|
|
36
54
|
readonly overlap: readonly IWiringTokenSite[];
|
|
37
55
|
/** Per-hop breakdown when the rule is a multi-hop `chain`. */
|
|
38
56
|
readonly hops?: readonly IWiringHopResult[];
|
|
39
|
-
/**
|
|
40
|
-
|
|
57
|
+
/**
|
|
58
|
+
* `passed` / `partial` / `failed` / `skipped` / `error` — skipped is never a
|
|
59
|
+
* pass, and neither is `partial`: a rule the engine reported `passed` whose
|
|
60
|
+
* {@link coverage} has a shortfall. Derived by core's `settleRuleStatus`, the
|
|
61
|
+
* same function the gate envelope uses, so `gates explain` and `gates check`
|
|
62
|
+
* report the same status for the same rule.
|
|
63
|
+
*/
|
|
64
|
+
readonly status: 'passed' | 'partial' | 'failed' | 'skipped' | 'error';
|
|
41
65
|
/** Why the rule checked nothing, when it was skipped. */
|
|
42
66
|
readonly skipReason?: string;
|
|
43
|
-
|
|
67
|
+
/**
|
|
68
|
+
* The engine's verdict, settled against {@link coverage}: a `pass` or
|
|
69
|
+
* `warnings` verdict over a coverage shortfall is `not-verified` — exactly the
|
|
70
|
+
* `0 → 2` the gate envelope applies. `errors` is never changed.
|
|
71
|
+
*/
|
|
72
|
+
readonly verdict: 'pass' | 'errors' | 'warnings' | 'not-verified';
|
|
73
|
+
/** What the rule examined against what it was asked to (the engine's own coverage). */
|
|
74
|
+
readonly coverage: IVerdictCoverage;
|
|
75
|
+
/** The coverage gap that vetoes a clean verdict, when there is one (`coverageShortfall`). */
|
|
76
|
+
readonly shortfall?: string;
|
|
77
|
+
/** A gap the rule's own config waived (`registeredExtras`), printed — never silent. */
|
|
78
|
+
readonly acceptance?: string;
|
|
79
|
+
/**
|
|
80
|
+
* The rule's `expectEmpty` acceptance (round 13) — the engine's settle record
|
|
81
|
+
* B, read off the rule result exactly as `check wiring` carries it — so a
|
|
82
|
+
* verdict settled over this explain (`settleWiringExplain`, `gates try`)
|
|
83
|
+
* folds it through `ruleVerdictRecords` and prints it. When the rule is
|
|
84
|
+
* intended-empty it IS {@link coverage}.
|
|
85
|
+
*/
|
|
86
|
+
readonly unitAcceptance?: IVerdictCoverage;
|
|
87
|
+
/** The rule's dead / intended-empty / went-live glob units as printed lines. */
|
|
88
|
+
readonly units?: IUnitStateLists;
|
|
89
|
+
/**
|
|
90
|
+
* The rule's non-live glob units as the engine settled them (round 13
|
|
91
|
+
* review, additive) — what the explain renderers' went-live / dead block
|
|
92
|
+
* reads, so explain says what `check wiring` and `gates coverage` say.
|
|
93
|
+
*/
|
|
94
|
+
readonly unitLiveness?: readonly IUnitLiveness[];
|
|
44
95
|
/** Rule-level misconfiguration messages (engine degrades gracefully). */
|
|
45
96
|
readonly diagnostics: readonly string[];
|
|
46
97
|
}
|
|
@@ -48,14 +99,5 @@ export interface IExplainWiringOptions {
|
|
|
48
99
|
/** Project-relative directories to prune from the walk. */
|
|
49
100
|
readonly excludeDirs?: readonly string[];
|
|
50
101
|
}
|
|
51
|
-
|
|
52
|
-
* Dry-run a single wiring rule against the live tree and return what each side
|
|
53
|
-
* extracted (declared set, registered set, the set-difference, the verdict) —
|
|
54
|
-
* WITHOUT writing config. Powers `wiring explain <ruleId>`, `wiring test
|
|
55
|
-
* <candidate>`, `gates explain <id>`, and `check wiring --explain <ruleId>`: the
|
|
56
|
-
* author can SEE the cross-file set-difference the gate computes before
|
|
57
|
-
* committing a rule. The diff/verdict reuse {@link evaluateWiring} so they match
|
|
58
|
-
* the gate exactly (incl. `groupBy` membership). Never throws.
|
|
59
|
-
*/
|
|
60
|
-
export declare function explainWiring(projectRoot: string, rule: IWiringRule, options?: IExplainWiringOptions): IWiringExplain;
|
|
102
|
+
export declare function explainWiring(projectRoot: string, authored: IWiringRule, options?: IExplainWiringOptions): IWiringExplain;
|
|
61
103
|
//# sourceMappingURL=explain-wiring.d.ts.map
|