@shrkcrft/boundaries 0.1.0-alpha.30 → 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 (226) 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 +8 -7
  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 +30 -0
  35. package/dist/extract/extract-tokens.d.ts.map +1 -1
  36. package/dist/extract/extract-tokens.js +233 -27
  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 +1 -0
  41. package/dist/extract/import-edges.d.ts.map +1 -1
  42. package/dist/extract/import-edges.js +20 -7
  43. package/dist/extract/inspect-source.d.ts +32 -3
  44. package/dist/extract/inspect-source.d.ts.map +1 -1
  45. package/dist/extract/inspect-source.js +50 -7
  46. package/dist/extract/parse-imports.d.ts +34 -12
  47. package/dist/extract/parse-imports.d.ts.map +1 -1
  48. package/dist/extract/parse-imports.js +119 -20
  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.map +1 -1
  56. package/dist/generated/check-provenance.js +3 -1
  57. package/dist/generated/read-regen-tree.d.ts +16 -0
  58. package/dist/generated/read-regen-tree.d.ts.map +1 -0
  59. package/dist/generated/read-regen-tree.js +61 -0
  60. package/dist/generated/scan-generated.d.ts +8 -0
  61. package/dist/generated/scan-generated.d.ts.map +1 -1
  62. package/dist/generated/scan-generated.js +25 -10
  63. package/dist/index.d.ts +48 -0
  64. package/dist/index.d.ts.map +1 -1
  65. package/dist/index.js +51 -0
  66. package/dist/model/boundary-intended-empty-line.d.ts +10 -0
  67. package/dist/model/boundary-intended-empty-line.d.ts.map +1 -0
  68. package/dist/model/boundary-intended-empty-line.js +11 -0
  69. package/dist/model/boundary-markable-list.d.ts +15 -0
  70. package/dist/model/boundary-markable-list.d.ts.map +1 -0
  71. package/dist/model/boundary-markable-list.js +15 -0
  72. package/dist/model/boundary-rule-input-keys.d.ts +11 -0
  73. package/dist/model/boundary-rule-input-keys.d.ts.map +1 -0
  74. package/dist/model/boundary-rule-input-keys.js +29 -0
  75. package/dist/model/boundary-rule-input.d.ts +23 -0
  76. package/dist/model/boundary-rule-input.d.ts.map +1 -0
  77. package/dist/model/boundary-rule-input.js +1 -0
  78. package/dist/model/boundary-rule-key-problems.d.ts +11 -0
  79. package/dist/model/boundary-rule-key-problems.d.ts.map +1 -0
  80. package/dist/model/boundary-rule-key-problems.js +36 -0
  81. package/dist/model/boundary-rule-marker-problems.d.ts +26 -0
  82. package/dist/model/boundary-rule-marker-problems.d.ts.map +1 -0
  83. package/dist/model/boundary-rule-marker-problems.js +94 -0
  84. package/dist/model/boundary-rule-scope.d.ts +88 -0
  85. package/dist/model/boundary-rule-scope.d.ts.map +1 -0
  86. package/dist/model/boundary-rule-scope.js +175 -0
  87. package/dist/model/boundary-rule.d.ts +120 -5
  88. package/dist/model/boundary-rule.d.ts.map +1 -1
  89. package/dist/model/boundary-rule.js +148 -1
  90. package/dist/model/boundary-unit-problem-issue.d.ts +9 -0
  91. package/dist/model/boundary-unit-problem-issue.d.ts.map +1 -0
  92. package/dist/model/boundary-unit-problem-issue.js +16 -0
  93. package/dist/model/normalize-boundary-rule.d.ts +21 -0
  94. package/dist/model/normalize-boundary-rule.d.ts.map +1 -0
  95. package/dist/model/normalize-boundary-rule.js +62 -0
  96. package/dist/policy/evaluate-policy.d.ts +80 -3
  97. package/dist/policy/evaluate-policy.d.ts.map +1 -1
  98. package/dist/policy/evaluate-policy.js +152 -19
  99. package/dist/policy/i-policy-rule-liveness.d.ts +15 -0
  100. package/dist/policy/i-policy-rule-liveness.d.ts.map +1 -0
  101. package/dist/policy/i-policy-rule-liveness.js +1 -0
  102. package/dist/policy/run-policy.d.ts +1 -1
  103. package/dist/policy/run-policy.d.ts.map +1 -1
  104. package/dist/policy/run-policy.js +171 -21
  105. package/dist/registry/load-boundary-rules.d.ts +29 -1
  106. package/dist/registry/load-boundary-rules.d.ts.map +1 -1
  107. package/dist/registry/load-boundary-rules.js +38 -8
  108. package/dist/scan/glob.d.ts +90 -0
  109. package/dist/scan/glob.d.ts.map +1 -1
  110. package/dist/scan/glob.js +205 -0
  111. package/dist/scan/i-glob-unit-measure.d.ts +22 -0
  112. package/dist/scan/i-glob-unit-measure.d.ts.map +1 -0
  113. package/dist/scan/i-glob-unit-measure.js +1 -0
  114. package/dist/scan/import-pattern.d.ts +109 -0
  115. package/dist/scan/import-pattern.d.ts.map +1 -0
  116. package/dist/scan/import-pattern.js +191 -0
  117. package/dist/scan/node-builtin-package-names.d.ts +13 -0
  118. package/dist/scan/node-builtin-package-names.d.ts.map +1 -0
  119. package/dist/scan/node-builtin-package-names.js +25 -0
  120. package/dist/scan/scan-imports.d.ts +36 -3
  121. package/dist/scan/scan-imports.d.ts.map +1 -1
  122. package/dist/scan/scan-imports.js +114 -45
  123. package/dist/util/blank-run-hazard-finding.d.ts +34 -0
  124. package/dist/util/blank-run-hazard-finding.d.ts.map +1 -0
  125. package/dist/util/blank-run-hazard-finding.js +1 -0
  126. package/dist/util/blank-run-hazard.d.ts +29 -0
  127. package/dist/util/blank-run-hazard.d.ts.map +1 -0
  128. package/dist/util/blank-run-hazard.js +450 -0
  129. package/dist/util/dead-glob-units.d.ts +46 -0
  130. package/dist/util/dead-glob-units.d.ts.map +1 -0
  131. package/dist/util/dead-glob-units.js +116 -0
  132. package/dist/util/glob-list-liveness-input.d.ts +31 -0
  133. package/dist/util/glob-list-liveness-input.d.ts.map +1 -0
  134. package/dist/util/glob-list-liveness-input.js +65 -0
  135. package/dist/util/i-dead-glob-unit.d.ts +24 -0
  136. package/dist/util/i-dead-glob-unit.d.ts.map +1 -0
  137. package/dist/util/i-dead-glob-unit.js +1 -0
  138. package/dist/util/i-glob-list-units.d.ts +25 -0
  139. package/dist/util/i-glob-list-units.d.ts.map +1 -0
  140. package/dist/util/i-glob-list-units.js +1 -0
  141. package/dist/util/i-glob-liveness-list.d.ts +23 -0
  142. package/dist/util/i-glob-liveness-list.d.ts.map +1 -0
  143. package/dist/util/i-glob-liveness-list.js +1 -0
  144. package/dist/util/i-glob-liveness-request.d.ts +11 -0
  145. package/dist/util/i-glob-liveness-request.d.ts.map +1 -0
  146. package/dist/util/i-glob-liveness-request.js +1 -0
  147. package/dist/util/i-glob-negation.d.ts +13 -0
  148. package/dist/util/i-glob-negation.d.ts.map +1 -0
  149. package/dist/util/i-glob-negation.js +1 -0
  150. package/dist/util/matched-files.d.ts +22 -0
  151. package/dist/util/matched-files.d.ts.map +1 -0
  152. package/dist/util/matched-files.js +1 -0
  153. package/dist/util/negation-cause.d.ts +15 -0
  154. package/dist/util/negation-cause.d.ts.map +1 -0
  155. package/dist/util/negation-cause.js +21 -0
  156. package/dist/util/plane-scan-exclude-dirs.d.ts +17 -0
  157. package/dist/util/plane-scan-exclude-dirs.d.ts.map +1 -0
  158. package/dist/util/plane-scan-exclude-dirs.js +22 -0
  159. package/dist/util/read-glob-list-liveness.d.ts +20 -0
  160. package/dist/util/read-glob-list-liveness.d.ts.map +1 -0
  161. package/dist/util/read-glob-list-liveness.js +20 -0
  162. package/dist/util/read-scope-coverage.d.ts +90 -0
  163. package/dist/util/read-scope-coverage.d.ts.map +1 -0
  164. package/dist/util/read-scope-coverage.js +173 -0
  165. package/dist/util/read-scope.d.ts +14 -0
  166. package/dist/util/read-scope.d.ts.map +1 -0
  167. package/dist/util/read-scope.js +1 -0
  168. package/dist/util/read-selected-files.d.ts +16 -0
  169. package/dist/util/read-selected-files.d.ts.map +1 -0
  170. package/dist/util/read-selected-files.js +25 -0
  171. package/dist/util/settle-glob-lists.d.ts +12 -0
  172. package/dist/util/settle-glob-lists.d.ts.map +1 -0
  173. package/dist/util/settle-glob-lists.js +13 -0
  174. package/dist/util/unread-file-reason.d.ts +26 -0
  175. package/dist/util/unread-file-reason.d.ts.map +1 -0
  176. package/dist/util/unread-file-reason.js +26 -0
  177. package/dist/util/unread-file.d.ts +10 -0
  178. package/dist/util/unread-file.d.ts.map +1 -0
  179. package/dist/util/unread-file.js +1 -0
  180. package/dist/util/walk-files.d.ts +74 -10
  181. package/dist/util/walk-files.d.ts.map +1 -1
  182. package/dist/util/walk-files.js +137 -31
  183. package/dist/wiring/evaluate-wiring.d.ts +79 -5
  184. package/dist/wiring/evaluate-wiring.d.ts.map +1 -1
  185. package/dist/wiring/evaluate-wiring.js +267 -12
  186. package/dist/wiring/explain-wiring.d.ts +49 -5
  187. package/dist/wiring/explain-wiring.d.ts.map +1 -1
  188. package/dist/wiring/explain-wiring.js +118 -10
  189. package/dist/wiring/i-idiom-role-coverage.d.ts +24 -0
  190. package/dist/wiring/i-idiom-role-coverage.d.ts.map +1 -0
  191. package/dist/wiring/i-idiom-role-coverage.js +1 -0
  192. package/dist/wiring/i-registration-query-verdict.d.ts +38 -0
  193. package/dist/wiring/i-registration-query-verdict.d.ts.map +1 -0
  194. package/dist/wiring/i-registration-query-verdict.js +1 -0
  195. package/dist/wiring/i-registration-roles.d.ts +71 -0
  196. package/dist/wiring/i-registration-roles.d.ts.map +1 -0
  197. package/dist/wiring/i-registration-roles.js +1 -0
  198. package/dist/wiring/measure-idiom-role-coverage.d.ts +12 -0
  199. package/dist/wiring/measure-idiom-role-coverage.d.ts.map +1 -0
  200. package/dist/wiring/measure-idiom-role-coverage.js +20 -0
  201. package/dist/wiring/measure-registration-roles.d.ts +25 -0
  202. package/dist/wiring/measure-registration-roles.d.ts.map +1 -0
  203. package/dist/wiring/measure-registration-roles.js +124 -0
  204. package/dist/wiring/plan-wiring-fix.js +2 -2
  205. package/dist/wiring/registration-graph.d.ts +12 -0
  206. package/dist/wiring/registration-graph.d.ts.map +1 -1
  207. package/dist/wiring/registration-graph.js +16 -5
  208. package/dist/wiring/registration-query-verdict.d.ts +23 -0
  209. package/dist/wiring/registration-query-verdict.d.ts.map +1 -0
  210. package/dist/wiring/registration-query-verdict.js +118 -0
  211. package/dist/wiring/registry-query.d.ts +8 -0
  212. package/dist/wiring/registry-query.d.ts.map +1 -1
  213. package/dist/wiring/registry-query.js +12 -5
  214. package/dist/wiring/scan-wiring-files.d.ts +4 -2
  215. package/dist/wiring/scan-wiring-files.d.ts.map +1 -1
  216. package/dist/wiring/scan-wiring-files.js +52 -15
  217. package/dist/wiring/sink-imports.d.ts +11 -0
  218. package/dist/wiring/sink-imports.d.ts.map +1 -1
  219. package/dist/wiring/sink-imports.js +11 -2
  220. package/dist/wiring/trace-literal.d.ts +6 -0
  221. package/dist/wiring/trace-literal.d.ts.map +1 -1
  222. package/dist/wiring/trace-literal.js +5 -2
  223. package/dist/wiring/wiring-labeled-sources.d.ts +12 -0
  224. package/dist/wiring/wiring-labeled-sources.d.ts.map +1 -0
  225. package/dist/wiring/wiring-labeled-sources.js +20 -0
  226. package/package.json +2 -2
