@nielspeter/eess 0.2.2 → 0.4.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 (157) hide show
  1. package/README.md +3 -3
  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.d.ts +69 -4
  7. package/dist/baseline.d.ts.map +1 -1
  8. package/dist/baseline.js +119 -13
  9. package/dist/baseline.js.map +1 -1
  10. package/dist/cache-registry.d.ts +14 -0
  11. package/dist/cache-registry.d.ts.map +1 -0
  12. package/dist/cache-registry.js +37 -0
  13. package/dist/cache-registry.js.map +1 -0
  14. package/dist/cardinality.d.ts +35 -0
  15. package/dist/cardinality.d.ts.map +1 -0
  16. package/dist/cardinality.js +65 -0
  17. package/dist/cardinality.js.map +1 -0
  18. package/dist/check-options.d.ts +14 -0
  19. package/dist/check-options.d.ts.map +1 -1
  20. package/dist/comment-suppression.d.ts +60 -0
  21. package/dist/comment-suppression.d.ts.map +1 -0
  22. package/dist/comment-suppression.js +89 -0
  23. package/dist/comment-suppression.js.map +1 -0
  24. package/dist/condition.d.ts +6 -0
  25. package/dist/condition.d.ts.map +1 -1
  26. package/dist/correspondence-core.d.ts +35 -0
  27. package/dist/correspondence-core.d.ts.map +1 -0
  28. package/dist/correspondence-core.js +38 -0
  29. package/dist/correspondence-core.js.map +1 -0
  30. package/dist/correspondence.d.ts +27 -5
  31. package/dist/correspondence.d.ts.map +1 -1
  32. package/dist/correspondence.js +39 -5
  33. package/dist/correspondence.js.map +1 -1
  34. package/dist/dedupe-config-findings.d.ts +56 -0
  35. package/dist/dedupe-config-findings.d.ts.map +1 -0
  36. package/dist/dedupe-config-findings.js +74 -0
  37. package/dist/dedupe-config-findings.js.map +1 -0
  38. package/dist/define.d.ts +44 -2
  39. package/dist/define.d.ts.map +1 -1
  40. package/dist/define.js +45 -4
  41. package/dist/define.js.map +1 -1
  42. package/dist/diff-aware.d.ts +8 -0
  43. package/dist/diff-aware.d.ts.map +1 -1
  44. package/dist/diff-aware.js +11 -2
  45. package/dist/diff-aware.js.map +1 -1
  46. package/dist/diff-disclosure.d.ts +72 -0
  47. package/dist/diff-disclosure.d.ts.map +1 -0
  48. package/dist/diff-disclosure.js +110 -0
  49. package/dist/diff-disclosure.js.map +1 -0
  50. package/dist/disk-set.d.ts +27 -0
  51. package/dist/disk-set.d.ts.map +1 -0
  52. package/dist/disk-set.js +12 -0
  53. package/dist/disk-set.js.map +1 -0
  54. package/dist/edge-coverage.d.ts +96 -0
  55. package/dist/edge-coverage.d.ts.map +1 -0
  56. package/dist/edge-coverage.js +108 -0
  57. package/dist/edge-coverage.js.map +1 -0
  58. package/dist/errors.d.ts +25 -0
  59. package/dist/errors.d.ts.map +1 -1
  60. package/dist/errors.js +32 -0
  61. package/dist/errors.js.map +1 -1
  62. package/dist/execute-rule.d.ts +2 -1
  63. package/dist/execute-rule.d.ts.map +1 -1
  64. package/dist/execute-rule.js +38 -6
  65. package/dist/execute-rule.js.map +1 -1
  66. package/dist/format-github.d.ts.map +1 -1
  67. package/dist/format-github.js +37 -5
  68. package/dist/format-github.js.map +1 -1
  69. package/dist/format-json.d.ts +83 -1
  70. package/dist/format-json.d.ts.map +1 -1
  71. package/dist/format-json.js +70 -9
  72. package/dist/format-json.js.map +1 -1
  73. package/dist/format.d.ts +4 -2
  74. package/dist/format.d.ts.map +1 -1
  75. package/dist/format.js +41 -19
  76. package/dist/format.js.map +1 -1
  77. package/dist/glob-site.d.ts +232 -0
  78. package/dist/glob-site.d.ts.map +1 -0
  79. package/dist/glob-site.js +156 -0
  80. package/dist/glob-site.js.map +1 -0
  81. package/dist/identity-root.d.ts +55 -0
  82. package/dist/identity-root.d.ts.map +1 -0
  83. package/dist/identity-root.js +170 -0
  84. package/dist/identity-root.js.map +1 -0
  85. package/dist/index.d.ts +41 -3
  86. package/dist/index.d.ts.map +1 -1
  87. package/dist/index.js +33 -3
  88. package/dist/index.js.map +1 -1
  89. package/dist/owns-empty-discovery.d.ts +5 -0
  90. package/dist/owns-empty-discovery.d.ts.map +1 -0
  91. package/dist/owns-empty-discovery.js +77 -0
  92. package/dist/owns-empty-discovery.js.map +1 -0
  93. package/dist/path-universe.d.ts +44 -0
  94. package/dist/path-universe.d.ts.map +1 -0
  95. package/dist/path-universe.js +21 -0
  96. package/dist/path-universe.js.map +1 -0
  97. package/dist/predicate.d.ts +33 -0
  98. package/dist/predicate.d.ts.map +1 -1
  99. package/dist/preset-dispatch.d.ts +19 -0
  100. package/dist/preset-dispatch.d.ts.map +1 -1
  101. package/dist/preset-dispatch.js +43 -3
  102. package/dist/preset-dispatch.js.map +1 -1
  103. package/dist/project-relative.d.ts +25 -0
  104. package/dist/project-relative.d.ts.map +1 -0
  105. package/dist/project-relative.js +54 -0
  106. package/dist/project-relative.js.map +1 -0
  107. package/dist/report.d.ts +11 -5
  108. package/dist/report.d.ts.map +1 -1
  109. package/dist/report.js +17 -0
  110. package/dist/report.js.map +1 -1
  111. package/dist/rule-builder-like.d.ts +11 -0
  112. package/dist/rule-builder-like.d.ts.map +1 -0
  113. package/dist/rule-builder-like.js +2 -0
  114. package/dist/rule-builder-like.js.map +1 -0
  115. package/dist/rule-builder.d.ts +109 -84
  116. package/dist/rule-builder.d.ts.map +1 -1
  117. package/dist/rule-builder.js +202 -158
  118. package/dist/rule-builder.js.map +1 -1
  119. package/dist/rule-description.d.ts +33 -1
  120. package/dist/rule-description.d.ts.map +1 -1
  121. package/dist/rule-description.js +17 -1
  122. package/dist/rule-description.js.map +1 -1
  123. package/dist/rule-metadata.d.ts +20 -3
  124. package/dist/rule-metadata.d.ts.map +1 -1
  125. package/dist/selection-memo.d.ts +47 -0
  126. package/dist/selection-memo.d.ts.map +1 -0
  127. package/dist/selection-memo.js +68 -0
  128. package/dist/selection-memo.js.map +1 -0
  129. package/dist/shallow-clone.d.ts +20 -0
  130. package/dist/shallow-clone.d.ts.map +1 -0
  131. package/dist/shallow-clone.js +27 -0
  132. package/dist/shallow-clone.js.map +1 -0
  133. package/dist/stderr.d.ts +16 -0
  134. package/dist/stderr.d.ts.map +1 -0
  135. package/dist/stderr.js +73 -0
  136. package/dist/stderr.js.map +1 -0
  137. package/dist/terminal-builder.d.ts +209 -9
  138. package/dist/terminal-builder.d.ts.map +1 -1
  139. package/dist/terminal-builder.js +264 -20
  140. package/dist/terminal-builder.js.map +1 -1
  141. package/dist/type-guards.d.ts +38 -0
  142. package/dist/type-guards.d.ts.map +1 -0
  143. package/dist/type-guards.js +42 -0
  144. package/dist/type-guards.js.map +1 -0
  145. package/dist/unsuppressable.d.ts +54 -0
  146. package/dist/unsuppressable.d.ts.map +1 -0
  147. package/dist/unsuppressable.js +76 -0
  148. package/dist/unsuppressable.js.map +1 -0
  149. package/dist/vacuity-findings.d.ts +55 -0
  150. package/dist/vacuity-findings.d.ts.map +1 -0
  151. package/dist/vacuity-findings.js +124 -0
  152. package/dist/vacuity-findings.js.map +1 -0
  153. package/dist/violation.d.ts +152 -4
  154. package/dist/violation.d.ts.map +1 -1
  155. package/dist/violation.js +262 -1
  156. package/dist/violation.js.map +1 -1
  157. package/package.json +1 -1
