@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
@@ -0,0 +1,76 @@
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 const UNSUPPRESSABLE = "This finding cannot be suppressed: not by .warn(), .asSeverity('warn'), " +
34
+ '.excluding(), a `// eess-exclude` comment, a baseline, or diff-aware mode. ' +
35
+ // The seventh thing an agent tries, and the one you least want it to learn.
36
+ // Measured: `recommended(p)` over a mis-scoped project reports four findings,
37
+ // each carrying the sentence above; adding `overrides: { id: 'off' }` reports
38
+ // three. One config line removed a finding that had just told the reader, in a
39
+ // sentence guarded by a parity test, that it could not be removed.
40
+ //
41
+ // Naming it here rather than leaving it discoverable by elimination: an agent
42
+ // that hits six walls and then finds `'off'` documented in `docs/presets.md`
43
+ // will stamp it, which is ADR-009 rule 1's trained-suppression dynamic produced
44
+ // by our own gate. It is NOT added to `UNSUPPRESSABLE_MECHANISMS`, because that
45
+ // list is the set the parity guard proves DO refuse — and this one does not.
46
+ "A preset's overrides: { id: 'off' } does remove it, by deleting the rule " +
47
+ 'entirely — that is not a suppression, it is a permanent decision that never expires.';
48
+ /**
49
+ * The declaration half, appended ONLY by the zero-examined producer.
50
+ *
51
+ * It used to live in {@link UNSUPPRESSABLE}, which every configuration finding
52
+ * carries — so it was stapled to findings a declaration cannot settle. Measured:
53
+ * on the assertion-gate finding ("this detector has no pattern") and on a dead
54
+ * selector glob, adding `.expectEmpty()` changes nothing, because both are
55
+ * decided before any declaration is consulted. The reader declares, re-runs, and
56
+ * gets the identical failure with the identical advice — ADR-009 rule 2's loop,
57
+ * introduced by the sentence written to close one.
58
+ *
59
+ * On the dead-glob kind it was worse than a no-op: there `overrides: { id: 'off' }`
60
+ * IS the working exit and declaring is not, so the message steered the reader off
61
+ * the only door that opens.
62
+ *
63
+ * Zero-examined is the one kind a declaration actually settles.
64
+ */
65
+ export const DECLARE_INSTEAD = 'If the rule genuinely has nothing to check, declare that instead — a declaration ' +
66
+ 'is an assertion that expires, where deleting the rule is not.';
67
+ /** The mechanism names the sentence claims to refuse, for the parity guard. */
68
+ export const UNSUPPRESSABLE_MECHANISMS = [
69
+ '.warn()',
70
+ ".asSeverity('warn')",
71
+ '.excluding()',
72
+ '// eess-exclude',
73
+ 'baseline',
74
+ 'diff-aware',
75
+ ];
76
+ //# sourceMappingURL=unsuppressable.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"unsuppressable.js","sourceRoot":"","sources":["../src/unsuppressable.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AACH,MAAM,CAAC,MAAM,cAAc,GACzB,0EAA0E;IAC1E,6EAA6E;IAC7E,4EAA4E;IAC5E,8EAA8E;IAC9E,8EAA8E;IAC9E,+EAA+E;IAC/E,mEAAmE;IACnE,EAAE;IACF,8EAA8E;IAC9E,6EAA6E;IAC7E,gFAAgF;IAChF,gFAAgF;IAChF,6EAA6E;IAC7E,2EAA2E;IAC3E,sFAAsF,CAAA;AAExF;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,CAAC,MAAM,eAAe,GAC1B,mFAAmF;IACnF,+DAA+D,CAAA;AAEjE,+EAA+E;AAC/E,MAAM,CAAC,MAAM,yBAAyB,GAAG;IACvC,SAAS;IACT,qBAAqB;IACrB,cAAc;IACd,iBAAiB;IACjB,UAAU;IACV,YAAY;CACJ,CAAA"}
@@ -0,0 +1,49 @@
1
+ import type { ArchViolation } from './violation.js';
2
+ import type { RuleDescription } from './rule-description.js';
3
+ /**
4
+ * What a rule can be asked about itself, without running it.
5
+ *
6
+ * Every finding here is a function of these three and nothing else — none of
7
+ * them evaluates the rule. That is what let them leave `TerminalBuilder`: they
8
+ * were never terminal behaviour, they were explanations OF it.
9
+ */
10
+ export interface RuleFacts {
11
+ /** The builder's own class, for naming the rule in a finding. */
12
+ readonly ruleClass: {
13
+ name: string;
14
+ };
15
+ readonly reason: string | undefined;
16
+ describeRule(): RuleDescription;
17
+ }
18
+ export declare function zeroExaminedViolation(facts: RuleFacts): ArchViolation;
19
+ /**
20
+ * The configuration finding for a rule that examined zero units BECAUSE one
21
+ * of its declared globs is diagnosably dead — `deadGlob` is the pre-formed
22
+ * reason a dialect's `RuleBuilder.deadGlobDiagnosis()` computed (a typo, an
23
+ * unanchored pattern, a directory glob pointed at a file — whatever the
24
+ * dialect's own glob-evaluation determined). Strictly more actionable than
25
+ * `zeroExaminedViolation()`'s generic message, which this replaces when a
26
+ * diagnosis is available; see `evidencedViolations()`.
27
+ */
28
+ export declare function deadGlobViolation(facts: RuleFacts, deadGlob: string): ArchViolation;
29
+ /**
30
+ * The configuration finding for a `.expectNonEmpty()` declaration that
31
+ * wasn't met — the corpus the author said should never be empty is empty
32
+ * right now. Overrides `assertsCardinality()`'s silent pass on purpose:
33
+ * the declaration is a stronger, caller-level claim than what the
34
+ * condition itself would otherwise tolerate.
35
+ */
36
+ export declare function unmetExpectNonEmptyViolation(facts: RuleFacts): ArchViolation;
37
+ /**
38
+ * The configuration finding for the ADR-010 part 3 precedence case: the
39
+ * family's own upstream source loaded nothing at all — a stronger claim
40
+ * than an ordinary dead selector, and worded accordingly (the fix is not
41
+ * "widen the selection", there is no selection yet to widen).
42
+ */
43
+ export declare function zeroLoadedSourceViolation(facts: RuleFacts): ArchViolation;
44
+ /**
45
+ * The configuration finding for a `.expectEmpty()` declaration that has
46
+ * expired — ADR-010 part 3: the number IS the finding, so it is named.
47
+ */
48
+ export declare function expiredExpectEmptyViolation(facts: RuleFacts, examined: number): ArchViolation;
49
+ //# sourceMappingURL=vacuity-findings.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"vacuity-findings.d.ts","sourceRoot":"","sources":["../src/vacuity-findings.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAA;AACnD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAA;AAE5D;;;;;;GAMG;AACH,MAAM,WAAW,SAAS;IACxB,iEAAiE;IACjE,QAAQ,CAAC,SAAS,EAAE;QAAE,IAAI,EAAE,MAAM,CAAA;KAAE,CAAA;IACpC,QAAQ,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAA;IACnC,YAAY,IAAI,eAAe,CAAA;CAChC;AAuCD,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,SAAS,GAAG,aAAa,CAOrE;AAED;;;;;;;;GAQG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,GAAG,aAAa,CAGnF;AAED;;;;;;GAMG;AACH,wBAAgB,4BAA4B,CAAC,KAAK,EAAE,SAAS,GAAG,aAAa,CAO5E;AAED;;;;;GAKG;AACH,wBAAgB,yBAAyB,CAAC,KAAK,EAAE,SAAS,GAAG,aAAa,CAOzE;AAED;;;GAGG;AACH,wBAAgB,2BAA2B,CAAC,KAAK,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,GAAG,aAAa,CAM7F"}
@@ -0,0 +1,94 @@
1
+ /**
2
+ * The configuration finding for a rule that examined zero units with no
3
+ * declared exemption — ADR-009 rule 2: named as a distinct cause (dead
4
+ * selector, empty corpus, or an unreachable examining seam), not folded
5
+ * into an ordinary violation message.
6
+ */
7
+ /**
8
+ * The shape every configuration finding in this module shares.
9
+ *
10
+ * All five differ ONLY in their `message`, which `no-copy-paste` reported as one
11
+ * cluster at 100%. Extracting it puts the contract they have in common in one
12
+ * place, and that contract is load-bearing rather than incidental:
13
+ *
14
+ * - `bypassFilters: true` — a vacuity finding must survive `--changed`, a
15
+ * baseline and an `eess-exclude`. A suppression mechanism that can suppress
16
+ * the complaint about itself is not a mechanism.
17
+ * - `file: ''`, `line: 0` — there is no source position, because the fault is
18
+ * the RULE, not a place in the code. That is also why the diff filter has to
19
+ * exempt these explicitly (see `diff-aware.ts`).
20
+ * - `suggestion` is the message — its own remedy, never the author's (bug 0021).
21
+ */
22
+ function configFinding(facts, message) {
23
+ const described = facts.describeRule();
24
+ const name = described.id ?? described.rule ?? facts.ruleClass.name;
25
+ return {
26
+ rule: described.rule ?? name,
27
+ ruleId: described.id,
28
+ element: name,
29
+ file: '',
30
+ line: 0,
31
+ message,
32
+ suggestion: message,
33
+ because: facts.reason,
34
+ bypassFilters: true,
35
+ };
36
+ }
37
+ export function zeroExaminedViolation(facts) {
38
+ const message = `this rule examined zero units. If this is expected (the corpus is legitimately ` +
39
+ `empty right now), declare it explicitly with .expectEmpty() — otherwise this is a ` +
40
+ `dead selector, an empty project, or a rule that never reaches its own examining ` +
41
+ `seam, and the fix is to widen the selection, not to suppress this finding.`;
42
+ return configFinding(facts, message);
43
+ }
44
+ /**
45
+ * The configuration finding for a rule that examined zero units BECAUSE one
46
+ * of its declared globs is diagnosably dead — `deadGlob` is the pre-formed
47
+ * reason a dialect's `RuleBuilder.deadGlobDiagnosis()` computed (a typo, an
48
+ * unanchored pattern, a directory glob pointed at a file — whatever the
49
+ * dialect's own glob-evaluation determined). Strictly more actionable than
50
+ * `zeroExaminedViolation()`'s generic message, which this replaces when a
51
+ * diagnosis is available; see `evidencedViolations()`.
52
+ */
53
+ export function deadGlobViolation(facts, deadGlob) {
54
+ const message = `this rule examined zero units — ${deadGlob}`;
55
+ return configFinding(facts, message);
56
+ }
57
+ /**
58
+ * The configuration finding for a `.expectNonEmpty()` declaration that
59
+ * wasn't met — the corpus the author said should never be empty is empty
60
+ * right now. Overrides `assertsCardinality()`'s silent pass on purpose:
61
+ * the declaration is a stronger, caller-level claim than what the
62
+ * condition itself would otherwise tolerate.
63
+ */
64
+ export function unmetExpectNonEmptyViolation(facts) {
65
+ const message = `this rule declared .expectNonEmpty() but examined zero units — the corpus this ` +
66
+ `rule asserted should never be empty is empty right now. If that's still true, fix ` +
67
+ `the selection (a glob typo, a missing folder); if the corpus legitimately can be ` +
68
+ `empty, remove .expectNonEmpty().`;
69
+ return configFinding(facts, message);
70
+ }
71
+ /**
72
+ * The configuration finding for the ADR-010 part 3 precedence case: the
73
+ * family's own upstream source loaded nothing at all — a stronger claim
74
+ * than an ordinary dead selector, and worded accordingly (the fix is not
75
+ * "widen the selection", there is no selection yet to widen).
76
+ */
77
+ export function zeroLoadedSourceViolation(facts) {
78
+ const message = `this rule's source loaded zero units before any selection ran — an empty project, ` +
79
+ `an unreadable tsconfig, or a glob resolving to nothing. This outranks any ` +
80
+ `.expectEmpty() declaration and any condition satisfied by emptiness: fix the ` +
81
+ `project/source configuration, not the rule.`;
82
+ return configFinding(facts, message);
83
+ }
84
+ /**
85
+ * The configuration finding for a `.expectEmpty()` declaration that has
86
+ * expired — ADR-010 part 3: the number IS the finding, so it is named.
87
+ */
88
+ export function expiredExpectEmptyViolation(facts, examined) {
89
+ const message = `this rule declared .expectEmpty() but examined ${String(examined)} unit(s) — the ` +
90
+ `declaration has expired. If the corpus legitimately grew past empty, remove ` +
91
+ `.expectEmpty() from this rule; the underlying violations (if any) above still stand.`;
92
+ return configFinding(facts, message);
93
+ }
94
+ //# sourceMappingURL=vacuity-findings.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"vacuity-findings.js","sourceRoot":"","sources":["../src/vacuity-findings.ts"],"names":[],"mappings":"AAiBA;;;;;GAKG;AACH;;;;;;;;;;;;;;GAcG;AACH,SAAS,aAAa,CAAC,KAAgB,EAAE,OAAe;IACtD,MAAM,SAAS,GAAG,KAAK,CAAC,YAAY,EAAE,CAAA;IACtC,MAAM,IAAI,GAAG,SAAS,CAAC,EAAE,IAAI,SAAS,CAAC,IAAI,IAAI,KAAK,CAAC,SAAS,CAAC,IAAI,CAAA;IACnE,OAAO;QACL,IAAI,EAAE,SAAS,CAAC,IAAI,IAAI,IAAI;QAC5B,MAAM,EAAE,SAAS,CAAC,EAAE;QACpB,OAAO,EAAE,IAAI;QACb,IAAI,EAAE,EAAE;QACR,IAAI,EAAE,CAAC;QACP,OAAO;QACP,UAAU,EAAE,OAAO;QACnB,OAAO,EAAE,KAAK,CAAC,MAAM;QACrB,aAAa,EAAE,IAAI;KACpB,CAAA;AACH,CAAC;AAED,MAAM,UAAU,qBAAqB,CAAC,KAAgB;IACpD,MAAM,OAAO,GACX,iFAAiF;QACjF,oFAAoF;QACpF,kFAAkF;QAClF,4EAA4E,CAAA;IAC9E,OAAO,aAAa,CAAC,KAAK,EAAE,OAAO,CAAC,CAAA;AACtC,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,iBAAiB,CAAC,KAAgB,EAAE,QAAgB;IAClE,MAAM,OAAO,GAAG,mCAAmC,QAAQ,EAAE,CAAA;IAC7D,OAAO,aAAa,CAAC,KAAK,EAAE,OAAO,CAAC,CAAA;AACtC,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,4BAA4B,CAAC,KAAgB;IAC3D,MAAM,OAAO,GACX,iFAAiF;QACjF,oFAAoF;QACpF,mFAAmF;QACnF,kCAAkC,CAAA;IACpC,OAAO,aAAa,CAAC,KAAK,EAAE,OAAO,CAAC,CAAA;AACtC,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,yBAAyB,CAAC,KAAgB;IACxD,MAAM,OAAO,GACX,oFAAoF;QACpF,4EAA4E;QAC5E,+EAA+E;QAC/E,6CAA6C,CAAA;IAC/C,OAAO,aAAa,CAAC,KAAK,EAAE,OAAO,CAAC,CAAA;AACtC,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,2BAA2B,CAAC,KAAgB,EAAE,QAAgB;IAC5E,MAAM,OAAO,GACX,kDAAkD,MAAM,CAAC,QAAQ,CAAC,iBAAiB;QACnF,8EAA8E;QAC9E,sFAAsF,CAAA;IACxF,OAAO,aAAa,CAAC,KAAK,EAAE,OAAO,CAAC,CAAA;AACtC,CAAC"}
@@ -1,10 +1,7 @@
1
1
  /**
2
2
  * A single architecture rule violation.
3
3
  *
4
- * Represents one element that failed to satisfy a condition. This is the
5
- * kernel's central data type — dialect-independent. Each dialect provides its
6
- * own adapter that builds an `ArchViolation` from that dialect's element type
7
- * (e.g. the TS dialect builds one from a ts-morph `Node`).
4
+ * Represents one element that failed to satisfy a condition.
8
5
  */
