@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,10 +1,16 @@
1
1
  import fs from 'node:fs';
2
+ import { collectResult } from './collect-result.js';
3
+ import { severityFor } from './violation.js';
2
4
  import { ArchRuleError } from './errors.js';
3
5
  import { formatViolations } from './format.js';
4
6
  import { formatViolationsJson } from './format-json.js';
5
7
  import { formatViolationsGitHub } from './format-github.js';
6
8
  import { reportViolations } from './report.js';
7
9
  import { parseExclusionComments, isExcludedByComment } from './exclusion-comments.js';
10
+ import { UNSUPPRESSABLE } from './unsuppressable.js';
11
+ import { writeStderr } from './stderr.js';
12
+ import { activeNotice } from './diff-disclosure.js';
13
+ import { recordCommentSuppression } from './comment-suppression.js';
8
14
  /**
9
15
  * Stamp rule metadata onto violations and apply the rule's exclusions.
10
16
  *
@@ -19,11 +25,16 @@ import { parseExclusionComments, isExcludedByComment } from './exclusion-comment
19
25
  */
20
26
  export function applyFilters(violations, ctx) {
21
27
  let result = violations;
22
- // Apply .excluding() chain exclusions
28
+ // Apply .excluding() chain exclusions — never against a bypassFilters
29
+ // (ADR-010 configuration) finding: it reports the rule's own instrument is
30
+ // broken, not a fault in what was examined, so an exclusion aimed at the
31
+ // latter cannot correctly suppress the former.
23
32
  const exclusions = ctx.exclusions ?? [];
24
33
  if (exclusions.length > 0) {
25
34
  const matchedPatterns = new Set();
26
35
  result = result.filter((v) => {
36
+ if (v.bypassFilters === true)
37
+ return true;
27
38
  // Match against element, file, or message — so that custom conditions
28
39
  // using createViolation() can be excluded by file path or message content,
29
40
  // not just by element name (which may be a generic AST node kind).
@@ -41,7 +52,7 @@ export function applyFilters(violations, ctx) {
41
52
  const silentIndices = ctx.silentIndices ?? new Set();
42
53
  exclusions.forEach((pattern, index) => {
43
54
  if (!matchedPatterns.has(index) && !silentIndices.has(index)) {
44
- console.warn(`[eess] Unused exclusion '${String(pattern)}' in rule '${ruleId}'. ` +
55
+ writeStderr(`[eess] Unused exclusion '${String(pattern)}' in rule '${ruleId}'. ` +
45
56
  `It matched zero violations — it may be stale after a rename.`);
46
57
  }
47
58
  });
@@ -85,16 +96,42 @@ export function applyFilters(violations, ctx) {
85
96
  v.docs = ctx.metadata.docs;
86
97
  }
87
98
  }
88
- // Scan source files for inline exclusion comments (when rule has an ID).
89
- // Matching is on ruleId, which the block above has just guaranteed is present.
90
- if (ctx.metadata?.id && result.length > 0) {
99
+ // Scan source files for inline exclusion comments.
100
+ //
101
+ // **Not gated on `ctx.metadata?.id` any more (bug 0255).** It used to be, and
102
+ // that made the worst case of an inert directive the quietest: with no
103
+ // `.rule({ id })` a comment can never match — `isExcludedByComment` returns
104
+ // false without a `ruleId` — and because the block was skipped, the file was
105
+ // never even parsed, so nothing could notice. An adopter following the
106
+ // documented sanction recipe got the same red build and no explanation.
107
+ //
108
+ // The cost is bounded the same way it always was: only files that already
109
+ // produced a violation are read. A rule with no id now pays one parse per
110
+ // violating file to be able to say why the sanction did nothing.
111
+ if (result.length > 0) {
91
112
  const filePaths = new Set(result.map((v) => v.file));
113
+ const undocumented = [];
92
114
  const allComments = [...filePaths].flatMap((filePath) => {
93
115
  try {
94
116
  const sourceText = fs.readFileSync(filePath, 'utf-8');
95
117
  const parseResult = parseExclusionComments(sourceText, filePath);
96
118
  for (const warning of parseResult.warnings) {
97
- console.warn(`[eess] ${warning.message}`);
119
+ // An undocumented exclusion is well-formed and APPLIES, so a stderr
120
+ // line would let a waiver nobody justified pass with exit 0. It
121
+ // becomes an unsuppressable finding below instead — ADR-009 rule 3's
122
+ // corollary, and bug 0039's design, which until ADR-012 only `eess-ts`
123
+ // implemented. The malformed shapes decline to create the exclusion at
124
+ // all, so the original violation still fires and the build is already
125
+ // red; stderr is the right weight for those.
126
+ if (warning.kind === 'undocumented' && ctx.metadata?.id !== undefined) {
127
+ // The promotion below builds an unsuppressable finding keyed on the
128
+ // rule id. Without one there is nothing to key it on — and the
129
+ // directive is inert anyway, which the inert report says with a
130
+ // remedy the author can act on. So it falls through to stderr.
131
+ undocumented.push(warning);
132
+ continue;
133
+ }
134
+ writeStderr(`[eess] ${warning.message}`);
98
135
  }
99
136
  return parseResult.exclusions;
100
137
  }
@@ -106,8 +143,152 @@ export function applyFilters(violations, ctx) {
106
143
  return [];
107
144
  }
108
145
  });
109
- if (allComments.length > 0) {
110
- result = result.filter((v) => !isExcludedByComment(v, allComments));
146
+ const ruleId = ctx.metadata?.id;
147
+ if (allComments.length > 0 && ruleId !== undefined) {
148
+ // Which comments actually did something, so the ones that did nothing can
149
+ // be named. Reuses `isExcludedByComment` one comment at a time rather than
150
+ // re-deriving the match, so this can never drift from the real rule.
151
+ const spent = new Set();
152
+ result = result.filter((v) => {
153
+ if (v.bypassFilters === true)
154
+ return true;
155
+ const excluded = isExcludedByComment(v, allComments);
156
+ if (excluded) {
157
+ recordCommentSuppression(ruleId, v.file);
158
+ for (const c of allComments)
159
+ if (isExcludedByComment(v, [c]))
160
+ spent.add(c);
161
+ }
162
+ return !excluded;
163
+ });
164
+ // A directive naming THIS rule that suppressed nothing. Same shape and the
165
+ // same weight as the `.excluding()` "Unused exclusion" line above — that
166
+ // mechanism has warned about a stale pattern since bug 0044; the comment
167
+ // form never did, which is bug 0255.
168
+ //
169
+ // **The wording is domain-neutral on purpose.** The first version explained
170
+ // the next-line scope in terms of markdown table rows, because that is the
171
+ // corpus bug 0255 was filed against. This function is the kernel's, and
172
+ // `eess-mermaid` and `eess-gherkin` run it unforked — so a stale directive
173
+ // in a `.mmd` or `.feature` file was told it sat "inside a markdown table".
174
+ // Review caught it, and caught the tell: the hand-port into `eess-ts`
175
+ // silently dropped the table clause because it made no sense there, which
176
+ // is the evidence it never belonged at the shared layer. The table nuance
177
+ // now lives in `docs/markdown.md`, where the dialect that has tables is
178
+ // documented.
179
+ //
180
+ // Scoped to this rule's id on purpose: a file may carry directives for many
181
+ // rules, and one naming another rule matched nothing here for a good
182
+ // reason. Without that scope this would fire once per unrelated directive
183
+ // per rule.
184
+ for (const c of allComments) {
185
+ if (c.ruleId !== ruleId || spent.has(c))
186
+ continue;
187
+ writeStderr(`[eess] Exclusion comment for '${ruleId}' at ${c.file}:${String(c.line)} ` +
188
+ `suppressed nothing. It may be stale, or out of reach: a single-line ` +
189
+ `directive covers only the NEXT line. eess-exclude-start/-end covers a ` +
190
+ `region instead.`);
191
+ }
192
+ }
193
+ else if (allComments.length > 0) {
194
+ // No rule id, so no comment here can ever match: `isExcludedByComment`
195
+ // refuses without one.
196
+ //
197
+ // **This branch cannot say whose directive it is looking at, and the first
198
+ // version pretended otherwise.** It named each comment's own rule id and
199
+ // told the reader to add `.rule({ id: <that id> })` — but from inside one
200
+ // rule's execution there is no way to know whether that id already belongs
201
+ // to a different, working rule. Review reproduced the harm: a directive
202
+ // correctly waiving `other/rule` in a file where an id-less rule also
203
+ // fired produced advice to claim `other/rule` for the id-less one, which
204
+ // nothing prevents and which would collide two rules on one id.
205
+ //
206
+ // So it states the fact and leaves the id to the author. One line per
207
+ // file, not per comment, for the same reason its sibling scopes by id: a
208
+ // file may carry directives for many rules and none of them is evidence
209
+ // about this one.
210
+ const byFile = new Map();
211
+ for (const c of allComments) {
212
+ const lines = byFile.get(c.file);
213
+ if (lines)
214
+ lines.push(c.line);
215
+ else
216
+ byFile.set(c.file, [c.line]);
217
+ }
218
+ // Name the rule by its `.because()` reason when it has one (bug 0258).
219
+ // An id-less rule has no id to name, so several id-less chains over one
220
+ // file printed byte-identical lines and a reader could not tell which
221
+ // chain needed the id. The reason is a discriminator that already exists:
222
+ // `.because()` works without `.rule({ id })`, and `ctx.reason` is stamped
223
+ // onto violations a few lines above this. Whitespace is collapsed because
224
+ // the reason is prose and may wrap, and this report is deliberately one
225
+ // line per file. A rule with neither id nor reason is genuinely anonymous
226
+ // and the message stays exactly as it was.
227
+ // Name the rule, preferring its own sentence over its reason.
228
+ //
229
+ // The first version of this used `.because()` alone, on the premise that
230
+ // no rule description was reachable here. Review measured that false:
231
+ // every builder implements `describeRule()`, `filterContext()` is a method
232
+ // on the class that has it, and the kernel already names an id-less rule
233
+ // this way for its assertion-less finding a couple of hundred lines above.
234
+ // `.because()` is optional prose — the chains this diagnostic targets
235
+ // often have neither an id nor a reason — so leaning on it alone was a
236
+ // weaker floor for the same cost.
237
+ //
238
+ // `'unnamed'` is `TerminalBuilder`'s own fallback for a rule with no id;
239
+ // naming a rule "unnamed" tells the reader nothing, so it counts as absent
240
+ // and the reason takes over. Whitespace is collapsed either way: both are
241
+ // prose that may wrap, and this report is one line per file.
242
+ // Collapse to one line, and escape the delimiter. A `.because()` reason is
243
+ // arbitrary prose, so one containing a quoted term rendered as
244
+ // `("ignore the "legacy" path")` — three quotes before the paren, and no
245
+ // way for a reader to see where the label ends. Escaping keeps the
246
+ // author's words exact where replacing them would not.
247
+ const oneLine = (t) => t.replace(/\s+/g, ' ').trim().replace(/"/g, '\\"');
248
+ const described = ctx.describe?.();
249
+ const label = described !== undefined && described !== 'unnamed' && described !== ''
250
+ ? oneLine(described)
251
+ : ctx.reason === undefined
252
+ ? undefined
253
+ : oneLine(ctx.reason);
254
+ const named = label === undefined || label === '' ? '' : ` ("${label}")`;
255
+ for (const [file, lines] of byFile) {
256
+ const where = lines.length === 1 ? `line ${String(lines[0])}` : `lines ${lines.join(', ')}`;
257
+ writeStderr(`[eess] This rule${named} declares no id, so no exclusion comment can apply to it — ` +
258
+ `a comment matches a violation by rule id. ${file} has a directive at ${where}. ` +
259
+ `If one was meant for this rule, give the rule an id with .rule({ id: '<your-id>' }); ` +
260
+ `directives naming other rules are not this rule's to honour.`);
261
+ }
262
+ }
263
+ // The waiver applied; this takes the suppressed finding's place, so the
264
+ // author is told what to fix rather than shown the violation they waived.
265
+ //
266
+ // Unsuppressable, because a suppression mechanism that can suppress the
267
+ // complaint about itself is not a mechanism.
268
+ //
269
+ // `undocumented` is only ever populated when the rule HAS an id (the push
270
+ // above is guarded on it), so this loop is unreachable without one — the
271
+ // local re-binding is what tells the compiler that after bug 0255 removed
272
+ // the outer `ctx.metadata?.id` gate.
273
+ for (const warning of undocumented) {
274
+ if (ruleId === undefined)
275
+ continue;
276
+ result.push({
277
+ rule: ruleId,
278
+ ruleId,
279
+ element: `${ruleId}@${warning.file}:${String(warning.line)}`,
280
+ file: warning.file,
281
+ line: warning.line,
282
+ message: `This exclusion states no reason, so nothing records why the rule is ` +
283
+ `waived here — and it is silently suppressing a real finding.`,
284
+ suggestion: `Add a reason: // eess-exclude ${ruleId}: <why>. ` +
285
+ `A reason is prose and nothing verifies it, so this raises the cost of a ` +
286
+ `suppression rather than preventing one — the audience is the reviewer ` +
287
+ `reading the diff. If the exemption is not justifiable, delete it and fix ` +
288
+ `the finding instead. ${UNSUPPRESSABLE}`,
289
+ severity: 'error',
290
+ bypassFilters: true,
291
+ });
111
292
  }
112
293
  }
113
294
  return result;
@@ -116,16 +297,33 @@ export function applyFilters(violations, ctx) {
116
297
  * Execute the terminal "check" action: apply options, format, throw on violations.
117
298
  */
118
299
  export function executeCheck(violations, ctx, options) {
300
+ // The evidence survives every filter below. Baseline and `--changed` remove
301
+ // violations, never units examined: a run that examined 900 files and had its
302
+ // findings baselined away examined 900 files, and reporting 0 there would be
303
+ // the denominator lie bug 0174 is about.
304
+ const evidence = {
305
+ examined: violations.examined,
306
+ sourceEmpty: violations.sourceEmpty,
307
+ declaredEmpty: violations.declaredEmpty,
308
+ deadGlob: violations.deadGlob,
309
+ };
119
310
  let filtered = applyFilters(violations, ctx);
120
311
  if (options?.baseline) {
121
312
  filtered = options.baseline.filterNew(filtered);
122
313
  }
123
314
  if (options?.diff) {
315
+ const before = filtered.length;
124
316
  filtered = options.diff.filterToChanged(filtered);
317
+ const notice = activeNotice(before - filtered.length);
318
+ if (notice !== undefined)
319
+ writeStderr(`${notice}\n`);
125
320
  }
126
321
  if (filtered.length > 0) {
127
322
  // One emitter for both paths (plan 0070) — text/json/github, then throw.
128
- reportViolations(filtered, { format: options?.format, reason: ctx.reason });
323
+ reportViolations(collectResult(filtered, evidence), {
324
+ format: options?.format,
325
+ reason: ctx.reason,
326
+ });
129
327
  throw new ArchRuleError(filtered, ctx.reason);
130
328
  }
131
329
  }
@@ -138,18 +336,29 @@ export function executeWarn(violations, ctx, options) {
138
336
  filtered = options.baseline.filterNew(filtered);
139
337
  }
140
338
  if (options?.diff) {
339
+ const before = filtered.length;
141
340
  filtered = options.diff.filterToChanged(filtered);
341
+ const notice = activeNotice(before - filtered.length);
342
+ if (notice !== undefined)
343
+ writeStderr(`${notice}\n`);
142
344
  }
143
345
  if (filtered.length > 0) {
144
346
  if (options?.format === 'json') {
145
- console.warn(formatViolationsJson(filtered, ctx.reason));
347
+ writeStderr(formatViolationsJson(filtered, ctx.reason));
146
348
  }
147
349
  else if (options?.format === 'github') {
148
350
  process.stdout.write(formatViolationsGitHub(filtered, 'warning') + '\n');
149
351
  }
150
352
  else {
151
- console.warn(formatViolations(filtered, ctx.reason));
353
+ writeStderr(formatViolations(filtered, ctx.reason));
152
354
  }
153
355
  }
356
+ // `bypassFilters` outranks `.warn()` (see `severityFor`) — a config finding
357
+ // whose own text promises "not by .warn()" must still fail the build even
358
+ // though the rest of this rule's findings are legitimately warn-only.
359
+ const escalated = filtered.filter((v) => severityFor(v, 'warn') === 'error');
360
+ if (escalated.length > 0) {
361
+ throw new ArchRuleError(escalated, ctx.reason);
362
+ }
154
363
  }
155
364
  //# sourceMappingURL=execute-rule.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"execute-rule.js","sourceRoot":"","sources":["../src/execute-rule.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"execute-rule.js","sourceRoot":"","sources":["../src/execute-rule.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Recursive file discovery, shared by the dialects that read a directory tree
3
+ * rather than a TypeScript project.
4
+ *
5
+ * `eess-md` and `eess-crossvalidate` each carried a copy of this — measured at
6
+ * 99% similar, differing only in whether the ignore set arrived as a parameter
7
+ * or sat in a module constant. `toPosix` was duplicated alongside it, byte for
8
+ * byte.
9
+ *
10
+ * Kernel-safe: node builtins only, no project and no AST.
11
+ */
12
+ /** Path separators normalised to `/`, so a corpus reads the same on Windows. */
13
+ export declare function toPosix(p: string): string;
14
+ /**
15
+ * Every file under `dir`, as paths relative to `root`, POSIX-separated.
16
+ *
17
+ * An unreadable directory — permissions, a dangling symlink — is skipped rather
18
+ * than thrown from: a corpus walk that dies on one bad entry reports nothing
19
+ * about the tree it could read, which is the worse failure for a gate.
20
+ */
21
+ export declare function walkFiles(dir: string, root: string, ignoreDirs: ReadonlySet<string>): string[];
22
+ //# sourceMappingURL=file-walk.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"file-walk.d.ts","sourceRoot":"","sources":["../src/file-walk.ts"],"names":[],"mappings":"AAGA;;;;;;;;;;GAUG;AAEH,gFAAgF;AAChF,wBAAgB,OAAO,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAEzC;AAED;;;;;;GAMG;AACH,wBAAgB,SAAS,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,UAAU,EAAE,WAAW,CAAC,MAAM,CAAC,GAAG,MAAM,EAAE,CAI9F"}
@@ -0,0 +1,51 @@
1
+ import { readdirSync } from 'node:fs';
2
+ import { join, relative, sep } from 'node:path';
3
+ /**
4
+ * Recursive file discovery, shared by the dialects that read a directory tree
5
+ * rather than a TypeScript project.
6
+ *
7
+ * `eess-md` and `eess-crossvalidate` each carried a copy of this — measured at
8
+ * 99% similar, differing only in whether the ignore set arrived as a parameter
9
+ * or sat in a module constant. `toPosix` was duplicated alongside it, byte for
10
+ * byte.
11
+ *
12
+ * Kernel-safe: node builtins only, no project and no AST.
13
+ */
14
+ /** Path separators normalised to `/`, so a corpus reads the same on Windows. */
15
+ export function toPosix(p) {
16
+ return sep === '/' ? p : p.split(sep).join('/');
17
+ }
18
+ /**
19
+ * Every file under `dir`, as paths relative to `root`, POSIX-separated.
20
+ *
21
+ * An unreadable directory — permissions, a dangling symlink — is skipped rather
22
+ * than thrown from: a corpus walk that dies on one bad entry reports nothing
23
+ * about the tree it could read, which is the worse failure for a gate.
24
+ */
25
+ export function walkFiles(dir, root, ignoreDirs) {
26
+ const acc = [];
27
+ walkInto(dir, root, ignoreDirs, acc);
28
+ return acc;
29
+ }
30
+ function walkInto(dir, root, ignoreDirs, acc) {
31
+ let entries;
32
+ try {
33
+ entries = readdirSync(dir, { withFileTypes: true });
34
+ }
35
+ catch (err) {
36
+ void err; // unreadable dir (permissions, dangling symlink) — deliberately skipped
37
+ return;
38
+ }
39
+ for (const e of entries) {
40
+ const abs = join(dir, e.name);
41
+ if (e.isDirectory()) {
42
+ if (ignoreDirs.has(e.name))
43
+ continue;
44
+ walkInto(abs, root, ignoreDirs, acc);
45
+ }
46
+ else if (e.isFile()) {
47
+ acc.push(toPosix(relative(root, abs)));
48
+ }
49
+ }
50
+ }
51
+ //# sourceMappingURL=file-walk.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"file-walk.js","sourceRoot":"","sources":["../src/file-walk.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,SAAS,CAAA;AACrC,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,GAAG,EAAE,MAAM,WAAW,CAAA;AAE/C;;;;;;;;;;GAUG;AAEH,gFAAgF;AAChF,MAAM,UAAU,OAAO,CAAC,CAAS;IAC/B,OAAO,GAAG,KAAK,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAA;AACjD,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,SAAS,CAAC,GAAW,EAAE,IAAY,EAAE,UAA+B;IAClF,MAAM,GAAG,GAAa,EAAE,CAAA;IACxB,QAAQ,CAAC,GAAG,EAAE,IAAI,EAAE,UAAU,EAAE,GAAG,CAAC,CAAA;IACpC,OAAO,GAAG,CAAA;AACZ,CAAC;AAED,SAAS,QAAQ,CAAC,GAAW,EAAE,IAAY,EAAE,UAA+B,EAAE,GAAa;IACzF,IAAI,OAAO,CAAA;IACX,IAAI,CAAC;QACH,OAAO,GAAG,WAAW,CAAC,GAAG,EAAE,EAAE,aAAa,EAAE,IAAI,EAAE,CAAC,CAAA;IACrD,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,KAAK,GAAG,CAAA,CAAC,wEAAwE;QACjF,OAAM;IACR,CAAC;IACD,KAAK,MAAM,CAAC,IAAI,OAAO,EAAE,CAAC;QACxB,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC,IAAI,CAAC,CAAA;QAC7B,IAAI,CAAC,CAAC,WAAW,EAAE,EAAE,CAAC;YACpB,IAAI,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC;gBAAE,SAAQ;YACpC,QAAQ,CAAC,GAAG,EAAE,IAAI,EAAE,UAAU,EAAE,GAAG,CAAC,CAAA;QACtC,CAAC;aAAM,IAAI,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC;YACtB,GAAG,CAAC,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC,CAAC,CAAA;QACxC,CAAC;IACH,CAAC;AACH,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"format-github.d.ts","sourceRoot":"","sources":["../src/format-github.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAA;AAEnD;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,sBAAsB,CACpC,UAAU,EAAE,aAAa,EAAE,EAC3B,QAAQ,GAAE,OAAO,GAAG,SAAmB,GACtC,MAAM,CAiBR;AAED;;;GAGG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAEjD"}
1
+ {"version":3,"file":"format-github.d.ts","sourceRoot":"","sources":["../src/format-github.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAA;AAGnD;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,sBAAsB,CACpC,UAAU,EAAE,aAAa,EAAE,EAC3B,QAAQ,GAAE,OAAO,GAAG,SAAmB,GACtC,MAAM,CAuCR;AAED;;;GAGG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAEjD"}
@@ -1,4 +1,5 @@
1
1
  import path from 'node:path';
2
+ import { remedyRepeatsMessage } from './violation.js';
2
3
  /**
3
4
  * Format violations as GitHub Actions annotation commands.
4
5
  *
@@ -20,17 +21,38 @@ export function formatViolationsGitHub(violations, severity = 'error') {
20
21
  const cwd = process.cwd();
21
22
  return violations
22
23
  .map((v) => {
23
- const relativePath = path.relative(cwd, v.file);
24
+ // Escaped as a PROPERTY: the runner splits the property list on commas,
25
+ // and a path containing one truncates the annotation onto a file that
26
+ // does not exist (measured). Same treatment as `title` two lines down.
27
+ const relativePath = escapeGitHubProperty(path.relative(cwd, v.file));
24
28
  const title = v.ruleId
25
29
  ? `Architecture Violation: ${v.ruleId}`
26
30
  : `Architecture Violation: ${v.rule}`;
27
31
  let message = v.because ? `${v.message} (${v.because})` : v.message;
28
- if (v.suggestion)
29
- message += `. Fix: ${v.suggestion}`;
32
+ // The annotation body opens with `message`, so a remedy identical to it is
33
+ // already there — see `remedyRepeatsMessage`.
34
+ //
35
+ // `join` rather than a bare `'. '`: this format is the one that
36
+ // concatenates the parts into a single line, and a message already ending
37
+ // in punctuation got a second period — `…(reading 'config').. Fix: …`,
38
+ // measured on the real CLI once a producer wrote a well-punctuated
39
+ // message. The rich and plain formats put `Fix:` on its own line and never
40
+ // had the problem.
41
+ const join = (text) => (/[.!?]$/.test(text) ? ' ' : '. ');
42
+ if (v.suggestion && !remedyRepeatsMessage(v))
43
+ message += `${join(message)}Fix: ${v.suggestion}`;
30
44
  if (v.docs)
31
- message += `. Docs: ${v.docs}`;
45
+ message += `${join(message)}Docs: ${v.docs}`;
32
46
  // GitHub annotation format: ::level file=path,line=N,title=T::message
33
- return `::${severity} file=${relativePath},line=${String(v.line)},title=${escapeGitHub(title)}::${escapeGitHub(message)}`;
47
+ //
48
+ // A configuration finding has no source location (file '', line 0), and
49
+ // `::error file=,line=0` is not a valid annotation — GitHub drops or
50
+ // misplaces it. Emit a run-level annotation instead, which renders on
51
+ // the workflow summary (plan 0070).
52
+ if (v.file === '') {
53
+ return `::${severity} title=${escapeGitHubProperty(title)}::${escapeGitHub(message)}`;
54
+ }
55
+ return `::${severity} file=${relativePath},line=${String(v.line)},title=${escapeGitHubProperty(title)}::${escapeGitHub(message)}`;
34
56
  })
35
57
  .join('\n');
36
58
  }
@@ -41,4 +63,14 @@ export function formatViolationsGitHub(violations, severity = 'error') {
41
63
  export function escapeGitHub(text) {
42
64
  return text.replace(/%/g, '%25').replace(/\r/g, '%0D').replace(/\n/g, '%0A');
43
65
  }
66
+ /**
67
+ * Property values (e.g. `title=`) additionally need `,` and `:` escaped —
68
+ * the workflow-command parser splits the property list on commas, and rule
69
+ * descriptions legitimately contain both (`{a,b}` globs, prose). The
70
+ * run-level annotation for locationless findings made `title` the sole
71
+ * identity carrier, which is what surfaced this.
72
+ */
73
+ function escapeGitHubProperty(text) {
74
+ return escapeGitHub(text).replace(/,/g, '%2C').replace(/:/g, '%3A');
75
+ }
44
76
  //# sourceMappingURL=format-github.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"format-github.js","sourceRoot":"","sources":["../src/format-github.ts"],"names":[],"mappings":"AAAA,OAAO,IAAI,MAAM,WAAW,CAAA;AAG5B;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,UAAU,sBAAsB,CACpC,UAA2B,EAC3B,WAAgC,OAAO;IAEvC,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,EAAE,CAAA;IAEzB,OAAO,UAAU;SACd,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE;QACT,MAAM,YAAY,GAAG,IAAI,CAAC,QAAQ,CAAC,GAAG,EAAE,CAAC,CAAC,IAAI,CAAC,CAAA;QAC/C,MAAM,KAAK,GAAG,CAAC,CAAC,MAAM;YACpB,CAAC,CAAC,2BAA2B,CAAC,CAAC,MAAM,EAAE;YACvC,CAAC,CAAC,2BAA2B,CAAC,CAAC,IAAI,EAAE,CAAA;QACvC,IAAI,OAAO,GAAG,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,OAAO,KAAK,CAAC,CAAC,OAAO,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAA;QACnE,IAAI,CAAC,CAAC,UAAU;YAAE,OAAO,IAAI,UAAU,CAAC,CAAC,UAAU,EAAE,CAAA;QACrD,IAAI,CAAC,CAAC,IAAI;YAAE,OAAO,IAAI,WAAW,CAAC,CAAC,IAAI,EAAE,CAAA;QAE1C,sEAAsE;QACtE,OAAO,KAAK,QAAQ,SAAS,YAAY,SAAS,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,UAAU,YAAY,CAAC,KAAK,CAAC,KAAK,YAAY,CAAC,OAAO,CAAC,EAAE,CAAA;IAC3H,CAAC,CAAC;SACD,IAAI,CAAC,IAAI,CAAC,CAAA;AACf,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,YAAY,CAAC,IAAY;IACvC,OAAO,IAAI,CAAC,OAAO,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,KAAK,CAAC,CAAA;AAC9E,CAAC"}
1
+ {"version":3,"file":"format-github.js","sourceRoot":"","sources":["../src/format-github.ts"],"names":[],"mappings":"AAAA,OAAO,IAAI,MAAM,WAAW,CAAA;AAE5B,OAAO,EAAE,oBAAoB,EAAE,MAAM,gBAAgB,CAAA;AAErD;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,UAAU,sBAAsB,CACpC,UAA2B,EAC3B,WAAgC,OAAO;IAEvC,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,EAAE,CAAA;IAEzB,OAAO,UAAU;SACd,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE;QACT,wEAAwE;QACxE,sEAAsE;QACtE,uEAAuE;QACvE,MAAM,YAAY,GAAG,oBAAoB,CAAC,IAAI,CAAC,QAAQ,CAAC,GAAG,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,CAAA;QACrE,MAAM,KAAK,GAAG,CAAC,CAAC,MAAM;YACpB,CAAC,CAAC,2BAA2B,CAAC,CAAC,MAAM,EAAE;YACvC,CAAC,CAAC,2BAA2B,CAAC,CAAC,IAAI,EAAE,CAAA;QACvC,IAAI,OAAO,GAAG,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,OAAO,KAAK,CAAC,CAAC,OAAO,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAA;QACnE,2EAA2E;QAC3E,8CAA8C;QAC9C,EAAE;QACF,gEAAgE;QAChE,0EAA0E;QAC1E,uEAAuE;QACvE,mEAAmE;QACnE,2EAA2E;QAC3E,mBAAmB;QACnB,MAAM,IAAI,GAAG,CAAC,IAAY,EAAU,EAAE,CAAC,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,CAAA;QACzE,IAAI,CAAC,CAAC,UAAU,IAAI,CAAC,oBAAoB,CAAC,CAAC,CAAC;YAC1C,OAAO,IAAI,GAAG,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,UAAU,EAAE,CAAA;QACnD,IAAI,CAAC,CAAC,IAAI;YAAE,OAAO,IAAI,GAAG,IAAI,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC,IAAI,EAAE,CAAA;QAExD,sEAAsE;QACtE,EAAE;QACF,wEAAwE;QACxE,qEAAqE;QACrE,sEAAsE;QACtE,oCAAoC;QACpC,IAAI,CAAC,CAAC,IAAI,KAAK,EAAE,EAAE,CAAC;YAClB,OAAO,KAAK,QAAQ,UAAU,oBAAoB,CAAC,KAAK,CAAC,KAAK,YAAY,CAAC,OAAO,CAAC,EAAE,CAAA;QACvF,CAAC;QACD,OAAO,KAAK,QAAQ,SAAS,YAAY,SAAS,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,UAAU,oBAAoB,CAAC,KAAK,CAAC,KAAK,YAAY,CAAC,OAAO,CAAC,EAAE,CAAA;IACnI,CAAC,CAAC;SACD,IAAI,CAAC,IAAI,CAAC,CAAA;AACf,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,YAAY,CAAC,IAAY;IACvC,OAAO,IAAI,CAAC,OAAO,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,KAAK,CAAC,CAAA;AAC9E,CAAC;AAED;;;;;;GAMG;AACH,SAAS,oBAAoB,CAAC,IAAY;IACxC,OAAO,YAAY,CAAC,IAAI,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,KAAK,CAAC,CAAA;AACrE,CAAC"}
@@ -1,4 +1,5 @@
1
1
  import type { ArchViolation } from './violation.js';
2
+ import type { EdgeCoverage } from './edge-coverage.js';
2
3
  /**
3
4
  * Format violations as a JSON string.
4
5
  *
@@ -8,5 +9,99 @@ import type { ArchViolation } from './violation.js';
8
9
  * const violations = collectViolations(rule1, rule2)
9
10
  * console.log(formatViolationsJson(violations))
10
11
  */
11
- export declare function formatViolationsJson(violations: ArchViolation[], reason?: string): string;
12
+ /**
13
+ * The `check --format json` document, as a type.
14
+ *
15
+ * Exported because it is a **contract**, and an unexported contract is one a
16
+ * consumer can only discover by breaking. Without it a TypeScript consumer gets
17
+ * a runtime failure where they could have had a red build — and our own tests
18
+ * had to hand-roll the shape and cast through `unknown`, which is the kind of
19
+ * ADR-005 exemption that should not be needed against our own output.
20
+ *
21
+ * Changing these types is changing the agent contract. Treat a field removal or
22
+ * a narrowed union as breaking.
23
+ */
24
+ export interface ArchJsonViolation {
25
+ readonly rule: string;
26
+ readonly ruleId: string | null;
27
+ readonly severity: 'error' | 'warn';
28
+ readonly element: string;
29
+ /**
30
+ * The source file, or `null` when the finding has no location.
31
+ *
32
+ * For `kind: 'configuration'` this is the **rule file** that declared the
33
+ * rule — not the code under test — because the CLI attributes findings to
34
+ * their origin before rendering. It is `null` only for findings produced
35
+ * after that step. Never use it to detect a configuration finding; use `kind`.
36
+ */
37
+ readonly file: string | null;
38
+ /** `null` when there is no location; `1` on an attributed file means the file, not a position. */
39
+ readonly line: number | null;
40
+ /**
41
+ * What kind of finding this is, and therefore what to do about it.
42
+ *
43
+ * `'violation'` — the code is wrong. `'configuration'` — the **rule**
44
+ * enforces nothing, so editing the code cannot clear it.
45
+ *
46
+ * A consumer should treat an **unrecognised** value as `'violation'`: that
47
+ * keeps widening this union non-breaking, and configuration findings today
48
+ * already span four distinct remedies (a rule that matched nothing, a rule
49
+ * file that could not be evaluated, rules after a failure that never ran, and
50
+ * a stale baseline) which may earn their own names later.
51
+ */
52
+ readonly kind: 'violation' | 'configuration';
53
+ readonly message: string;
54
+ readonly because: string | null;
55
+ readonly suggestion: string | null;
56
+ readonly docs: string | null;
57
+ readonly codeFrame: string | null;
58
+ readonly measured: number | null;
59
+ /**
60
+ * Other files this one finding concerns, or `null` when it concerns only its
61
+ * own (bug 0239).
62
+ *
63
+ * A finding about a RELATIONSHIP — two duplicate bodies, a cluster of them —
64
+ * is reported at one file and is about several. A consumer that filters by
65
+ * `file` alone will hide it from whoever edited any of the others, which is
66
+ * the bug this field exists to stop being reproduced downstream. If you
67
+ * filter, read this too.
68
+ */
69
+ readonly relatedFiles: readonly string[] | null;
70
+ }
71
+ /** One finding removed by an inline `// eess-exclude` comment. */
72
+ export interface ArchJsonSuppression {
73
+ readonly ruleId: string;
74
+ readonly file: string;
75
+ }
76
+ /** An allowlist rule that had subjects but tested no edges. */
77
+ export interface ArchJsonUntestedAllowlist {
78
+ readonly rule: string;
79
+ readonly subjects: number;
80
+ readonly edges: number;
81
+ }
82
+ /** The whole document emitted by `check --format json`. */
83
+ export interface ArchJsonReport {
84
+ readonly summary: {
85
+ readonly total: number;
86
+ readonly errors: number;
87
+ readonly warnings: number;
88
+ readonly reason: string | null;
89
+ /** Units examined — `null` when the caller supplied no evidence. */
90
+ readonly examined: number | null;
91
+ };
92
+ readonly untestedAllowlists: readonly ArchJsonUntestedAllowlist[];
93
+ readonly commentSuppressed: readonly ArchJsonSuppression[];
94
+ readonly violations: readonly ArchJsonViolation[];
95
+ }
96
+ export declare function formatViolationsJson(violations: ArchViolation[], reason?: string,
97
+ /**
98
+ * Allowlist rules that tested no edges (bug 0015).
99
+ *
100
+ * **Passed in, not read from module state.** The first cut called
101
+ * `untestedRules()` here, which made a documented pure formatter impure: a
102
+ * per-rule JSON document in a vitest suite named rules from every earlier
103
+ * rule in the process, because nothing resets between them. Same input,
104
+ * different output depending on history.
105
+ */
106
+ untested?: readonly EdgeCoverage[]): string;
12
107
  //# sourceMappingURL=format-json.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"format-json.d.ts","sourceRoot":"","sources":["../src/format-json.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAA;AAEnD;;;;;;;;GAQG;AACH,wBAAgB,oBAAoB,CAAC,UAAU,EAAE,aAAa,EAAE,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,MAAM,CAmBzF"}
1
+ {"version":3,"file":"format-json.d.ts","sourceRoot":"","sources":["../src/format-json.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAA;AAEnD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAA;AAGtD;;;;;;;;GAQG;AACH;;;;;;;;;;;GAWG;AACH,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,QAAQ,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAA;IAC9B,QAAQ,CAAC,QAAQ,EAAE,OAAO,GAAG,MAAM,CAAA;IACnC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;IACxB;;;;;;;OAOG;IACH,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAA;IAC5B,kGAAkG;IAClG,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAA;IAC5B;;;;;;;;;;;OAWG;IACH,QAAQ,CAAC,IAAI,EAAE,WAAW,GAAG,eAAe,CAAA;IAC5C,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;IACxB,QAAQ,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAA;IAC/B,QAAQ,CAAC,UAAU,EAAE,MAAM,GAAG,IAAI,CAAA;IAClC,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAA;IAC5B,QAAQ,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,CAAA;IACjC,QAAQ,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAA;IAChC;;;;;;;;;OASG;IACH,QAAQ,CAAC,YAAY,EAAE,SAAS,MAAM,EAAE,GAAG,IAAI,CAAA;CAChD;AAED,kEAAkE;AAClE,MAAM,WAAW,mBAAmB;IAClC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;IACvB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;CACtB;AAED,+DAA+D;AAC/D,MAAM,WAAW,yBAAyB;IACxC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAA;IACzB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;CACvB;AAED,2DAA2D;AAC3D,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,OAAO,EAAE;QAChB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;QACtB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;QACvB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAA;QACzB,QAAQ,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAA;QAC9B,oEAAoE;QACpE,QAAQ,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAA;KACjC,CAAA;IACD,QAAQ,CAAC,kBAAkB,EAAE,SAAS,yBAAyB,EAAE,CAAA;IACjE,QAAQ,CAAC,iBAAiB,EAAE,SAAS,mBAAmB,EAAE,CAAA;IAC1D,QAAQ,CAAC,UAAU,EAAE,SAAS,iBAAiB,EAAE,CAAA;CAClD;AAED,wBAAgB,oBAAoB,CAClC,UAAU,EAAE,aAAa,EAAE,EAC3B,MAAM,CAAC,EAAE,MAAM;AACf;;;;;;;;GAQG;AACH,QAAQ,GAAE,SAAS,YAAY,EAAO,GACrC,MAAM,CAqGR"}