@nielspeter/eess 0.2.2 → 0.5.0

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 (209) hide show
  1. package/README.md +97 -4
  2. package/dist/assertion-advice.d.ts +26 -0
  3. package/dist/assertion-advice.d.ts.map +1 -0
  4. package/dist/assertion-advice.js +29 -0
  5. package/dist/assertion-advice.js.map +1 -0
  6. package/dist/baseline-generator.d.ts +9 -0
  7. package/dist/baseline-generator.d.ts.map +1 -1
  8. package/dist/baseline-generator.js +9 -0
  9. package/dist/baseline-generator.js.map +1 -1
  10. package/dist/baseline.d.ts +69 -4
  11. package/dist/baseline.d.ts.map +1 -1
  12. package/dist/baseline.js +119 -13
  13. package/dist/baseline.js.map +1 -1
  14. package/dist/cache-registry.d.ts +14 -0
  15. package/dist/cache-registry.d.ts.map +1 -0
  16. package/dist/cache-registry.js +37 -0
  17. package/dist/cache-registry.js.map +1 -0
  18. package/dist/cardinality.d.ts +35 -0
  19. package/dist/cardinality.d.ts.map +1 -0
  20. package/dist/cardinality.js +80 -0
  21. package/dist/cardinality.js.map +1 -0
  22. package/dist/check-options.d.ts +14 -0
  23. package/dist/check-options.d.ts.map +1 -1
  24. package/dist/cli-config.d.ts +59 -0
  25. package/dist/cli-config.d.ts.map +1 -0
  26. package/dist/cli-config.js +84 -0
  27. package/dist/cli-config.js.map +1 -0
  28. package/dist/collect-result.d.ts +94 -0
  29. package/dist/collect-result.d.ts.map +1 -0
  30. package/dist/collect-result.js +142 -0
  31. package/dist/collect-result.js.map +1 -0
  32. package/dist/combinators.d.ts +14 -0
  33. package/dist/combinators.d.ts.map +1 -1
  34. package/dist/combinators.js +53 -16
  35. package/dist/combinators.js.map +1 -1
  36. package/dist/comment-suppression.d.ts +60 -0
  37. package/dist/comment-suppression.d.ts.map +1 -0
  38. package/dist/comment-suppression.js +89 -0
  39. package/dist/comment-suppression.js.map +1 -0
  40. package/dist/condition.d.ts +43 -0
  41. package/dist/condition.d.ts.map +1 -1
  42. package/dist/condition.js +48 -1
  43. package/dist/condition.js.map +1 -1
  44. package/dist/correspondence-core.d.ts +35 -0
  45. package/dist/correspondence-core.d.ts.map +1 -0
  46. package/dist/correspondence-core.js +38 -0
  47. package/dist/correspondence-core.js.map +1 -0
  48. package/dist/correspondence.d.ts +57 -5
  49. package/dist/correspondence.d.ts.map +1 -1
  50. package/dist/correspondence.js +74 -5
  51. package/dist/correspondence.js.map +1 -1
  52. package/dist/dedupe-config-findings.d.ts +56 -0
  53. package/dist/dedupe-config-findings.d.ts.map +1 -0
  54. package/dist/dedupe-config-findings.js +101 -0
  55. package/dist/dedupe-config-findings.js.map +1 -0
  56. package/dist/define.d.ts +44 -2
  57. package/dist/define.d.ts.map +1 -1
  58. package/dist/define.js +45 -4
  59. package/dist/define.js.map +1 -1
  60. package/dist/diff-aware.d.ts +8 -1
  61. package/dist/diff-aware.d.ts.map +1 -1
  62. package/dist/diff-aware.js +39 -9
  63. package/dist/diff-aware.js.map +1 -1
  64. package/dist/diff-disclosure.d.ts +72 -0
  65. package/dist/diff-disclosure.d.ts.map +1 -0
  66. package/dist/diff-disclosure.js +110 -0
  67. package/dist/diff-disclosure.js.map +1 -0
  68. package/dist/disk-set.d.ts +27 -0
  69. package/dist/disk-set.d.ts.map +1 -0
  70. package/dist/disk-set.js +12 -0
  71. package/dist/disk-set.js.map +1 -0
  72. package/dist/edge-coverage.d.ts +96 -0
  73. package/dist/edge-coverage.d.ts.map +1 -0
  74. package/dist/edge-coverage.js +108 -0
  75. package/dist/edge-coverage.js.map +1 -0
  76. package/dist/emitter-findings.d.ts +167 -0
  77. package/dist/emitter-findings.d.ts.map +1 -0
  78. package/dist/emitter-findings.js +237 -0
  79. package/dist/emitter-findings.js.map +1 -0
  80. package/dist/errors.d.ts +73 -0
  81. package/dist/errors.d.ts.map +1 -1
  82. package/dist/errors.js +93 -0
  83. package/dist/errors.js.map +1 -1
  84. package/dist/exclusion-comments.d.ts +36 -1
  85. package/dist/exclusion-comments.d.ts.map +1 -1
  86. package/dist/exclusion-comments.js +185 -37
  87. package/dist/exclusion-comments.js.map +1 -1
  88. package/dist/execute-rule.d.ts +11 -1
  89. package/dist/execute-rule.d.ts.map +1 -1
  90. package/dist/execute-rule.js +220 -11
  91. package/dist/execute-rule.js.map +1 -1
  92. package/dist/file-walk.d.ts +22 -0
  93. package/dist/file-walk.d.ts.map +1 -0
  94. package/dist/file-walk.js +51 -0
  95. package/dist/file-walk.js.map +1 -0
  96. package/dist/format-github.d.ts.map +1 -1
  97. package/dist/format-github.js +37 -5
  98. package/dist/format-github.js.map +1 -1
  99. package/dist/format-json.d.ts +96 -1
  100. package/dist/format-json.d.ts.map +1 -1
  101. package/dist/format-json.js +94 -9
  102. package/dist/format-json.js.map +1 -1
  103. package/dist/format.d.ts +4 -2
  104. package/dist/format.d.ts.map +1 -1
  105. package/dist/format.js +41 -19
  106. package/dist/format.js.map +1 -1
  107. package/dist/glob-site.d.ts +232 -0
  108. package/dist/glob-site.d.ts.map +1 -0
  109. package/dist/glob-site.js +156 -0
  110. package/dist/glob-site.js.map +1 -0
  111. package/dist/identity-root.d.ts +55 -0
  112. package/dist/identity-root.d.ts.map +1 -0
  113. package/dist/identity-root.js +170 -0
  114. package/dist/identity-root.js.map +1 -0
  115. package/dist/index.d.ts +18 -12
  116. package/dist/index.d.ts.map +1 -1
  117. package/dist/index.js +12 -10
  118. package/dist/index.js.map +1 -1
  119. package/dist/internal.d.ts +55 -0
  120. package/dist/internal.d.ts.map +1 -0
  121. package/dist/internal.js +92 -0
  122. package/dist/internal.js.map +1 -0
  123. package/dist/mask-non-comment.d.ts +49 -0
  124. package/dist/mask-non-comment.d.ts.map +1 -0
  125. package/dist/mask-non-comment.js +261 -0
  126. package/dist/mask-non-comment.js.map +1 -0
  127. package/dist/owns-empty-discovery.d.ts +5 -0
  128. package/dist/owns-empty-discovery.d.ts.map +1 -0
  129. package/dist/owns-empty-discovery.js +85 -0
  130. package/dist/owns-empty-discovery.js.map +1 -0
  131. package/dist/path-suffix.d.ts +57 -0
  132. package/dist/path-suffix.d.ts.map +1 -0
  133. package/dist/path-suffix.js +73 -0
  134. package/dist/path-suffix.js.map +1 -0
  135. package/dist/path-universe.d.ts +44 -0
  136. package/dist/path-universe.d.ts.map +1 -0
  137. package/dist/path-universe.js +21 -0
  138. package/dist/path-universe.js.map +1 -0
  139. package/dist/predicate.d.ts +45 -0
  140. package/dist/predicate.d.ts.map +1 -1
  141. package/dist/predicate.js +20 -1
  142. package/dist/predicate.js.map +1 -1
  143. package/dist/preset-dispatch.d.ts +6 -15
  144. package/dist/preset-dispatch.d.ts.map +1 -1
  145. package/dist/preset-dispatch.js +35 -17
  146. package/dist/preset-dispatch.js.map +1 -1
  147. package/dist/project-relative.d.ts +25 -0
  148. package/dist/project-relative.d.ts.map +1 -0
  149. package/dist/project-relative.js +54 -0
  150. package/dist/project-relative.js.map +1 -0
  151. package/dist/report.d.ts +42 -12
  152. package/dist/report.d.ts.map +1 -1
  153. package/dist/report.js +190 -14
  154. package/dist/report.js.map +1 -1
  155. package/dist/rule-builder-like.d.ts +17 -0
  156. package/dist/rule-builder-like.d.ts.map +1 -0
  157. package/dist/rule-builder-like.js +2 -0
  158. package/dist/rule-builder-like.js.map +1 -0
  159. package/dist/rule-builder.d.ts +124 -84
  160. package/dist/rule-builder.d.ts.map +1 -1
  161. package/dist/rule-builder.js +230 -181
  162. package/dist/rule-builder.js.map +1 -1
  163. package/dist/rule-description.d.ts +83 -1
  164. package/dist/rule-description.d.ts.map +1 -1
  165. package/dist/rule-description.js +75 -1
  166. package/dist/rule-description.js.map +1 -1
  167. package/dist/rule-metadata.d.ts +20 -3
  168. package/dist/rule-metadata.d.ts.map +1 -1
  169. package/dist/selection-memo.d.ts +45 -0
  170. package/dist/selection-memo.d.ts.map +1 -0
  171. package/dist/selection-memo.js +66 -0
  172. package/dist/selection-memo.js.map +1 -0
  173. package/dist/shallow-clone.d.ts +20 -0
  174. package/dist/shallow-clone.d.ts.map +1 -0
  175. package/dist/shallow-clone.js +27 -0
  176. package/dist/shallow-clone.js.map +1 -0
  177. package/dist/silent-exclusion.d.ts +12 -0
  178. package/dist/silent-exclusion.d.ts.map +1 -1
  179. package/dist/silent-exclusion.js +22 -0
  180. package/dist/silent-exclusion.js.map +1 -1
  181. package/dist/stderr.d.ts +16 -0
  182. package/dist/stderr.d.ts.map +1 -0
  183. package/dist/stderr.js +73 -0
  184. package/dist/stderr.js.map +1 -0
  185. package/dist/terminal-builder.d.ts +224 -10
  186. package/dist/terminal-builder.d.ts.map +1 -1
  187. package/dist/terminal-builder.js +336 -46
  188. package/dist/terminal-builder.js.map +1 -1
  189. package/dist/type-guards.d.ts +38 -0
  190. package/dist/type-guards.d.ts.map +1 -0
  191. package/dist/type-guards.js +42 -0
  192. package/dist/type-guards.js.map +1 -0
  193. package/dist/unsuppressable.d.ts +54 -0
  194. package/dist/unsuppressable.d.ts.map +1 -0
  195. package/dist/unsuppressable.js +76 -0
  196. package/dist/unsuppressable.js.map +1 -0
  197. package/dist/vacuity-findings.d.ts +49 -0
  198. package/dist/vacuity-findings.d.ts.map +1 -0
  199. package/dist/vacuity-findings.js +94 -0
  200. package/dist/vacuity-findings.js.map +1 -0
  201. package/dist/violation.d.ts +173 -4
  202. package/dist/violation.d.ts.map +1 -1
  203. package/dist/violation.js +262 -1
  204. package/dist/violation.js.map +1 -1
  205. package/dist/watch.d.ts +71 -0
  206. package/dist/watch.d.ts.map +1 -0
  207. package/dist/watch.js +129 -0
  208. package/dist/watch.js.map +1 -0
  209. package/package.json +6 -1