9
6
  /**
10
7
  * A deterministic, unique text edit that repairs a violation (plan 0066). Emitted
@@ -35,8 +32,79 @@ export interface ArchViolation {
35
32
  file: string;
36
33
  /** Line number where the violating element starts */
37
34
  line: number;
35
+ /**
36
+ * Other files this one finding concerns, when it concerns more than one.
37
+ *
38
+ * A finding carries a single `file`, which is where it is REPORTED. Some
39
+ * findings are about a relationship — two bodies that duplicate each other,
40
+ * a cluster of them — and for those, one path cannot say what the finding is
41
+ * about. Anything that filters by file must see the rest, or it hides the
42
+ * finding from the very person who caused it.
43
+ *
44
+ * Bug 0239 is the measured case: a duplicate-body finding anchors on whichever
45
+ * member the source walk reached first, so a developer who pasted a body into
46
+ * a second file ran `--changed` and was told nothing — the finding sat on the
47
+ * file they had not touched, and which file that was came down to how the OS
48
+ * enumerated a directory.
49
+ *
50
+ * Optional, and additive. A single-file finding omits it and every existing
51
+ * producer keeps compiling; a consumer that ignores it behaves exactly as
52
+ * before. `file` remains where the finding is reported, so messages, editors
53
+ * and baselines are unaffected.
54
+ */
55
+ relatedFiles?: readonly string[];
38
56
  /** Human-readable description of what went wrong */
