@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.
Files changed (229) hide show
  1. package/dist/baseline/compute-baseline.d.ts +8 -0
  2. package/dist/baseline/compute-baseline.d.ts.map +1 -1
  3. package/dist/baseline/compute-baseline.js +10 -8
  4. package/dist/baseline/diff-baseline.d.ts +28 -0
  5. package/dist/baseline/diff-baseline.d.ts.map +1 -1
  6. package/dist/baseline/diff-baseline.js +29 -0
  7. package/dist/evaluate/boundary-unit-finding.d.ts +10 -0
  8. package/dist/evaluate/boundary-unit-finding.d.ts.map +1 -0
  9. package/dist/evaluate/boundary-unit-finding.js +21 -0
  10. package/dist/evaluate/boundary-unit-kind.d.ts +7 -0
  11. package/dist/evaluate/boundary-unit-kind.d.ts.map +1 -0
  12. package/dist/evaluate/boundary-unit-kind.js +14 -0
  13. package/dist/evaluate/evaluate-boundaries.d.ts +230 -3
  14. package/dist/evaluate/evaluate-boundaries.d.ts.map +1 -1
  15. package/dist/evaluate/evaluate-boundaries.js +481 -53
  16. package/dist/evaluate/i-boundary-rule-settle-input.d.ts +27 -0
  17. package/dist/evaluate/i-boundary-rule-settle-input.d.ts.map +1 -0
  18. package/dist/evaluate/i-boundary-rule-settle-input.js +1 -0
  19. package/dist/evaluate/i-boundary-rule-settlement.d.ts +23 -0
  20. package/dist/evaluate/i-boundary-rule-settlement.d.ts.map +1 -0
  21. package/dist/evaluate/i-boundary-rule-settlement.js +1 -0
  22. package/dist/evaluate/i-boundary-unit-finding.d.ts +22 -0
  23. package/dist/evaluate/i-boundary-unit-finding.d.ts.map +1 -0
  24. package/dist/evaluate/i-boundary-unit-finding.js +1 -0
  25. package/dist/evaluate/settle-boundary-rule.d.ts +23 -0
  26. package/dist/evaluate/settle-boundary-rule.d.ts.map +1 -0
  27. package/dist/evaluate/settle-boundary-rule.js +73 -0
  28. package/dist/evaluate/with-boundary-rule-settlement.d.ts +11 -0
  29. package/dist/evaluate/with-boundary-rule-settlement.d.ts.map +1 -0
  30. package/dist/evaluate/with-boundary-rule-settlement.js +44 -0
  31. package/dist/extract/code-zones.d.ts +95 -3
  32. package/dist/extract/code-zones.d.ts.map +1 -1
  33. package/dist/extract/code-zones.js +313 -4
  34. package/dist/extract/extract-tokens.d.ts +49 -1
  35. package/dist/extract/extract-tokens.d.ts.map +1 -1
  36. package/dist/extract/extract-tokens.js +283 -25
  37. package/dist/extract/i-labeled-source.d.ts +12 -0
  38. package/dist/extract/i-labeled-source.d.ts.map +1 -0
  39. package/dist/extract/i-labeled-source.js +1 -0
  40. package/dist/extract/import-edges.d.ts +50 -0
  41. package/dist/extract/import-edges.d.ts.map +1 -0
  42. package/dist/extract/import-edges.js +170 -0
  43. package/dist/extract/inspect-source.d.ts +38 -3
  44. package/dist/extract/inspect-source.d.ts.map +1 -1
  45. package/dist/extract/inspect-source.js +56 -8
  46. package/dist/extract/parse-imports.d.ts +80 -0
  47. package/dist/extract/parse-imports.d.ts.map +1 -0
  48. package/dist/extract/parse-imports.js +254 -0
  49. package/dist/extract/scan-literals.d.ts +12 -1
  50. package/dist/extract/scan-literals.d.ts.map +1 -1
  51. package/dist/extract/scan-literals.js +15 -1
  52. package/dist/extract/source-liveness-request.d.ts +24 -0
  53. package/dist/extract/source-liveness-request.d.ts.map +1 -0
  54. package/dist/extract/source-liveness-request.js +45 -0
  55. package/dist/generated/check-provenance.d.ts +20 -2
  56. package/dist/generated/check-provenance.d.ts.map +1 -1
  57. package/dist/generated/check-provenance.js +27 -6
  58. package/dist/generated/read-regen-tree.d.ts +16 -0
  59. package/dist/generated/read-regen-tree.d.ts.map +1 -0
  60. package/dist/generated/read-regen-tree.js +61 -0
  61. package/dist/generated/scan-generated.d.ts +57 -3
  62. package/dist/generated/scan-generated.d.ts.map +1 -1
  63. package/dist/generated/scan-generated.js +111 -11
  64. package/dist/index.d.ts +51 -0
  65. package/dist/index.d.ts.map +1 -1
  66. package/dist/index.js +54 -0
  67. package/dist/model/boundary-intended-empty-line.d.ts +10 -0
  68. package/dist/model/boundary-intended-empty-line.d.ts.map +1 -0
  69. package/dist/model/boundary-intended-empty-line.js +11 -0
  70. package/dist/model/boundary-markable-list.d.ts +15 -0
  71. package/dist/model/boundary-markable-list.d.ts.map +1 -0
  72. package/dist/model/boundary-markable-list.js +15 -0
  73. package/dist/model/boundary-rule-input-keys.d.ts +11 -0
  74. package/dist/model/boundary-rule-input-keys.d.ts.map +1 -0
  75. package/dist/model/boundary-rule-input-keys.js +29 -0
  76. package/dist/model/boundary-rule-input.d.ts +23 -0
  77. package/dist/model/boundary-rule-input.d.ts.map +1 -0
  78. package/dist/model/boundary-rule-input.js +1 -0
  79. package/dist/model/boundary-rule-key-problems.d.ts +11 -0
  80. package/dist/model/boundary-rule-key-problems.d.ts.map +1 -0
  81. package/dist/model/boundary-rule-key-problems.js +36 -0
  82. package/dist/model/boundary-rule-marker-problems.d.ts +26 -0
  83. package/dist/model/boundary-rule-marker-problems.d.ts.map +1 -0
  84. package/dist/model/boundary-rule-marker-problems.js +94 -0
  85. package/dist/model/boundary-rule-scope.d.ts +88 -0
  86. package/dist/model/boundary-rule-scope.d.ts.map +1 -0
  87. package/dist/model/boundary-rule-scope.js +175 -0
  88. package/dist/model/boundary-rule.d.ts +120 -5
  89. package/dist/model/boundary-rule.d.ts.map +1 -1
  90. package/dist/model/boundary-rule.js +148 -1
  91. package/dist/model/boundary-unit-problem-issue.d.ts +9 -0
  92. package/dist/model/boundary-unit-problem-issue.d.ts.map +1 -0
  93. package/dist/model/boundary-unit-problem-issue.js +16 -0
  94. package/dist/model/normalize-boundary-rule.d.ts +21 -0
  95. package/dist/model/normalize-boundary-rule.d.ts.map +1 -0
  96. package/dist/model/normalize-boundary-rule.js +62 -0
  97. package/dist/policy/evaluate-policy.d.ts +80 -3
  98. package/dist/policy/evaluate-policy.d.ts.map +1 -1
  99. package/dist/policy/evaluate-policy.js +152 -19
  100. package/dist/policy/i-policy-rule-liveness.d.ts +15 -0
  101. package/dist/policy/i-policy-rule-liveness.d.ts.map +1 -0
  102. package/dist/policy/i-policy-rule-liveness.js +1 -0
  103. package/dist/policy/run-policy.d.ts +1 -1
  104. package/dist/policy/run-policy.d.ts.map +1 -1
  105. package/dist/policy/run-policy.js +171 -21
  106. package/dist/registry/load-boundary-rules.d.ts +29 -1
  107. package/dist/registry/load-boundary-rules.d.ts.map +1 -1
  108. package/dist/registry/load-boundary-rules.js +38 -8
  109. package/dist/scan/glob.d.ts +90 -0
  110. package/dist/scan/glob.d.ts.map +1 -1
  111. package/dist/scan/glob.js +205 -0
  112. package/dist/scan/i-glob-unit-measure.d.ts +22 -0
  113. package/dist/scan/i-glob-unit-measure.d.ts.map +1 -0
  114. package/dist/scan/i-glob-unit-measure.js +1 -0
  115. package/dist/scan/import-pattern.d.ts +109 -0
  116. package/dist/scan/import-pattern.d.ts.map +1 -0
  117. package/dist/scan/import-pattern.js +191 -0
  118. package/dist/scan/node-builtin-package-names.d.ts +13 -0
  119. package/dist/scan/node-builtin-package-names.d.ts.map +1 -0
  120. package/dist/scan/node-builtin-package-names.js +25 -0
  121. package/dist/scan/scan-imports.d.ts +36 -3
  122. package/dist/scan/scan-imports.d.ts.map +1 -1
  123. package/dist/scan/scan-imports.js +114 -45
  124. package/dist/util/blank-run-hazard-finding.d.ts +34 -0
  125. package/dist/util/blank-run-hazard-finding.d.ts.map +1 -0
  126. package/dist/util/blank-run-hazard-finding.js +1 -0
  127. package/dist/util/blank-run-hazard.d.ts +29 -0
  128. package/dist/util/blank-run-hazard.d.ts.map +1 -0
  129. package/dist/util/blank-run-hazard.js +450 -0
  130. package/dist/util/dead-glob-units.d.ts +46 -0
  131. package/dist/util/dead-glob-units.d.ts.map +1 -0
  132. package/dist/util/dead-glob-units.js +116 -0
  133. package/dist/util/glob-list-liveness-input.d.ts +31 -0
  134. package/dist/util/glob-list-liveness-input.d.ts.map +1 -0
  135. package/dist/util/glob-list-liveness-input.js +65 -0
  136. package/dist/util/i-dead-glob-unit.d.ts +24 -0
  137. package/dist/util/i-dead-glob-unit.d.ts.map +1 -0
  138. package/dist/util/i-dead-glob-unit.js +1 -0
  139. package/dist/util/i-glob-list-units.d.ts +25 -0
  140. package/dist/util/i-glob-list-units.d.ts.map +1 -0
  141. package/dist/util/i-glob-list-units.js +1 -0
  142. package/dist/util/i-glob-liveness-list.d.ts +23 -0
  143. package/dist/util/i-glob-liveness-list.d.ts.map +1 -0
  144. package/dist/util/i-glob-liveness-list.js +1 -0
  145. package/dist/util/i-glob-liveness-request.d.ts +11 -0
  146. package/dist/util/i-glob-liveness-request.d.ts.map +1 -0
  147. package/dist/util/i-glob-liveness-request.js +1 -0
  148. package/dist/util/i-glob-negation.d.ts +13 -0
  149. package/dist/util/i-glob-negation.d.ts.map +1 -0
  150. package/dist/util/i-glob-negation.js +1 -0
  151. package/dist/util/matched-files.d.ts +22 -0
  152. package/dist/util/matched-files.d.ts.map +1 -0
  153. package/dist/util/matched-files.js +1 -0
  154. package/dist/util/negation-cause.d.ts +15 -0
  155. package/dist/util/negation-cause.d.ts.map +1 -0
  156. package/dist/util/negation-cause.js +21 -0
  157. package/dist/util/plane-scan-exclude-dirs.d.ts +17 -0
  158. package/dist/util/plane-scan-exclude-dirs.d.ts.map +1 -0
  159. package/dist/util/plane-scan-exclude-dirs.js +22 -0
  160. package/dist/util/read-glob-list-liveness.d.ts +20 -0
  161. package/dist/util/read-glob-list-liveness.d.ts.map +1 -0
  162. package/dist/util/read-glob-list-liveness.js +20 -0
  163. package/dist/util/read-scope-coverage.d.ts +90 -0
  164. package/dist/util/read-scope-coverage.d.ts.map +1 -0
  165. package/dist/util/read-scope-coverage.js +173 -0
  166. package/dist/util/read-scope.d.ts +14 -0
  167. package/dist/util/read-scope.d.ts.map +1 -0
  168. package/dist/util/read-scope.js +1 -0
  169. package/dist/util/read-selected-files.d.ts +16 -0
  170. package/dist/util/read-selected-files.d.ts.map +1 -0
  171. package/dist/util/read-selected-files.js +25 -0
  172. package/dist/util/settle-glob-lists.d.ts +12 -0
  173. package/dist/util/settle-glob-lists.d.ts.map +1 -0
  174. package/dist/util/settle-glob-lists.js +13 -0
  175. package/dist/util/unread-file-reason.d.ts +26 -0
  176. package/dist/util/unread-file-reason.d.ts.map +1 -0
  177. package/dist/util/unread-file-reason.js +26 -0
  178. package/dist/util/unread-file.d.ts +10 -0
  179. package/dist/util/unread-file.d.ts.map +1 -0
  180. package/dist/util/unread-file.js +1 -0
  181. package/dist/util/walk-files.d.ts +90 -7
  182. package/dist/util/walk-files.d.ts.map +1 -1
  183. package/dist/util/walk-files.js +206 -19
  184. package/dist/wiring/evaluate-wiring.d.ts +88 -7
  185. package/dist/wiring/evaluate-wiring.d.ts.map +1 -1
  186. package/dist/wiring/evaluate-wiring.js +276 -16
  187. package/dist/wiring/explain-wiring.d.ts +56 -14
  188. package/dist/wiring/explain-wiring.d.ts.map +1 -1
  189. package/dist/wiring/explain-wiring.js +136 -10
  190. package/dist/wiring/i-idiom-role-coverage.d.ts +24 -0
  191. package/dist/wiring/i-idiom-role-coverage.d.ts.map +1 -0
  192. package/dist/wiring/i-idiom-role-coverage.js +1 -0
  193. package/dist/wiring/i-registration-query-verdict.d.ts +38 -0
  194. package/dist/wiring/i-registration-query-verdict.d.ts.map +1 -0
  195. package/dist/wiring/i-registration-query-verdict.js +1 -0
  196. package/dist/wiring/i-registration-roles.d.ts +71 -0
  197. package/dist/wiring/i-registration-roles.d.ts.map +1 -0
  198. package/dist/wiring/i-registration-roles.js +1 -0
  199. package/dist/wiring/measure-idiom-role-coverage.d.ts +12 -0
  200. package/dist/wiring/measure-idiom-role-coverage.d.ts.map +1 -0
  201. package/dist/wiring/measure-idiom-role-coverage.js +20 -0
  202. package/dist/wiring/measure-registration-roles.d.ts +25 -0
  203. package/dist/wiring/measure-registration-roles.d.ts.map +1 -0
  204. package/dist/wiring/measure-registration-roles.js +124 -0
  205. package/dist/wiring/plan-wiring-fix.d.ts +16 -1
  206. package/dist/wiring/plan-wiring-fix.d.ts.map +1 -1
  207. package/dist/wiring/plan-wiring-fix.js +177 -4
  208. package/dist/wiring/registration-graph.d.ts +13 -1
  209. package/dist/wiring/registration-graph.d.ts.map +1 -1
  210. package/dist/wiring/registration-graph.js +22 -6
  211. package/dist/wiring/registration-query-verdict.d.ts +23 -0
  212. package/dist/wiring/registration-query-verdict.d.ts.map +1 -0
  213. package/dist/wiring/registration-query-verdict.js +118 -0
  214. package/dist/wiring/registry-query.d.ts +9 -1
  215. package/dist/wiring/registry-query.d.ts.map +1 -1
  216. package/dist/wiring/registry-query.js +17 -6
  217. package/dist/wiring/scan-wiring-files.d.ts +5 -3
  218. package/dist/wiring/scan-wiring-files.d.ts.map +1 -1
  219. package/dist/wiring/scan-wiring-files.js +55 -14
  220. package/dist/wiring/sink-imports.d.ts +95 -0
  221. package/dist/wiring/sink-imports.d.ts.map +1 -0
  222. package/dist/wiring/sink-imports.js +124 -0
  223. package/dist/wiring/trace-literal.d.ts +6 -0
  224. package/dist/wiring/trace-literal.d.ts.map +1 -1
  225. package/dist/wiring/trace-literal.js +5 -2
  226. package/dist/wiring/wiring-labeled-sources.d.ts +12 -0
  227. package/dist/wiring/wiring-labeled-sources.d.ts.map +1 -0
  228. package/dist/wiring/wiring-labeled-sources.js +20 -0
  229. 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 actually ran a comparison. A rule whose source side
