@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 @@
1
+ {"version":3,"file":"cache-registry.js","sourceRoot":"","sources":["../src/cache-registry.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,MAAM,GAAmB,EAAE,CAAA;AAEjC,4DAA4D;AAC5D,MAAM,UAAU,kBAAkB,CAAC,KAAiB;IAClD,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;AACpB,CAAC;AAED;;;;;;;;;GASG;AACH,MAAM,UAAU,qBAAqB;IACnC,KAAK,MAAM,KAAK,IAAI,MAAM;QAAE,KAAK,EAAE,CAAA;AACrC,CAAC"}
@@ -0,0 +1,35 @@
1
+ /**
2
+ * Declare that this condition is satisfied by an empty selection.
3
+ *
4
+ * **A registry, because a symbol was forgeable.** The first version keyed a
5
+ * module-private `unique symbol` onto the condition, reasoning that a consumer
6
+ * could not import it to name the key. They do not need to import it — four
7
+ * shipped conditions carry it as an own property, and `notExist` is publicly
8
+ * exported, so:
9
+ *
10
+ * ```ts
11
+ * const stolen = Object.getOwnPropertySymbols(notExist())[0]
12
+ * const mine = { description: 'x', evaluate: () => [], [stolen]: true }
13
+ * ```
14
+ *
15
+ * Measured: an honest condition on an empty selection produces **1** configuration
16
+ * finding and that forgery produces **0**. One line, through documented exports,
17
+ * to exempt any rule from the empty-selection gate — the gate this library is
18
+ * named around, and precisely the `.allowEmpty()` hazard the symbol was chosen to
19
+ * prevent.
20
+ *
21
+ * Found by review, immediately after the identical hole was closed in
22
+ * [ts-archunit plan 0081](https://github.com/nielspeter/ts-archunit/blob/main/plans/completed/0081-a-condition-declares-discovery-ownership.md)'s
23
+ * symbol — while this one was being cited in that module's docstring as the safe
24
+ * precedent. The lesson is worth more than the fix: **"module-private" describes
25
+ * the binding, not the value.** A symbol keyed onto a public object is unlisted,
26
+ * not unreachable.
27
+ *
28
+ * `WeakSet` membership cannot be read off an object, copied, or forged: a caller
29
+ * would need this module's binding, and it is not exported. Weak so a condition is
30
+ * not retained after its rule is discarded.
31
+ */
32
+ export declare function marksAssertsCardinality<T extends object>(condition: T): T;
33
+ /** Is this condition satisfied by an empty selection? */
34
+ export declare function assertsCardinality(condition: object): boolean;
35
+ //# sourceMappingURL=cardinality.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cardinality.d.ts","sourceRoot":"","sources":["../src/cardinality.ts"],"names":[],"mappings":"AAyCA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AACH,wBAAgB,uBAAuB,CAAC,CAAC,SAAS,MAAM,EAAE,SAAS,EAAE,CAAC,GAAG,CAAC,CAGzE;AAED,yDAAyD;AACzD,wBAAgB,kBAAkB,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAE7D"}
@@ -0,0 +1,80 @@
1
+ /**
2
+ * The cardinality exemption, keyed so that only this library can set it.
3
+ *
4
+ * Plan 0074 (R3b), and specifically constraint 2 of 0069's appendix:
5
+ *
6
+ * > `Condition` is a public export and `defineCondition` is its sanctioned
7
+ * > constructor. If the flag is expressible there, it is a one-line silent
8
+ * > opt-out on any user condition — the hazard the Decisions section used to
9
+ * > reject `.allowEmpty()`, relocated onto the condition object. A
10
+ * > module-private `unique symbol`, with no `defineCondition` parameter, closes
11
+ * > it by construction (ADR-009 rule 3's corollary).
12
+ *
13
+ * A first cut of this shipped as a plain `assertsCardinality?: true` property,
14
+ * which is exactly the rejected shape: `Condition` is exported from
15
+ * `src/index.ts`, so any user object literal could set it and silence the
16
+ * empty-selection gate on any rule, permanently and invisibly. That is
17
+ * `.allowEmpty()` wearing a different hat.
18
+ *
19
+ * **What actually stops a consumer doing this: not enough.** Say it plainly,
20
+ * because two earlier versions of this comment did not.
21
+ *
22
+ * The `WeakSet` below defends the KEY — membership cannot be read off an object,
23
+ * copied, or forged. It does not defend the CALL. Anyone holding
24
+ * `marksAssertsCardinality` can mark any condition, and it is reachable from
25
+ * `@nielspeter/eess/internal`. The first rationale here ("the exports map has no
26
+ * wildcard subpath") stopped being true when ADR-011 added the subpath; the
27
+ * second ("the `WeakSet` is the real defence") was simply wrong about which half
28
+ * it guards, and was written in the same branch that added the subpath.
29
+ *
30
+ * The honest position: this is narrower than it was — on `main` the marker was
31
+ * exported from the kernel ROOT and re-exported by eess-ts's root, so it sat on
32
+ * two published surfaces; it is now on one, named `/internal`, which no dialect
33
+ * re-exports. It is not closed. Closing it means validating at the registration
34
+ * site (accept only conditions from a sanctioned constructor), which is a design
35
+ * change, not a comment.
36
+ *
37
+ * The escape hatch users are *meant* to have is `.expectEmpty()` — an assertion
38
+ * that fails when the selection stops being empty, not a silencer.
39
+ */
40
+ const CARDINALITY_ASSERTERS = new WeakSet();
41
+ /**
42
+ * Declare that this condition is satisfied by an empty selection.
43
+ *
44
+ * **A registry, because a symbol was forgeable.** The first version keyed a
45
+ * module-private `unique symbol` onto the condition, reasoning that a consumer
46
+ * could not import it to name the key. They do not need to import it — four
47
+ * shipped conditions carry it as an own property, and `notExist` is publicly
48
+ * exported, so:
49
+ *
50
+ * ```ts
51
+ * const stolen = Object.getOwnPropertySymbols(notExist())[0]
52
+ * const mine = { description: 'x', evaluate: () => [], [stolen]: true }
53
+ * ```
54
+ *
55
+ * Measured: an honest condition on an empty selection produces **1** configuration
56
+ * finding and that forgery produces **0**. One line, through documented exports,
57
+ * to exempt any rule from the empty-selection gate — the gate this library is
58
+ * named around, and precisely the `.allowEmpty()` hazard the symbol was chosen to
59
+ * prevent.
60
+ *
61
+ * Found by review, immediately after the identical hole was closed in
62
+ * [ts-archunit plan 0081](https://github.com/nielspeter/ts-archunit/blob/main/plans/completed/0081-a-condition-declares-discovery-ownership.md)'s
63
+ * symbol — while this one was being cited in that module's docstring as the safe
64
+ * precedent. The lesson is worth more than the fix: **"module-private" describes
65
+ * the binding, not the value.** A symbol keyed onto a public object is unlisted,
66
+ * not unreachable.
67
+ *
68
+ * `WeakSet` membership cannot be read off an object, copied, or forged: a caller
69
+ * would need this module's binding, and it is not exported. Weak so a condition is
70
+ * not retained after its rule is discarded.
71
+ */
72
+ export function marksAssertsCardinality(condition) {
73
+ CARDINALITY_ASSERTERS.add(condition);
74
+ return condition;
75
+ }
76
+ /** Is this condition satisfied by an empty selection? */
77
+ export function assertsCardinality(condition) {
78
+ return CARDINALITY_ASSERTERS.has(condition);
79
+ }
80
+ //# sourceMappingURL=cardinality.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cardinality.js","sourceRoot":"","sources":["../src/cardinality.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsCG;AACH,MAAM,qBAAqB,GAAG,IAAI,OAAO,EAAU,CAAA;AAEnD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AACH,MAAM,UAAU,uBAAuB,CAAmB,SAAY;IACpE,qBAAqB,CAAC,GAAG,CAAC,SAAS,CAAC,CAAA;IACpC,OAAO,SAAS,CAAA;AAClB,CAAC;AAED,yDAAyD;AACzD,MAAM,UAAU,kBAAkB,CAAC,SAAiB;IAClD,OAAO,qBAAqB,CAAC,GAAG,CAAC,SAAS,CAAC,CAAA;AAC7C,CAAC"}
@@ -22,6 +22,20 @@ export interface BaselineFilter {
22
22
  export interface DiffFilterLike {
23
23
  /** Filter violations to only those in changed files. */
24
24
  filterToChanged(violations: ArchViolation[]): ArchViolation[];
25
+ /**
26
+ * How many files this filter considers changed, or a negative number when it
27
+ * could not tell (`DiffFilter` returns -1 when git was unavailable).
28
+ *
29
+ * **Optional, and the disclosure degrades rather than guesses.** This is a
30
+ * structural interface, so a consumer may pass any object with
31
+ * `filterToChanged`; when `size` is absent the suppression notice omits the
32
+ * file count instead of inventing one. What it never omits is the suppressed
33
+ * count itself, because the caller derives that by subtraction rather than
34
+ * asking the filter (see `core/diff-disclosure.ts`).
35
+ */
36
+ readonly size?: number;
37
+ /** The branch diffed against, when the filter knows it. Optional, as `size`. */
38
+ readonly baseBranch?: string;
25
39
  }
26
40
  /**
27
41
  * Options passed to .check() and .warn() to filter violations.
@@ -1 +1 @@
1
- {"version":3,"file":"check-options.d.ts","sourceRoot":"","sources":["../src/check-options.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAA;AAEnD;;;;;;GAMG;AACH,MAAM,MAAM,YAAY,GAAG,UAAU,GAAG,MAAM,GAAG,QAAQ,CAAA;AAEzD;;;GAGG;AACH,MAAM,WAAW,cAAc;IAC7B,4DAA4D;IAC5D,SAAS,CAAC,UAAU,EAAE,aAAa,EAAE,GAAG,aAAa,EAAE,CAAA;CACxD;AAED;;;GAGG;AACH,MAAM,WAAW,cAAc;IAC7B,wDAAwD;IACxD,eAAe,CAAC,UAAU,EAAE,aAAa,EAAE,GAAG,aAAa,EAAE,CAAA;CAC9D;AAED;;;;;;;;;;;;GAYG;AACH,MAAM,WAAW,YAAY;IAC3B,uDAAuD;IACvD,QAAQ,CAAC,EAAE,cAAc,CAAA;IACzB,iDAAiD;IACjD,IAAI,CAAC,EAAE,cAAc,CAAA;IACrB,wDAAwD;IACxD,MAAM,CAAC,EAAE,YAAY,CAAA;CACtB"}
1
+ {"version":3,"file":"check-options.d.ts","sourceRoot":"","sources":["../src/check-options.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAA;AAEnD;;;;;;GAMG;AACH,MAAM,MAAM,YAAY,GAAG,UAAU,GAAG,MAAM,GAAG,QAAQ,CAAA;AAEzD;;;GAGG;AACH,MAAM,WAAW,cAAc;IAC7B,4DAA4D;IAC5D,SAAS,CAAC,UAAU,EAAE,aAAa,EAAE,GAAG,aAAa,EAAE,CAAA;CACxD;AAED;;;GAGG;AACH,MAAM,WAAW,cAAc;IAC7B,wDAAwD;IACxD,eAAe,CAAC,UAAU,EAAE,aAAa,EAAE,GAAG,aAAa,EAAE,CAAA;IAC7D;;;;;;;;;;OAUG;IACH,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAA;IACtB,gFAAgF;IAChF,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAA;CAC7B;AAED;;;;;;;;;;;;GAYG;AACH,MAAM,WAAW,YAAY;IAC3B,uDAAuD;IACvD,QAAQ,CAAC,EAAE,cAAc,CAAA;IACzB,iDAAiD;IACjD,IAAI,CAAC,EAAE,cAAc,CAAA;IACrB,wDAAwD;IACxD,MAAM,CAAC,EAAE,YAAY,CAAA;CACtB"}
@@ -0,0 +1,59 @@
1
+ import type { OutputFormat } from './check-options.js';
2
+ /**
3
+ * The parts of dialect CLI config handling that are the same everywhere.
4
+ *
5
+ * **Split along the kernel's existing seam, not behind a new injection point.**
6
+ * `PathUniverse` sets the pattern: the kernel takes materialized data and the
7
+ * dialect does the I/O. Here the pure halves are finding the config file and
8
+ * validating what a loaded module claims — both were byte-identical between
9
+ * `eess-ts` and `eess-mermaid` (`findConfigFile` scored 100%). The impure half,
10
+ * actually importing the file, stays in each dialect because it needs `jiti`,
11
+ * which the kernel cannot depend on.
12
+ *
13
+ * That leaves one genuinely shared subtlety in the impure half —
14
+ * {@link isModuleFormatRefusal} — so it lives here too. It is pure, and it is
15
+ * the part that is easy to get dangerously wrong.
16
+ */
17
+ /** Config fields every dialect CLI understands. A dialect may add its own. */
18
+ export interface SharedCliConfig {
19
+ rules?: string[];
20
+ format?: OutputFormat | 'auto';
21
+ watchDirs?: string[];
22
+ }
23
+ /** The first of `filenames` that exists in the working directory. */
24
+ export declare function findConfigFile(filenames: readonly string[]): string | undefined;
25
+ /**
26
+ * The shared config a loaded module claims, validated.
27
+ *
28
+ * Everything is checked rather than trusted: a config file is arbitrary user
29
+ * code and its default export can be any shape at all. An unrecognised `format`
30
+ * is dropped rather than passed through, so a typo falls back to the default
31
+ * instead of reaching a formatter that has no case for it.
32
+ */
33
+ export declare function extractSharedConfig(mod: unknown): SharedCliConfig;
34
+ /**
35
+ * Did Node refuse this file for its MODULE FORMAT, rather than for anything the
36
+ * file does?
37
+ *
38
+ * Narrow on purpose. A broad `catch` that fell back to a transpiling loader on
39
+ * any error would re-execute a rule file that had already run — doubling its
40
+ * output — and would hide a genuine syntax error behind a second,
41
+ * differently-worded failure.
42
+ *
43
+ * The condition it detects is bug 0074: the CONSUMER project is
44
+ * `"type": "commonjs"` (what `npm init -y` writes) and the config or rule file
45
+ * uses ESM syntax.
46
+ */
47
+ export declare function isModuleFormatRefusal(error: unknown): boolean;
48
+ /**
49
+ * Refuse a run that named no rule files, saying where to name them.
50
+ *
51
+ * Sets `process.exitCode` rather than throwing: the caller is a CLI entry point
52
+ * that still wants to return normally. Returns whether the run may proceed.
53
+ *
54
+ * `configName` is the dialect's own config filename, because "set them in
55
+ * config" is not an instruction — the two copies of this differed in exactly
56
+ * that word, one naming the file and one not.
57
+ */
58
+ export declare function requireRuleFiles(ruleFiles: readonly string[], configName: string): boolean;
59
+ //# sourceMappingURL=cli-config.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli-config.d.ts","sourceRoot":"","sources":["../src/cli-config.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAA;AAGtD;;;;;;;;;;;;;;GAcG;AAEH,8EAA8E;AAC9E,MAAM,WAAW,eAAe;IAC9B,KAAK,CAAC,EAAE,MAAM,EAAE,CAAA;IAChB,MAAM,CAAC,EAAE,YAAY,GAAG,MAAM,CAAA;IAC9B,SAAS,CAAC,EAAE,MAAM,EAAE,CAAA;CACrB;AAID,qEAAqE;AACrE,wBAAgB,cAAc,CAAC,SAAS,EAAE,SAAS,MAAM,EAAE,GAAG,MAAM,GAAG,SAAS,CAO/E;AAOD;;;;;;;GAOG;AACH,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,OAAO,GAAG,eAAe,CAiBjE;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAM7D;AAED;;;;;;;;;GASG;AACH,wBAAgB,gBAAgB,CAAC,SAAS,EAAE,SAAS,MAAM,EAAE,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO,CAO1F"}
@@ -0,0 +1,84 @@
1
+ import path from 'node:path';
2
+ import fs from 'node:fs';
3
+ import { isRecord } from './type-guards.js';
4
+ const FORMATS = new Set(['terminal', 'json', 'github', 'auto']);
5
+ /** The first of `filenames` that exists in the working directory. */
6
+ export function findConfigFile(filenames) {
7
+ const cwd = process.cwd();
8
+ for (const name of filenames) {
9
+ const candidate = path.join(cwd, name);
10
+ if (fs.existsSync(candidate))
11
+ return candidate;
12
+ }
13
+ return undefined;
14
+ }
15
+ /** Strings from an unknown array, dropping anything that is not one. */
16
+ function stringsOf(value) {
17
+ return Array.isArray(value) ? value.filter((v) => typeof v === 'string') : undefined;
18
+ }
19
+ /**
20
+ * The shared config a loaded module claims, validated.
21
+ *
22
+ * Everything is checked rather than trusted: a config file is arbitrary user
23
+ * code and its default export can be any shape at all. An unrecognised `format`
24
+ * is dropped rather than passed through, so a typo falls back to the default
25
+ * instead of reaching a formatter that has no case for it.
26
+ */
27
+ export function extractSharedConfig(mod) {
28
+ if (!isRecord(mod) || !('default' in mod))
29
+ return {};
30
+ const defaultExport = mod.default;
31
+ if (!isRecord(defaultExport))
32
+ return {};
33
+ const config = {};
34
+ const format = defaultExport['format'];
35
+ if (typeof format === 'string' && FORMATS.has(format)) {
36
+ // Narrowed by the set above; the cast-free form is this re-check.
37
+ config.format =
38
+ format === 'terminal' || format === 'json' || format === 'github' ? format : 'auto';
39
+ }
40
+ const rules = stringsOf(defaultExport['rules']);
41
+ if (rules)
42
+ config.rules = rules;
43
+ const watchDirs = stringsOf(defaultExport['watchDirs']);
44
+ if (watchDirs)
45
+ config.watchDirs = watchDirs;
46
+ return config;
47
+ }
48
+ /**
49
+ * Did Node refuse this file for its MODULE FORMAT, rather than for anything the
50
+ * file does?
51
+ *
52
+ * Narrow on purpose. A broad `catch` that fell back to a transpiling loader on
53
+ * any error would re-execute a rule file that had already run — doubling its
54
+ * output — and would hide a genuine syntax error behind a second,
55
+ * differently-worded failure.
56
+ *
57
+ * The condition it detects is bug 0074: the CONSUMER project is
58
+ * `"type": "commonjs"` (what `npm init -y` writes) and the config or rule file
59
+ * uses ESM syntax.
60
+ */
61
+ export function isModuleFormatRefusal(error) {
62
+ if (!(error instanceof SyntaxError))
63
+ return false;
64
+ return (error.message.includes('Cannot use import statement outside a module') ||
65
+ error.message.includes("Unexpected token 'export'"));
66
+ }
67
+ /**
68
+ * Refuse a run that named no rule files, saying where to name them.
69
+ *
70
+ * Sets `process.exitCode` rather than throwing: the caller is a CLI entry point
71
+ * that still wants to return normally. Returns whether the run may proceed.
72
+ *
73
+ * `configName` is the dialect's own config filename, because "set them in
74
+ * config" is not an instruction — the two copies of this differed in exactly
75
+ * that word, one naming the file and one not.
76
+ */
77
+ export function requireRuleFiles(ruleFiles, configName) {
78
+ if (ruleFiles.length > 0)
79
+ return true;
80
+ console.error(`Error: No rule files specified. Pass rule files as arguments or set them in ${configName}.`);
81
+ process.exitCode = 1;
82
+ return false;
83
+ }
84
+ //# sourceMappingURL=cli-config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli-config.js","sourceRoot":"","sources":["../src/cli-config.ts"],"names":[],"mappings":"AAAA,OAAO,IAAI,MAAM,WAAW,CAAA;AAC5B,OAAO,EAAE,MAAM,SAAS,CAAA;AAExB,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAA;AAyB3C,MAAM,OAAO,GAAG,IAAI,GAAG,CAAC,CAAC,UAAU,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,CAAC,CAAC,CAAA;AAE/D,qEAAqE;AACrE,MAAM,UAAU,cAAc,CAAC,SAA4B;IACzD,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,EAAE,CAAA;IACzB,KAAK,MAAM,IAAI,IAAI,SAAS,EAAE,CAAC;QAC7B,MAAM,SAAS,GAAG,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,IAAI,CAAC,CAAA;QACtC,IAAI,EAAE,CAAC,UAAU,CAAC,SAAS,CAAC;YAAE,OAAO,SAAS,CAAA;IAChD,CAAC;IACD,OAAO,SAAS,CAAA;AAClB,CAAC;AAED,wEAAwE;AACxE,SAAS,SAAS,CAAC,KAAc;IAC/B,OAAO,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,EAAe,EAAE,CAAC,OAAO,CAAC,KAAK,QAAQ,CAAC,CAAC,CAAC,CAAC,SAAS,CAAA;AACnG,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,mBAAmB,CAAC,GAAY;IAC9C,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,SAAS,IAAI,GAAG,CAAC;QAAE,OAAO,EAAE,CAAA;IACpD,MAAM,aAAa,GAAG,GAAG,CAAC,OAAO,CAAA;IACjC,IAAI,CAAC,QAAQ,CAAC,aAAa,CAAC;QAAE,OAAO,EAAE,CAAA;IAEvC,MAAM,MAAM,GAAoB,EAAE,CAAA;IAClC,MAAM,MAAM,GAAG,aAAa,CAAC,QAAQ,CAAC,CAAA;IACtC,IAAI,OAAO,MAAM,KAAK,QAAQ,IAAI,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC;QACtD,kEAAkE;QAClE,MAAM,CAAC,MAAM;YACX,MAAM,KAAK,UAAU,IAAI,MAAM,KAAK,MAAM,IAAI,MAAM,KAAK,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,CAAA;IACvF,CAAC;IACD,MAAM,KAAK,GAAG,SAAS,CAAC,aAAa,CAAC,OAAO,CAAC,CAAC,CAAA;IAC/C,IAAI,KAAK;QAAE,MAAM,CAAC,KAAK,GAAG,KAAK,CAAA;IAC/B,MAAM,SAAS,GAAG,SAAS,CAAC,aAAa,CAAC,WAAW,CAAC,CAAC,CAAA;IACvD,IAAI,SAAS;QAAE,MAAM,CAAC,SAAS,GAAG,SAAS,CAAA;IAC3C,OAAO,MAAM,CAAA;AACf,CAAC;AAED;;;;;;;;;;;;GAYG;AACH,MAAM,UAAU,qBAAqB,CAAC,KAAc;IAClD,IAAI,CAAC,CAAC,KAAK,YAAY,WAAW,CAAC;QAAE,OAAO,KAAK,CAAA;IACjD,OAAO,CACL,KAAK,CAAC,OAAO,CAAC,QAAQ,CAAC,8CAA8C,CAAC;QACtE,KAAK,CAAC,OAAO,CAAC,QAAQ,CAAC,2BAA2B,CAAC,CACpD,CAAA;AACH,CAAC;AAED;;;;;;;;;GASG;AACH,MAAM,UAAU,gBAAgB,CAAC,SAA4B,EAAE,UAAkB;IAC/E,IAAI,SAAS,CAAC,MAAM,GAAG,CAAC;QAAE,OAAO,IAAI,CAAA;IACrC,OAAO,CAAC,KAAK,CACX,+EAA+E,UAAU,GAAG,CAC7F,CAAA;IACD,OAAO,CAAC,QAAQ,GAAG,CAAC,CAAA;IACpB,OAAO,KAAK,CAAA;AACd,CAAC"}
@@ -0,0 +1,94 @@
1
+ import type { ArchViolation } from './violation.js';
2
+ /**
3
+ * The receipt — [ADR-014](../../../adr/014-the-emitter-refuses-a-verdict-without-evidence.md).
4
+ *
5
+ * **An `ArchViolation[]` carrying its own evidence**, not an object wrapping
6
+ * one. That shape is load-bearing and the review measured why: a plain object
7
+ * turns every untyped consumer green, because `result.length` reads `undefined`
8
+ * and `undefined > 0` is `false`. An array keeps `.length`, iteration,
9
+ * `flatMap` and every existing `for…of` working and correct, so a JavaScript
10
+ * consumer who never reads `examined` still exits non-zero on violations.
11
+ * "`CollectResult`'s shape by name" is a tidiness argument and it loses to that.
12
+ *
13
+ * **The name stays `CollectResult`.** Plan 0235 calls it a "receipt" in prose;
14
+ * there is deliberately no second exported name for one concept.
15
+ *
16
+ * `JSON.stringify` drops an array's own properties, which is why
17
+ * `formatViolationsJson` carries `examined` across by hand — see the note there.
18
+ */
19
+ export interface CollectResult extends Array<ArchViolation> {
20
+ /** Units this rule actually reached its assertion over. The denominator. */
21
+ readonly examined: number;
22
+ /** The source loaded nothing at all, before any selection ran. */
23
+ readonly sourceEmpty?: boolean;
24
+ /** A caller declared this set legitimately empty, and the claim can expire. */
25
+ readonly declaredEmpty?: boolean;
26
+ /**
27
+ * This rule was never run — explicitly disabled, not merely empty.
28
+ *
29
+ * A third state, and it has to be distinguishable from both. A rule that RAN
30
+ * and examined nothing is dead and the merge must say so. A rule turned off
31
+ * never ran, so it has no denominator to be suspicious about — and marking it
32
+ * `declaredEmpty` instead would mint the declaration ADR-014 §3's amendment
33
+ * refuses to infer from `overrides: { id: 'off' }`.
34
+ *
35
+ * **It is falsifiable, like every other declaration here.** A rule that never
36
+ * ran examined nothing and found nothing, so `notRun` beside a non-zero
37
+ * `examined` or a violation is `emitter/contradictory-evidence` — checked at
38
+ * the gate and in the merge. Without that check this would be the one
39
+ * suppressing flag in the vocabulary that nothing can contradict, which is the
40
+ * shape ADR-014 §3's amendment exists to refuse.
41
+ *
42
+ * The two are not interchangeable: without this, one disabled rule reddened a
43
+ * whole preset whose other rules examined plenty (measured — `adrEnforcement`
44
+ * with `adr/valid-tiers` off). With it, a preset where EVERY member is `notRun`
45
+ * still sums to zero examined with no declaration, so the all-off case reports
46
+ * exactly as §3 requires.
47
+ */
48
+ readonly notRun?: boolean;
49
+ /**
50
+ * A specific dead-glob explanation for a zero-examined rule, pre-formatted
51
+ * by the dialect that computed it (`RuleBuilder.deadGlobDiagnosis()`) — the
52
+ * kernel never touches picomatch or a project type to build this itself.
53
+ */
54
+ readonly deadGlob?: string;
55
+ }
56
+ /** Evidence a caller supplies alongside the violations. */
57
+ export interface Evidence {
58
+ readonly examined: number;
59
+ readonly sourceEmpty?: boolean;
60
+ readonly declaredEmpty?: boolean;
61
+ readonly notRun?: boolean;
62
+ readonly deadGlob?: string;
63
+ }
64
+ /**
65
+ * The one constructor. Every terminal produces its receipt through this.
66
+ *
67
+ * **Stamps a FRESH array, deliberately.** `applyFilters` returns the *same
68
+ * reference* when no exclusion applies, so stamping `examined` onto what it
69
+ * hands back would mutate whatever a family memoized — a rule's evidence
70
+ * appearing on someone else's array, changing when it is re-read. The copy is
71
+ * the cost of not having that bug.
72
+ */
73
+ export declare function collectResult(violations: readonly ArchViolation[], evidence: Evidence): CollectResult;
74
+ /** Does this value carry evidence at all, or is it a bare array? */
75
+ export declare function hasEvidence(value: readonly ArchViolation[]): value is CollectResult;
76
+ /**
77
+ * The one merge — ADR-014 §7. Fail-closed, and each rule below is a rule a
78
+ * hand-rolled sum got wrong somewhere (bug 0205's class: four emitters
79
+ * restating one rule and disagreeing).
80
+ *
81
+ * - **`examined` sums.** Zero members sums to zero — an absent denominator is
82
+ * not a large one.
83
+ * - **`declaredEmpty` requires EVERY zero-contributing member to declare.** A
84
+ * `some()` here lets one declared part vouch for a hand-counted zero beside
85
+ * it, which is the whole failure this merge exists to prevent.
86
+ * - **`sourceEmpty` on any member outranks every declaration.** ADR-010 §3's
87
+ * precedence: a declaration asserts a fact about a loaded corpus, and over
88
+ * an empty source it asserts nothing.
89
+ * - **A member with no evidence poisons the merge**, rather than being counted
90
+ * as zero. A dead member must not be able to hide inside a healthy sum, so
91
+ * the result carries no evidence either and the emitter says so.
92
+ */
93
+ export declare function mergeCollectResults(parts: readonly (readonly ArchViolation[])[]): CollectResult;
94
+ //# sourceMappingURL=collect-result.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"collect-result.d.ts","sourceRoot":"","sources":["../src/collect-result.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAA;AAQnD;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,WAAW,aAAc,SAAQ,KAAK,CAAC,aAAa,CAAC;IACzD,4EAA4E;IAC5E,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAA;IACzB,kEAAkE;IAClE,QAAQ,CAAC,WAAW,CAAC,EAAE,OAAO,CAAA;IAC9B,+EAA+E;IAC/E,QAAQ,CAAC,aAAa,CAAC,EAAE,OAAO,CAAA;IAChC;;;;;;;;;;;;;;;;;;;;;OAqBG;IACH,QAAQ,CAAC,MAAM,CAAC,EAAE,OAAO,CAAA;IACzB;;;;OAIG;IACH,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;CAC3B;AAED,2DAA2D;AAC3D,MAAM,WAAW,QAAQ;IACvB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAA;IACzB,QAAQ,CAAC,WAAW,CAAC,EAAE,OAAO,CAAA;IAC9B,QAAQ,CAAC,aAAa,CAAC,EAAE,OAAO,CAAA;IAChC,QAAQ,CAAC,MAAM,CAAC,EAAE,OAAO,CAAA;IACzB,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;CAC3B;AAED;;;;;;;;GAQG;AACH,wBAAgB,aAAa,CAC3B,UAAU,EAAE,SAAS,aAAa,EAAE,EACpC,QAAQ,EAAE,QAAQ,GACjB,aAAa,CAWf;AAED,oEAAoE;AACpE,wBAAgB,WAAW,CAAC,KAAK,EAAE,SAAS,aAAa,EAAE,GAAG,KAAK,IAAI,aAAa,CAGnF;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,SAAS,CAAC,SAAS,aAAa,EAAE,CAAC,EAAE,GAAG,aAAa,CA0G/F"}
@@ -0,0 +1,142 @@
1
+ import { noReceiptViolation, passWithoutEvidenceViolation, contradictoryEvidenceViolation, sourceEmptyViolation, } from './emitter-findings.js';
2
+ /**
3
+ * The one constructor. Every terminal produces its receipt through this.
4
+ *
5
+ * **Stamps a FRESH array, deliberately.** `applyFilters` returns the *same
6
+ * reference* when no exclusion applies, so stamping `examined` onto what it
7
+ * hands back would mutate whatever a family memoized — a rule's evidence
8
+ * appearing on someone else's array, changing when it is re-read. The copy is
9
+ * the cost of not having that bug.
10
+ */
11
+ export function collectResult(violations, evidence) {
12
+ // `Object.assign` returns the intersection type, so no assertion is needed —
13
+ // ADR-005. An earlier cut wrote `[...violations] as ArchViolation[] & {…}` and
14
+ // the repo's own `check:arch` reported it, in the module this plan adds.
15
+ return Object.assign([...violations], {
16
+ examined: evidence.examined,
17
+ ...(evidence.sourceEmpty === true ? { sourceEmpty: true } : {}),
18
+ ...(evidence.declaredEmpty === true ? { declaredEmpty: true } : {}),
19
+ ...(evidence.notRun === true ? { notRun: true } : {}),
20
+ ...(evidence.deadGlob === undefined ? {} : { deadGlob: evidence.deadGlob }),
21
+ });
22
+ }
23
+ /** Does this value carry evidence at all, or is it a bare array? */
24
+ export function hasEvidence(value) {
25
+ // `in` narrows without an assertion — ADR-005.
26
+ return 'examined' in value && typeof value.examined === 'number';
27
+ }
28
+ /**
29
+ * The one merge — ADR-014 §7. Fail-closed, and each rule below is a rule a
30
+ * hand-rolled sum got wrong somewhere (bug 0205's class: four emitters
31
+ * restating one rule and disagreeing).
32
+ *
33
+ * - **`examined` sums.** Zero members sums to zero — an absent denominator is
34
+ * not a large one.
35
+ * - **`declaredEmpty` requires EVERY zero-contributing member to declare.** A
36
+ * `some()` here lets one declared part vouch for a hand-counted zero beside
37
+ * it, which is the whole failure this merge exists to prevent.
38
+ * - **`sourceEmpty` on any member outranks every declaration.** ADR-010 §3's
39
+ * precedence: a declaration asserts a fact about a loaded corpus, and over
40
+ * an empty source it asserts nothing.
41
+ * - **A member with no evidence poisons the merge**, rather than being counted
42
+ * as zero. A dead member must not be able to hide inside a healthy sum, so
43
+ * the result carries no evidence either and the emitter says so.
44
+ */
45
+ export function mergeCollectResults(parts) {
46
+ const violations = parts.flat();
47
+ if (!parts.every(hasEvidence)) {
48
+ // A member with no evidence at all. The merge REPORTS it rather than
49
+ // returning an evidence-free value for the emitter to notice later: the
50
+ // guarantee is this function's, and deferring it was also the last `as` in
51
+ // this file (an evidence-free value is not a `CollectResult`).
52
+ return collectResult([...violations, noReceiptViolation()], { examined: 0 });
53
+ }
54
+ // **ADR-014 §7's headline guarantee, and it needs its own pass.** Summing
55
+ // alone lets a dead member hide: measured, a check examining 0 merged with one
56
+ // examining 900 produced `examined: 900` and no finding — nine checks in
57
+ // `check-corpus.mjs`, one of them with a `continue` planted at the top of its
58
+ // loop, and the total still reads healthy. That is the plan's own top success
59
+ // criterion, and the field failure proposal 009 records, reproduced inside the
60
+ // primitive built to prevent it.
61
+ //
62
+ // A member is dead when it examined nothing, said nothing, and declared
63
+ // nothing. One that carries its own finding has spoken; one that declared
64
+ // empty has been accounted for.
65
+ // A member that never RAN is not a dead check — it is an absent one, with no
66
+ // denominator to be suspicious about. Excluding it is what keeps ONE disabled
67
+ // rule from reddening a preset whose others examined plenty (measured:
68
+ // `adrEnforcement` with `adr/valid-tiers` off). The all-off case still
69
+ // reports: every member is then `notRun`, the sum is zero and nobody
70
+ // declared, which the emitter's own gate catches downstream.
71
+ // A member whose `notRun` contradicts its own evidence, checked HERE and not
72
+ // only at the gate: the merged receipt carries no `notRun`, so a contradictory
73
+ // member is invisible downstream — it would slip through the one door that
74
+ // could see it. `notRun` is also the flag that exempts a member from the dead
75
+ // filter below, so an unchecked one is a suppressor with no falsifier (the
76
+ // asymmetry with `declaredEmpty` an enforcement review found in plan 0235).
77
+ const contradictory = parts.find((p) => p.notRun === true && (p.examined > 0 || p.length > 0));
78
+ if (contradictory !== undefined) {
79
+ return collectResult([...violations, contradictoryEvidenceViolation(contradictory.examined, contradictory.length)], { examined: parts.reduce((n, p) => n + p.examined, 0) });
80
+ }
81
+ // **An empty source is a member-level fault, checked HERE for the same reason
82
+ // the contradictory `notRun` above is** — ADR-014 §4: "an empty source
83
+ // outranks any declaration and names the source."
84
+ //
85
+ // The dead filter below EXEMPTS a `sourceEmpty` member, which is right for
86
+ // "did this member contribute nothing without saying why" — it said why. But
87
+ // §4 makes that answer a fault rather than an excuse, so the exemption alone
88
+ // left the merge disagreeing with the gate about the same receipt: measured,
89
+ // `finishPreset` reds a `{ examined: 0, sourceEmpty: true, declaredEmpty: true }`
90
+ // member while `mergeCollectResults([thatMember, aHealthyOne])` was GREEN,
91
+ // because the healthy sibling's `examined` carries the merged receipt past the
92
+ // gate's own check. That is the `notRun` asymmetry this ADR already fixed once,
93
+ // recreated for `sourceEmpty` by the change that fixed it at the gate.
94
+ const emptySource = parts.find((p) => p.sourceEmpty === true && p.examined === 0);
95
+ if (emptySource !== undefined) {
96
+ const summed = parts.reduce((n, p) => n + p.examined, 0);
97
+ return collectResult([...violations, sourceEmptyViolation()], {
98
+ examined: summed,
99
+ // **Only when it is true of the WHOLE.** Stamping it unconditionally
100
+ // minted a receipt saying "the source loaded nothing" beside a non-zero
101
+ // sum from healthy siblings — and the gate then contradicted it, blaming a
102
+ // flag nobody set. Four reviewers measured that on this branch's own
103
+ // headline scenario. The member-level fact is already carried by the
104
+ // finding above; the flag describes the merged whole, and of the whole it
105
+ // is only true when nothing was examined anywhere.
106
+ sourceEmpty: summed === 0 ? true : undefined,
107
+ });
108
+ }
109
+ const dead = parts.filter((p) => p.examined === 0 &&
110
+ p.length === 0 &&
111
+ p.notRun !== true &&
112
+ p.declaredEmpty !== true &&
113
+ p.sourceEmpty !== true);
114
+ if (dead.length > 0 && parts.length > 1) {
115
+ return collectResult([...violations, passWithoutEvidenceViolation()], {
116
+ examined: parts.reduce((n, p) => n + p.examined, 0),
117
+ });
118
+ }
119
+ const examined = parts.reduce((n, p) => n + p.examined, 0);
120
+ const anySourceEmpty = parts.some((p) => p.sourceEmpty === true);
121
+ return collectResult(violations, {
122
+ examined,
123
+ sourceEmpty: anySourceEmpty ? true : undefined,
124
+ // **Only when the WHOLE verdict examined nothing.** A member's declaration
125
+ // is about that member: `honestyAtClose` merges three rules and one of them
126
+ // legitimately declares itself empty while the other two examine plenty.
127
+ // Propagating that up marked the merged verdict declared-empty over a
128
+ // non-zero total, and the emitter's expiry then fired on it — correctly,
129
+ // which is how this was found. The member's own declaration is already
130
+ // satisfied by its own zero; the merge has nothing to add.
131
+ //
132
+ // `every`, not `some`: one declared part must never vouch for a
133
+ // hand-counted zero beside it.
134
+ declaredEmpty: !anySourceEmpty &&
135
+ examined === 0 &&
136
+ parts.length > 0 &&
137
+ parts.every((p) => p.declaredEmpty === true)
138
+ ? true
139
+ : undefined,
140
+ });
141
+ }
142
+ //# sourceMappingURL=collect-result.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"collect-result.js","sourceRoot":"","sources":["../src/collect-result.ts"],"names":[],"mappings":"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"}
@@ -22,6 +22,20 @@ export type Matcher<V> = (value: V) => boolean;
22
22
  */
23
23
  export declare function not<T>(input: Predicate<T>): Predicate<T>;
24
24
  export declare function not<V>(input: Matcher<V>): Matcher<V>;
25
+ /**
26
+ * Every input to `and()`/`or()` must be the same kind.
27
+ *
28
+ * `eess-ts` carried a byte-identical copy — `no-copy-paste` reported it at
29
+ * 100%, with only the noun in the message differing — so the noun is now the
30
+ * parameter and the check has one owner.
31
+ *
32
+ * A `TypeError` and not a filtered result: mixing an object predicate with a
33
+ * matcher function silently drops one kind (the implementations below filter
34
+ * by `typeof`), so a rule composed that way would narrow by half of what its
35
+ * author wrote and pass on the rest. Refusing at composition time is the only
36
+ * point where the mistake is still visible.
37
+ */
38
+ export declare function assertHomogeneous<T, V>(inputs: (Predicate<T> | Matcher<V>)[], matcherNoun?: string): void;
25
39
  /**
26
40
  * Combines predicates or matchers with AND logic.
27
41
  *
@@ -1 +1 @@
1
- {"version":3,"file":"combinators.d.ts","sourceRoot":"","sources":["../src/combinators.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAA;AAE/C;;;;;;GAMG;AACH,MAAM,MAAM,OAAO,CAAC,CAAC,IAAI,CAAC,KAAK,EAAE,CAAC,KAAK,OAAO,CAAA;AAE9C;;;;;;;;;;;;GAYG;AACH,wBAAgB,GAAG,CAAC,CAAC,EAAE,KAAK,EAAE,SAAS,CAAC,CAAC,CAAC,GAAG,SAAS,CAAC,CAAC,CAAC,CAAA;AACzD,wBAAgB,GAAG,CAAC,CAAC,EAAE,KAAK,EAAE,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAAA;AAmBrD;;;;;;;;;;;;GAYG;AACH,wBAAgB,GAAG,CAAC,CAAC,EAAE,GAAG,UAAU,EAAE,SAAS,CAAC,CAAC,CAAC,EAAE,GAAG,SAAS,CAAC,CAAC,CAAC,CAAA;AACnE,wBAAgB,GAAG,CAAC,CAAC,EAAE,GAAG,QAAQ,EAAE,OAAO,CAAC,CAAC,CAAC,EAAE,GAAG,OAAO,CAAC,CAAC,CAAC,CAAA;AAe7D;;;;;;;;;;;;GAYG;AACH,wBAAgB,EAAE,CAAC,CAAC,EAAE,GAAG,UAAU,EAAE,SAAS,CAAC,CAAC,CAAC,EAAE,GAAG,SAAS,CAAC,CAAC,CAAC,CAAA;AAClE,wBAAgB,EAAE,CAAC,CAAC,EAAE,GAAG,QAAQ,EAAE,OAAO,CAAC,CAAC,CAAC,EAAE,GAAG,OAAO,CAAC,CAAC,CAAC,CAAA"}
1
+ {"version":3,"file":"combinators.d.ts","sourceRoot":"","sources":["../src/combinators.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAA;AAG/C;;;;;;GAMG;AACH,MAAM,MAAM,OAAO,CAAC,CAAC,IAAI,CAAC,KAAK,EAAE,CAAC,KAAK,OAAO,CAAA;AAE9C;;;;;;;;;;;;GAYG;AACH,wBAAgB,GAAG,CAAC,CAAC,EAAE,KAAK,EAAE,SAAS,CAAC,CAAC,CAAC,GAAG,SAAS,CAAC,CAAC,CAAC,CAAA;AACzD,wBAAgB,GAAG,CAAC,CAAC,EAAE,KAAK,EAAE,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAAA;AAiBrD;;;;;;;;;;;;GAYG;AACH,wBAAgB,iBAAiB,CAAC,CAAC,EAAE,CAAC,EACpC,MAAM,EAAE,CAAC,SAAS,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC,EAAE,EACrC,WAAW,SAAY,GACtB,IAAI,CAMN;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,GAAG,CAAC,CAAC,EAAE,GAAG,UAAU,EAAE,SAAS,CAAC,CAAC,CAAC,EAAE,GAAG,SAAS,CAAC,CAAC,CAAC,CAAA;AACnE,wBAAgB,GAAG,CAAC,CAAC,EAAE,GAAG,QAAQ,EAAE,OAAO,CAAC,CAAC,CAAC,EAAE,GAAG,OAAO,CAAC,CAAC,CAAC,CAAA;AAK7D;;;;;;;;;;;;GAYG;AACH,wBAAgB,EAAE,CAAC,CAAC,EAAE,GAAG,UAAU,EAAE,SAAS,CAAC,CAAC,CAAC,EAAE,GAAG,SAAS,CAAC,CAAC,CAAC,CAAA;AAClE,wBAAgB,EAAE,CAAC,CAAC,EAAE,GAAG,QAAQ,EAAE,OAAO,CAAC,CAAC,CAAC,EAAE,GAAG,OAAO,CAAC,CAAC,CAAC,CAAA"}