@@ -0,0 +1,60 @@
1
+ /**
2
+ * What inline exclusion comments suppressed, said out loud.
3
+ *
4
+ * Every other filter in the pipeline discloses itself: `.excluding()` warns on
5
+ * a pattern that matched nothing (`execute-rule.ts`), diff-aware has
6
+ * `suppressionNotice`, the baseline has `unmatchedBaselineFinding`. The inline
7
+ * comment filter dropped violations and returned no count — and after
8
+ * [ts-archunit bug 0041](https://github.com/nielspeter/ts-archunit/blob/main/bugs/fixed/0041-an-exclusion-comment-is-a-no-op-for-most-conditions.md)
9
+ * made comments work for every condition family, it became the **widest** filter
10
+ * in the pipeline and the only silent one.
11
+ *
12
+ * `diff-disclosure.ts` states the principle this module inherits: *a run with
13
+ * every finding suppressed is indistinguishable from a clean run.* That is the
14
+ * false green ADR-008 exists to prevent, and it is worse here than for
15
+ * `--changed`, because a comment is written once and then read by nobody. The
16
+ * flag at least appears in the CI invocation.
17
+ *
18
+ * ## Identities, not a total (ADR-009 rule 4)
19
+ *
20
+ * The notice names **which rule in which file**, not just how many. A count is a
21
+ * snapshot: it moves when anything moves and tells the reader nothing about what
22
+ * to look at. `arch/no-cycles in src/legacy/gateway.ts` is an identity — it
23
+ * survives a renumbering and it is greppable.
24
+ *
25
+ * Capped, and the cap is disclosed rather than silent: a suite with hundreds of
26
+ * exemptions should not print hundreds of lines on every green run, but
27
+ * "and 47 more" is a very different statement from printing 3 and implying that
28
+ * is all of them.
29
+ *
30
+ * ## Why module state
31
+ *
32
+ * `applyFilters` runs once per rule and has no view of the run. This is the same
33
+ * shape `edge-coverage.ts` uses for the same reason: accumulate per rule, reset
34
+ * at the run boundary, render once. The two aggregating surfaces — CLI `check`
35
+ * and `checkAll` — both already call a reset and read a notice, so this hooks
36
+ * into a pattern that exists rather than inventing one.
37
+ *
38
+ * The per-rule terminals (`.check()` / `.warn()` on a single rule) have no run
39
+ * boundary, so they do not render it. Same honest limitation `diff-disclosure`
40
+ * documents and for the same reason: a per-rule count presented as a run total
41
+ * would be wrong, and 79 lines on a 79-rule suite is not disclosure.
42
+ */
43
+ /** Start a run. Both aggregating surfaces call this before evaluating. */
44
+ export declare function resetCommentSuppression(): void;
45
+ /** Record that a comment suppressed a finding. Called from `applyFilters`. */
46
+ export declare function recordCommentSuppression(ruleId: string, file: string): void;
47
+ /** Every suppression recorded this run, for `--format json`. */
48
+ export declare function commentSuppressions(): readonly {
49
+ ruleId: string;
50
+ file: string;
51
+ }[];
52
+ /**
53
+ * One or more lines naming what inline comments suppressed, or `undefined` when
54
+ * nothing was.
55
+ *
56
+ * `undefined` rather than `''` so a caller cannot print a blank line by
57
+ * forgetting to check — both call sites feed this straight to `writeStderr`.
58
+ */
59
+ export declare function commentSuppressionNotice(): string | undefined;
60
+ //# sourceMappingURL=comment-suppression.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"comment-suppression.d.ts","sourceRoot":"","sources":["../src/comment-suppression.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAyCG;AAaH,0EAA0E;AAC1E,wBAAgB,uBAAuB,IAAI,IAAI,CAE9C;AAED,8EAA8E;AAC9E,wBAAgB,wBAAwB,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI,CAE3E;AAED,gEAAgE;AAChE,wBAAgB,mBAAmB,IAAI,SAAS;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,EAAE,CAEjF;AAED;;;;;;GAMG;AACH,wBAAgB,wBAAwB,IAAI,MAAM,GAAG,SAAS,CA0B7D"}
@@ -0,0 +1,89 @@
1
+ /**
2
+ * What inline exclusion comments suppressed, said out loud.
3
+ *
4
+ * Every other filter in the pipeline discloses itself: `.excluding()` warns on
5
+ * a pattern that matched nothing (`execute-rule.ts`), diff-aware has
6
+ * `suppressionNotice`, the baseline has `unmatchedBaselineFinding`. The inline
7
+ * comment filter dropped violations and returned no count — and after
8
+ * [ts-archunit bug 0041](https://github.com/nielspeter/ts-archunit/blob/main/bugs/fixed/0041-an-exclusion-comment-is-a-no-op-for-most-conditions.md)
9
+ * made comments work for every condition family, it became the **widest** filter
10
+ * in the pipeline and the only silent one.
11
+ *
12
+ * `diff-disclosure.ts` states the principle this module inherits: *a run with
13
+ * every finding suppressed is indistinguishable from a clean run.* That is the
14
+ * false green ADR-008 exists to prevent, and it is worse here than for
15
+ * `--changed`, because a comment is written once and then read by nobody. The
16
+ * flag at least appears in the CI invocation.
17
+ *
18
+ * ## Identities, not a total (ADR-009 rule 4)
19
+ *
20
+ * The notice names **which rule in which file**, not just how many. A count is a
21
+ * snapshot: it moves when anything moves and tells the reader nothing about what
22
+ * to look at. `arch/no-cycles in src/legacy/gateway.ts` is an identity — it
23
+ * survives a renumbering and it is greppable.
24
+ *
25
+ * Capped, and the cap is disclosed rather than silent: a suite with hundreds of
26
+ * exemptions should not print hundreds of lines on every green run, but
27
+ * "and 47 more" is a very different statement from printing 3 and implying that
28
+ * is all of them.
29
+ *
30
+ * ## Why module state
31
+ *
32
+ * `applyFilters` runs once per rule and has no view of the run. This is the same
33
+ * shape `edge-coverage.ts` uses for the same reason: accumulate per rule, reset
34
+ * at the run boundary, render once. The two aggregating surfaces — CLI `check`
35
+ * and `checkAll` — both already call a reset and read a notice, so this hooks
36
+ * into a pattern that exists rather than inventing one.
37
+ *
38
+ * The per-rule terminals (`.check()` / `.warn()` on a single rule) have no run
39
+ * boundary, so they do not render it. Same honest limitation `diff-disclosure`
40
+ * documents and for the same reason: a per-rule count presented as a run total
41
+ * would be wrong, and 79 lines on a 79-rule suite is not disclosure.
42
+ */
43
+ let suppressions = [];
44
+ /** How many identities the notice lists before summarising the remainder. */
45
+ const LISTED = 5;
46
+ /** Start a run. Both aggregating surfaces call this before evaluating. */
47
+ export function resetCommentSuppression() {
48
+ suppressions = [];
49
+ }
50
+ /** Record that a comment suppressed a finding. Called from `applyFilters`. */
51
+ export function recordCommentSuppression(ruleId, file) {
52
+ suppressions.push({ ruleId, file });
53
+ }
54
+ /** Every suppression recorded this run, for `--format json`. */
55
+ export function commentSuppressions() {
56
+ return suppressions;
57
+ }
58
+ /**
59
+ * One or more lines naming what inline comments suppressed, or `undefined` when
60
+ * nothing was.
61
+ *
62
+ * `undefined` rather than `''` so a caller cannot print a blank line by
63
+ * forgetting to check — both call sites feed this straight to `writeStderr`.
64
+ */
65
+ export function commentSuppressionNotice() {
66
+ if (suppressions.length === 0)
67
+ return undefined;
68
+ const total = suppressions.length;
69
+ const findings = total === 1 ? 'finding' : 'findings';
70
+ const head = `[eess] ${String(total)} ${findings} suppressed by inline ` +
71
+ `// eess-exclude comments. They are exemptions, not passes:`;
72
+ // Deduplicate by identity so one rule silenced ten times in one file reads as
73
+ // one line with a count, rather than ten lines that look like ten problems.
74
+ const byIdentity = new Map();
75
+ for (const s of suppressions) {
76
+ const key = `${s.ruleId} in ${s.file === '' ? '(no file)' : s.file}`;
77
+ byIdentity.set(key, (byIdentity.get(key) ?? 0) + 1);
78
+ }
79
+ const entries = [...byIdentity.entries()];
80
+ const shown = entries.slice(0, LISTED);
81
+ const lines = shown.map(([key, n]) => ` - ${key}${n > 1 ? ` (${String(n)}×)` : ''}`);
82
+ // The cap is stated. A silent truncation reads as "that is all of them", which
83
+ // is the same lie as no disclosure at all.
84
+ if (entries.length > LISTED) {
85
+ lines.push(` - …and ${String(entries.length - LISTED)} more`);
86
+ }
87
+ return [head, ...lines].join('\n');
88
+ }
89
+ //# sourceMappingURL=comment-suppression.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"comment-suppression.js","sourceRoot":"","sources":["../src/comment-suppression.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAyCG;AAQH,IAAI,YAAY,GAAkB,EAAE,CAAA;AAEpC,6EAA6E;AAC7E,MAAM,MAAM,GAAG,CAAC,CAAA;AAEhB,0EAA0E;AAC1E,MAAM,UAAU,uBAAuB;IACrC,YAAY,GAAG,EAAE,CAAA;AACnB,CAAC;AAED,8EAA8E;AAC9E,MAAM,UAAU,wBAAwB,CAAC,MAAc,EAAE,IAAY;IACnE,YAAY,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,CAAA;AACrC,CAAC;AAED,gEAAgE;AAChE,MAAM,UAAU,mBAAmB;IACjC,OAAO,YAAY,CAAA;AACrB,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,wBAAwB;IACtC,IAAI,YAAY,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,SAAS,CAAA;IAE/C,MAAM,KAAK,GAAG,YAAY,CAAC,MAAM,CAAA;IACjC,MAAM,QAAQ,GAAG,KAAK,KAAK,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,UAAU,CAAA;IACrD,MAAM,IAAI,GACR,UAAU,MAAM,CAAC,KAAK,CAAC,IAAI,QAAQ,wBAAwB;QAC3D,4DAA4D,CAAA;IAE9D,8EAA8E;IAC9E,4EAA4E;IAC5E,MAAM,UAAU,GAAG,IAAI,GAAG,EAAkB,CAAA;IAC5C,KAAK,MAAM,CAAC,IAAI,YAAY,EAAE,CAAC;QAC7B,MAAM,GAAG,GAAG,GAAG,CAAC,CAAC,MAAM,OAAO,CAAC,CAAC,IAAI,KAAK,EAAE,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAA;QACpE,UAAU,CAAC,GAAG,CAAC,GAAG,EAAE,CAAC,UAAU,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,CAAA;IACrD,CAAC;IAED,MAAM,OAAO,GAAG,CAAC,GAAG,UAAU,CAAC,OAAO,EAAE,CAAC,CAAA;IACzC,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,MAAM,CAAC,CAAA;IACtC,MAAM,KAAK,GAAG,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,OAAO,GAAG,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,KAAK,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAA;IACrF,+EAA+E;IAC/E,2CAA2C;IAC3C,IAAI,OAAO,CAAC,MAAM,GAAG,MAAM,EAAE,CAAC;QAC5B,KAAK,CAAC,IAAI,CAAC,YAAY,MAAM,CAAC,OAAO,CAAC,MAAM,GAAG,MAAM,CAAC,OAAO,CAAC,CAAA;IAChE,CAAC;IACD,OAAO,CAAC,IAAI,EAAE,GAAG,KAAK,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;AACpC,CAAC"}
@@ -1,4 +1,5 @@
1
1
  import type { ArchViolation } from './violation.js';