84
- * matched 0 files or extracted 0 ids is NOT evaluated (see {@link skipped}).
85
- * Misconfigured rules count as evaluated so their error is not swallowed.
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
- export declare function evaluateWiring(rules: readonly IWiringRule[], resolve: WiringFileResolver): IWiringReport;
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,EAGL,KAAK,WAAW,EAChB,KAAK,aAAa,EACnB,MAAM,gBAAgB,CAAC;AACxB,OAAO,EAEL,KAAK,iBAAiB,EACtB,KAAK,cAAc,EACpB,MAAM,8BAA8B,CAAC;AAEtC,eAAO,MAAM,aAAa,EAAG,sBAA+B,CAAC;AAE7D,2CAA2C;AAC3C,MAAM,MAAM,gBAAgB,GAAG,iBAAiB,CAAC;AAEjD,gDAAgD;AAChD,MAAM,MAAM,gBAAgB,GAAG,cAAc,CAAC;AAE9C;;;;;GAKG;AACH,MAAM,MAAM,gBAAgB,GAAG,QAAQ,GAAG,QAAQ,GAAG,SAAS,GAAG,OAAO,CAAC;AAEzE,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,8EAA8E;IAC9E,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,yEAAyE;IACzE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,QAAQ,EAAE,OAAO,GAAG,SAAS,CAAC;IACvC;;;;OAIG;IACH,QAAQ,CAAC,SAAS,CAAC,EAAE,kBAAkB,GAAG,oBAAoB,GAAG,SAAS,CAAC;IAC3E,2DAA2D;IAC3D,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,CAAC;IACtB,2DAA2D;IAC3D,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;CACxB;AAED,kFAAkF;AAClF,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,oEAAoE;IACpE,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,2EAA2E;IAC3E,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAC;IACzB,QAAQ,CAAC,QAAQ,EAAE,OAAO,GAAG,SAAS,CAAC;CACxC;AAED,4DAA4D;AAC5D,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;CAC1B;AAED,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,QAAQ,EAAE,OAAO,GAAG,SAAS,CAAC;IACvC,QAAQ,CAAC,MAAM,EAAE,gBAAgB,CAAC;IAClC,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAC/B,QAAQ,CAAC,eAAe,EAAE,MAAM,CAAC;IACjC,uFAAuF;IACvF,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAC/B,QAAQ,CAAC,eAAe,EAAE,MAAM,CAAC;IACjC,QAAQ,CAAC,UAAU,EAAE,SAAS,gBAAgB,EAAE,CAAC;IACjD,sFAAsF;IACtF,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IACxB;;;;;OAKG;IACH,QAAQ,CAAC,SAAS,CAAC,EAAE,OAAO,CAAC;IAC7B,4CAA4C;IAC5C,QAAQ,CAAC,IAAI,CAAC,EAAE,SAAS,gBAAgB,EAAE,CAAC;CAC7C;AAED,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,MAAM,EAAE,OAAO,aAAa,CAAC;IACtC,QAAQ,CAAC,KAAK,EAAE,SAAS,iBAAiB,EAAE,CAAC;IAC7C,QAAQ,CAAC,UAAU,EAAE,SAAS,gBAAgB,EAAE,CAAC;IACjD,iFAAiF;IACjF,QAAQ,CAAC,WAAW,EAAE,SAAS,MAAM,EAAE,CAAC;IACxC,8EAA8E;IAC9E,QAAQ,CAAC,OAAO,EAAE,SAAS,WAAW,EAAE,CAAC;IACzC;;;;OAIG;IACH,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,OAAO,EAAE,MAAM,GAAG,QAAQ,GAAG,UAAU,CAAC;CAClD;AAED,mFAAmF;AACnF,MAAM,MAAM,kBAAkB,GAAG,CAAC,MAAM,EAAE,aAAa,KAAK,SAAS,gBAAgB,EAAE,CAAC;AAExF,mFAAmF;AACnF,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,WAAW,CAAC,YAAY,CAAC,GAAG,SAAS,aAAa,EAAE,CAG1F;AAED;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,WAAW,GAAG,aAAa,GAAG,SAAS,CAG7E;AAED,4DAA4D;AAC5D,wBAAgB,eAAe,CAAC,IAAI,EAAE,WAAW,GAAG,SAAS,aAAa,EAAE,CAM3E;AAED,6DAA6D;AAC7D,wBAAgB,aAAa,CAAC,IAAI,EAAE,WAAW,GAAG,MAAM,EAAE,CAEzD;AAmBD;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAChC,MAAM,EAAE,aAAa,EACrB,KAAK,EAAE,SAAS,gBAAgB,EAAE,GACjC;IAAE,KAAK,EAAE,SAAS,gBAAgB,EAAE,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,CAGxD;AAyCD,mFAAmF;AACnF,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,WAAW,GAAG,MAAM,GAAG,SAAS,CAyBxE;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,cAAc,CAC5B,KAAK,EAAE,SAAS,WAAW,EAAE,EAC7B,OAAO,EAAE,kBAAkB,GAC1B,aAAa,CAoMf"}
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
- return res.error ? { sites: [], error: res.error } : { sites: res.sites };
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 rule of rules) {
145
- const severity = rule.severity ?? 'error';
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 fromSites = extractTokens(pair.from, fromFiles).sites;
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
- sinkKeySets.push(firstSites(extractTokens(sink, files).sites, groupBy));
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 k of [...unionKeys.keys()].sort()) {
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
- ? '0 files matched the source globs'
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
- const failed = failsWhenEmpty(rule);
281
- skipped.push({ ruleId: rule.id, reason: skipReason, failed, severity });
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 { IWiringRule } from '@shrkcrft/core';
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
- /** `passed` / `failed` / `skipped` / `error` — skipped is never a pass. */
40
- readonly status: 'passed' | 'failed' | 'skipped' | 'error';
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
- readonly verdict: 'pass' | 'errors' | 'warnings';
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