@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
package/dist/report.js CHANGED
@@ -1,3 +1,5 @@
1
+ import { collectResult, hasEvidence } from './collect-result.js';
2
+ import { isEmitterFinding, noReceiptViolation, sourceEmptyViolation, passWithoutEvidenceViolation, expiredDeclarationViolation, contradictoryEvidenceViolation, } from './emitter-findings.js';
1
3
  import { ArchRuleError } from './errors.js';
2
4
  import { formatViolations } from './format.js';
3
5
  import { formatViolationsJson } from './format-json.js';
@@ -9,37 +11,211 @@ import { formatViolationsGitHub } from './format-github.js';
9
11
  * path) both delegate here, so the two reporting paths can no longer diverge
10
12
  * (plan 0070). Emits nothing for an empty set.
11
13
  */
14
+ let violationsEmitted = 0;
15
+ /**
16
+ * How many violations this module has actually WRITTEN, ever.
17
+ *
18
+ * Read as a delta by a caller that aggregates reporting, to answer one question it
19
+ * cannot otherwise answer: *did something emit while I was loading that module?*
20
+ *
21
+ * The alternative a caller might reach for — counting the writes it SUPPRESSED and
22
+ * inferring "then nothing was written" — is a double negative and is unsound: a
23
+ * module that suppresses one terminal and leaks through another satisfies it while
24
+ * leaking. Measured on `eess-ts`, which shipped that inference and fell into
25
+ * exactly that case. Count emissions, not silences.
26
+ */
27
+ export function violationsEmittedCount() {
28
+ return violationsEmitted;
29
+ }
30
+ /**
31
+ * The evidence gate both emitters share — ADR-014 §1, §4.
32
+ *
33
+ * Produced BEFORE the delivery mode is chosen, so the finding is in the value
34
+ * returned under `return`, rides the throw under `throw`, and is in the text
35
+ * written under `warn`. A contract that binds only one door does not bind.
36
+ *
37
+ * A value that already carries a finding passes through untouched (§4): a
38
+ * terminal that examined nothing has already named its own cause, and "you
39
+ * constructed nothing" beside "your glob is dead" is a false second remedy.
40
+ */
41
+ function withEvidenceGate(violations) {
42
+ // Deliberately looser than the exported signatures. The TYPE refuses a bare
43
+ // array (D1/D2); this guard is for the untyped JavaScript caller the type
44
+ // cannot reach — ADR-014's stated residual, and the field failure in
45
+ // proposal 009 was exactly that consumer.
46
+ if (!hasEvidence(violations)) {
47
+ return collectResult([...violations, noReceiptViolation()], { examined: 0 });
48
+ }
49
+ // Expiry FIRST, and before the `examined > 0` exit — ADR-014 §3. A declaration
50
+ // that cannot be contradicted is not an assertion, which is the whole reason
51
+ // §3's amendment refuses to infer one from a config. Checked even when real
52
+ // violations are present: the declaration is wrong independently of them.
53
+ if (violations.declaredEmpty === true && violations.examined > 0) {
54
+ return collectResult([...violations, expiredDeclarationViolation(violations.examined)], {
55
+ examined: violations.examined,
56
+ sourceEmpty: violations.sourceEmpty,
57
+ });
58
+ }
59
+ // The same check for the third state — ADR-014 §3, added after review found
60
+ // `notRun` shipped as a suppressor with no falsifier while `declaredEmpty` had
61
+ // one. A rule that never ran examined nothing and found nothing; either half
62
+ // present contradicts the flag, and BOTH are read because either alone would
63
+ // leave the other as an unchecked claim.
64
+ //
65
+ // Before the two exits below, deliberately: `examined > 0` is precisely the
66
+ // state that contradicts `notRun`, so checking after them would make the
67
+ // finding unreachable — the mistake this ADR's §2 warns about in its own
68
+ // implementation notes.
69
+ if (violations.notRun === true && (violations.examined > 0 || violations.length > 0)) {
70
+ return collectResult([...violations, contradictoryEvidenceViolation(violations.examined, violations.length)], { examined: violations.examined, sourceEmpty: violations.sourceEmpty });
71
+ }
72
+ // **`sourceEmpty` beside real evidence is a contradiction, like its siblings.**
73
+ // A source that loaded nothing cannot have yielded units to examine — the flag
74
+ // is set precisely when `examined === 0`. Checked before the two exits below,
75
+ // for the same reason the `notRun` check above is: after them it would be
76
+ // unreachable.
77
+ //
78
+ // Without this, `sourceEmpty` was the one flag in the vocabulary that could
79
+ // quiet a zero and never be contradicted — `notRun` beside evidence is
80
+ // `emitter/contradictory-evidence`, `declaredEmpty` beside evidence is
81
+ // `emitter/expired-declaration`, and `{ examined: 900, sourceEmpty: true }` was
82
+ // silent. That asymmetry is what ADR-014's "every flag that can quiet a zero
83
+ // can be contradicted" row exists to refuse, and promoting `sourceEmpty` to a
84
+ // verdict-changing flag with its own id is what put it in scope.
85
+ if (violations.sourceEmpty === true && violations.examined > 0) {
86
+ return collectResult([
87
+ ...violations,
88
+ contradictoryEvidenceViolation(violations.examined, violations.length, 'sourceEmpty'),
89
+ ], { examined: violations.examined });
90
+ }
91
+ if (violations.length > 0)
92
+ return violations;
93
+ if (violations.examined > 0)
94
+ return violations;
95
+ // **An empty source outranks any declaration, and names the source** — ADR-014
96
+ // §4, and ADR-010 §3's own `gated` row ("Zero loaded source files outranks any
97
+ // `.expectEmpty()` declaration") which the terminal has always honoured.
98
+ //
99
+ // This branch is checked BEFORE `declaredEmpty` and `notRun`, and that ordering
100
+ // is the whole point. The comment that used to sit here said `sourceEmpty` does
101
+ // not legitimise a zero — and the next line returned green on `declaredEmpty`
102
+ // without ever reading it. Measured:
103
+ // `finishPreset(collectResult([], { examined: 0, sourceEmpty: true, declaredEmpty: true }))`
104
+ // was GREEN. A hand-assembled receipt claiming an empty source and declaring it
105
+ // away is precisely the escape hatch that comment claimed this ADR had closed.
106
+ //
107
+ // A terminal that loaded nothing already carries `zeroLoadedSourceViolation` and
108
+ // exits at the `length > 0` line above, so this fires for the hand-assembled
109
+ // receipt — the population ADR-014's gate exists for.
110
+ if (violations.sourceEmpty === true) {
111
+ return collectResult([...violations, sourceEmptyViolation()], {
112
+ examined: violations.examined,
113
+ sourceEmpty: true,
114
+ // The input's own declarations are carried through, not dropped. They do
115
+ // not rescue the verdict — that is the precedence this branch enforces —
116
+ // but a downstream reader still needs to know they were made, and the
117
+ // sibling branches above preserve what they were handed for the same
118
+ // reason.
119
+ declaredEmpty: violations.declaredEmpty,
120
+ notRun: violations.notRun,
121
+ deadGlob: violations.deadGlob,
122
+ });
123
+ }
124
+ if (violations.declaredEmpty === true)
125
+ return violations;
126
+ // `notRun` legitimises a zero here for the same reason `declaredEmpty` does,
127
+ // and for the reason the MERGE already exempts it: a rule turned off examined
128
+ // nothing and found nothing, and reporting that as a vacuous pass is the
129
+ // "one disabled rule reddens a whole preset" failure `notRun` was added to
130
+ // prevent (measured on `adrEnforcement` with `adr/valid-tiers` off).
131
+ //
132
+ // **The two doors disagreed until this line.** `mergeCollectResults` exempts a
133
+ // `notRun` member from its dead filter; this gate had no such branch, so the
134
+ // per-builder doors judged an off rule alone and reddened it. Measured:
135
+ // `checkAll([healthy, off])` was GREEN while `eess-ts check` over the same two
136
+ // builders reported `1 of 2 rules failing`. Same receipts, opposite verdicts,
137
+ // decided by which door you walked through — and `check.ts` claimed per-builder
138
+ // gating was "strictly stronger, not weaker", which is how a false positive is
139
+ // manufactured.
140
+ //
141
+ // It is not a mute button: a `notRun` beside a non-zero `examined` or any
142
+ // violation is `emitter/contradictory-evidence`, checked above and reached
143
+ // before this line. And the all-off case still reports, because the merged
144
+ // receipt carries no `notRun` — so a preset whose every member is off sums to
145
+ // zero examined, undeclared, and reds exactly as ADR-014 §3 requires.
146
+ if (violations.notRun === true)
147
+ return violations;
148
+ return collectResult([passWithoutEvidenceViolation()], { examined: violations.examined });
149
+ }
12
150
  export function reportViolations(violations, options = {}) {
13
- if (violations.length === 0)
14
- return;
151
+ const gated = withEvidenceGate(violations);
152
+ if (gated.length === 0)
153
+ return gated;
154
+ violationsEmitted += gated.length;
15
155
  const format = options.format ?? 'terminal';
16
156
  if (format === 'json') {
17
- process.stdout.write(formatViolationsJson(violations, options.reason) + '\n');
157
+ process.stdout.write(formatViolationsJson(gated, options.reason) + '\n');
18
158
  }
19
159
  else if (format === 'github') {
20
- process.stdout.write(formatViolationsGitHub(violations, 'error') + '\n');
160
+ process.stdout.write(formatViolationsGitHub(gated, 'error') + '\n');
21
161
  }
22
162
  else {
23
163
  // Rich text to stderr — test runners show the plain-text Error message,
24
164
  // while stderr carries the colorized Why/Fix/Docs output.
25
- process.stderr.write(formatViolations(violations, options.reason) + '\n');
165
+ process.stderr.write(formatViolations(gated, options.reason) + '\n');
166
+ }
167
+ // ADR-014 §5: a bare `reportViolations` hands nothing back that a caller must
168
+ // act on, so a printed unsuppressable finding above a zero exit is the lie by
169
+ // another name. The gate's own finding escalates to a throw here.
170
+ if (gated.some((v) => v.bypassFilters === true && isEmitterFinding(v))) {
171
+ throw new ArchRuleError(gated, options.reason);
26
172
  }
173
+ return gated;
27
174
  }