2
+ import type { DeclaredGlobs } from './glob-site.js';
2
3
  /**
3
4
  * Context passed to conditions during evaluation.
4
5
  *
@@ -52,5 +53,10 @@ export interface Condition<T> {
52
53
  * @returns Violations for elements that don't satisfy the condition
53
54
  */
54
55
  evaluate(elements: T[], context: ConditionContext): ArchViolation[];
56
+ /**
57
+ * The path globs this condition matches against, if any. See
58
+ * `Predicate.globs` — same contract, stamped with `position: 'condition'`.
59
+ */
60
+ readonly globs?: DeclaredGlobs;
55
61
  }
56
62
  //# sourceMappingURL=condition.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"condition.d.ts","sourceRoot":"","sources":["../src/condition.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAA;AAEnD;;;;;GAKG;AACH,MAAM,WAAW,gBAAgB;IAC/B,sEAAsE;IACtE,IAAI,EAAE,MAAM,CAAA;IACZ,iDAAiD;IACjD,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,gDAAgD;IAChD,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,2DAA2D;IAC3D,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,iDAAiD;IACjD,IAAI,CAAC,EAAE,MAAM,CAAA;IACb;;;;;;;;;;;;;OAaG;IACH,kBAAkB,CAAC,EAAE,MAAM,CAAA;CAC5B;AAED;;;;;;;;GAQG;AACH,MAAM,WAAW,SAAS,CAAC,CAAC;IAC1B,+DAA+D;IAC/D,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAA;IAE5B;;;;;;OAMG;IACH,QAAQ,CAAC,QAAQ,EAAE,CAAC,EAAE,EAAE,OAAO,EAAE,gBAAgB,GAAG,aAAa,EAAE,CAAA;CACpE"}