@@ -1,95 +1,523 @@
1
- import { matchesAny } from "../scan/glob.js";
1
+ import { UnitDeadCause, UnitDeadWeight, unitProblemsOf, } from '@shrkcrft/core';
2
+ import { BoundaryMarkableList } from "../model/boundary-markable-list.js";
3
+ import { boundaryForbiddenMatch, boundaryPatternOverlaps, boundaryRuleScope, boundaryRuleSeverity, boundaryScopeDecision, } from "../model/boundary-rule-scope.js";
4
+ import { normalizeBoundaryRule } from "../model/normalize-boundary-rule.js";
5
+ import { globToRegex, matchesAny } from "../scan/glob.js";
6
+ import { couldMatchPackageName, importPatternDefect, LEADING_WILDCARD_NEVER_DEAD, matchImportPattern, RELATIVE_PATTERN_NEVER_DEAD, } from "../scan/import-pattern.js";
2
7
  import { resolveAliasCandidates, } from "../scan/tsconfig-aliases.js";
3
- function severityOf(rule) {
4
- return rule.severity ?? 'error';
5
- }
8
+ import { settleBoundaryRule } from "./settle-boundary-rule.js";
9
+ import { withBoundaryRuleSettlement } from "./with-boundary-rule-settlement.js";
10
+ /** The skip reason an unnormalisable rule carries — the loaders' errored-rule words. */
11
+ const NOT_EVALUATED = 'failed validation — NOT evaluated';
6
12
  /**
7
13
  * Evaluate every rule against every scanned edge. Returns the list of
8
- * violations + counts. Pure functionno I/O.
14
+ * violations + counts, and round 11 what every rule and every selector
15
+ * unit actually matched, so a rule that enforced nothing can never read as
16
+ * evaluated. Pure function — no I/O.
17
+ *
18
+ * Round 13: each rule is normalised IDEMPOTENTLY at entry
19
+ * (`normalizeBoundaryRule` — a loaded rule comes back unchanged), so a
20
+ * hand-built rule with a `{ pattern, expectEmpty: true }` entry is honoured,
21
+ * never a `pattern.includes is not a function` crash; and every rule's units
22
+ * and emptiness settle through the ONE boundary settle (`settleBoundaryRule`).
9
23
  */