39
57
  message: string;
58
+ /**
59
+ * Stable identity for baseline matching, when the rendered message is not a
60
+ * safe identifier.
61
+ *
62
+ * By default a violation is identified by `rule::element::message`, which is
63
+ * right as long as the message says only what is wrong. It breaks when the
64
+ * message also encodes *circumstances*:
65
+ *
66
+ * - a derived population — `"3 of 5 files … use X"` becomes `"4 of 6"` when
67
+ * an unrelated sibling is added, and every accepted finding in that folder
68
+ * changes identity;
69
+ * - an ordering — a pairwise detector that reports `A → B` reports `B → A`
70
+ * when the file walk runs in a different order, which is a property of the
71
+ * filesystem, not of the code;
72
+ * - a coordinate — `"at line 12"` moves when anything above it is edited.
73
+ *
74
+ * Set this to a canonical form and identity survives all three. It replaces
75
+ * both `element` and `message` in the hash, so it must be unique per finding
76
+ * within a rule: two distinct violations sharing one identity are one
77
+ * violation to the baseline, and accepting either accepts both. Absolute
78
+ * paths inside it are fine — they are normalised away with the rest
79
+ * (`src/core/identity-root.ts`).
80
+ *
81
+ * The rendered output is unaffected; this is identity only.
82
+ */
83
+ identity?: string;
84
+ /**
85
+ * The measurement this finding reports, for a metric condition — bug 0012.
86
+ *
87
+ * The baseline stores it and compares rather than equates, so improving a
88
+ * metric stays green while regressing past the accepted value fails. Absent
89
+ * on every non-metric finding, where equality of identity is the right test.
90
+ */
91
+ measured?: number;
92
+ /**
93
+ * What {@link measured} counts — `code-lines`, `methods`, `complexity`.
94
+ *
95
+ * The baseline persists it and refuses to compare a stored measurement
96
+ * against a current one carrying a different unit
97
+ * ([bug 0171](../../../work/bugs/fixed/0171-a-metric-unit-change-silently-loosens-every-baselined-ratchet.md)).
98
+ * Without it, changing what a metric COUNTS silently moved every baselined
99
+ * ceiling: `linesOfCode` went from span lines to code lines, entries kept
100
+ * matching on identity, and a class could then grow to roughly three times
101
+ * its accepted size while the build stayed green.
102
+ *
103
+ * Absent on findings from a producer that predates this, which the baseline
104
+ * treats as "unit unknown" — comparable only for the metrics whose meaning
105
+ * has never changed.
106
+ */
107
+ measuredUnit?: string;
40
108
  /** Optional rationale provided via .because() */
