@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,29 +1,66 @@
1
- import { isSilent } from './silent-exclusion.js';
1
+ import { zeroExaminedViolation, deadGlobViolation, unmetExpectNonEmptyViolation, zeroLoadedSourceViolation, expiredExpectEmptyViolation, } from './vacuity-findings.js';
2
+ import { UNSUPPRESSABLE } from './unsuppressable.js';
3
+ import { collectResult } from './collect-result.js';
4
+ import { ruleDescriptionFrom } from './rule-description.js';
5
+ import { recordExclusions } from './silent-exclusion.js';
2
6
  import { executeCheck, executeWarn, applyFilters } from './execute-rule.js';
7
+ import { shallowClone } from './shallow-clone.js';
3
8
  /**
4
9
  * Abstract base class for builders that share the terminal method pattern
5
10
  * (because, rule, excluding, check, warn, severity) but have different
6
11
  * element collection and evaluation models.
7
12
  *
8
13
  * Used by SliceRuleBuilder, SchemaRuleBuilder, ResolverRuleBuilder,
9
- * PairFinalBuilder, and SmellBuilder — all of which implement the same
10
- * terminal methods but differ in how they collect and evaluate elements.
11
- *
12
- * RuleBuilder<T> does NOT extend this because it predates it and has
13
- * additional concerns (predicate/condition pipeline, fork semantics).
14
+ * PairFinalBuilder, SmellBuilder, CorrespondenceBuilder, TsconfigBuilder, and
15
+ * (as of plan 0088 Phase 4) RuleBuilder — all of which implement the same
16
+ * terminal methods and now share evidence-of-examination (ADR-010) alongside
17
+ * evaluation-model differences.
14
18
  */