1
+ {"version":3,"file":"condition.d.ts","sourceRoot":"","sources":["../src/condition.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAA;AACnD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAA;AAEnD;;;;;GAKG;AACH,MAAM,WAAW,gBAAgB;IAC/B,sEAAsE;IACtE,IAAI,EAAE,MAAM,CAAA;IACZ,iDAAiD;IACjD,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,gDAAgD;IAChD,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,2DAA2D;IAC3D,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,iDAAiD;IACjD,IAAI,CAAC,EAAE,MAAM,CAAA;IACb;;;;;;;;;;;;;OAaG;IACH,kBAAkB,CAAC,EAAE,MAAM,CAAA;CAC5B;AAED;;;;;;;;GAQG;AACH,MAAM,WAAW,SAAS,CAAC,CAAC;IAC1B,+DAA+D;IAC/D,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAA;IAE5B;;;;;;OAMG;IACH,QAAQ,CAAC,QAAQ,EAAE,CAAC,EAAE,EAAE,OAAO,EAAE,gBAAgB,GAAG,aAAa,EAAE,CAAA;IAEnE;;;OAGG;IACH,QAAQ,CAAC,KAAK,CAAC,EAAE,aAAa,CAAA;CAC/B"}
@@ -0,0 +1,35 @@
1
+ /**
2
+ * F2 — the shared set-difference + non-vacuity core.
3
+ *
4
+ * Pure and engine-neutral: it compares two key sets by **identity** and reports
5
+ * the asymmetric differences plus emptiness. It never counts and never decides
6
+ * policy — the caller chooses which differences are violations (coverage /
7
+ * orphans / bijection) and how to treat an empty side (non-vacuity).
8
+ *
9
+ * Both `correspondence()` (plan 0064+/proposal 017) and `crossLayer`'s
10
+ * existence check (`haveMatchingCounterpart`) build on this one function, so
11
+ * the two "every X has a matching Y" engines can never drift — and neither can
12
+ * silently green on an empty side (ADR-008).
13
+ */
14
+ /** The identity comparison of two key sets. No counts — sets of keys. */
15
+ export interface CorrespondenceResult {
16
+ /** Side-A keys with no match in side B — coverage gaps. */
17
+ readonly missing: readonly string[];
18
+ /** Side-B keys with no match in side A — orphans. */
19
+ readonly orphans: readonly string[];
20
+ /** True when side A contributed zero keys (non-vacuity signal). */
21
+ readonly aEmpty: boolean;
22
+ /** True when side B contributed zero keys (non-vacuity signal). */
23
+ readonly bEmpty: boolean;
24
+ }
25
+ /**
26
+ * Compare two key sets by identity.
27
+ *
28
+ * `missing` = A \ B (every A-key must have a B match for coverage).
29
+ * `orphans` = B \ A (every B-key must have an A source for no-orphans).
30
+ * Bijection holds iff both are empty. Duplicate keys within a side collapse
31
+ * (set semantics); order of `missing`/`orphans` follows first appearance in
32
+ * the respective input for stable, readable output.
33
+ */
34
+ export declare function setCorrespondence(aKeys: Iterable<string>, bKeys: Iterable<string>): CorrespondenceResult;
35
+ //# sourceMappingURL=correspondence-core.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"correspondence-core.d.ts","sourceRoot":"","sources":["../src/correspondence-core.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,yEAAyE;AACzE,MAAM,WAAW,oBAAoB;IACnC,2DAA2D;IAC3D,QAAQ,CAAC,OAAO,EAAE,SAAS,MAAM,EAAE,CAAA;IACnC,qDAAqD;IACrD,QAAQ,CAAC,OAAO,EAAE,SAAS,MAAM,EAAE,CAAA;IACnC,mEAAmE;IACnE,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAA;IACxB,mEAAmE;IACnE,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAA;CACzB;AAED;;;;;;;;GAQG;AACH,wBAAgB,iBAAiB,CAC/B,KAAK,EAAE,QAAQ,CAAC,MAAM,CAAC,EACvB,KAAK,EAAE,QAAQ,CAAC,MAAM,CAAC,GACtB,oBAAoB,CAetB"}
@@ -0,0 +1,38 @@
1
+ /**
2
+ * F2 — the shared set-difference + non-vacuity core.
3
+ *
4
+ * Pure and engine-neutral: it compares two key sets by **identity** and reports
5
+ * the asymmetric differences plus emptiness. It never counts and never decides
6
+ * policy — the caller chooses which differences are violations (coverage /
7
+ * orphans / bijection) and how to treat an empty side (non-vacuity).
8
+ *
9
+ * Both `correspondence()` (plan 0064+/proposal 017) and `crossLayer`'s
10
+ * existence check (`haveMatchingCounterpart`) build on this one function, so
11
+ * the two "every X has a matching Y" engines can never drift — and neither can
12
+ * silently green on an empty side (ADR-008).
13
+ */
14
+ /**
15
+ * Compare two key sets by identity.
16
+ *
17
+ * `missing` = A \ B (every A-key must have a B match for coverage).
18
+ * `orphans` = B \ A (every B-key must have an A source for no-orphans).
19
+ * Bijection holds iff both are empty. Duplicate keys within a side collapse
20
+ * (set semantics); order of `missing`/`orphans` follows first appearance in
21
+ * the respective input for stable, readable output.
22
+ */
23
+ export function setCorrespondence(aKeys, bKeys) {
24
+ const a = new Set(aKeys);
25
+ const b = new Set(bKeys);
26
+ const missing = [];
27
+ for (const key of a) {
28
+ if (!b.has(key))
29
+ missing.push(key);
30
+ }
31
+ const orphans = [];
32
+ for (const key of b) {
33
+ if (!a.has(key))
34
+ orphans.push(key);
35
+ }
36
+ return { missing, orphans, aEmpty: a.size === 0, bEmpty: b.size === 0 };
37
+ }
38
+ //# sourceMappingURL=correspondence-core.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"correspondence-core.js","sourceRoot":"","sources":["../src/correspondence-core.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAcH;;;;;;;;GAQG;AACH,MAAM,UAAU,iBAAiB,CAC/B,KAAuB,EACvB,KAAuB;IAEvB,MAAM,CAAC,GAAG,IAAI,GAAG,CAAC,KAAK,CAAC,CAAA;IACxB,MAAM,CAAC,GAAG,IAAI,GAAG,CAAC,KAAK,CAAC,CAAA;IAExB,MAAM,OAAO,GAAa,EAAE,CAAA;IAC5B,KAAK,MAAM,GAAG,IAAI,CAAC,EAAE,CAAC;QACpB,IAAI,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC;YAAE,OAAO,CAAC,IAAI,CAAC,GAAG,CAAC,CAAA;IACpC,CAAC;IAED,MAAM,OAAO,GAAa,EAAE,CAAA;IAC5B,KAAK,MAAM,GAAG,IAAI,CAAC,EAAE,CAAC;QACpB,IAAI,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC;YAAE,OAAO,CAAC,IAAI,CAAC,GAAG,CAAC,CAAA;IACpC,CAAC;IAED,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,CAAC,CAAC,IAAI,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC,CAAC,IAAI,KAAK,CAAC,EAAE,CAAA;AACzE,CAAC"}
@@ -1,5 +1,4 @@
1
- import type { ArchViolation } from './violation.js';
2
- import { TerminalBuilder } from './terminal-builder.js';
1
+ import { TerminalBuilder, type CollectResult } from './terminal-builder.js';
3
2
  /** How to identify an element for a two-sided violation message. */
4
3
  export interface ElementInfo {
5
4
  readonly name: string;
@@ -65,13 +64,36 @@ export declare class CorrespondenceBuilder<L, R> extends TerminalBuilder {
65
64
  should(): this;
66
65
  /** Chain another correspondence check (AND). */
67
66
  andShould(): this;
68
- /** Every element on the named side(s) has a counterpart on the other. */
67
+ /**
68
+ * Every element on the named side(s) has a counterpart on the other. An
69
+ * absence assertion — "no element lacks a counterpart" — so it's marked
70
+ * cardinality-exempt (see `assertsCardinality()`).
71
+ */
69
72
  beComplete(opts?: {
70
73
  direction?: Direction;
71
74
  }): this;
72
- /** Related endpoints on one side have matching relations on the other. */
75
+ /**
76
+ * Related endpoints on one side have matching relations on the other. Also
77
+ * an absence assertion — "no matched pair disagrees" — cardinality-exempt
78
+ * for the same reason `beComplete()` is.
79
+ */
73
80
  preserveRelations(spec: RelationSpec<L, R>): this;
74
- protected collectViolations(): ArchViolation[];
81
+ protected collectViolations(): CollectResult;
82
+ /**
83
+ * ADR-010: mirrors `RuleBuilder.assertsCardinality()` — per-check, not a
84
+ * blanket class-wide exemption. `beComplete()`/`preserveRelations()` mark
85
+ * the function they push via `marksAssertsCardinality()`, the same
86
+ * unforgeable registry `.notExist()` uses, because both are absence
87
+ * assertions ("no element lacks a counterpart", "no matched pair
88
+ * disagrees") that can never produce a violation from an empty side — an
89
+ * empty selection here is the correctly computed input (e.g. release-gate's
90
+ * "no packages changed this diff" on a clean tree), not a broken instrument.
91
+ * `.every()`, not `.some()`, for the same reason `RuleBuilder` uses it: a
92
+ * future check type that isn't an absence assertion must not silently
93
+ * inherit this exemption just because it's chained alongside one that is.
94
+ * An empty `_checks` list is not exempt — nothing was asserted yet.
95
+ */
96
+ protected assertsCardinality(): boolean;
75
97
  }
76
98
  /** Bind two selections into a correspondence. */
77
99
  export declare function correspondence<L, R>(opts: CorrespondenceOptions<L, R>): CorrespondenceBuilder<L, R>;
@@ -1 +1 @@
1
- {"version":3,"file":"correspondence.d.ts","sourceRoot":"","sources":["../src/correspondence.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"correspondence.d.ts","sourceRoot":"","sources":["../src/correspondence.ts"],"names":[],"mappings":"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"}
@@ -1,5 +1,6 @@
1
1
  import { TerminalBuilder } from './terminal-builder.js';
2
2
  import { matchSelections } from './matching.js';
3
+ import { marksAssertsCardinality, assertsCardinality as checkAssertsCardinality, } from './cardinality.js';
3
4
  function violationFor(info, message, ruleId) {
4
5
  return {
5
6
  rule: 'correspondence',
@@ -33,19 +34,52 @@ export class CorrespondenceBuilder extends TerminalBuilder {
33
34
  andShould() {
34
35
  return this;
35
36
  }
36
- /** Every element on the named side(s) has a counterpart on the other. */
37
+ /**
38
+ * Every element on the named side(s) has a counterpart on the other. An
39
+ * absence assertion — "no element lacks a counterpart" — so it's marked
40
+ * cardinality-exempt (see `assertsCardinality()`).
41
+ */
37
42
  beComplete(opts = {}) {
38
- this._checks.push((o, ruleId) => completeness(o, opts.direction ?? 'both', ruleId));
43
+ this._checks.push(marksAssertsCardinality((o, ruleId) => completeness(o, opts.direction ?? 'both', ruleId)));
39
44
  return this;
40
45
  }
41
- /** Related endpoints on one side have matching relations on the other. */
46
+ /**
47
+ * Related endpoints on one side have matching relations on the other. Also
48
+ * an absence assertion — "no matched pair disagrees" — cardinality-exempt
49
+ * for the same reason `beComplete()` is.
50
+ */
42
51
  preserveRelations(spec) {
43
- this._checks.push((o, ruleId) => relations(o, spec, ruleId));
52
+ this._checks.push(marksAssertsCardinality((o, ruleId) => relations(o, spec, ruleId)));
44
53
  return this;
45
54
  }
46
55
  collectViolations() {
47
56
  const ruleId = this._metadata?.id;
48
- return this._checks.flatMap((check) => check(this.opts, ruleId));
57
+ const violations = this._checks.flatMap((check) => check(this.opts, ruleId));
58
+ // ADR-010: the unit is the key sets of both sides — a correspondence over
59
+ // an empty left AND an empty right examined nothing, regardless of how
60
+ // many checks were chained; one side alone being empty is still real
61
+ // examination of the other.
62
+ const examined = this.opts.left.elements.length + this.opts.right.elements.length;
63
+ return { violations, examined };
64
+ }
65
+ /**
66
+ * ADR-010: mirrors `RuleBuilder.assertsCardinality()` — per-check, not a
67
+ * blanket class-wide exemption. `beComplete()`/`preserveRelations()` mark
68
+ * the function they push via `marksAssertsCardinality()`, the same
69
+ * unforgeable registry `.notExist()` uses, because both are absence
70
+ * assertions ("no element lacks a counterpart", "no matched pair
71
+ * disagrees") that can never produce a violation from an empty side — an
72
+ * empty selection here is the correctly computed input (e.g. release-gate's
73
+ * "no packages changed this diff" on a clean tree), not a broken instrument.
74
+ * `.every()`, not `.some()`, for the same reason `RuleBuilder` uses it: a
75
+ * future check type that isn't an absence assertion must not silently
76
+ * inherit this exemption just because it's chained alongside one that is.
77
+ * An empty `_checks` list is not exempt — nothing was asserted yet.
78
+ */
79
+ assertsCardinality() {
80
+ if (this._checks.length === 0)
81
+ return false;
82
+ return this._checks.every((check) => checkAssertsCardinality(check));
49
83
  }
50
84
  }
51
85
  /** Bind two selections into a correspondence. */
@@ -1 +1 @@
1
- {"version":3,"file":"correspondence.js","sourceRoot":"","sources":["../src/correspondence.ts"],"names":[],"mappings":"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"}
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;AAUnD;;;;;GAKG;AACH,wBAAgB,oBAAoB,CAAC,UAAU,EAAE,SAAS,aAAa,EAAE,GAAG,aAAa,EAAE,CAsC1F"}
@@ -0,0 +1,74 @@
1
+ /** How the surviving finding states the fan-out it stands for. */
2
+ function affectedNote(count) {
3
+ return (` This one option generated ${String(count)} rules that cannot enforce anything; they are ` +
4
+ `not listed separately because they are one edit.`);
5
+ }
6
+ /**
7
+ * Collapse each configuration finding that a preset fanned out.
8
+ *
9
+ * Order-preserving, keeping the **first** occurrence, so the report reads in
10
+ * the order the rules were declared rather than in hash order.
11
+ */
12
+ export function dedupeConfigFindings(violations) {
13
+ // Count first, then emit. A single pass that appends the note on first sight
14
+ // cannot know the total yet, and appending it on the last sight would reorder
15
+ // the report.
16
+ const counts = new Map();
17
+ for (const violation of violations) {
18
+ const key = keyFor(violation);
19
+ if (key === undefined)
20
+ continue;
21
+ counts.set(key, (counts.get(key) ?? 0) + 1);
22
+ }
23
+ const emitted = new Set();
24
+ const result = [];
25
+ for (const violation of violations) {
26
+ const key = keyFor(violation);
27
+ if (key === undefined) {
28
+ result.push(violation);
29
+ continue;
30
+ }
31
+ if (emitted.has(key))
32
+ continue;
33
+ emitted.add(key);
34
+ const total = counts.get(key) ?? 1;
35
+ if (total === 1) {
36
+ result.push(violation);
37
+ continue;
38
+ }
39
+ // The note goes on `message` AND `suggestion`. They are separate fields with
40
+ // separate consumers — the terminal prints `Fix:` from `suggestion`, and
41
+ // `--format json` carries both — and a reader who sees only one of them
42
+ // otherwise learns a different number of affected rules from each.
43
+ result.push({
44
+ ...violation,
45
+ message: violation.message + affectedNote(total),
46
+ suggestion: violation.suggestion === undefined ? undefined : violation.suggestion + affectedNote(total),
47
+ });
48
+ }
49
+ return result;
50
+ }
51
+ /**
52
+ * The dedupe key, or `undefined` for a finding that must never be collapsed.
53
+ *
54
+ * `undefined` for anything that is not a configuration finding, and for a
55
+ * configuration finding with no `element` to key on — a missing key must mean
56
+ * "keep it", never "merge everything that lacks one".
57
+ */
58
+ function keyFor(violation) {
59
+ if (violation.bypassFilters !== true)
60
+ return undefined;
61
+ const identity = violation.ruleId ?? violation.rule;
62
+ // `'unnamed'` is `TerminalBuilder.describeRule()`'s SENTINEL, not an identity.
63
+ // A family that never overrides it gives every one of its rules the same key,
64
+ // so distinct rules merged and the survivor claimed they were "one edit" —
65
+ // measured on three different `duplicateBodies` detectors in plan 0099. A
66
+ // missing key must mean "keep it", never "merge everything that lacks one",
67
+ // which is the rule the empty-string guard beside this already follows.
68
+ if (identity === '' || identity === 'unnamed')
69
+ return undefined;
70
+ if (violation.element === '' || violation.element === 'unnamed')
71
+ return undefined;
72
+ return `${violation.file} ${identity} ${violation.element}`;
73
+ }
74
+ //# 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":"AAiDA,kEAAkE;AAClE,SAAS,YAAY,CAAC,KAAa;IACjC,OAAO,CACL,8BAA8B,MAAM,CAAC,KAAK,CAAC,gDAAgD;QAC3F,kDAAkD,CACnD,CAAA;AACH,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,oBAAoB,CAAC,UAAoC;IACvE,6EAA6E;IAC7E,8EAA8E;IAC9E,cAAc;IACd,MAAM,MAAM,GAAG,IAAI,GAAG,EAAkB,CAAA;IACxC,KAAK,MAAM,SAAS,IAAI,UAAU,EAAE,CAAC;QACnC,MAAM,GAAG,GAAG,MAAM,CAAC,SAAS,CAAC,CAAA;QAC7B,IAAI,GAAG,KAAK,SAAS;YAAE,SAAQ;QAC/B,MAAM,CAAC,GAAG,CAAC,GAAG,EAAE,CAAC,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,CAAA;IAC7C,CAAC;IAED,MAAM,OAAO,GAAG,IAAI,GAAG,EAAU,CAAA;IACjC,MAAM,MAAM,GAAoB,EAAE,CAAA;IAClC,KAAK,MAAM,SAAS,IAAI,UAAU,EAAE,CAAC;QACnC,MAAM,GAAG,GAAG,MAAM,CAAC,SAAS,CAAC,CAAA;QAC7B,IAAI,GAAG,KAAK,SAAS,EAAE,CAAC;YACtB,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC,CAAA;YACtB,SAAQ;QACV,CAAC;QACD,IAAI,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC;YAAE,SAAQ;QAC9B,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,CAAA;QAChB,MAAM,KAAK,GAAG,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,CAAA;QAClC,IAAI,KAAK,KAAK,CAAC,EAAE,CAAC;YAChB,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC,CAAA;YACtB,SAAQ;QACV,CAAC;QACD,6EAA6E;QAC7E,yEAAyE;QACzE,wEAAwE;QACxE,mEAAmE;QACnE,MAAM,CAAC,IAAI,CAAC;YACV,GAAG,SAAS;YACZ,OAAO,EAAE,SAAS,CAAC,OAAO,GAAG,YAAY,CAAC,KAAK,CAAC;YAChD,UAAU,EACR,SAAS,CAAC,UAAU,KAAK,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,UAAU,GAAG,YAAY,CAAC,KAAK,CAAC;SAC9F,CAAC,CAAA;IACJ,CAAC;IACD,OAAO,MAAM,CAAA;AACf,CAAC;AAED;;;;;;GAMG;AACH,SAAS,MAAM,CAAC,SAAwB;IACtC,IAAI,SAAS,CAAC,aAAa,KAAK,IAAI;QAAE,OAAO,SAAS,CAAA;IACtD,MAAM,QAAQ,GAAG,SAAS,CAAC,MAAM,IAAI,SAAS,CAAC,IAAI,CAAA;IACnD,+EAA+E;IAC/E,8EAA8E;IAC9E,2EAA2E;IAC3E,0EAA0E;IAC1E,4EAA4E;IAC5E,wEAAwE;IACxE,IAAI,QAAQ,KAAK,EAAE,IAAI,QAAQ,KAAK,SAAS;QAAE,OAAO,SAAS,CAAA;IAC/D,IAAI,SAAS,CAAC,OAAO,KAAK,EAAE,IAAI,SAAS,CAAC,OAAO,KAAK,SAAS;QAAE,OAAO,SAAS,CAAA;IACjF,OAAO,GAAG,SAAS,CAAC,IAAI,IAAI,QAAQ,IAAI,SAAS,CAAC,OAAO,EAAE,CAAA;AAC7D,CAAC"}
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"}