41
109
  because?: string;
42
110
  /** Source code snippet around the violation line */
@@ -47,5 +115,106 @@ export interface ArchViolation {
47
115
  docs?: string;
48
116
  /** A deterministic, unique text edit that repairs this violation (plan 0066). */
49
117
  fix?: ArchFix;
118
+ /** Severity of this violation. Absent means 'error' (the default). */
119
+ severity?: 'error' | 'warn';
120
+ /**
121
+ * When true, this is a meta-finding about rule *configuration* (e.g. an empty
122
+ * selector or empty discovery), not about a source file. It has no changed
123
+ * file to attribute to, so the diff-aware and baseline filters must NOT drop
124
+ * it — otherwise the guard silently re-greens under the standard CI mode
125
+ * (ADR-008; plan 0067).
126
+ */
127
+ bypassFilters?: boolean;
50
128
  }
129
+ /**
130
+ * Extract a human-readable name from a ts-morph Node.
131
+ *
132
+ * If the node itself is a named declaration (class, function, method, etc.),
133
+ * returns its name directly. Otherwise, walks up the AST ancestors to find
134
+ * the nearest named declaration and builds a qualified name like
135
+ * "ClassName.methodName". This ensures that inner nodes (e.g., AsExpression,
136
+ * CallExpression) produce meaningful element names for `.excluding()` matching.
137
+ *
138
+ * Falls back to the node's kind name only if no named ancestor is found
139
+ * (e.g., top-level expressions in a module).
140
+ */
141
+ /**
142
+ * Force a configuration meta-finding to `error`, whatever the rule asked for.
143
+ *
144
+ * A `bypassFilters` finding reports that the rule enforces **nothing**. That is
145
+ * not a violation the author gets to grade: `.asSeverity('warn')` says "these
146
+ * violations are advisory", and a rule that cannot fire has no violations to
147
+ * be advisory about. Under ADR-009 rule 1 an actionable finding must fail, and
148
+ * the other suppression paths already refuse to silence these — `.excluding()`
149
+ * refuses explicitly, baseline and diff honour the flag. The full roster is
150
+ * `UNSUPPRESSABLE_MECHANISMS` in `unsuppressable.ts`, which is also what the
151
+ * user-facing sentence is built from; it has grown twice, so it is named here
152
+ * rather than counted. (This paragraph said "three of the four" while the list
153
+ * stood at six.)
154
+ *
155
+ * Applied at all three severity-stamping sites. `stampSeverity` alone is not
156
+ * enough: `.violations()` inlines its own map in both root builders, and the
157
+ * `executeWarn` path resolves an unset severity to `warn`, which is where most
158
+ * producers landed.
159
+ */
160
+ export declare function severityFor(violation: ArchViolation, fallback: 'error' | 'warn'): 'error' | 'warn';
161
+ /**
162
+ * True when a violation's remedy **is** its message, so a renderer that has
163
+ * already shown the message must not append a `Fix:` line repeating it.
164
+ *
165
+ * The assertion gate's finding reports that a rule cannot fire; there, the fault
166
+ * and its remedy are one sentence, and both fields carry that sentence on
167
+ * purpose — the JSON payload reads `suggestion`, a human reads the body, and a
168
+ * configuration finding is the one kind that cannot fall back to the author's
169
+ * `suggestion` (`execute-rule.ts` refuses it, bug 0021). Measured before the
170
+ * fix: every surface printed the same paragraph twice.
171
+ *
172
+ * One definition, because the three renderers must not disagree about it.
173
+ * Whether a given renderer *did* render the message stays renderer-local: the
174
+ * rich terminal format shows it only for a location-less finding, while the
175
+ * plain and GitHub formats always do.
176
+ */
177
+ export declare function remedyRepeatsMessage(violation: ArchViolation): boolean;
178
+ /**
179
+ * Unicode codepoint order — the only comparator admissible anywhere a baseline identity, an
180
+ * `element`, or a reported location is derived from a sort.
181
+ *
182
+ * `String.prototype.localeCompare` without an explicit locale reads the **host** locale from
183
+ * `LANG`/`LC_ALL`, so a value sorted with it differs between a developer's machine and CI.
184
+ * Measured on plain ASCII: `['zebra','aardvark']` sorts to `aardvark,zebra` under `en-US` and
185
+ * to `zebra,aardvark` under `da-DK`, because Danish collates `aa` as `å`, after `z`. One
186
+ * finding, two baseline hashes, diverging only in the place hardest to debug.
187
+ *
188
+ * The codebase already knew: `module-edges.ts` wrote *"`localeCompare` is ICU/locale sensitive
189
+ * — exactly the machine-dependent ordering `conditions/slice.ts` goes out of its way to
190
+ * eliminate, because a value that differs per machine gives one finding two identities"* — 60
191
+ * lines above a `localeCompare` in the identity discriminator, while `conditions/slice.ts`
192
+ * eliminated its own machine-dependence *using `localeCompare`*. Four sites in all.
193
+ *
194
+ * `tests/tools/scan-locale-ordering.test.ts` is what keeps that from recurring: a runtime row
195
+ * cannot catch it, because on an `en-US` machine both comparators agree — a guard written for
196
+ * this defect passed 16/16 with the defect reinstated.
197
+ */
198
+ export declare function byCodepoint(a: string, b: string): number;
199
+ /**
200
+ * The subject `hashViolation` keys a baseline entry on: `identity`, or `element::message`.
201
+ *
202
+ * **Exported and consumed by `hashViolation`/`hashSubject`** rather than duplicated. An earlier
203
+ * draft kept a private copy here on the reasoning that `core/` must not depend on `helpers/` —
204
+ * true, but backwards: `helpers/baseline.ts` already imports from `core/`, which is the
205
+ * permitted direction, and `core/identity-root.ts` records the same move for the same reason.
206
+ * A copy plus a test asserting the copies agree is strictly worse than one definition: the
207
+ * test can only compare the formulas over fixtures it happens to build, and the first draft's
208
+ * fixture shared every field, so it agreed under any formula.
209
+ */
210
+ export declare function subjectOf(violation: ArchViolation): string;
211
+ /** Every subject a rule produced more than once, since the last reset. */
212
+ export declare function identityCollisions(): readonly {
213
+ rule: string;
214
+ subject: string;
215
+ findings: number;
216
+ }[];
217
+ /** Clear the record — call in a `beforeEach` when asserting on a specific rule. */
218
+ export declare function resetIdentityCollisions(): void;
219
+ export declare function disambiguateIdentities(violations: ArchViolation[]): ArchViolation[];
51
220
  //# sourceMappingURL=violation.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"violation.d.ts","sourceRoot":"","sources":["../src/violation.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH;;;;;GAKG;AACH,MAAM,WAAW,OAAO;IACtB,yCAAyC;IACzC,IAAI,EAAE,MAAM,CAAA;IACZ,oEAAoE;IACpE,KAAK,EAAE,MAAM,CAAA;IACb,mEAAmE;IACnE,GAAG,EAAE,MAAM,CAAA;IACX,2CAA2C;IAC3C,WAAW,EAAE,MAAM,CAAA;IACnB,sFAAsF;IACtF,QAAQ,EAAE,MAAM,CAAA;CACjB;AAED,MAAM,WAAW,aAAa;IAC5B,8DAA8D;IAC9D,IAAI,EAAE,MAAM,CAAA;IACZ,gDAAgD;IAChD,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,0EAA0E;IAC1E,OAAO,EAAE,MAAM,CAAA;IACf,oDAAoD;IACpD,IAAI,EAAE,MAAM,CAAA;IACZ,qDAAqD;IACrD,IAAI,EAAE,MAAM,CAAA;IACZ,oDAAoD;IACpD,OAAO,EAAE,MAAM,CAAA;IACf,iDAAiD;IACjD,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,oDAAoD;IACpD,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,0GAA0G;IAC1G,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,qDAAqD;IACrD,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,iFAAiF;IACjF,GAAG,CAAC,EAAE,OAAO,CAAA;CACd"}