10
24
  export function evaluateBoundaries(scan, rules, options = {}) {
11
- const filtered = options.onlyRuleId
25
+ const selected = options.onlyRuleId
12
26
  ? rules.filter((r) => r.id === options.onlyRuleId)
13
27
  : rules;
28
+ const fileUniverse = scan.files ? 'scan' : 'edges';
29
+ const files = scan.files ?? [...new Set(scan.edges.map((e) => e.from))];
30
+ const edgesByFile = new Map();
31
+ for (const e of scan.edges) {
32
+ const list = edgesByFile.get(e.from);
33
+ if (list)
34
+ list.push(e);
35
+ else
36
+ edgesByFile.set(e.from, [e]);
37
+ }
38
+ // Literal specifier + tsconfig alias resolutions, computed once per specifier.
39
+ const candidateCache = new Map();
40
+ const candidatesOf = (specifier) => {
41
+ const hit = candidateCache.get(specifier);
42
+ if (hit)
43
+ return hit;
44
+ const out = [specifier];
45
+ if (options.tsconfigPaths) {
46
+ for (const resolved of resolveAliasCandidates(specifier, options.tsconfigPaths))
47
+ out.push(resolved);
48
+ }
49
+ candidateCache.set(specifier, out);
50
+ return out;
51
+ };
52
+ const distinctSpecifiers = [...new Set(scan.edges.map((e) => e.importSpecifier))];
53
+ const anywhere = new PatternReach(distinctSpecifiers, candidatesOf, files, options);
14
54
  const violations = [];
15
- for (const rule of filtered) {
16
- for (const edge of scan.edges) {
17
- const matchedFrom = firstMatch(edge.from, rule.from);
18
- if (!matchedFrom)
55
+ const suppressed = [];
56
+ const staleExceptions = [];
57
+ const coverage = [];
58
+ const invalidRules = [];
59
+ for (const input of selected) {
60
+ const normalized = normalizeBoundaryRule(input);
61
+ if (!normalized.ok) {
62
+ const problems = unitProblemsOf(normalized.error);
63
+ const ruleId = typeof input.id === 'string' ? input.id : '(no id)';
64
+ invalidRules.push({ ruleId, problems });
65
+ coverage.push({
66
+ ruleId,
67
+ severity: boundaryRuleSeverity(input),
68
+ status: 'skipped',
69
+ skipReason: `${NOT_EVALUATED}: ${problems.join('; ')}`,
70
+ failedOnEmpty: true,
71
+ filesInScope: 0,
72
+ exemptFilesInScope: 0,
73
+ edgesInScope: 0,
74
+ violations: 0,
75
+ suppressed: 0,
76
+ fromGlobs: [],
77
+ forbidden: [],
78
+ allowed: [],
79
+ exemptions: [],
80
+ exceptions: [],
81
+ deadUnits: [],
82
+ coverage: { unit: 'rules', expected: 1, examined: 0, reason: NOT_EVALUATED },
83
+ });
84
+ continue;
85
+ }
86
+ const rule = normalized.value;
87
+ const scope = boundaryRuleScope(rule);
88
+ const mode = boundaryForbiddenMatch(rule);
89
+ const severity = boundaryRuleSeverity(rule);
90
+ const forbiddenPatterns = rule.forbiddenImports ?? [];
91
+ const allowedPatterns = rule.allowedImports ?? [];
92
+ const exceptions = rule.exceptions ?? [];
93
+ const governedPerGlob = new Map(scope.include.map((g) => [g, 0]));
94
+ const anyPerGlob = new Map(scope.include.map((g) => [g, 0]));
95
+ const exemptPerIndex = scope.exemptions.map(() => 0);
96
+ const hitsInScope = new Map(forbiddenPatterns.map((p) => [p, 0]));
97
+ const exceptionMatched = exceptions.map(() => 0);
98
+ let filesInScope = 0;
99
+ let exemptFilesInScope = 0;
100
+ let edgesInScope = 0;
101
+ let ruleViolations = 0;
102
+ let ruleSuppressed = 0;
103
+ for (const file of files) {
104
+ // THE scope decision — the same function why-file, the rule-graph bridge
105
+ // and every other reader call, so none of them can disagree with this.
106
+ const decision = boundaryScopeDecision(scope, file);
107
+ if (decision === 'out')
19
108
  continue;
20
- // Build the candidate list: the literal specifier + any tsconfig
21
- // alias resolutions. The first match wins; resolved paths give the
22
- // boundary rule a chance to match against `libs/app/adapter/**`
23
- // even when the source code wrote `@app/adapter-core`.
24
- const specifiers = [edge.importSpecifier];
25
- if (options.tsconfigPaths) {
26
- for (const resolved of resolveAliasCandidates(edge.importSpecifier, options.tsconfigPaths)) {
27
- specifiers.push(resolved);
28
- }
109
+ const matchedInclude = scope.include.filter((g) => globToRegex(g).test(file));
110
+ for (const g of matchedInclude)
111
+ anyPerGlob.set(g, (anyPerGlob.get(g) ?? 0) + 1);
112
+ let exemptGlob;
113
+ if (decision === 'exempt') {
114
+ exemptFilesInScope += 1;
115
+ scope.exemptions.forEach((e, i) => {
116
+ if (globToRegex(e.glob).test(file)) {
117
+ exemptPerIndex[i] = (exemptPerIndex[i] ?? 0) + 1;
118
+ exemptGlob ??= e.glob;
119
+ }
120
+ });
121
+ }
122
+ else {
123
+ filesInScope += 1;
124
+ for (const g of matchedInclude)
125
+ governedPerGlob.set(g, (governedPerGlob.get(g) ?? 0) + 1);
29
126
  }
30
- // Forbidden imports match any candidate.
31
- let matchedForbidden;
32
- let matchedVia;
33
- if (rule.forbiddenImports) {
127
+ const matchedFrom = matchedInclude[0] ?? scope.include[0] ?? '';
128
+ for (const edge of edgesByFile.get(file) ?? []) {
129
+ if (decision === 'in')
130
+ edgesInScope += 1;
131
+ const specifiers = candidatesOf(edge.importSpecifier);
132
+ let violation;
133
+ // Forbidden imports — the first candidate any pattern reaches wins.
34
134
  for (const spec of specifiers) {
35
- const m = firstMatch(spec, rule.forbiddenImports);
36
- if (m) {
37
- matchedForbidden = m;
38
- matchedVia = spec !== edge.importSpecifier ? spec : undefined;
135
+ let hit;
136
+ for (const p of forbiddenPatterns) {
137
+ const kind = matchImportPattern(spec, p, mode);
138
+ if (kind) {
139
+ hit = { pattern: p, kind };
140
+ break;
141
+ }
142
+ }
143
+ if (hit) {
144
+ violation = violationFor(rule, severity, edge, matchedFrom, hit.pattern, hit.kind);
145
+ if (spec !== edge.importSpecifier)
146
+ violation.resolvedVia = spec;
147
+ if (decision === 'in')
148
+ hitsInScope.set(hit.pattern, (hitsInScope.get(hit.pattern) ?? 0) + 1);
39
149
  break;
40
150
  }
41
151
  }
42
- }
43
- if (matchedForbidden) {
44
- const v = violationFor(rule, edge, matchedFrom, matchedForbidden);
45
- if (matchedVia)
46
- v.resolvedVia = matchedVia;
47
- violations.push(v);
48
- continue;
49
- }
50
- // Allowed imports — when set, anything NOT matching (any candidate)
51
- // is a violation.
52
- if (rule.allowedImports && rule.allowedImports.length > 0) {
53
- const matchedAllowed = specifiers.some((s) => matchesAny(s, rule.allowedImports));
54
- if (!matchedAllowed && !edge.importSpecifier.startsWith('.')) {
55
- violations.push({
56
- ...violationFor(rule, edge, matchedFrom, undefined),
57
- notAllowed: true,
58
- });
152
+ // Allowed imports — when set, anything NOT matching (any candidate) is
153
+ // a violation. Exact glob semantics: an allow-list is never widened.
154
+ if (!violation && allowedPatterns.length > 0) {
155
+ const matchedAllowed = specifiers.some((s) => matchesAny(s, allowedPatterns));
156
+ if (!matchedAllowed && !edge.importSpecifier.startsWith('.')) {
157
+ violation = { ...violationFor(rule, severity, edge, matchedFrom, undefined, undefined), notAllowed: true };
158
+ }
159
+ }
160
+ if (!violation)
161
+ continue;
162
+ if (decision === 'exempt') {
163
+ suppressed.push({ violation, reason: 'exempt-file', ...(exemptGlob ? { glob: exemptGlob } : {}) });
164
+ ruleSuppressed += 1;
165
+ continue;
166
+ }
167
+ // Credit EVERY exception whose (path, target) matches this edge, and
168
+ // suppress the violation once (attributed to the first). Crediting
169
+ // only the first made an overlapping exception — an area-wide one next
170
+ // to a file-level one for the same edge — read as STALE (exit 1) while
171
+ // it matched a real edge. Only an exception matching no violating edge
172
+ // at all is stale.
173
+ let exceptionIndex = -1;
174
+ exceptions.forEach((ex, idx) => {
175
+ if (!matchesAny(edge.from, [ex.path]))
176
+ return;
177
+ if (!specifiers.some((s) => matchImportPattern(s, ex.target, mode) !== null))
178
+ return;
179
+ exceptionMatched[idx] = (exceptionMatched[idx] ?? 0) + 1;
180
+ if (exceptionIndex < 0)
181
+ exceptionIndex = idx;
182
+ });
183
+ if (exceptionIndex >= 0) {
184
+ suppressed.push({ violation, reason: 'exception', exception: exceptions[exceptionIndex] });
185
+ ruleSuppressed += 1;
186
+ continue;
59
187
  }
188
+ violations.push(violation);
189
+ ruleViolations += 1;
60
190
  }
61
191
  }
192
+ // ── selector units: OBSERVE, then settle through the one boundary settle ──
193
+ // Each unit reports two facts (round 13): whether its target EXISTS at all
194
+ // (raw — before exemptions and effectiveness), which decides a MARKED unit
195
+ // (intended-empty or went-live); and whether it is LIVE (the dead predicate
196
+ // this plane has always used, negated), which decides an unmarked one.
197
+ const includedAny = filesInScope + exemptFilesInScope;
198
+ const marked = new Set((rule.expectEmptyUnits ?? []).map((m) => `${m.list} ${m.unit}`));
199
+ const observations = [];
200
+ for (const g of scope.include) {
201
+ const any = anyPerGlob.get(g) ?? 0;
202
+ const governed = governedPerGlob.get(g) ?? 0;
203
+ observations.push({
204
+ list: BoundaryMarkableList.From,
205
+ unit: g,
206
+ label: g,
207
+ weight: UnitDeadWeight.Coverage,
208
+ exists: any > 0,
209
+ live: governed > 0,
210
+ matched: any,
211
+ liveBecause: governed > 0 ? `it governs ${governed} scanned file(s)` : `it matches ${any} scanned file(s)`,
212
+ deadReason: any > 0 ? `matches only exempt files (${any})` : `matched 0 of ${files.length} scanned files`,
213
+ });
214
+ }
215
+ scope.exemptions.forEach((e, i) => {
216
+ // The `excludeTests` shorthand is allowed to be vacuous (a repo with no
217
+ // tests yet). An exemption of a rule whose from globs match nothing
218
+ // exempts nothing by construction, so it is judged only when marked —
219
+ // a marker is never left unobserved.
220
+ if (e.origin === 'excludeTests')
221
+ return;
222
+ const list = e.origin === 'from-negation' ? BoundaryMarkableList.From : BoundaryMarkableList.ExemptFiles;
223
+ const unit = e.origin === 'from-negation' ? `!${e.glob}` : e.glob;
224
+ if (includedAny === 0 && !marked.has(`${list} ${unit}`))
225
+ return;
226
+ const n = exemptPerIndex[i] ?? 0;
227
+ observations.push({
228
+ list,
229
+ unit,
230
+ label: unit,
231
+ exists: n > 0,
232
+ live: n > 0,
233
+ matched: n,
234
+ liveBecause: `it exempts ${n} file(s)`,
235
+ deadReason: includedAny > 0
236
+ ? `exempts none of the ${includedAny} file(s) the rule's from globs match`
237
+ : "exempts nothing — the rule's from globs match no file",
238
+ });
239
+ });
240
+ // Patterns that can never change the verdict — the one static answer
241
+ // `boundaries explain` and MCP `get_boundary_rule` print too.
242
+ const overlaps = boundaryPatternOverlaps(rule);
243
+ const subsumedBy = new Map(overlaps.redundantForbidden.map((o) => [o.index, o.by]));
244
+ const shadowedBy = new Map(overlaps.shadowedAllowed.map((o) => [o.index, o.by]));
245
+ const forbiddenCounts = forbiddenPatterns.map((p, i) => {
246
+ const reach = anywhere.forbidden(p, mode);
247
+ const resolvedBy = reach.hits > 0 ? undefined : anywhere.resolvedBy(p, mode);
248
+ const resolvable = reach.hits > 0 || resolvedBy !== undefined;
249
+ // A malformed pattern says WHY (the validator rejects it at load; a
250
+ // hand-built rule reaches here) — never the "typo, retired target" causes.
251
+ const defect = resolvable ? undefined : importPatternDefect(p, mode);
252
+ const subpathNote = mode === 'exact' && reach.subpathHits > 0
253
+ ? ` — ${reach.subpathHits} subpath import(s) of it exist; forbiddenMatch: 'exact' excludes them`
254
+ : '';
255
+ observations.push({
256
+ list: BoundaryMarkableList.ForbiddenImports,
257
+ unit: p,
258
+ label: p,
259
+ exists: resolvable,
260
+ live: resolvable,
261
+ matched: reach.hits,
262
+ liveBecause: reach.hits > 0 ? `${reach.hits} import(s) match it` : `no import yet, but ${resolvedBy ?? 'it resolves'}`,
263
+ deadReason: defect ??
264
+ `matches no import anywhere in the repo, no workspace/dependency package name, no tsconfig alias and no file${subpathNote}`,
265
+ ...(defect !== undefined ? { cause: UnitDeadCause.Defect } : {}),
266
+ });
267
+ const coveredBy = subsumedBy.get(i);
268
+ return {
269
+ pattern: p,
270
+ hitsInScope: hitsInScope.get(p) ?? 0,
271
+ hitsAnywhere: reach.hits,
272
+ resolvable,
273
+ ...(mode === 'exact' ? { subpathHitsAnywhere: reach.subpathHits } : {}),
274
+ ...(coveredBy !== undefined ? { subsumedBy: coveredBy } : {}),
275
+ };
276
+ });
277
+ const allowedCounts = allowedPatterns.map((p, i) => {
278
+ const hits = anywhere.allowed(p);
279
+ const shadow = shadowedBy.get(i);
280
+ if (shadow !== undefined) {
281
+ // Forbidden is checked first and allowed never re-admits: an allowance
282
+ // a forbidden pattern covers can never admit anything — the carve-out
283
+ // under a bare forbidden package that package semantics made inert.
284
+ observations.push({
285
+ list: BoundaryMarkableList.AllowedImports,
286
+ unit: p,
287
+ label: p,
288
+ exists: hits > 0,
289
+ live: false,
290
+ matched: hits,
291
+ deadReason: `shadowed by forbidden '${shadow}' — forbiddenImports is checked first` +
292
+ (mode === 'package' ? ` and '${shadow}' covers every subpath (package semantics)` : '') +
293
+ `, so this allowance can never admit an import; carve the subpath out with exceptions[{ path, target: '${p}', reason }]` +
294
+ (mode === 'package' ? " or set forbiddenMatch: 'exact'" : ''),
295
+ cause: UnitDeadCause.Shadowed,
296
+ });
297
+ }
298
+ else {
299
+ const resolvedBy = hits > 0 ? undefined : anywhere.resolvedBy(p, 'exact');
300
+ const resolvable = hits > 0 || resolvedBy !== undefined;
301
+ const defect = resolvable ? undefined : importPatternDefect(p, 'exact');
302
+ observations.push({
303
+ list: BoundaryMarkableList.AllowedImports,
304
+ unit: p,
305
+ label: p,
306
+ exists: resolvable,
307
+ live: resolvable,
308
+ matched: hits,
309
+ liveBecause: hits > 0 ? `${hits} import(s) match it` : `no import yet, but ${resolvedBy ?? 'it resolves'}`,
310
+ deadReason: defect ??
311
+ 'matches no import anywhere in the repo, no workspace/dependency package name, no tsconfig alias and no file',
312
+ ...(defect !== undefined ? { cause: UnitDeadCause.Defect } : {}),
313
+ });
314
+ }
315
+ return { pattern: p, hitsAnywhere: hits, ...(shadow !== undefined ? { shadowedBy: shadow } : {}) };
316
+ });
317
+ // ── stale exceptions ──────────────────────────────────────────────────
318
+ exceptions.forEach((ex, index) => {
319
+ if ((exceptionMatched[index] ?? 0) > 0)
320
+ return;
321
+ staleExceptions.push({
322
+ ruleId: rule.id,
323
+ index,
324
+ exception: ex,
325
+ severity: 'error',
326
+ message: `exception ${index} of ${rule.id} (path ${ex.path} → target ${ex.target}; reason: ${ex.reason}) no longer matches any violating edge — delete it, or the rule it narrows is not what you think`,
327
+ });
328
+ });
329
+ // ── status + coverage: THE boundary settle ────────────────────────────
330
+ const settlement = settleBoundaryRule({
331
+ rule,
332
+ observations,
333
+ includeGlobs: scope.include,
334
+ filesScanned: files.length,
335
+ fileUniverse,
336
+ filesInScope,
337
+ exemptFilesInScope,
338
+ violations: ruleViolations,
339
+ unread: false,
340
+ });
341
+ coverage.push(withBoundaryRuleSettlement({
342
+ ruleId: rule.id,
343
+ severity,
344
+ filesInScope,
345
+ exemptFilesInScope,
346
+ edgesInScope,
347
+ violations: ruleViolations,
348
+ suppressed: ruleSuppressed,
349
+ fromGlobs: scope.include.map((g) => ({ glob: g, files: governedPerGlob.get(g) ?? 0 })),
350
+ forbidden: forbiddenCounts,
351
+ allowed: allowedCounts,
352
+ exemptions: scope.exemptions.map((e, i) => ({ ...e, files: exemptPerIndex[i] ?? 0 })),
353
+ exceptions: exceptions.map((ex, i) => ({ ...ex, matched: exceptionMatched[i] ?? 0 })),
354
+ }, settlement));
62
355
  }
63
356
  const counts = { error: 0, warning: 0, info: 0 };
64
357
  for (const v of violations)
65
358
  counts[v.severity] += 1;
359
+ // Every rule's settled dead list, in rule order — read, never re-derived.
360
+ const deadUnits = coverage.flatMap((c) => c.deadUnits);
66
361
  return {
67
- rulesEvaluated: filtered.length,
362
+ rulesConfigured: selected.length,
363
+ rulesEvaluated: coverage.filter((c) => c.status !== 'skipped' && c.acceptedAsIntendedEmpty !== true).length,
364
+ rulesAcceptedEmpty: coverage.filter((c) => c.acceptedAsIntendedEmpty === true).length,
68
365
  edgesEvaluated: scan.edges.length,
366
+ filesScanned: files.length,
367
+ fileUniverse,
69
368
  violations,
70
369
  counts,
370
+ suppressed,
371
+ suppressedCounts: {
372
+ exemptFile: suppressed.filter((s) => s.reason === 'exempt-file').length,
373
+ exception: suppressed.filter((s) => s.reason === 'exception').length,
374
+ },
375
+ staleExceptions,
376
+ coverage,
377
+ skipped: coverage
378
+ .filter((c) => c.status === 'skipped')
379
+ .map((c) => ({
380
+ ruleId: c.ruleId,
381
+ reason: c.skipReason ?? 'checked nothing',
382
+ severity: c.severity,
383
+ failed: c.failedOnEmpty === true,
384
+ })),
385
+ deadUnits,
386
+ ...(invalidRules.length > 0 ? { invalidRules } : {}),
71
387
  };
72
388
  }
73
- function firstMatch(value, patterns) {
74
- for (const p of patterns) {
75
- if (matchesAny(value, [p]))
76
- return p;
389
+ /**
390
+ * "Could this pattern ever match?" — the whole-scan reach of a pattern,
391
+ * memoised per (pattern, mode) because rule sets share patterns.
392
+ *
393
+ * 0 violations in scope is the NORMAL state of a healthy fence, so a pattern is
394
+ * dead only when it resolves to nothing ANYWHERE: no import in the repo, no
395
+ * workspace / dependency / builtin package name, no tsconfig alias, no file.
396
+ */
397
+ class PatternReach {
398
+ specifiers;
399
+ candidatesOf;
400
+ files;
401
+ options;
402
+ forbiddenCache = new Map();
403
+ allowedCache = new Map();
404
+ resolveCache = new Map();
405
+ constructor(specifiers, candidatesOf, files, options) {
406
+ this.specifiers = specifiers;
407
+ this.candidatesOf = candidatesOf;
408
+ this.files = files;
409
+ this.options = options;
410
+ }
411
+ forbidden(pattern, mode) {
412
+ const key = [mode, pattern].join(String.fromCharCode(0));
413
+ const cached = this.forbiddenCache.get(key);
414
+ if (cached)
415
+ return cached;
416
+ let hits = 0;
417
+ let subpathHits = 0;
418
+ for (const s of this.specifiers) {
419
+ const candidates = this.candidatesOf(s);
420
+ if (candidates.some((c) => matchImportPattern(c, pattern, mode) !== null)) {
421
+ hits += 1;
422
+ }
423
+ else if (mode === 'exact' &&
424
+ candidates.some((c) => matchImportPattern(c, pattern, 'package') === 'subpath')) {
425
+ subpathHits += 1;
426
+ }
427
+ }
428
+ const out = { hits, subpathHits };
429
+ this.forbiddenCache.set(key, out);
430
+ return out;
431
+ }
432
+ allowed(pattern) {
433
+ const cached = this.allowedCache.get(pattern);
434
+ if (cached !== undefined)
435
+ return cached;
436
+ let hits = 0;
437
+ for (const s of this.specifiers) {
438
+ if (this.candidatesOf(s).some((c) => matchesAny(c, [pattern])))
439
+ hits += 1;
440
+ }
441
+ this.allowedCache.set(pattern, hits);
442
+ return hits;
443
+ }
444
+ /**
445
+ * WHY the pattern is resolvable with no import of it today — a relative
446
+ * target, a known package, an alias, a file — or `undefined` when nothing
447
+ * names it. The went-live line quotes it (round 13), so a stale marker says
448
+ * what made its target real.
449
+ */
450
+ resolvedBy(pattern, mode) {
451
+ const key = [mode, pattern].join(String.fromCharCode(0));
452
+ if (this.resolveCache.has(key))
453
+ return this.resolveCache.get(key);
454
+ let out;
455
+ // A malformed pattern (`importPatternDefect`) cannot mean what it says —
456
+ // never "resolvable" through a package name it merely spells
457
+ // (`'@scope/pkg/'` used to read resolvable via the known `@scope/pkg`).
458
+ if (importPatternDefect(pattern, mode) === undefined) {
459
+ if (pattern.startsWith('.')) {
460
+ // A relative pattern cannot be judged without an importing file — so
461
+ // it is never reported dead, and a marker on it has nothing to wait for
462
+ // (the rule validator refuses one: `importPatternNeverJudgedDead`).
463
+ out = RELATIVE_PATTERN_NEVER_DEAD;
464
+ }
465
+ else {
466
+ // Name a piece of EVIDENCE only when it is one: a known package the
467
+ // pattern actually matches (or names a subpath / prefix of), an alias,
468
+ // a file. A LEADING `*` "could match" every package name — the
469
+ // permissive half of the dead predicate, never proof that the target
470
+ // exists — so it is said as what it is, never as "'<builtin>' is a
471
+ // known package" (round 13 review: `*-legacy` read "'_http_agent' is a
472
+ // known package"). The resolvable/not answer is unchanged.
473
+ const known = this.options.knownPackages ?? [];
474
+ const leadingWildcard = pattern.startsWith('*');
475
+ const pkg = known.find((k) => matchImportPattern(k, pattern, mode) !== null) ??
476
+ (leadingWildcard ? undefined : known.find((k) => couldMatchPackageName(pattern, k, mode)));
477
+ const alias = pkg === undefined
478
+ ? [...(this.options.tsconfigPaths?.aliases.keys() ?? [])].find((aliasKey) => aliasKey.endsWith('*')
479
+ ? pattern.startsWith(aliasKey.slice(0, -1)) ||
480
+ couldMatchPackageName(pattern, aliasKey.slice(0, -1) + 'x', mode)
481
+ : couldMatchPackageName(pattern, aliasKey, mode))
482
+ : undefined;
483
+ const file = pkg === undefined && alias === undefined
484
+ ? this.files.find((f) => matchImportPattern(f, pattern, 'package') !== null)
485
+ : undefined;
486
+ const wildcardOnly = pkg === undefined &&
487
+ alias === undefined &&
488
+ file === undefined &&
489
+ leadingWildcard &&
490
+ known.some((k) => couldMatchPackageName(pattern, k, mode));
491
+ out =
492
+ pkg !== undefined
493
+ ? `'${pkg}' is a known package (a node builtin, a workspace package or a declared dependency)`
494
+ : alias !== undefined
495
+ ? `the tsconfig alias '${alias}' names it`
496
+ : file !== undefined
497
+ ? `the file '${file}' matches it`
498
+ : wildcardOnly
499
+ ? LEADING_WILDCARD_NEVER_DEAD
500
+ : undefined;
501
+ }
502
+ }
503
+ this.resolveCache.set(key, out);
504
+ return out;
77
505
  }
78
- return undefined;
79
506
  }
80
- function violationFor(rule, edge, matchedFrom, matchedForbidden) {
507
+ function violationFor(rule, severity, edge, matchedFrom, matchedForbidden, matchKind) {
81
508
  return {
82
509
  ruleId: rule.id,
83
510
  ruleTitle: rule.title,
84
- severity: severityOf(rule),
511
+ severity,
85
512
  file: edge.from,
86
513
  importSpecifier: edge.importSpecifier,
87
514
  line: edge.line,
88
515
  matchedFrom,
89
516
  ...(matchedForbidden ? { matchedForbidden } : {}),
517
+ ...(matchKind ? { matchKind } : {}),
90
518
  message: rule.message ??
91
519
  (matchedForbidden
92
- ? `Forbidden import in ${edge.from}: "${edge.importSpecifier}" matched "${matchedForbidden}"`
520
+ ? `Forbidden import in ${edge.from}: "${edge.importSpecifier}" matched "${matchedForbidden}"${matchKind === 'subpath' ? ' (a subpath of the forbidden package)' : ''}`
93
521
  : `Import "${edge.importSpecifier}" not in allowed list for ${rule.id}`),
94
522
  ...(rule.suggestedFix ? { suggestedFix: rule.suggestedFix } : {}),
95
523
  };
@@ -0,0 +1,27 @@
1
+ import type { IUnitObservation } from '@shrkcrft/core';
2
+ import type { IBoundaryRule } from '../model/boundary-rule.js';
3
+ /** What `settleBoundaryRule` needs: one rule's selector-unit observations and the counts its emptiness is decided from. */
4
+ export interface IBoundaryRuleSettleInput {
5
+ readonly rule: Pick<IBoundaryRule, 'id' | 'severity' | 'failOnEmpty' | 'expectEmptyUnits'>;
6
+ /**
7
+ * One per judged unit: `from` inclusions (Coverage weight), `from` negations
8
+ * and `exemptFiles`, `forbiddenImports`, `allowedImports` (Advisory). The
9
+ * `list` of each is its `BoundaryMarkableList` value — the list its marks carry.
10
+ */
11
+ readonly observations: readonly IUnitObservation[];
12
+ /** The rule's `from` inclusion globs — named when the rule matched no file. */
13
+ readonly includeGlobs: readonly string[];
14
+ /** Scanned files the scopes were counted against. */
15
+ readonly filesScanned: number;
16
+ /** Where that file list came from (`edges`: a hand-built scan without a file list). */
17
+ readonly fileUniverse: 'scan' | 'edges';
18
+ /** Governed files: inside `from`, not exempt. */
19
+ readonly filesInScope: number;
20
+ /** Inside `from` but exempt. */
21
+ readonly exemptFilesInScope: number;
22
+ /** The rule's unsuppressed violations. */
23
+ readonly violations: number;
24
+ /** A governed file in the rule's scope could not be read: the rule is partial, never "matched nothing". */
25
+ readonly unread: boolean;
26
+ }
27
+ //# sourceMappingURL=i-boundary-rule-settle-input.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"i-boundary-rule-settle-input.d.ts","sourceRoot":"","sources":["../../src/evaluate/i-boundary-rule-settle-input.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AACvD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,2BAA2B,CAAC;AAE/D,2HAA2H;AAC3H,MAAM,WAAW,wBAAwB;IACvC,QAAQ,CAAC,IAAI,EAAE,IAAI,CAAC,aAAa,EAAE,IAAI,GAAG,UAAU,GAAG,aAAa,GAAG,kBAAkB,CAAC,CAAC;IAC3F;;;;OAIG;IACH,QAAQ,CAAC,YAAY,EAAE,SAAS,gBAAgB,EAAE,CAAC;IACnD,+EAA+E;IAC/E,QAAQ,CAAC,YAAY,EAAE,SAAS,MAAM,EAAE,CAAC;IACzC,qDAAqD;IACrD,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,uFAAuF;IACvF,QAAQ,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC;IACxC,iDAAiD;IACjD,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,gCAAgC;IAChC,QAAQ,CAAC,kBAAkB,EAAE,MAAM,CAAC;IACpC,0CAA0C;IAC1C,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,2GAA2G;IAC3G,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAC;CAC1B"}
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,23 @@
1
+ import type { ISettledRuleEmptiness, ISettledUnitLiveness, IUnitStateLists, IVerdictCoverage } from '@shrkcrft/core';
2
+ import type { BoundaryRuleStatus, IBoundaryDeadUnit } from './evaluate-boundaries.js';
3
+ /** `settleBoundaryRule`'s answer: how one rule settled — its units, its emptiness, and the fields its coverage record carries. */
4
+ export interface IBoundaryRuleSettlement {
5
+ /** Every unit's state (core `settleUnitLiveness`). */
6
+ readonly settled: ISettledUnitLiveness;
7
+ /** What the rule's empty scope is (core `settleRuleEmptiness`). */
8
+ readonly emptiness: ISettledRuleEmptiness;
9
+ readonly status: BoundaryRuleStatus;
10
+ /** Why the rule checked nothing, when `skipped`. */
11
+ readonly skipReason?: string;
12
+ /** A skip that fails the run (`failOnEmpty`). */
13
+ readonly failedOnEmpty: boolean;
14
+ /** The primary verdict record: the scope-glob shortfall (record A), or the acceptance itself for an IntendedEmpty rule. */
15
+ readonly coverage: IVerdictCoverage;
16
+ /** Record B — the rule's intended-empty units, `acceptedBy: 'expectEmpty'`. */
17
+ readonly unitAcceptance?: IVerdictCoverage;
18
+ /** The rule's dead / intended-empty / went-live unit lines (`unitStateLists`), when it has any. */
19
+ readonly units?: IUnitStateLists;
20
+ /** The settled `.dead` (UNMARKED units only), as the boundary JSON has always reported them. */
21
+ readonly deadUnits: readonly IBoundaryDeadUnit[];
22
+ }
23
+ //# sourceMappingURL=i-boundary-rule-settlement.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"i-boundary-rule-settlement.d.ts","sourceRoot":"","sources":["../../src/evaluate/i-boundary-rule-settlement.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,qBAAqB,EACrB,oBAAoB,EACpB,eAAe,EACf,gBAAgB,EACjB,MAAM,gBAAgB,CAAC;AACxB,OAAO,KAAK,EAAE,kBAAkB,EAAE,iBAAiB,EAAE,MAAM,0BAA0B,CAAC;AAEtF,kIAAkI;AAClI,MAAM,WAAW,uBAAuB;IACtC,sDAAsD;IACtD,QAAQ,CAAC,OAAO,EAAE,oBAAoB,CAAC;IACvC,mEAAmE;IACnE,QAAQ,CAAC,SAAS,EAAE,qBAAqB,CAAC;IAC1C,QAAQ,CAAC,MAAM,EAAE,kBAAkB,CAAC;IACpC,oDAAoD;IACpD,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;IAC7B,iDAAiD;IACjD,QAAQ,CAAC,aAAa,EAAE,OAAO,CAAC;IAChC,2HAA2H;IAC3H,QAAQ,CAAC,QAAQ,EAAE,gBAAgB,CAAC;IACpC,+EAA+E;IAC/E,QAAQ,CAAC,cAAc,CAAC,EAAE,gBAAgB,CAAC;IAC3C,mGAAmG;IACnG,QAAQ,CAAC,KAAK,CAAC,EAAE,eAAe,CAAC;IACjC,gGAAgG;IAChG,QAAQ,CAAC,SAAS,EAAE,SAAS,iBAAiB,EAAE,CAAC;CAClD"}
@@ -0,0 +1 @@
1
+ export {};