@@ -1,5 +1,7 @@
1
+ import { collectResult } from './collect-result.js';
1
2
  import { TerminalBuilder } from './terminal-builder.js';
2
3
  import { matchSelections } from './matching.js';
4
+ import { marksAssertsCardinality, assertsCardinality as checkAssertsCardinality, } from './cardinality.js';
3
5
  function violationFor(info, message, ruleId) {
4
6
  return {
5
7
  rule: 'correspondence',
@@ -33,19 +35,52 @@ export class CorrespondenceBuilder extends TerminalBuilder {
33
35
  andShould() {
34
36
  return this;
35
37
  }
36
- /** Every element on the named side(s) has a counterpart on the other. */
38
+ /**
39
+ * Every element on the named side(s) has a counterpart on the other. An
40
+ * absence assertion — "no element lacks a counterpart" — so it's marked
41
+ * cardinality-exempt (see `assertsCardinality()`).
42
+ */
37
43
  beComplete(opts = {}) {
38
- this._checks.push((o, ruleId) => completeness(o, opts.direction ?? 'both', ruleId));
44
+ this._checks.push(marksAssertsCardinality((o, ruleId) => completeness(o, opts.direction ?? 'both', ruleId)));
39
45
  return this;
40
46
  }
41
- /** Related endpoints on one side have matching relations on the other. */
47
+ /**
48
+ * Related endpoints on one side have matching relations on the other. Also
49
+ * an absence assertion — "no matched pair disagrees" — cardinality-exempt
50
+ * for the same reason `beComplete()` is.
51
+ */
42
52
  preserveRelations(spec) {
43
- this._checks.push((o, ruleId) => relations(o, spec, ruleId));
53
+ this._checks.push(marksAssertsCardinality((o, ruleId) => relations(o, spec, ruleId)));
44
54
  return this;
45
55
  }
46
56
  collectViolations() {
47
57
  const ruleId = this._metadata?.id;
48
- return this._checks.flatMap((check) => check(this.opts, ruleId));
58
+ const violations = this._checks.flatMap((check) => check(this.opts, ruleId));
59
+ // ADR-010: the unit is the key sets of both sides — a correspondence over
60
+ // an empty left AND an empty right examined nothing, regardless of how
61
+ // many checks were chained; one side alone being empty is still real
62
+ // examination of the other.
63
+ const examined = this.opts.left.elements.length + this.opts.right.elements.length;
64
+ return collectResult(violations, { examined });
65
+ }
66
+ /**
67
+ * ADR-010: mirrors `RuleBuilder.assertsCardinality()` — per-check, not a
68
+ * blanket class-wide exemption. `beComplete()`/`preserveRelations()` mark
69
+ * the function they push via `marksAssertsCardinality()`, the same
70
+ * unforgeable registry `.notExist()` uses, because both are absence
71
+ * assertions ("no element lacks a counterpart", "no matched pair
72
+ * disagrees") that can never produce a violation from an empty side — an
73
+ * empty selection here is the correctly computed input (e.g. release-gate's
74
+ * "no packages changed this diff" on a clean tree), not a broken instrument.
75
+ * `.every()`, not `.some()`, for the same reason `RuleBuilder` uses it: a
76
+ * future check type that isn't an absence assertion must not silently
77
+ * inherit this exemption just because it's chained alongside one that is.
78
+ * An empty `_checks` list is not exempt — nothing was asserted yet.
79
+ */
80
+ assertsCardinality() {
81
+ if (this._checks.length === 0)
82
+ return false;
83
+ return this._checks.every((check) => checkAssertsCardinality(check));
49
84
  }
50
85
  }
51
86
  /** Bind two selections into a correspondence. */
@@ -124,4 +159,38 @@ function relations(o, spec, ruleId) {
124
159
  }
125
160
  return out;
126
161
  }
162
+ /**
163
+ * The elements a rule's predicates admit, as a `Selection`.
164
+ *
165
+ * A free function over the elements and the predicates rather than a method,
166
+ * for the same reason as `recordExclusions`: the two builders that need it sit
167
+ * in incompatible hierarchies — `eess-ts` has three levels where the kernel has
168
+ * two — so there is no common base to hold it. The two copies were
169
+ * byte-identical.
170
+ */
171
+ export function selectMatching(elements, predicates, opts) {
172
+ return {
173
+ elements: matchingElements(elements, predicates),
174
+ label: opts.label,
175
+ identify: opts.identify,
176
+ };
177
+ }
178
+ /**
179
+ * The elements every predicate admits.
180
+ *
181
+ * The same conjunction as `selectMatching`, without a `Selection` around it —
182
+ * split out because `eess-ts`'s two GraphQL builders want the ARRAY, to memoise
183
+ * it as the one set both `check()` and `diagnose()` read (plan 0096). They each
184
+ * carried this filter inline, byte for byte, which `no-copy-paste` reported at
185
+ * 100%; folding them onto `selectMatching` was not open, since a `Selection`
186
+ * carries a label and an `identify` neither of them has at that point.
187
+ *
188
+ * Sharing it with `selectMatching` matters beyond the duplicate count: "the
189
+ * elements a rule selected" must be ONE answer, or a builder's `.select()` side
190
+ * and its condition side can disagree about which elements the predicates
191
+ * admitted — the divergence ADR-010 exists to make impossible.
192
+ */
193
+ export function matchingElements(elements, predicates) {
194
+ return elements.filter((element) => predicates.every((predicate) => predicate.test(element)));
195
+ }
127
196
  //# sourceMappingURL=correspondence.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"correspondence.js","sourceRoot":"","sources":["../src/correspondence.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAA;AACvD,OAAO,EAAE,eAAe,EAAqB,MAAM,eAAe,CAAA;AAyDlE,SAAS,YAAY,CAAC,IAAiB,EAAE,OAAe,EAAE,MAAe;IACvE,OAAO;QACL,IAAI,EAAE,gBAAgB;QACtB,MAAM;QACN,OAAO,EAAE,IAAI,CAAC,IAAI;QAClB,IAAI,EAAE,IAAI,CAAC,IAAI,IAAI,aAAa;QAChC,IAAI,EAAE,IAAI,CAAC,IAAI,IAAI,CAAC;QACpB,OAAO;QACP,SAAS,EAAE,SAAS;KACrB,CAAA;AACH,CAAC;AAID;;;;;;GAMG;AACH,MAAM,OAAO,qBAA4B,SAAQ,eAAe;IAGjC;IAFZ,OAAO,GAAkB,EAAE,CAAA;IAE5C,YAA6B,IAAiC;QAC5D,KAAK,EAAE,CAAA;QADoB,SAAI,GAAJ,IAAI,CAA6B;IAE9D,CAAC;IAED,2DAA2D;IAC3D,MAAM;QACJ,OAAO,IAAI,CAAA;IACb,CAAC;IAED,gDAAgD;IAChD,SAAS;QACP,OAAO,IAAI,CAAA;IACb,CAAC;IAED,yEAAyE;IACzE,UAAU,CAAC,OAAkC,EAAE;QAC7C,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,EAAE,CAAC,YAAY,CAAC,CAAC,EAAE,IAAI,CAAC,SAAS,IAAI,MAAM,EAAE,MAAM,CAAC,CAAC,CAAA;QACnF,OAAO,IAAI,CAAA;IACb,CAAC;IAED,0EAA0E;IAC1E,iBAAiB,CAAC,IAAwB;QACxC,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,EAAE,CAAC,SAAS,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,CAAC,CAAC,CAAA;QAC5D,OAAO,IAAI,CAAA;IACb,CAAC;IAES,iBAAiB;QACzB,MAAM,MAAM,GAAG,IAAI,CAAC,SAAS,EAAE,EAAE,CAAA;QACjC,OAAO,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC,CAAA;IAClE,CAAC;CACF;AAED,iDAAiD;AACjD,MAAM,UAAU,cAAc,CAC5B,IAAiC;IAEjC,OAAO,IAAI,qBAAqB,CAAC,IAAI,CAAC,CAAA;AACxC,CAAC;AAED,6EAA6E;AAE7E,SAAS,YAAY,CAAO,CAA8B;IACxD,IAAI,CAAC,CAAC,OAAO;QAAE,OAAO,EAAE,OAAO,EAAE,CAAC,CAAC,OAAO,EAAE,CAAA;IAC5C,MAAM,EAAE,KAAK,EAAE,IAAI,EAAE,KAAK,EAAE,GAAG,CAAC,CAAA;IAChC,2EAA2E;IAC3E,wEAAwE;IACxE,IAAI,KAAK,KAAK,SAAS,EAAE,CAAC;QACxB,OAAO;YACL,OAAO,EAAE,CAAC,EAAE,EAAE,EAAE,CAAC,IAAI,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,IAAI;YACvC,QAAQ,EAAE,CAAC,EAAE,EAAE,EAAE,CAAC,KAAK,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,IAAI;SAC1C,CAAA;IACH,CAAC;IACD,IAAI,OAAO,KAAK,KAAK,UAAU,EAAE,CAAC;QAChC,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,QAAQ,EAAE,KAAK,EAAE,CAAA;IAC5C,CAAC;IACD,OAAO,EAAE,OAAO,EAAE,KAAK,CAAC,IAAI,EAAE,QAAQ,EAAE,KAAK,CAAC,KAAK,EAAE,CAAA;AACvD,CAAC;AAED,SAAS,YAAY,CACnB,CAA8B,EAC9B,SAAoB,EACpB,MAAe;IAEf,MAAM,GAAG,GAAoB,EAAE,CAAA;IAC/B,MAAM,WAAW,GAAG,CAAC,CAAC,OAAO,EAAE,IAAI,CAAA;IACnC,MAAM,YAAY,GAAG,CAAC,CAAC,OAAO,EAAE,KAAK,CAAA;IACrC,MAAM,MAAM,GAAG,eAAe,CAAC,CAAC,CAAC,IAAI,CAAC,QAAQ,EAAE,CAAC,CAAC,KAAK,CAAC,QAAQ,EAAE,YAAY,CAAC,CAAC,CAAC,CAAC,CAAA;IAElF,IAAI,SAAS,KAAK,eAAe,EAAE,CAAC;QAClC,KAAK,MAAM,CAAC,IAAI,MAAM,CAAC,aAAa,EAAE,CAAC;YACrC,MAAM,IAAI,GAAG,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAA;YAC/B,MAAM,KAAK,GAAG,WAAW,CAAC,CAAC,CAAC,OAAO,WAAW,CAAC,IAAI,EAAE,CAAC,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAA;YACpE,GAAG,CAAC,IAAI,CACN,YAAY,CACV,IAAI,EACJ,GAAG,CAAC,CAAC,IAAI,CAAC,KAAK,KAAK,IAAI,CAAC,IAAI,qBAAqB,CAAC,CAAC,KAAK,CAAC,KAAK,GAAG,KAAK,EAAE,EACzE,MAAM,CACP,CACF,CAAA;QACH,CAAC;QACD,KAAK,MAAM,CAAC,IAAI,MAAM,CAAC,aAAa,EAAE,CAAC;YACrC,MAAM,IAAI,GAAG,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAA;YAC/B,GAAG,CAAC,IAAI,CACN,YAAY,CACV,IAAI,EACJ,GAAG,CAAC,CAAC,IAAI,CAAC,KAAK,KAAK,IAAI,CAAC,IAAI,sBAAsB,CAAC,CAAC,KAAK,CAAC,KAAK,qCAAqC,EACrG,MAAM,CACP,CACF,CAAA;QACH,CAAC;IACH,CAAC;IACD,IAAI,SAAS,KAAK,eAAe,EAAE,CAAC;QAClC,KAAK,MAAM,CAAC,IAAI,MAAM,CAAC,cAAc,EAAE,CAAC;YACtC,MAAM,IAAI,GAAG,CAAC,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAA;YAChC,MAAM,KAAK,GAAG,YAAY,CAAC,CAAC,CAAC,OAAO,YAAY,CAAC,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAA;YACrE,GAAG,CAAC,IAAI,CACN,YAAY,CACV,IAAI,EACJ,GAAG,CAAC,CAAC,KAAK,CAAC,KAAK,KAAK,IAAI,CAAC,IAAI,qBAAqB,CAAC,CAAC,IAAI,CAAC,KAAK,GAAG,KAAK,EAAE,EACzE,MAAM,CACP,CACF,CAAA;QACH,CAAC;IACH,CAAC;IACD,OAAO,GAAG,CAAA;AACZ,CAAC;AAED,SAAS,SAAS,CAChB,CAA8B,EAC9B,IAAwB,EACxB,MAAe;IAEf,MAAM,GAAG,GAAoB,EAAE,CAAA;IAC/B,MAAM,SAAS,GAAG,IAAI,CAAC,SAAS,IAAI,MAAM,CAAA;IAC1C,MAAM,MAAM,GAAG,eAAe,CAAC,CAAC,CAAC,IAAI,CAAC,QAAQ,EAAE,CAAC,CAAC,KAAK,CAAC,QAAQ,EAAE,YAAY,CAAC,CAAC,CAAC,CAAC,CAAA;IAElF,mEAAmE;IACnE,MAAM,MAAM,GAAG,IAAI,GAAG,EAAU,CAAA;IAChC,KAAK,MAAM,CAAC,IAAI,MAAM,CAAC,KAAK,EAAE,CAAC;QAC7B,MAAM,IAAI,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,CAAA;QAC/B,IAAI,IAAI;YAAE,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,KAAK,CAAC,CAAA;;YACvB,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAA;IACpC,CAAC;IAED,IAAI,SAAS,KAAK,eAAe,EAAE,CAAC;QAClC,KAAK,MAAM,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,QAAQ,EAAE,CAAC;YAChC,MAAM,YAAY,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,EAAE,CAAA;YACxC,IAAI,YAAY,CAAC,MAAM,KAAK,CAAC;gBAAE,SAAQ;YACvC,MAAM,cAAc,GAAG,IAAI,GAAG,CAAC,YAAY,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAA;YAC1E,MAAM,IAAI,GAAG,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAA;YAC/B,KAAK,MAAM,GAAG,IAAI,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC;gBAC/B,IAAI,CAAC,cAAc,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC;oBAC7B,GAAG,CAAC,IAAI,CACN,YAAY,CACV,IAAI,EACJ,GAAG,CAAC,CAAC,IAAI,CAAC,KAAK,KAAK,IAAI,CAAC,IAAI,iBAAiB,GAAG,aAAa,CAAC,CAAC,KAAK,CAAC,KAAK,uBAAuB,EAClG,MAAM,CACP,CACF,CAAA;gBACH,CAAC;YACH,CAAC;QACH,CAAC;IACH,CAAC;IACD,OAAO,GAAG,CAAA;AACZ,CAAC"}
1
+ {"version":3,"file":"correspondence.js","sourceRoot":"","sources":["../src/correspondence.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,56 @@
1
+ /**
2
+ * One option, one finding — plan 0074, deciding 0069's appendix item 4.
3
+ *
4
+ * A preset generates rules combinatorially. `strictBoundaries` emits one
5
+ * `shared-isolation` rule per (shared glob × boundary folder) pair, so one
6
+ * wrong character in one option produces a finding per pair — same id, same
7
+ * glob, same cause, same remedy.
8
+ *
9
+ * **Measured on this repository**, `strictBoundaries({ folders: '**\/src/*',
10
+ * shared: ['**\/src/not-built-yet/**'] })`: 84 rules, **83 configuration
11
+ * findings, 2 distinct messages**. (0069's appendix recorded 38 for the same
12
+ * shape; the repo has grown more boundary folders since.) Every one is *true*,
13
+ * which is why exempting them is wrong — bug 0018 is precisely a preset
14
+ * silently enforcing nothing. But a report shaped like that makes a one-line
15
+ * fix look like an 83-line disaster, and it contradicts this project's own rule
16
+ * that findings are **identities, never totals**.
17
+ *
18
+ * So the fan-out collapses to the identity that matches the edit: the option
19
+ * the user actually wrote. The count survives as *context on that finding*
20
+ * rather than as 83 rows — which is not the snapshot ADR-009 rule 4 bars,
21
+ * because it is attached to a named identity rather than standing in for one.
22
+ *
23
+ * ## What the key is, and why each part is in it
24
+ *
25
+ * `(rule file, rule id, offending glob)`.
26
+ *
27
+ * - **The glob, not the message.** 0069's appendix is explicit: two different
28
+ * `shared` entries that both match nothing are **two findings, because they
29
+ * are two edits**. Keying on the message would merge them whenever the
30
+ * wording happened to coincide, and split them whenever it did not.
31
+ * - **The rule id**, so `shared-discovery` and `shared-isolation` stay
32
+ * distinct: they are different faults about the same glob and have different
33
+ * remedies.
34
+ * - **The rule file.** Two rule files that instantiate the same preset with the
35
+ * same bad glob are two edits in two places, and collapsing them would hide
36
+ * one. Empty in a test-file context, where every rule shares one origin, so
37
+ * the key degrades to `(id, glob)` there rather than merging across files
38
+ * that were never distinguished.
39
+ *
40
+ * ## What is deliberately NOT deduplicated
41
+ *
42
+ * Ordinary violations. Each names a distinct element at a distinct position and
43
+ * every one is a separate edit — collapsing them is the snapshot rule 4 bars,
44
+ * and it would hide real work. Only **configuration findings** fan out from a
45
+ * single authored mistake, and `bypassFilters` is exactly the flag that marks
46
+ * one.
47
+ */
48
+ import type { ArchViolation } from './violation.js';
49
+ /**
50
+ * Collapse each configuration finding that a preset fanned out.
51
+ *
52
+ * Order-preserving, keeping the **first** occurrence, so the report reads in
53
+ * the order the rules were declared rather than in hash order.
54
+ */
55
+ export declare function dedupeConfigFindings(violations: readonly ArchViolation[]): ArchViolation[];
56
+ //# sourceMappingURL=dedupe-config-findings.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dedupe-config-findings.d.ts","sourceRoot":"","sources":["../src/dedupe-config-findings.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8CG;AACH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAA;AAenD;;;;;GAKG;AACH,wBAAgB,oBAAoB,CAAC,UAAU,EAAE,SAAS,aAAa,EAAE,GAAG,aAAa,EAAE,CAsC1F"}
@@ -0,0 +1,101 @@
1
+ // The ids that are never collapsed — see `keyFor`. Imported rather than
2
+ // re-listed: `emitter-findings.ts` declares them and owns the set, so the next
3
+ // id is added in one place. A second local list here is how this guard would
4
+ // silently stop covering a fifth id.
5
+ import { EMITTER_IDS } from './emitter-findings.js';
6
+ /** How the surviving finding states the fan-out it stands for. */
7
+ function affectedNote(count) {
8
+ return (` This one option generated ${String(count)} rules that cannot enforce anything; they are ` +
9
+ `not listed separately because they are one edit.`);
10
+ }
11
+ /**
12
+ * Collapse each configuration finding that a preset fanned out.
13
+ *
14
+ * Order-preserving, keeping the **first** occurrence, so the report reads in
15
+ * the order the rules were declared rather than in hash order.
16
+ */
17
+ export function dedupeConfigFindings(violations) {
18
+ // Count first, then emit. A single pass that appends the note on first sight
19
+ // cannot know the total yet, and appending it on the last sight would reorder
20
+ // the report.
21
+ const counts = new Map();
22
+ for (const violation of violations) {
23
+ const key = keyFor(violation);
24
+ if (key === undefined)
25
+ continue;
26
+ counts.set(key, (counts.get(key) ?? 0) + 1);
27
+ }
28
+ const emitted = new Set();
29
+ const result = [];
30
+ for (const violation of violations) {
31
+ const key = keyFor(violation);
32
+ if (key === undefined) {
33
+ result.push(violation);
34
+ continue;
35
+ }
36
+ if (emitted.has(key))
37
+ continue;
38
+ emitted.add(key);
39
+ const total = counts.get(key) ?? 1;
40
+ if (total === 1) {
41
+ result.push(violation);
42
+ continue;
43
+ }
44
+ // The note goes on `message` AND `suggestion`. They are separate fields with
45
+ // separate consumers — the terminal prints `Fix:` from `suggestion`, and
46
+ // `--format json` carries both — and a reader who sees only one of them
47
+ // otherwise learns a different number of affected rules from each.
48
+ result.push({
49
+ ...violation,
50
+ message: violation.message + affectedNote(total),
51
+ suggestion: violation.suggestion === undefined ? undefined : violation.suggestion + affectedNote(total),
52
+ });
53
+ }
54
+ return result;
55
+ }
56
+ /**
57
+ * The dedupe key, or `undefined` for a finding that must never be collapsed.
58
+ *
59
+ * `undefined` for anything that is not a configuration finding, and for a
60
+ * configuration finding with no `element` to key on — a missing key must mean
61
+ * "keep it", never "merge everything that lacks one".
62
+ */
63
+ function keyFor(violation) {
64
+ if (violation.bypassFilters !== true)
65
+ return undefined;
66
+ const identity = violation.ruleId ?? violation.rule;
67
+ // `'unnamed'` is `TerminalBuilder.describeRule()`'s SENTINEL, not an identity.
68
+ // A family that never overrides it gives every one of its rules the same key,
69
+ // so distinct rules merged and the survivor claimed they were "one edit" —
70
+ // measured on three different `duplicateBodies` detectors in plan 0099. A
71
+ // missing key must mean "keep it", never "merge everything that lacks one",
72
+ // which is the rule the empty-string guard beside this already follows.
73
+ if (identity === '' || identity === 'unnamed')
74
+ return undefined;
75
+ if (violation.element === '' || violation.element === 'unnamed')
76
+ return undefined;
77
+ // An emitter finding is about a VERDICT, not about a rule's narrowing, so the
78
+ // collapse's premise — one authored option fanned out by a preset — never
79
+ // holds for it. Each one stands for a different builder that hands back no
80
+ // evidence, and each is its own edit in its own place.
81
+ //
82
+ // It cannot be told apart by shape: `emitter-findings.ts` sets
83
+ // `element: ruleId` (the fault is the verdict, so there is no place in anyone's
84
+ // code to point at) and `file: ''`, so the key degenerates to `('' , id, id)`
85
+ // and every occurrence in a run merges. Measured before this guard: three
86
+ // hand-rolled builders in one rule file reported as ONE finding whose note said
87
+ // they were "one edit". They are three edits — the case 0069's appendix decides
88
+ // the other way ("two different `shared` entries that both match nothing are
89
+ // two findings, because they are two edits").
90
+ //
91
+ // **Keyed on the id set, not on the `element === identity` shape.** A first cut
92
+ // refused a key whenever the element repeated the identity, and that is too
93
+ // broad: a real rule with a real narrowing and no glob to name does the same,
94
+ // and two instances of it genuinely ARE one edit — `the-floor.test.ts`'s
95
+ // "CONTROL: genuinely identical findings still collapse" reds on it, over
96
+ // `smells.duplicateBodies(p).minLines(500)`.
97
+ if (EMITTER_IDS.has(identity))
98
+ return undefined;
99
+ return `${violation.file} ${identity} ${violation.element}`;
100
+ }
101
+ //# sourceMappingURL=dedupe-config-findings.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dedupe-config-findings.js","sourceRoot":"","sources":["../src/dedupe-config-findings.ts"],"names":[],"mappings":"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"}
package/dist/define.d.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  import type { Predicate } from './predicate.js';
2
+ import type { DeclaredGlobs } from './glob-site.js';
2
3
  import type { Condition, ConditionContext } from './condition.js';
3
4
  import type { ArchViolation } from './violation.js';
4
5
  /**
@@ -15,8 +16,40 @@ import type { ArchViolation } from './violation.js';
15
16
  *
16
17
  * classes(p).that().satisfy(isAbstract).should().beExported().check()
17
18
  * ```
19
+ *
20
+ * ## Declaring a glob
21
+ *
22
+ * If the predicate matches paths against a glob, pass it as `globs` — otherwise
23
+ * `doctor` cannot see it, and a typo'd glob narrows the selection to nothing
24
+ * while reporting a clean bill of health. This is a **selector**-position glob,
25
+ * the one position that is acted on today, which is why the omission was bug
26
+ * 0030's higher-severity half.
27
+ *
28
+ * @example
29
+ * ```ts
30
+ * const inGeneratedFolder = definePredicate<SourceFile>(
31
+ * "reside in '**\/generated/**'",
32
+ * (file) => picomatch('**\/generated/**')(file.getFilePath()),
33
+ * globNode({ glob: '**\/generated/**', kind: 'file-path' }),
34
+ * )
35
+ * ```
36
+ *
37
+ * The `kind` must say what the glob is really matched against, because it
38
+ * selects which paths the glob is checked for satisfiability:
39
+ *
40
+ * - `file-path` — an **absolute** file path. Anchor the glob (`'**\/src/**'`,
41
+ * not `'src/**'`), or it matches nothing.
42
+ * - `parent-dir` — the immediate parent directory of a file.
43
+ * - `import-target` — a resolved module path **or a bare specifier**.
44
+ * Deliberately has no path universe, so `'fastify'` is never reported dead.
45
+ * - `specifier` / `literal` — a string in the source rather than a path.
46
+ *
47
+ * A wrong `kind` is believed: declaring a bare specifier as `file-path` earns a
48
+ * false dead-glob report, and declaring a real path as `import-target` silently
49
+ * exempts it from checking. When unsure, the honest choice is to declare
50
+ * nothing — the same position as before this parameter existed.
18
51
  */
19
- export declare function definePredicate<T>(description: string, test: (element: T) => boolean): Predicate<T>;
52
+ export declare function definePredicate<T>(description: string, test: (element: T) => boolean, globs?: DeclaredGlobs): Predicate<T>;
20
53
  /**
21
54
  * Create a custom condition for use in `.should().satisfy()` chains.
22
55
  *
@@ -36,6 +69,15 @@ export declare function definePredicate<T>(description: string, test: (element:
36
69
  *
37
70
  * classes(p).that().extend('Base').should().satisfy(useSharedHelper).check()
38
71
  * ```
72
+ *
73
+ * ## Declaring a glob
74
+ *
75
+ * As with `definePredicate`, pass `globs` if the condition matches paths against
76
+ * one — see that function for the `kind` values and what a wrong one costs. The
77
+ * difference is what happens next: a **condition**-position glob is deliberately
78
+ * *not* reported when it matches nothing, because a denylist glob matching
79
+ * nothing is indistinguishable from a ban being respected. Declaring it makes it
80
+ * visible to `globs()` and `explain`; it does not make it a finding.
39
81
  */
40
- export declare function defineCondition<T>(description: string, evaluate: (elements: T[], context: ConditionContext) => ArchViolation[]): Condition<T>;
82
+ export declare function defineCondition<T>(description: string, evaluate: (elements: T[], context: ConditionContext) => ArchViolation[], globs?: DeclaredGlobs): Condition<T>;
41
83
  //# sourceMappingURL=define.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"define.d.ts","sourceRoot":"","sources":["../src/define.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAA;AAC/C,OAAO,KAAK,EAAE,SAAS,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAA;AACjE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAA;AAEnD;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,eAAe,CAAC,CAAC,EAC/B,WAAW,EAAE,MAAM,EACnB,IAAI,EAAE,CAAC,OAAO,EAAE,CAAC,KAAK,OAAO,GAC5B,SAAS,CAAC,CAAC,CAAC,CAEd;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,eAAe,CAAC,CAAC,EAC/B,WAAW,EAAE,MAAM,EACnB,QAAQ,EAAE,CAAC,QAAQ,EAAE,CAAC,EAAE,EAAE,OAAO,EAAE,gBAAgB,KAAK,aAAa,EAAE,GACtE,SAAS,CAAC,CAAC,CAAC,CAEd"}
1
+ {"version":3,"file":"define.d.ts","sourceRoot":"","sources":["../src/define.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAA;AAC/C,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAA;AACnD,OAAO,KAAK,EAAE,SAAS,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAA;AACjE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAA;AAEnD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8CG;AACH,wBAAgB,eAAe,CAAC,CAAC,EAC/B,WAAW,EAAE,MAAM,EACnB,IAAI,EAAE,CAAC,OAAO,EAAE,CAAC,KAAK,OAAO,EAC7B,KAAK,CAAC,EAAE,aAAa,GACpB,SAAS,CAAC,CAAC,CAAC,CAEd;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,wBAAgB,eAAe,CAAC,CAAC,EAC/B,WAAW,EAAE,MAAM,EACnB,QAAQ,EAAE,CAAC,QAAQ,EAAE,CAAC,EAAE,EAAE,OAAO,EAAE,gBAAgB,KAAK,aAAa,EAAE,EACvE,KAAK,CAAC,EAAE,aAAa,GACpB,SAAS,CAAC,CAAC,CAAC,CAEd"}
package/dist/define.js CHANGED
@@ -12,9 +12,41 @@
12
12
  *
13
13
  * classes(p).that().satisfy(isAbstract).should().beExported().check()
14
14
  * ```
15
+ *
16
+ * ## Declaring a glob
17
+ *
18
+ * If the predicate matches paths against a glob, pass it as `globs` — otherwise
19
+ * `doctor` cannot see it, and a typo'd glob narrows the selection to nothing
20
+ * while reporting a clean bill of health. This is a **selector**-position glob,
21
+ * the one position that is acted on today, which is why the omission was bug
22
+ * 0030's higher-severity half.
23
+ *
24
+ * @example
25
+ * ```ts
26
+ * const inGeneratedFolder = definePredicate<SourceFile>(
27
+ * "reside in '**\/generated/**'",
28
+ * (file) => picomatch('**\/generated/**')(file.getFilePath()),
29
+ * globNode({ glob: '**\/generated/**', kind: 'file-path' }),
30
+ * )
31
+ * ```
32
+ *
33
+ * The `kind` must say what the glob is really matched against, because it
34
+ * selects which paths the glob is checked for satisfiability:
35
+ *
36
+ * - `file-path` — an **absolute** file path. Anchor the glob (`'**\/src/**'`,
37
+ * not `'src/**'`), or it matches nothing.
38
+ * - `parent-dir` — the immediate parent directory of a file.
39
+ * - `import-target` — a resolved module path **or a bare specifier**.
40
+ * Deliberately has no path universe, so `'fastify'` is never reported dead.
41
+ * - `specifier` / `literal` — a string in the source rather than a path.
42
+ *
43
+ * A wrong `kind` is believed: declaring a bare specifier as `file-path` earns a
44
+ * false dead-glob report, and declaring a real path as `import-target` silently
45
+ * exempts it from checking. When unsure, the honest choice is to declare
46
+ * nothing — the same position as before this parameter existed.
15
47
  */
16
- export function definePredicate(description, test) {
17
- return { description, test };
48
+ export function definePredicate(description, test, globs) {
49
+ return { description, test, globs };
18
50
  }
19
51
  /**
20
52
  * Create a custom condition for use in `.should().satisfy()` chains.
@@ -35,8 +67,17 @@ export function definePredicate(description, test) {
35
67
  *
36
68
  * classes(p).that().extend('Base').should().satisfy(useSharedHelper).check()
37
69
  * ```
70
+ *
71
+ * ## Declaring a glob
72
+ *
73
+ * As with `definePredicate`, pass `globs` if the condition matches paths against
74
+ * one — see that function for the `kind` values and what a wrong one costs. The
75
+ * difference is what happens next: a **condition**-position glob is deliberately
76
+ * *not* reported when it matches nothing, because a denylist glob matching
77
+ * nothing is indistinguishable from a ban being respected. Declaring it makes it
78
+ * visible to `globs()` and `explain`; it does not make it a finding.
38
79
  */
39
- export function defineCondition(description, evaluate) {
40
- return { description, evaluate };
80
+ export function defineCondition(description, evaluate, globs) {
81
+ return { description, evaluate, globs };
41
82
  }
42
83
  //# sourceMappingURL=define.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"define.js","sourceRoot":"","sources":["../src/define.ts"],"names":[],"mappings":"AAIA;;;;;;;;;;;;;;GAcG;AACH,MAAM,UAAU,eAAe,CAC7B,WAAmB,EACnB,IAA6B;IAE7B,OAAO,EAAE,WAAW,EAAE,IAAI,EAAE,CAAA;AAC9B,CAAC;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,MAAM,UAAU,eAAe,CAC7B,WAAmB,EACnB,QAAuE;IAEvE,OAAO,EAAE,WAAW,EAAE,QAAQ,EAAE,CAAA;AAClC,CAAC"}
1
+ {"version":3,"file":"define.js","sourceRoot":"","sources":["../src/define.ts"],"names":[],"mappings":"AAKA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8CG;AACH,MAAM,UAAU,eAAe,CAC7B,WAAmB,EACnB,IAA6B,EAC7B,KAAqB;IAErB,OAAO,EAAE,WAAW,EAAE,IAAI,EAAE,KAAK,EAAE,CAAA;AACrC,CAAC;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,MAAM,UAAU,eAAe,CAC7B,WAAmB,EACnB,QAAuE,EACvE,KAAqB;IAErB,OAAO,EAAE,WAAW,EAAE,QAAQ,EAAE,KAAK,EAAE,CAAA;AACzC,CAAC"}
@@ -10,7 +10,14 @@ import type { ArchViolation } from './violation.js';
10
10
  */
11
11
  export declare class DiffFilter {
12
12
  private readonly changedFiles;
13
- constructor(changedFiles: Set<string> | null);
13
+ /**
14
+ * The branch this filter diffed against, for the suppression notice plan
15
+ * 0071 added. Defaulted rather than required: the constructor is public API
16
+ * (`src/index.ts` exports `DiffFilter`), so an existing
17
+ * `new DiffFilter(files)` must keep compiling.
18
+ */
19
+ readonly baseBranch: string;
20
+ constructor(changedFiles: Set<string> | null, baseBranch?: string);
14
21
  /**
15
22
  * Filter violations to only those in changed files.
16
23
  * If changedFiles is null (git error), returns all violations unfiltered.
@@ -1 +1 @@
1
- {"version":3,"file":"diff-aware.d.ts","sourceRoot":"","sources":["../src/diff-aware.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAA;AAEnD;;;;;;;;GAQG;AACH,qBAAa,UAAU;IACrB,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAoB;gBAErC,YAAY,EAAE,GAAG,CAAC,MAAM,CAAC,GAAG,IAAI;IAI5C;;;OAGG;IACH,eAAe,CAAC,UAAU,EAAE,aAAa,EAAE,GAAG,aAAa,EAAE;IAM7D,kEAAkE;IAClE,IAAI,IAAI,IAAI,MAAM,CAEjB;CACF;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,SAAS,CAAC,UAAU,GAAE,MAAe,GAAG,UAAU,CA6BjE"}
1
+ {"version":3,"file":"diff-aware.d.ts","sourceRoot":"","sources":["../src/diff-aware.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAA;AAGnD;;;;;;;;GAQG;AACH,qBAAa,UAAU;IACrB,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAoB;IAEjD;;;;;OAKG;IACH,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAA;gBAEf,YAAY,EAAE,GAAG,CAAC,MAAM,CAAC,GAAG,IAAI,EAAE,UAAU,GAAE,MAA0B;IAKpF;;;OAGG;IACH,eAAe,CAAC,UAAU,EAAE,aAAa,EAAE,GAAG,aAAa,EAAE;IA0B7D,kEAAkE;IAClE,IAAI,IAAI,IAAI,MAAM,CAEjB;CACF;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,SAAS,CAAC,UAAU,GAAE,MAAe,GAAG,UAAU,CAiCjE"}
@@ -1,5 +1,6 @@
1
1
  import { execFileSync } from 'node:child_process';
2
2
  import path from 'node:path';
3
+ import { writeStderr } from './stderr.js';
3
4
  /**
4
5
  * A diff filter that restricts violation reporting to files
5
6
  * changed since a base branch.
@@ -11,8 +12,16 @@ import path from 'node:path';
11
12
  */
12
13
  export class DiffFilter {
13
14
  changedFiles;
14
- constructor(changedFiles) {
15
+ /**
16
+ * The branch this filter diffed against, for the suppression notice plan
17
+ * 0071 added. Defaulted rather than required: the constructor is public API
18
+ * (`src/index.ts` exports `DiffFilter`), so an existing
19
+ * `new DiffFilter(files)` must keep compiling.
20
+ */
21
+ baseBranch;
22
+ constructor(changedFiles, baseBranch = 'the base branch') {
15
23
  this.changedFiles = changedFiles;
24
+ this.baseBranch = baseBranch;
16
25
  }
17
26
  /**
18
27
  * Filter violations to only those in changed files.
@@ -22,7 +31,24 @@ export class DiffFilter {
22
31
  const files = this.changedFiles;
23
32
  if (files === null)
24
33
  return violations;
25
- return violations.filter((v) => files.has(v.file));
34
+ // Config-level meta-findings (empty selector/discovery) have no changed file
35
+ // to attribute to — never filter them out (ADR-008; plan 0067).
36
+ //
37
+ // Kept from the kernel's copy when the two were unified, because it is the
38
+ // half that says WHY: a `bypassFilters` configuration finding (ADR-010's
39
+ // zero-examined / expired-`.expectEmpty()` violations) has `file: ''` and can
40
+ // never be a member of `changedFiles`. Without this branch, "unsuppressable"
41
+ // was false for the most realistic adoption path — a rule whose instrument
42
+ // silently broke reported nothing at all under `--changed`, with no
43
+ // diagnostic that anything had been hidden.
44
+ // `relatedFiles` (bug 0239): a finding about a RELATIONSHIP concerns several
45
+ // files and can only be reported at one of them. Keeping it when any of them
46
+ // changed is what makes `--changed` honest for it — the alternative measured
47
+ // in 0239 was that the developer who introduced a duplicate saw nothing,
48
+ // because the finding was anchored on the file they had not edited.
49
+ return violations.filter((v) => v.bypassFilters === true ||
50
+ files.has(v.file) ||
51
+ (v.relatedFiles?.some((f) => files.has(f)) ?? false));
26
52
  }
27
53
  /** Number of changed files detected, or -1 if diff unavailable */
28
54
  get size() {
@@ -53,17 +79,21 @@ export function diffAware(baseBranch = 'main') {
53
79
  stdio: ['pipe', 'pipe', 'pipe'],
54
80
  }).trim();
55
81
  }
56
- catch (err) {
57
- // Not a git repo, or base branch doesn't exist — skip filtering (report all violations)
58
- const detail = err instanceof Error ? ` (${err.message.split('\n')[0] ?? ''})` : '';
59
- console.warn(`[eess] Could not run git diff against '${baseBranch}'${detail}. All violations will be reported.`);
60
- return new DiffFilter(null);
82
+ catch (error) {
83
+ // Not a git repo, or the base branch doesn't exist — skip filtering (report
84
+ // all violations). The cause is named: "could not run git diff" alone sends
85
+ // a reader looking for a missing branch when the real answer may be that git
86
+ // is not installed, or that the cwd is not a repository at all.
87
+ writeStderr(`[eess] Could not run git diff against '${baseBranch}' ` +
88
+ `(${error instanceof Error ? error.message.split('\n')[0] : String(error)}). ` +
89
+ 'All violations will be reported.');
90
+ return new DiffFilter(null, baseBranch);
61
91
  }
62
92
  if (output === '') {
63
93
  // No changes — empty set means nothing is "changed", so all violations are filtered out
64
- return new DiffFilter(new Set());
94
+ return new DiffFilter(new Set(), baseBranch);
65
95
  }
66
96
  const changedFiles = new Set(output.split('\n').map((relativePath) => path.resolve(cwd, relativePath)));
67
- return new DiffFilter(changedFiles);
97
+ return new DiffFilter(changedFiles, baseBranch);
68
98
  }
69
99
  //# sourceMappingURL=diff-aware.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"diff-aware.js","sourceRoot":"","sources":["../src/diff-aware.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAA;AACjD,OAAO,IAAI,MAAM,WAAW,CAAA;AAG5B;;;;;;;;GAQG;AACH,MAAM,OAAO,UAAU;IACJ,YAAY,CAAoB;IAEjD,YAAY,YAAgC;QAC1C,IAAI,CAAC,YAAY,GAAG,YAAY,CAAA;IAClC,CAAC;IAED;;;OAGG;IACH,eAAe,CAAC,UAA2B;QACzC,MAAM,KAAK,GAAG,IAAI,CAAC,YAAY,CAAA;QAC/B,IAAI,KAAK,KAAK,IAAI;YAAE,OAAO,UAAU,CAAA;QACrC,OAAO,UAAU,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAA;IACpD,CAAC;IAED,kEAAkE;IAClE,IAAI,IAAI;QACN,OAAO,IAAI,CAAC,YAAY,KAAK,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,YAAY,CAAC,IAAI,CAAA;IACjE,CAAC;CACF;AAED;;;;;;;;;;;;;GAaG;AACH,MAAM,UAAU,SAAS,CAAC,aAAqB,MAAM;IACnD,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,EAAE,CAAA;IAEzB,IAAI,MAAc,CAAA;IAClB,IAAI,CAAC;QACH,MAAM,GAAG,YAAY,CAAC,KAAK,EAAE,CAAC,MAAM,EAAE,aAAa,EAAE,GAAG,UAAU,SAAS,CAAC,EAAE;YAC5E,QAAQ,EAAE,OAAO;YACjB,GAAG;YACH,KAAK,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC;SAChC,CAAC,CAAC,IAAI,EAAE,CAAA;IACX,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,wFAAwF;QACxF,MAAM,MAAM,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,GAAG,CAAC,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC,CAAC,EAAE,CAAA;QACnF,OAAO,CAAC,IAAI,CACV,0CAA0C,UAAU,IAAI,MAAM,oCAAoC,CACnG,CAAA;QACD,OAAO,IAAI,UAAU,CAAC,IAAI,CAAC,CAAA;IAC7B,CAAC;IAED,IAAI,MAAM,KAAK,EAAE,EAAE,CAAC;QAClB,wFAAwF;QACxF,OAAO,IAAI,UAAU,CAAC,IAAI,GAAG,EAAE,CAAC,CAAA;IAClC,CAAC;IAED,MAAM,YAAY,GAAG,IAAI,GAAG,CAC1B,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,YAAY,EAAE,EAAE,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,EAAE,YAAY,CAAC,CAAC,CAC1E,CAAA;IAED,OAAO,IAAI,UAAU,CAAC,YAAY,CAAC,CAAA;AACrC,CAAC"}
1
+ {"version":3,"file":"diff-aware.js","sourceRoot":"","sources":["../src/diff-aware.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,72 @@
1
+ /**
2
+ * What diff-aware mode suppressed, said out loud.
3
+ *
4
+ * Plan 0071's first instrument. `--changed` (and `check({ diff })`) filter
5
+ * **reporting**, not evaluation — so a run with every finding suppressed is
6
+ * indistinguishable from a clean run: exit 0, no output, `total: 0`. That is
7
+ * the false green ADR-008 exists to prevent, and it is worse here than
8
+ * elsewhere because the reader chose the flag and then forgot it: CI configures
9
+ * `--changed` once and every subsequent run reads as clean.
10
+ *
11
+ * **The count is derived by the caller, never self-reported.** Each call site
12
+ * computes `before.length - after.length` around its own `filterToChanged`
13
+ * call. `DiffFilter` could keep that tally itself, and it deliberately does
14
+ * not: `CheckOptions.diff` is a structural interface (`DiffFilterLike`), so a
15
+ * consumer can pass any object with a `filterToChanged` method, and a filter
16
+ * that under-reports its own suppression would produce exactly the silence
17
+ * this module exists to break. Caller-side subtraction is a differently-derived
18
+ * value (ADR-009 rule 5) and it works for filters we did not write.
19
+ *
20
+ * **Two surfaces, because only one of them can count.**
21
+ *
22
+ * | surface | disclosure |
23
+ * | ---------------------------------------- | -------------------- |
24
+ * | CLI `check --changed` | `suppressionNotice` |
25
+ * | `checkAll([...])` | `suppressionNotice` |
26
+ * | `.check({ diff })` / `.warn({ diff })` | `activeNotice`, once |
27
+ *
28
+ * The first two filter once over every collected violation, so they know the
29
+ * whole number. The per-rule terminals do not: `filterToChanged` is called
30
+ * once per rule (`execute-rule.ts`), and a diff-aware suite with 79 rules
31
+ * would print 79 lines — on the channel v0.26.0 made unconditionally visible.
32
+ * There is no per-run aggregation point on that path, so those terminals state
33
+ * the **configuration** instead, once per process. A configuration statement
34
+ * cannot be wrong; a per-rule count presented as a run total would be.
35
+ *
36
+ * A `process.on('exit')` flush was considered and rejected: it would let the
37
+ * per-rule path print one complete total, but a library that installs an exit
38
+ * hook to deliver a correctness-relevant message has made that message
39
+ * dependent on stderr surviving process teardown — untestable in the shape the
40
+ * rest of this project asserts (a real child `vitest run`, bug 0024) without
41
+ * asserting a race. Stating the configuration is weaker and honest.
42
+ */
43
+ /**
44
+ * One line naming what was suppressed, or `undefined` when nothing was.
45
+ *
46
+ * `undefined` rather than an empty string so a call site cannot print a blank
47
+ * line by forgetting to check — the two callers both feed this straight into
48
+ * `writeStderr`.
49
+ *
50
+ * @param suppressed - `before.length - after.length`, computed by the caller.
51
+ * @param changedFiles - How many files the filter considered changed, when the
52
+ * filter exposes it. A user-supplied `DiffFilterLike` need not, in which case
53
+ * the sentence drops that clause rather than guessing.
54
+ * @param baseBranch - The branch diffed against, when known.
55
+ */
56
+ export declare function suppressionNotice(suppressed: number, changedFiles?: number, baseBranch?: string): string | undefined;
57
+ /**
58
+ * State that diff-aware filtering is on, once per process. Returns `undefined`
59
+ * on every call after the first, and whenever nothing was suppressed.
60
+ *
61
+ * Keyed on a call that actually suppressed something: a diff-aware run whose
62
+ * findings all happen to be in changed files has hidden nothing, and a notice
63
+ * there would train the reader to skip the line that matters.
64
+ */
65
+ export declare function activeNotice(suppressed: number, changedFiles?: number, baseBranch?: string): string | undefined;
66
+ /**
67
+ * Forget that {@link activeNotice} has spoken. **Tests only** — a suite
68
+ * asserting the once-per-process contract has to observe the first call, and
69
+ * whichever earlier test filtered first would otherwise have consumed it.
70
+ */
71
+ export declare function resetDiffDisclosureForTests(): void;
72
+ //# sourceMappingURL=diff-disclosure.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"diff-disclosure.d.ts","sourceRoot":"","sources":["../src/diff-disclosure.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAyCG;AAKH;;;;;;;;;;;;GAYG;AACH,wBAAgB,iBAAiB,CAC/B,UAAU,EAAE,MAAM,EAClB,YAAY,CAAC,EAAE,MAAM,EACrB,UAAU,CAAC,EAAE,MAAM,GAClB,MAAM,GAAG,SAAS,CAkBpB;AAYD;;;;;;;GAOG;AACH,wBAAgB,YAAY,CAC1B,UAAU,EAAE,MAAM,EAClB,YAAY,CAAC,EAAE,MAAM,EACrB,UAAU,CAAC,EAAE,MAAM,GAClB,MAAM,GAAG,SAAS,CAepB;AAED;;;;GAIG;AACH,wBAAgB,2BAA2B,IAAI,IAAI,CAElD"}