28
175
  /**
29
176
  * Finish a preset: deliver its aggregated violations per the requested mode
30
- * (plan 0070). `throw` (default) emits then throws — backward-compatible with
31
- * `throwIfViolations`; `return` hands the violations back untouched (the caller
32
- * owns emission and control flow); `warn` emits without throwing. Always
33
- * returns the violations, so a caller can inspect them regardless of mode.
177
+ * (plan 0070). `throw` (default) emits then throws; `return` hands the
178
+ * violations back untouched (the caller owns emission and control flow);
179
+ * `warn` emits without throwing. Always returns the violations, so a caller can
180
+ * inspect them regardless of mode.
34
181
  */
35
182
  export function finishPreset(violations, options = {}) {
36
183
  const mode = options.report ?? 'throw';
184
+ // A caller's declaration reaches the gate ON the receipt (ADR-014 §3), never
185
+ // as a delivery option the gate reads separately — one boolean beside a sum
186
+ // cannot be weighed against that sum's parts.
187
+ const declared = options.expectEmpty === true && !violations.declaredEmpty
188
+ ? collectResult(violations, {
189
+ examined: violations.examined,
190
+ sourceEmpty: violations.sourceEmpty,
191
+ deadGlob: violations.deadGlob,
192
+ declaredEmpty: true,
193
+ })
194
+ : violations;
195
+ // The gate runs before the mode is read, so every door carries the finding.
196
+ const gated = withEvidenceGate(declared);
37
197
  if (mode === 'return')
38
- return violations;
39
- reportViolations(violations, options);
40
- if (mode === 'throw' && violations.length > 0) {
41
- throw new ArchRuleError(violations, options.reason);
198
+ return gated;
199
+ const emitterFinding = gated.some((v) => isEmitterFinding(v));
200
+ if (gated.length > 0) {
201
+ violationsEmitted += gated.length;
202
+ const format = options.format ?? 'terminal';
203
+ if (format === 'json') {
204
+ process.stdout.write(formatViolationsJson(gated, options.reason) + '\n');
205
+ }
206
+ else if (format === 'github') {
207
+ process.stdout.write(formatViolationsGitHub(gated, 'error') + '\n');
208
+ }
209
+ else {
210
+ process.stderr.write(formatViolations(gated, options.reason) + '\n');
211
+ }
212
+ }
213
+ // §5 again: `warn` prints without failing for ordinary violations, but an
214
+ // unsuppressable configuration finding printed above a zero exit is exactly
215
+ // the silent green this ADR closes.
216
+ if ((mode === 'throw' && gated.length > 0) || emitterFinding) {
217
+ throw new ArchRuleError(gated, options.reason);
42
218
  }
43
- return violations;
219
+ return gated;
44
220
  }
45
221
  //# sourceMappingURL=report.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"report.js","sourceRoot":"","sources":["../src/report.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAA;AAC3C,OAAO,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAA;AAC9C,OAAO,EAAE,oBAAoB,EAAE,MAAM,kBAAkB,CAAA;AACvD,OAAO,EAAE,sBAAsB,EAAE,MAAM,oBAAoB,CAAA;AAwB3D;;;;;;GAMG;AACH,MAAM,UAAU,gBAAgB,CAAC,UAA2B,EAAE,UAAyB,EAAE;IACvF,IAAI,UAAU,CAAC,MAAM,KAAK,CAAC;QAAE,OAAM;IACnC,MAAM,MAAM,GAAiB,OAAO,CAAC,MAAM,IAAI,UAAU,CAAA;IACzD,IAAI,MAAM,KAAK,MAAM,EAAE,CAAC;QACtB,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,oBAAoB,CAAC,UAAU,EAAE,OAAO,CAAC,MAAM,CAAC,GAAG,IAAI,CAAC,CAAA;IAC/E,CAAC;SAAM,IAAI,MAAM,KAAK,QAAQ,EAAE,CAAC;QAC/B,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,sBAAsB,CAAC,UAAU,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC,CAAA;IAC1E,CAAC;SAAM,CAAC;QACN,wEAAwE;QACxE,0DAA0D;QAC1D,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,gBAAgB,CAAC,UAAU,EAAE,OAAO,CAAC,MAAM,CAAC,GAAG,IAAI,CAAC,CAAA;IAC3E,CAAC;AACH,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,YAAY,CAC1B,UAA2B,EAC3B,UAA+B,EAAE;IAEjC,MAAM,IAAI,GAAe,OAAO,CAAC,MAAM,IAAI,OAAO,CAAA;IAClD,IAAI,IAAI,KAAK,QAAQ;QAAE,OAAO,UAAU,CAAA;IACxC,gBAAgB,CAAC,UAAU,EAAE,OAAO,CAAC,CAAA;IACrC,IAAI,IAAI,KAAK,OAAO,IAAI,UAAU,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAC9C,MAAM,IAAI,aAAa,CAAC,UAAU,EAAE,OAAO,CAAC,MAAM,CAAC,CAAA;IACrD,CAAC;IACD,OAAO,UAAU,CAAA;AACnB,CAAC"}
1
+ {"version":3,"file":"report.js","sourceRoot":"","sources":["../src/report.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,17 @@
1
+ import type { CollectResult } from './collect-result.js';
2
+ /**
3
+ * Minimal shape the CLI runner and presets collect violations from: anything
4
+ * exposing a non-throwing, severity-stamped `.violations()`. Lives in core (not
5
+ * `cli/`) so presets can return `RuleBuilderLike[]` without depending on CLI
6
+ * infrastructure.
7
+ *
8
+ * **The single member carries the evidence** — plan 0235's D1. An accessor
9
+ * beside it could be walked around: a caller keeps calling `violations()`,
10
+ * never asks for the count, and the evidence is optional again by another
11
+ * route. One member, and it hands back the receipt, so a rule file that cannot
12
+ * say what it examined does not compile.
13
+ */
14
+ export interface RuleBuilderLike {
15
+ violations: () => CollectResult;
16
+ }
17
+ //# sourceMappingURL=rule-builder-like.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rule-builder-like.d.ts","sourceRoot":"","sources":["../src/rule-builder-like.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAA;AAExD;;;;;;;;;;;GAWG;AACH,MAAM,WAAW,eAAe;IAC9B,UAAU,EAAE,MAAM,aAAa,CAAA;CAChC"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=rule-builder-like.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rule-builder-like.js","sourceRoot":"","sources":["../src/rule-builder-like.ts"],"names":[],"mappings":""}
@@ -1,11 +1,24 @@
1
1
  import type { Predicate } from './predicate.js';
2
2
  import type { Condition, ConditionContext } from './condition.js';
3
- import type { ArchViolation } from './violation.js';
4
- import type { CheckOptions } from './check-options.js';
5
- import type { RuleMetadata } from './rule-metadata.js';
6
3
  import type { RuleDescription } from './rule-description.js';
7
- import type { SilentExclusion } from './silent-exclusion.js';
8
4
  import type { Selection, ElementInfo } from './correspondence.js';
5
+ import type { GlobNode } from './glob-site.js';
6
+ import { TerminalBuilder, type CollectResult } from './terminal-builder.js';
7
+ /**
8
+ * The actual walk `RuleBuilder.globs()` delegates to — a free function
9
+ * rather than a method body, to keep the class itself under this repo's own
10
+ * 300-line class-length gate (`arch.internal.rules.ts`).
11
+ */
12
+ /**
13
+ * The globs a rule declares, stamped with where each came from.
14
+ *
15
+ * Exported (as family plumbing) so `eess-ts` can call it instead of keeping its
16
+ * own copy — they were 94% similar, and the difference was not cosmetic: only
17
+ * the eess-ts copy honoured `originLabel`. The kernel's `Predicate` has carried
18
+ * that field all along and this function ignored it, so a preset's label was
19
+ * dropped for `eess-md`, `eess-mermaid` and `eess-gherkin`. Found by unifying.
20
+ */
21
+ export declare function declaredGlobsOf<T>(predicates: readonly Predicate<T>[], conditions: readonly Condition<T>[]): GlobNode[];
9
22
  /**
10
23
  * Abstract base class for all rule builders.
11
24
  *
@@ -15,20 +28,35 @@ import type { Selection, ElementInfo } from './correspondence.js';
15
28
  * 3. Add condition methods that call `addCondition()`
16
29
  *
17
30
  * The builder accumulates predicates and conditions. Nothing executes
18
- * until a terminal method (`.check()`, `.warn()`, `.severity()`) is called.
31
+ * until a terminal method (`.check()`, `.warn()`, `.severity()`) is called —
32
+ * inherited from {@link TerminalBuilder} (plan 0088 Phase 4), which also
33
+ * supplies `because`/`rule`/`excluding`/`violations`/`expectEmpty`/
34
+ * `expectNonEmpty` and the ADR-010 evidence gate. `RuleBuilder` keeps its own
35
+ * `<T, P>` two-param generic — `P` is the dialect's project type, kept
36
+ * separate from `TerminalBuilder` (which has no project concept, since
37
+ * `correspondence()`, `SmellBuilder`, and the graphql builders don't share a
38
+ * uniform notion of "one project") — every builder across all five dialects
39
+ * depends on this exact two-param signature, so it is not replaced by the
40
+ * fold, only extended.
19
41
  */
20
- export declare abstract class RuleBuilder<T, P = unknown> {
42
+ export declare abstract class RuleBuilder<T, P = unknown> extends TerminalBuilder {
21
43
  protected readonly project: P;
22
44
  protected _predicates: Predicate<T>[];
23
45
  protected _conditions: Condition<T>[];
24
- protected _reason?: string;
25
- protected _metadata?: RuleMetadata;
26
- protected _exclusions: (string | RegExp)[];
27
- protected _silentIndices: Set<number>;
28
46
  protected _phase: 'predicate' | 'condition';
47
+ /** Whether `.should()` was ever reached — bug 0155 state 3/4 vs state 1. */
48
+ protected _reachedShould: boolean;
49
+ /**
50
+ * Descriptions of predicate-only methods used AFTER `.should()` — bug 0155
51
+ * state 2. A dual-use method dispatches to a condition in that phase and
52
+ * never lands here, so anything recorded is genuinely a filter written where
53
+ * an assertion was meant.
54
+ */
55
+ protected _misplaced: string[];
29
56
  constructor(project: P);
30
57
  /**
31
- * Begin the predicate phase. Returns `this` for chaining.
58
+ * Begin the predicate phase. Returns a COPY — a held selection is never
59
+ * edited by narrowing it (see `TerminalBuilder.copy()`).
32
60
  * Purely a readability marker — `.that().haveNameMatching(...)` reads like English.
33
61
  * Explicitly resets phase to 'predicate' — defensive against `.should().that()` misuse.
34
62
  */
@@ -59,50 +87,6 @@ export declare abstract class RuleBuilder<T, P = unknown> {
59
87
  * as a predicate; if it has `evaluate` it is treated as a condition.
60
88
  */
61
89
  satisfy(custom: Predicate<T> | Condition<T>): this;
62
- /**
63
- * Attach a human-readable rationale to the rule.
64
- * Included in violation messages when `.check()` throws.
65
- */
66
- because(reason: string): this;
67
- /**
68
- * Attach rich metadata to the rule.
69
- * Provides educational context in violation output: why, how to fix, docs link.
70
- *
71
- * If `metadata.because` is set, it also sets the reason (same as `.because()`).
72
- */
73
- rule(metadata: RuleMetadata): this;
74
- /**
75
- * Exclude specific violations from reporting by matching against
76
- * the violation's `element`, `file`, or `message` fields.
77
- *
78
- * Matched violations are silently suppressed. Use for permanent,
79
- * intentional exceptions — not for temporary violations (use baseline for those).
80
- *
81
- * Patterns are matched against all three fields. Prefer anchored regexes
82
- * or full string matches over short substrings, especially for `message`
83
- * matching, to avoid accidentally suppressing unrelated violations whose
84
- * messages happen to contain the same text.
85
- *
86
- * Emits a warning if an exclusion matches zero violations — so renamed
87
- * or deleted exceptions don't silently stay in the rule.
88
- *
89
- * For narrowing a rule's scope at the predicate phase (so the rule never
90
- * evaluates the excluded element), use `satisfy(not(<predicate>))` instead.
91
- * See the "Excluding a file from a rule's scope" recipe in docs/recipes.md.
92
- *
93
- * @example
94
- * // Exclude by element name (fully qualified)
95
- * .excluding('Asset.getImageUrl')
96
- *
97
- * @example
98
- * // Exclude by file path (regex anchored to suffix)
99
- * .excluding(/repositories\/index\.ts$/)
100
- *
101
- * @example
102
- * // Multiple exclusions, mixed forms
103
- * .excluding('Asset.getImageUrl', /\/legacy\//, /generated/)
104
- */
105
- excluding(...patterns: (string | RegExp | SilentExclusion)[]): this;
106
90
  /**
107
91
  * Return the predicate-filtered elements as a labelled `Selection<T>` for
108
92
  * cross-validation (`correspondence()`). The set is exactly the elements this
@@ -121,38 +105,61 @@ export declare abstract class RuleBuilder<T, P = unknown> {
121
105
  */
122
106
  describeRule(): RuleDescription;
123
107
  /**
124
- * Execute the rule and return violations after exclusion filtering.
125
- * Does not throw — use for programmatic access (presets, aggregation).
126
- */
127
- violations(): ArchViolation[];
128
- /**
129
- * Execute the rule and throw `ArchRuleError` if any violations are found.
130
- * This is the primary terminal method — use in test assertions.
108
+ * ADR-010: is this rule's assertion satisfied by an empty selection, as a
109
+ * property of what its conditions assert (`.notExist()`) rather than a
110
+ * caller's `.expectEmpty()` declaration?
131
111
  *
132
- * @param options - Optional baseline and diff filtering
133
- */
134
- check(options?: CheckOptions): void;
135
- /**
136
- * Execute the rule and log violations to stderr. Does not throw.
137
- * Use for rules that should warn but not fail CI.
112
+ * `.every()`, not `.some()` — `.andShould()` ANDs, so a rule reading
113
+ * `.should().notExist().andShould().beExported()` still asserts something
114
+ * about subjects that exist, and exempting it on the strength of the one
115
+ * cardinality condition would silence the other. An empty condition list is
116
+ * NOT exempt: `[].every()` is vacuously `true`, and a rule with zero
117
+ * conditions is the assertion-less case `collectViolations()` now reports as
118
+ * a configuration finding (bug 0155; it used to be an unreachable stderr
119
+ * warning, which this comment named until that fix landed) — this override
120
+ * must not paper over it by also declaring it cardinality-satisfied.
138
121
  *
139
- * @param options - Optional baseline and diff filtering
122
+ * **Load-bearing:** for a DEAD selector this early return is the only thing
123
+ * between an assertion-less rule and a silent pass (the gate runs after the
124
+ * zero-examined branch). Pinned in `assertion-less-rules.test.ts`.
140
125
  */
141
- warn(options?: CheckOptions): void;
142
126
  /**
143
- * Execute the rule with the given severity.
144
- * `.severity('error')` is equivalent to `.check()`.
145
- * `.severity('warn')` is equivalent to `.warn()`.
146
- */
147
- severity(level: 'error' | 'warn'): void;
127
+ * Does this rule assert anything at all? — bug 0155.
128
+ *
129
+ * Conditions alone are not enough: a predicate-only method written *after*
130
+ * `.should()` silently shrank the set the conditions run over, possibly to
131
+ * empty, in which case they hold vacuously. That rule asserts nothing
132
+ * meaningful even though `_conditions` is non-empty.
133
+ */
134
+ protected assertsSomething(): boolean;
135
+ protected assertionAdvice(): string;
136
+ protected assertsCardinality(): boolean;
137
+ /**
138
+ * ADR-010 part 3: did this rule's own underlying source (the project, a
139
+ * diagram, an SDL string — whatever `P` loads) produce nothing at all,
140
+ * before `getElements()`'s own domain-specific extraction or any
141
+ * predicate ran? Default `false` — the kernel has no way to know, since
142
+ * `P` is opaque here. A dialect's concrete builder, which knows `P`'s real
143
+ * shape (e.g. `ArchProject`), overrides this to check its own project's
144
+ * file count directly — never `getElements().length`, which conflates "the
145
+ * source is empty" with "this domain legitimately has nothing in a
146
+ * healthy project" (e.g. zero JSX elements in a backend-only codebase).
147
+ */
148
+ protected sourceEmpty(): boolean;
149
+ /** Declared globs, position derived from `_phase`. Pure — no `P` needed. */
150
+ globs(): readonly GlobNode[];
151
+ /** This rule's own project, for `diagnose()`/`doctor` outside `.check()`. */
152
+ getProject(): P;
153
+ /** A `globs()` tree diagnosably dead, when zero examined — same shape as `sourceEmpty()`. */
154
+ protected deadGlobDiagnosis(): string | undefined;
148
155
  /**
149
156
  * Register a predicate. Called by concrete builder methods like
150
- * `.haveNameMatching()`, `.extend()`, etc.
157
+ * `.haveNameMatching()`, `.extend()`, etc. Returns a COPY — see `copy()`.
151
158
  */
152
159
  protected addPredicate(predicate: Predicate<T>): this;
153
160
  /**
154
161
  * Register a condition. Called by concrete builder methods like
155
- * `.notContain()`, `.notExist()`, etc.
162
+ * `.notContain()`, `.notExist()`, etc. Returns a COPY — see `copy()`.
156
163
  */
157
164
  protected addCondition(condition: Condition<T>): this;
158
165
  /**
@@ -161,10 +168,31 @@ export declare abstract class RuleBuilder<T, P = unknown> {
161
168
  */
162
169
  protected abstract getElements(): T[];
163
170
  /**
164
- * Create a fork of this builder with the same predicates but empty conditions.
165
- * Used by `.should()` to support named selections without mutation.
171
+ * An independent copy, carrying **both** predicates and conditions.
172
+ * Extends `TerminalBuilder.copy()` with the fields this class adds.
173
+ *
174
+ * Subclasses with additional mutable-reference fields of their own (arrays,
175
+ * sets, maps a chain method pushes onto) MUST override this and call
176
+ * `super.copy()` first, the same reason `TerminalBuilder.copy()`'s own
177
+ * docstring gives — `Object.assign` shallow-copies a field, it does not
178
+ * give it independent identity.
179
+ */
180
+ protected copy(): this;
181
+ /**
182
+ * A fork of this builder, carrying BOTH lists. Used by `.should()` to support
183
+ * named selections without mutation.
184
+ *
185
+ * **Nothing here clears the conditions** (bug 0156). It used to, and a second
186
+ * `.should()` therefore silently discarded the first assertion — a rule that
187
+ * asserted something turned into one that asserted less, by a chain method.
188
+ * `.should().X().should().Y()` now accumulates exactly as `.andShould()` does.
166
189
  *
167
- * Subclasses with additional constructor args MUST override this method.
190
+ * Ported from `packages/ts/src/core/rule-builder.ts`, where the engine copy
191
+ * landed the fix and left the kernel behind. Found by the architect review of
192
+ * PR #72: `eess-md`, `eess-mermaid` and `eess-gherkin` all extend THIS class,
193
+ * so they carried the defect — and `check:corpus`, `check:ledger` and
194
+ * `check:diagram` are md/mermaid gates, meaning this repo's own corpus
195
+ * enforcement ran on the broken copy.
168
196
  */
169
197
  protected fork(): this;
170
198
  /**
@@ -172,10 +200,22 @@ export declare abstract class RuleBuilder<T, P = unknown> {
172
200
  */
173
201
  private buildRuleDescription;
174
202
  /**
175
- * Execute the full pipeline: filter elements with predicates,
176
- * evaluate conditions, return violations.
177
- */
178
- private evaluate;
203
+ * Execute the full pipeline: filter elements with predicates, evaluate
204
+ * conditions, return violations plus the ADR-010 evidence — the
205
+ * post-predicate subject count, whatever it is. `TerminalBuilder.check()` /
206
+ * `.warn()` / `.violations()` decide what a zero count means (a
207
+ * configuration finding, unless `.expectEmpty()` was declared); this method
208
+ * only reports the count, never interprets it.
209
+ *
210
+ * `sourceEmpty` (ADR-010 part 3) is deliberately not `allElements.length
211
+ * === 0` — a domain's own `getElements()` legitimately returning nothing in
212
+ * a healthy, fully-loaded project (e.g. zero JSX elements in a
213
+ * backend-only codebase) is not brokenness, and conflating the two broke
214
+ * `.notExist()`'s cardinality exemption for exactly that case. Subclasses
215
+ * that can answer "did my underlying source load anything at all" override
216
+ * `sourceEmpty()`; the default assumes it did.
217
+ */
218
+ protected collectViolations(): CollectResult;
179
219
  /**
180
220
  * Build the `ConditionContext` passed to each condition.
181
221
  *
@@ -1 +1 @@
1
- {"version":3,"file":"rule-builder.d.ts","sourceRoot":"","sources":["../src/rule-builder.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"rule-builder.d.ts","sourceRoot":"","sources":["../src/rule-builder.ts"],"names":[],"mappings":"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"}