15
19
  export class TerminalBuilder {
16
20
  _reason;
17
21
  _metadata;
18
22
  _exclusions = [];
19
23
  _silentIndices = new Set();
24
+ /** ADR-010 part 3 — `.expectEmpty()`/`.expectNonEmpty()`. `undefined` = no declaration. */
25
+ _expectEmpty;
26
+ /**
27
+ * Declare that this rule's examined set is expected to be empty. A pass is
28
+ * legitimate only while the declaration stays true — the day a unit is
29
+ * examined, this rule reddens (ADR-010 part 3's expiry property). The
30
+ * complement of `expectNonEmpty()`; declaring both is nonsensical and the
31
+ * later call wins (last-write, matching `.rule()`'s own semantics).
32
+ */
33
+ expectEmpty() {
34
+ const next = this.copy();
35
+ next._expectEmpty = true;
36
+ return next;
37
+ }
38
+ /**
39
+ * Declare that this rule's examined set must be non-empty — the opposite
40
+ * assertion, for a corpus the author has positive knowledge should never
41
+ * legitimately be empty. Unlike a bare, undeclared rule, this **overrides
42
+ * `assertsCardinality()`'s exemption**: a `.notExist()`-shaped condition
43
+ * that finds zero subjects normally passes silently (emptiness satisfies
44
+ * what it asserts) — but if the author declared `.expectNonEmpty()`, that
45
+ * silence is exactly what they said should never happen, so it reddens
46
+ * instead, naming the declaration as the reason. This is the real
47
+ * difference from not declaring anything: without it, a `.notExist()` rule
48
+ * over an accidentally-emptied corpus (a glob typo, a temporarily-missing
49
+ * folder) passes and says nothing; with it, that same emptiness is caught.
50
+ */
51
+ expectNonEmpty() {
52
+ const next = this.copy();
53
+ next._expectEmpty = false;
54
+ return next;
55
+ }
20
56
  /**
21
57
  * Attach a human-readable rationale to the rule.
22
58
  * Included in violation messages when `.check()` throws.
23
59
  */
24
60
  because(reason) {
25
- this._reason = reason;
26
- return this;
61
+ const next = this.copy();
62
+ next._reason = reason;
63
+ return next;
27
64
  }
28
65
  /**
29
66
  * Attach rich metadata to the rule.
@@ -32,11 +69,12 @@ export class TerminalBuilder {
32
69
  * If `metadata.because` is set, it also sets the reason (same as `.because()`).
33
70
  */
34
71
  rule(metadata) {
35
- this._metadata = metadata;
72
+ const next = this.copy();
73
+ next._metadata = metadata;
36
74
  if (metadata.because) {
37
- this._reason = metadata.because;
75
+ next._reason = metadata.because;
38
76
  }
39
- return this;
77
+ return next;
40
78
  }
41
79
  /**
42
80
  * Exclude specific elements from violation reporting.
@@ -50,42 +88,69 @@ export class TerminalBuilder {
50
88
  * Emits a warning if an exclusion matches zero violations (stale exclusion).
51
89
  */
52
90
  excluding(...patterns) {
53
- for (const p of patterns) {
54
- if (isSilent(p)) {
55
- this._exclusions.push(p.pattern);
56
- this._silentIndices.add(this._exclusions.length - 1);
57
- }
58
- else {
59
- this._exclusions.push(p);
60
- }
61
- }
62
- return this;
91
+ const next = this.copy();
92
+ recordExclusions(patterns, next._exclusions, next._silentIndices);
93
+ return next;
63
94
  }
64
95
  /**
65
96
  * Return a structured description of this rule without executing it.
66
97
  * Used by the `explain` CLI subcommand.
67
98
  */
68
99
  describeRule() {
69
- return {
100
+ return ruleDescriptionFrom({
101
+ metadata: this._metadata,
102
+ reason: this._reason,
70
103
  rule: this._metadata?.id ?? 'unnamed',
71
- id: this._metadata?.id,
72
- because: this._reason,
73
- suggestion: this._metadata?.suggestion,
74
- docs: this._metadata?.docs,
75
- imperative: this._metadata?.imperative,
104
+ });
105
+ }
106
+ /** This rule's own account of itself, for `vacuity-findings.ts`. */
107
+ facts() {
108
+ return {
109
+ ruleClass: { name: this.constructor.name },
110
+ reason: this._reason,
111
+ describeRule: () => this.describeRule(),
76
112
  };
77
113
  }
78
114
  /**
79
- * Execute the rule and return violations after exclusion filtering.
80
- * Does not throw — use for programmatic access (presets, aggregation).
115
+ * What every terminal method hands the filter pipeline.
116
+ *
117
+ * The same four fields were written out at each of `violations`, `check` and
118
+ * `warn` — `no-copy-paste` reported the three at 100% with `applyFilters ->
119
+ * executeCheck` as the only varying axis. `eess-ts`'s `TerminalBuilder`
120
+ * already had exactly this method; the kernel never adopted it.
121
+ *
122
+ * Not just tidier: three terminal methods that build the filter context
123
+ * separately can drift, and a field present on `.check()` but missing on
124
+ * `.violations()` means a rule fails in CI and passes when a preset
125
+ * aggregates it — the same finding, two answers.
81
126
  */
82
- violations() {
83
- const raw = this.collectViolations();
84
- return applyFilters(raw, {
127
+ filterContext() {
128
+ return {
85
129
  reason: this._reason,
86
130
  metadata: this._metadata,
87
131
  exclusions: this._exclusions,
88
132
  silentIndices: this._silentIndices,
133
+ // Bug 0258 round 2: an id-less rule still has a sentence, and every
134
+ // builder already implements `describeRule()`. Deferred, because this
135
+ // runs on every terminal call and is read only when an id-less rule meets
136
+ // an exclusion comment it cannot honour.
137
+ describe: () => this.describeRule().rule,
138
+ };
139
+ }
140
+ /**
141
+ * Execute the rule and return violations after exclusion filtering.
142
+ * Does not throw — use for programmatic access (presets, aggregation).
143
+ */
144
+ violations() {
145
+ const evidenced = this.evidencedViolations();
146
+ // `applyFilters` returns a bare array (and the SAME reference when no
147
+ // exclusion applies), so the evidence is re-stamped onto a fresh one here —
148
+ // stamping onto its return would mutate whatever a family memoized.
149
+ return collectResult(applyFilters(evidenced, this.filterContext()), {
150
+ examined: evidenced.examined,
151
+ sourceEmpty: evidenced.sourceEmpty,
152
+ declaredEmpty: evidenced.declaredEmpty,
153
+ deadGlob: evidenced.deadGlob,
89
154
  });
90
155
  }
91
156
  /**
@@ -95,13 +160,7 @@ export class TerminalBuilder {
95
160
  * @param options - Optional baseline, diff filtering, and output format
96
161
  */
97
162
  check(options) {
98
- const violations = this.collectViolations();
99
- executeCheck(violations, {
100
- reason: this._reason,
101
- metadata: this._metadata,
102
- exclusions: this._exclusions,
103
- silentIndices: this._silentIndices,
104
- }, options);
163
+ executeCheck(this.evidencedViolations(), this.filterContext(), options);
105
164
  }
106
165
  /**
107
166
  * Execute the rule and log violations to stderr. Does not throw.
@@ -110,13 +169,106 @@ export class TerminalBuilder {
110
169
  * @param options - Optional baseline, diff filtering, and output format
111
170
  */
112
171
  warn(options) {
113
- const violations = this.collectViolations();
114
- executeWarn(violations, {
115
- reason: this._reason,
116
- metadata: this._metadata,
117
- exclusions: this._exclusions,
118
- silentIndices: this._silentIndices,
119
- }, options);
172
+ executeWarn(this.evidencedViolations(), this.filterContext(), options);
173
+ }
174
+ /**
175
+ * ADR-010 — the evidence gate. Calls `collectViolations()` once and turns
176
+ * its `{ violations, examined, sourceEmpty }` verdict into the
177
+ * `ArchViolation[]` every terminal method still emits, applying the
178
+ * declared-empty precedence:
179
+ *
180
+ * - `examined === 0`, `sourceEmpty === true`: ADR-010 part 3's precedence
181
+ * rule — "an empty project outranks every token." The family's own
182
+ * upstream source (files loaded, a diagram parsed) produced nothing at
183
+ * all, before any predicate ran. Neither `.expectEmpty()` nor
184
+ * `assertsCardinality()` can rescue this: there is no selection to
185
+ * widen, and a `.notExist()`-shaped condition "passing" against an
186
+ * instrument that never loaded anything is not evidence of anything.
187
+ * Always an unsuppressable configuration finding.
188
+ * - `examined === 0`, `.expectNonEmpty()` declared: the caller has positive
189
+ * knowledge this corpus should never be empty — this OVERRIDES
190
+ * `assertsCardinality()`'s exemption (a `.notExist()` rule that would
191
+ * otherwise pass silently on zero subjects reddens instead, since silent
192
+ * emptiness is exactly what the declaration rules out). Fails, naming the
193
+ * unmet declaration.
194
+ * - `examined === 0`, no `.expectEmpty()` declared: the rule's own
195
+ * instrument is broken (a dead selector, an unreachable seam) — this is
196
+ * a configuration finding, unsuppressable (`bypassFilters`), not a silent
197
+ * pass. This is the guarantee ADR-009/010 exist for.
198
+ * - `examined === 0`, `.expectEmpty()` declared: satisfied. Passes.
199
+ * - `examined === 0`, `assertsCardinality()` true (and no `.expectNonEmpty()`
200
+ * override above): the rule's own conditions are satisfied BY emptiness
201
+ * (e.g. `.notExist()`) — a different exemption from `.expectEmpty()`,
202
+ * because it is a property of what the condition asserts, not a caller's
203
+ * declaration about the corpus. Passes, with no finding at all (not even
204
+ * a silent one — the condition already reported truthfully that it found
205
+ * nothing wrong).
206
+ * - `examined > 0`, `.expectEmpty()` declared: the declaration has expired
207
+ * — a unit was examined despite the author's "this stays empty" claim.
208
+ * Fails, appending an expiry finding to whatever `collectViolations()`
209
+ * found (never replacing it — the underlying findings are still real).
210
+ * - `examined > 0`, no declaration or `.expectNonEmpty()` (the ordinary
211
+ * case — `.expectNonEmpty()` has nothing left to assert once subjects
212
+ * exist): the rule's own violations stand as computed.
213
+ */
214
+ evidencedViolations() {
215
+ const collected = this.collectViolations();
216
+ const { examined, sourceEmpty, deadGlob } = collected;
217
+ const violations = [...collected];
218
+ // The evidence travels with every return below. A declaration reaches the
219
+ // emitter ON the receipt (ADR-014 §3) rather than through delivery options:
220
+ // one boolean over a sum cannot carry per-rule declarations, and this is the
221
+ // moment the terminal knows the fact and used to discard it.
222
+ const evidence = {
223
+ examined,
224
+ sourceEmpty,
225
+ deadGlob,
226
+ // `assertsCardinality()` is a declaration ONLY over zero subjects — ADR-010
227
+ // §3: "`.notExist()` over zero subjects is a declaration by construction."
228
+ // Over a non-empty selection it is an ordinary assertion that examined
229
+ // real units, and marking it declared there made every passing
230
+ // `.notExist()` expire the moment the emitter checked. Measured: it
231
+ // reddened six crossvalidate tests.
232
+ declaredEmpty: this._expectEmpty === true || (examined === 0 && this.assertsCardinality())
233
+ ? true
234
+ : undefined,
235
+ };
236
+ const withFinding = (f) => collectResult([...violations, f], evidence);
237
+ // Split out because threading the evidence through every branch pushed this
238
+ // method to cyclomatic complexity 11 and the repo's own `check:arch` said so.
239
+ // The ORDER is the content here, and it is unchanged.
240
+ if (examined === 0) {
241
+ const zero = this.zeroExaminedFinding(sourceEmpty, deadGlob);
242
+ return zero === undefined ? collectResult(violations, evidence) : withFinding(zero);
243
+ }
244
+ if (this._expectEmpty === true) {
245
+ return withFinding(expiredExpectEmptyViolation(this.facts(), examined));
246
+ }
247
+ return collectResult(violations, evidence);
248
+ }
249
+ /**
250
+ * Which finding a zero-examined rule earns, or `undefined` when it earned none
251
+ * because it declared the emptiness.
252
+ *
253
+ * Extracted from `evidencedViolations` under `check:arch`'s complexity rule.
254
+ * The precedence is the whole content and is unchanged: an empty source
255
+ * outranks every declaration (ADR-010 §3), an explicit declaration is
256
+ * satisfied, `.expectNonEmpty()` is unmet, a cardinality condition is
257
+ * satisfied by emptiness, and a nameable dead glob outranks the generic
258
+ * message because it is strictly more actionable.
259
+ */
260
+ zeroExaminedFinding(sourceEmpty, deadGlob) {
261
+ if (sourceEmpty === true)
262
+ return zeroLoadedSourceViolation(this.facts());
263
+ if (this._expectEmpty === true)
264
+ return undefined;
265
+ if (this._expectEmpty === false)
266
+ return unmetExpectNonEmptyViolation(this.facts());
267
+ if (this.assertsCardinality())
268
+ return undefined;
269
+ if (deadGlob !== undefined)
270
+ return deadGlobViolation(this.facts(), deadGlob);
271
+ return zeroExaminedViolation(this.facts());
120
272
  }
121
273
  /**
122
274
  * Execute the rule with the given severity.
@@ -131,5 +283,143 @@ export class TerminalBuilder {
131
283
  this.warn();
132
284
  }
133
285
  }
286
+ /**
287
+ * An independent copy of this builder.
288
+ *
289
+ * **A held selection is immutable** (a bug class ported alongside the
290
+ * kernel — plan 0088 Phase 4, matching `ts-archunit`'s own documented
291
+ * "bug 0016"). Before this, every chain method mutated `this` in place:
292
+ *
293
+ * ```ts
294
+ * const repos = classes(p).that().extend('BaseRepository')
295
+ * repos.that().haveNameEndingWith('Legacy').should().notExist().check()
296
+ * repos.should().beExported().check() // silently narrowed to Legacy too
297
+ * ```
298
+ *
299
+ * A held selection reused for a second rule silently inherited the first
300
+ * rule's narrowing, exclusions, and rule id — found live in this repo's own
301
+ * test suite once the ADR-010 evidence gate stopped treating "examined
302
+ * zero units because a stale narrowing ate the selection" as an ordinary,
303
+ * silent pass. `because`/`rule`/`excluding`/`expectEmpty`/`expectNonEmpty`
304
+ * on `TerminalBuilder`, and `that`/`addPredicate`/`addCondition` on
305
+ * `RuleBuilder`, all return a copy rather than mutating `this`.
306
+ *
307
+ * Subclasses with additional fields beyond `TerminalBuilder`'s own MUST
308
+ * override this (see `RuleBuilder.copy()`) and call `super.copy()` first.
309
+ */
310
+ copy() {
311
+ const clone = shallowClone(this);
312
+ clone.adoptFilterState(this);
313
+ clone._metadata = this._metadata ? { ...this._metadata } : undefined;
314
+ return clone;
315
+ }
316
+ /**
317
+ * Give this builder independent copies of another's filter state.
318
+ *
319
+ * `copy()` shallow-copies every field via `Object.assign`, so without this
320
+ * a copy would share its source's `_exclusions` array and `_silentIndices`
321
+ * set by reference — `.excluding()` on one would silently mutate the
322
+ * other, the exact hazard `copy()` exists to close, one field deeper.
323
+ */
324
+ adoptFilterState(source) {
325
+ this._exclusions = [...source._exclusions];
326
+ this._silentIndices = new Set(source._silentIndices);
327
+ }
328
+ /**
329
+ * Is this rule's own assertion satisfied by an empty examined set, as a
330
+ * matter of what it asserts (`.notExist()`-shaped), not a caller's
331
+ * `.expectEmpty()` declaration? Default `false` — most families have no
332
+ * such condition and stay covered by the ordinary zero-examined gate.
333
+ * `RuleBuilder` overrides this to check its own `_conditions` against the
334
+ * kernel's `assertsCardinality()` registry (ADR-010, `cardinality.ts`).
335
+ */
336
+ assertsCardinality() {
337
+ return false;
338
+ }
339
+ }
340
+ /**
341
+ * The configuration finding for a rule that asserts nothing — bug 0155.
342
+ *
343
+ * An assertion-less rule — subjects found, nothing asserted about them —
344
+ * cannot fail, so it certifies nothing while reading as coverage.
345
+ *
346
+ * **The guard was unreachable, not merely quiet.** It used to read
347
+ * `_conditions.length === 0 && _phase === 'predicate'`, and `should()` sets
348
+ * the phase to `'condition'`, so for every rule shape the DSL documents it
349
+ * could never fire — the defect passed in total silence, never even reaching
350
+ * the stderr warning it was routed to. Hence no `_phase` term at the call
351
+ * site.
352
+ *
353
+ * **A finding, not a warning**, per ADR-009 rule 1's discriminator: the remedy
354
+ * is not optional. There is no state in which "keeps asserting nothing" is
355
+ * correct — add a condition, or delete the rule. The two rules ADR-009 names
356
+ * as deliberately `warn` (`no-silent-catch`, `no-empty-bodies`) warn *because*
357
+ * they carry suppressible false positives a reader must judge case by case.
358
+ * This carries none.
359
+ *
360
+ * **A declared emptiness expectation is an assertion**, so `_expectEmpty`
361
+ * exempts a rule from this gate. `.expectNonEmpty()` reddens when the corpus
362
+ * it says must never be empty becomes empty; `.expectEmpty()` reddens the day
363
+ * the set it says must stay empty gains a member. Neither lives in
364
+ * `_conditions`. Without that term the gate called a working corpus guard
365
+ * assertion-less and told its author to "add a condition or delete the rule" —
366
+ * both of which destroy the guard — and for `.expectEmpty()` reported two
367
+ * findings for one fault. Found in PR #71's review.
368
+ *
369
+ * **Placed AFTER the zero-examined branch**, so a dead selector still reports
370
+ * as a dead selector. Measured: `resideInFolder('srcc/**')` with no condition
371
+ * reports the dead glob, not this finding. An earlier draft of this docstring
372
+ * (and of the changeset and bug record) claimed the opposite — "reports the
373
+ * missing assertion only" — and justified it as gate-first. Both were wrong:
374
+ * `getElements()` and the predicate filter already ran, so the ordering saves
375
+ * no work, and the behaviour is the reverse. The real precedence is the better
376
+ * one, and is now stated as what it is: this finding fires only when subjects
377
+ * were actually selected.
378
+ *
379
+ * **Exported** rather than private to `RuleBuilder`. Its five siblings
380
+ * (`zeroExaminedViolation`, `deadGlobViolation`, `unmetExpectNonEmptyViolation`,
381
+ * `expiredExpectEmptyViolation`, `zeroLoadedSourceViolation`) have since moved to
382
+ * `vacuity-findings.ts`; this one stayed, because unlike them it is constructed
383
+ * from a builder's own condition list rather than from `RuleFacts` alone. The *detection* has to stay per-builder —
384
+ * `TerminalBuilder` has no `_conditions` — but a private constructor meant
385
+ * `eess-ts`'s slice/schema/resolver builders could not reuse it and were left
386
+ * warning while the kernel's rules failed: the same defect, one DSL, four
387
+ * different answers.
388
+ *
389
+ * `bypassFilters` makes it a **configuration** finding — `error` regardless of
390
+ * `.asSeverity('warn')`, refused by `.excluding()`, skipped by diff and
391
+ * baseline. It reports that the rule's own instrument is broken, not a fault
392
+ * in what was examined, so a filter aimed at the latter must not suppress it.
393
+ */
394
+ export function assertionLessViolation(ruleId, advice) {
395
+ const remedy = advice ??
396
+ 'Add a condition after .should() (a predicate-only method such as ' +
397
+ 'areExported/areAsync filters elements, it does not assert), or delete the rule.';
398
+ // `message` is the diagnosis; `suggestion` is the remedy. Keeping them
399
+ // DISTINCT matters: `remedyRepeatsMessage()` suppresses a `Fix:` line only
400
+ // when it is byte-identical to `What:`, so folding the remedy into both
401
+ // printed the whole thing twice — plan 0147's double-print, which an earlier
402
+ // draft of this function reintroduced by appending UNSUPPRESSABLE to a
403
+ // suggestion that already repeated the message.
404
+ const message = `Rule '${ruleId}' selects subjects but asserts nothing about them, so it ` +
405
+ `cannot fail and certifies nothing.`;
406
+ return {
407
+ rule: ruleId,
408
+ element: ruleId,
409
+ file: '',
410
+ line: 0,
411
+ message,
412
+ // The `.expectNonEmpty()` carve-out is named ON PURPOSE. It satisfies this
413
+ // gate (a declared emptiness expectation IS an assertion), so an agent
414
+ // told "add a condition, or delete the rule" and liking neither will find
415
+ // that one token clears the finding — and on a non-empty corpus
416
+ // `.expectNonEmpty()` asserts nothing further. ADR-009 rule 3: a marker an
417
+ // agent can stamp to go green is worse than no marker unless the cost is
418
+ // stated, so state it here rather than let it be discovered.
419
+ suggestion: `${remedy} (\`.expectNonEmpty()\` also satisfies this gate, but asserts ` +
420
+ `only that the corpus is non-empty — it is not a substitute for the ` +
421
+ `condition you meant.) ${UNSUPPRESSABLE}`,
422
+ bypassFilters: true,
423
+ };
134
424
  }
135
425
  //# sourceMappingURL=terminal-builder.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"terminal-builder.js","sourceRoot":"","sources":["../src/terminal-builder.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"terminal-builder.js","sourceRoot":"","sources":["../src/terminal-builder.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,38 @@
1
+ /**
2
+ * The narrowings this codebase needs at its JS-interop edges, in one place.
3
+ *
4
+ * [ADR-005](../../../adr/005-no-any-no-type-assertions.md) forbids `as` and
5
+ * prescribes type guards. The guards existed — `isRecord` was written **twice**,
6
+ * verbatim, in `tsconfig/tsconfig-builder.ts` and `cli/commands/init.ts` — while a
7
+ * third site cast instead of calling either.
8
+ * [ts-archunit Bug 0049](https://github.com/nielspeter/ts-archunit/blob/main/bugs/fixed/0049-the-type-assertion-self-check-selected-classes.md).
9
+ *
10
+ * A duplicated predicate is not a style problem in this repo: bug 0044 was a
11
+ * measurement error caused by exactly that, and the fix was to delete the
12
+ * duplicate rather than to test both copies.
13
+ */
14
+ /**
15
+ * A plain object, indexable by string.
16
+ *
17
+ * Excludes `null`, which `typeof` does not — and excludes **arrays**, which is
18
+ * load-bearing rather than tidy. Both retired copies had `!Array.isArray(value)`
19
+ * and the first draft of this shared one did not, which would have quietly
20
+ * widened `tsconfig-builder`'s deep-compare and `init`'s tsconfig reader to accept
21
+ * an array as a record. Consolidating two copies means picking one, and picking
22
+ * the weaker one is how a refactor becomes a defect.
23
+ */
24
+ export declare function isRecord(value: unknown): value is Record<string, unknown>;
25
+ /**
26
+ * A value that can be called with no arguments.
27
+ *
28
+ * Narrower than it looks, and deliberately so: the return type is `unknown`, so
29
+ * every caller has to establish what came back rather than inheriting a claim.
30
+ *
31
+ * This exists because *removing* a cast is not automatically an improvement.
32
+ * Dropping `(exported as () => unknown)()` after a `typeof === 'function'` check
33
+ * left `exported` typed as `Function`, which trades an ADR-005 violation for an
34
+ * `@typescript-eslint/no-unsafe-call` error — the same unchecked call, differently
35
+ * spelled. A predicate narrows to a signature and satisfies both.
36
+ */
37
+ export declare function isNullaryCallable(value: unknown): value is () => unknown;
38
+ //# sourceMappingURL=type-guards.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"type-guards.d.ts","sourceRoot":"","sources":["../src/type-guards.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH;;;;;;;;;GASG;AACH,wBAAgB,QAAQ,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAEzE;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,MAAM,OAAO,CAExE"}
@@ -0,0 +1,42 @@
1
+ /**
2
+ * The narrowings this codebase needs at its JS-interop edges, in one place.
3
+ *
4
+ * [ADR-005](../../../adr/005-no-any-no-type-assertions.md) forbids `as` and
5
+ * prescribes type guards. The guards existed — `isRecord` was written **twice**,
6
+ * verbatim, in `tsconfig/tsconfig-builder.ts` and `cli/commands/init.ts` — while a
7
+ * third site cast instead of calling either.
8
+ * [ts-archunit Bug 0049](https://github.com/nielspeter/ts-archunit/blob/main/bugs/fixed/0049-the-type-assertion-self-check-selected-classes.md).
9
+ *
10
+ * A duplicated predicate is not a style problem in this repo: bug 0044 was a
11
+ * measurement error caused by exactly that, and the fix was to delete the
12
+ * duplicate rather than to test both copies.
13
+ */
14
+ /**
15
+ * A plain object, indexable by string.
16
+ *
17
+ * Excludes `null`, which `typeof` does not — and excludes **arrays**, which is
18
+ * load-bearing rather than tidy. Both retired copies had `!Array.isArray(value)`
19
+ * and the first draft of this shared one did not, which would have quietly
20
+ * widened `tsconfig-builder`'s deep-compare and `init`'s tsconfig reader to accept
21
+ * an array as a record. Consolidating two copies means picking one, and picking
22
+ * the weaker one is how a refactor becomes a defect.
23
+ */
24
+ export function isRecord(value) {
25
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
26
+ }
27
+ /**
28
+ * A value that can be called with no arguments.
29
+ *
30
+ * Narrower than it looks, and deliberately so: the return type is `unknown`, so
31
+ * every caller has to establish what came back rather than inheriting a claim.
32
+ *
33
+ * This exists because *removing* a cast is not automatically an improvement.
34
+ * Dropping `(exported as () => unknown)()` after a `typeof === 'function'` check
35
+ * left `exported` typed as `Function`, which trades an ADR-005 violation for an
36
+ * `@typescript-eslint/no-unsafe-call` error — the same unchecked call, differently
37
+ * spelled. A predicate narrows to a signature and satisfies both.
38
+ */
39
+ export function isNullaryCallable(value) {
40
+ return typeof value === 'function';
41
+ }
42
+ //# sourceMappingURL=type-guards.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"type-guards.js","sourceRoot":"","sources":["../src/type-guards.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH;;;;;;;;;GASG;AACH,MAAM,UAAU,QAAQ,CAAC,KAAc;IACrC,OAAO,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAA;AAC7E,CAAC;AAED;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,iBAAiB,CAAC,KAAc;IAC9C,OAAO,OAAO,KAAK,KAAK,UAAU,CAAA;AACpC,CAAC"}
@@ -0,0 +1,54 @@
1
+ /**
2
+ * The one sentence that tells a reader a configuration finding has no escape
3
+ * hatch — [ADR-009](../../../adr/009-agent-first-failure-surfaces.md) rule 3.
4
+ *
5
+ * Stated on the finding rather than inside each remedy so the per-shape advice
6
+ * stays one sentence each and this stays one sentence in one place. Measured
7
+ * before it existed: a reader given only the remedy tries `.asSeverity('warn')`,
8
+ * `.excluding()`, the baseline and `--changed` — four CI cycles — because
9
+ * nothing told them those were refused.
10
+ *
11
+ * ## Why it lives here rather than inline
12
+ *
13
+ * It was written twice, inline, at two sites in `terminal-builder.ts`, and
14
+ * prettier had already split the second copy across lines. Two copies of a
15
+ * sentence that enumerates a list is the shape that goes stale: the code grew a
16
+ * sixth refusing surface and both copies still named five.
17
+ *
18
+ * ## The list is a claim, so it is guarded
19
+ *
20
+ * `tests/core/unsuppressable-sentence.test.ts` derives the refusing mechanisms
21
+ * **behaviourally** — one probe each — and compares that set against the names
22
+ * parsed out of this string. Disagreement fails in both directions: naming a
23
+ * mechanism that does not refuse (bug 0017's shape) and refusing by a mechanism
24
+ * this does not name. The second is what actually happened.
25
+ *
26
+ * The sixth surface is the inline `// eess-exclude` comment, refused in
27
+ * `execute-rule.ts` by the `v.bypassFilters === true ||` clause. It was omitted
28
+ * while it was hard to reach; [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)
29
+ * made inline comments work for every condition family, so an agent reading a
30
+ * five-item list and inferring exhaustiveness now reaches for the one mechanism
31
+ * the sentence forgot to mention.
32
+ */
33
+ export declare const UNSUPPRESSABLE: string;
34
+ /**
35
+ * The declaration half, appended ONLY by the zero-examined producer.
36
+ *
37
+ * It used to live in {@link UNSUPPRESSABLE}, which every configuration finding
38
+ * carries — so it was stapled to findings a declaration cannot settle. Measured:
39
+ * on the assertion-gate finding ("this detector has no pattern") and on a dead
40
+ * selector glob, adding `.expectEmpty()` changes nothing, because both are
41
+ * decided before any declaration is consulted. The reader declares, re-runs, and
42
+ * gets the identical failure with the identical advice — ADR-009 rule 2's loop,
43
+ * introduced by the sentence written to close one.
44
+ *
45
+ * On the dead-glob kind it was worse than a no-op: there `overrides: { id: 'off' }`
46
+ * IS the working exit and declaring is not, so the message steered the reader off
47
+ * the only door that opens.
48
+ *
49
+ * Zero-examined is the one kind a declaration actually settles.
50
+ */
51
+ export declare const DECLARE_INSTEAD: string;
52
+ /** The mechanism names the sentence claims to refuse, for the parity guard. */
53
+ export declare const UNSUPPRESSABLE_MECHANISMS: readonly [".warn()", ".asSeverity('warn')", ".excluding()", "// eess-exclude", "baseline", "diff-aware"];
54
+ //# sourceMappingURL=unsuppressable.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"unsuppressable.d.ts","sourceRoot":"","sources":["../src/unsuppressable.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AACH,eAAO,MAAM,cAAc,QAe6D,CAAA;AAExF;;;;;;;;;;;;;;;;GAgBG;AACH,eAAO,MAAM,eAAe,QAEqC,CAAA;AAEjE,+EAA+E;AAC/E,eAAO,MAAM,yBAAyB,0GAO5B,CAAA"}