1
+ {"version":3,"file":"violation.d.ts","sourceRoot":"","sources":["../src/violation.ts"],"names":[],"mappings":"AAEA;;;;GAIG;AACH;;;;;GAKG;AACH,MAAM,WAAW,OAAO;IACtB,yCAAyC;IACzC,IAAI,EAAE,MAAM,CAAA;IACZ,oEAAoE;IACpE,KAAK,EAAE,MAAM,CAAA;IACb,mEAAmE;IACnE,GAAG,EAAE,MAAM,CAAA;IACX,2CAA2C;IAC3C,WAAW,EAAE,MAAM,CAAA;IACnB,sFAAsF;IACtF,QAAQ,EAAE,MAAM,CAAA;CACjB;AAED,MAAM,WAAW,aAAa;IAC5B,8DAA8D;IAC9D,IAAI,EAAE,MAAM,CAAA;IACZ,gDAAgD;IAChD,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,0EAA0E;IAC1E,OAAO,EAAE,MAAM,CAAA;IACf,oDAAoD;IACpD,IAAI,EAAE,MAAM,CAAA;IACZ,qDAAqD;IACrD,IAAI,EAAE,MAAM,CAAA;IACZ;;;;;;;;;;;;;;;;;;;OAmBG;IACH,YAAY,CAAC,EAAE,SAAS,MAAM,EAAE,CAAA;IAChC,oDAAoD;IACpD,OAAO,EAAE,MAAM,CAAA;IACf;;;;;;;;;;;;;;;;;;;;;;;;OAwBG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAA;IAEjB;;;;;;OAMG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB;;;;;;;;;;;;;;OAcG;IACH,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,iDAAiD;IACjD,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,oDAAoD;IACpD,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,0GAA0G;IAC1G,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,qDAAqD;IACrD,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,iFAAiF;IACjF,GAAG,CAAC,EAAE,OAAO,CAAA;IACb,sEAAsE;IACtE,QAAQ,CAAC,EAAE,OAAO,GAAG,MAAM,CAAA;IAC3B;;;;;;OAMG;IACH,aAAa,CAAC,EAAE,OAAO,CAAA;CACxB;AAED;;;;;;;;;;;GAWG;AACH;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,WAAW,CACzB,SAAS,EAAE,aAAa,EACxB,QAAQ,EAAE,OAAO,GAAG,MAAM,GACzB,OAAO,GAAG,MAAM,CAElB;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,oBAAoB,CAAC,SAAS,EAAE,aAAa,GAAG,OAAO,CAEtE;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,WAAW,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM,CAExD;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,SAAS,CAAC,SAAS,EAAE,aAAa,GAAG,MAAM,CAE1D;AA0GD,0EAA0E;AAC1E,wBAAgB,kBAAkB,IAAI,SAAS;IAC7C,IAAI,EAAE,MAAM,CAAA;IACZ,OAAO,EAAE,MAAM,CAAA;IACf,QAAQ,EAAE,MAAM,CAAA;CACjB,EAAE,CAEF;AAED,mFAAmF;AACnF,wBAAgB,uBAAuB,IAAI,IAAI,CAE9C;AAID,wBAAgB,sBAAsB,CAAC,UAAU,EAAE,aAAa,EAAE,GAAG,aAAa,EAAE,CA6DnF"}