@nielspeter/eess-ts 0.2.1 → 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 (467) hide show
  1. package/README.md +61 -35
  2. package/dist/builders/call-rule-builder.d.ts +66 -26
  3. package/dist/builders/call-rule-builder.d.ts.map +1 -1
  4. package/dist/builders/call-rule-builder.js +75 -32
  5. package/dist/builders/call-rule-builder.js.map +1 -1
  6. package/dist/builders/class-rule-builder.d.ts +125 -26
  7. package/dist/builders/class-rule-builder.d.ts.map +1 -1
  8. package/dist/builders/class-rule-builder.js +141 -55
  9. package/dist/builders/class-rule-builder.js.map +1 -1
  10. package/dist/builders/correspondence-builder.d.ts +287 -0
  11. package/dist/builders/correspondence-builder.d.ts.map +1 -0
  12. package/dist/builders/correspondence-builder.js +385 -0
  13. package/dist/builders/correspondence-builder.js.map +1 -0
  14. package/dist/builders/correspondence-findings.d.ts +104 -0
  15. package/dist/builders/correspondence-findings.d.ts.map +1 -0
  16. package/dist/builders/correspondence-findings.js +301 -0
  17. package/dist/builders/correspondence-findings.js.map +1 -0
  18. package/dist/builders/cross-layer-builder.d.ts +99 -16
  19. package/dist/builders/cross-layer-builder.d.ts.map +1 -1
  20. package/dist/builders/cross-layer-builder.js +182 -35
  21. package/dist/builders/cross-layer-builder.js.map +1 -1
  22. package/dist/builders/function-rule-builder.d.ts +144 -25
  23. package/dist/builders/function-rule-builder.d.ts.map +1 -1
  24. package/dist/builders/function-rule-builder.js +177 -39
  25. package/dist/builders/function-rule-builder.js.map +1 -1
  26. package/dist/builders/jsx-rule-builder.d.ts +70 -14
  27. package/dist/builders/jsx-rule-builder.d.ts.map +1 -1
  28. package/dist/builders/jsx-rule-builder.js +73 -14
  29. package/dist/builders/jsx-rule-builder.js.map +1 -1
  30. package/dist/builders/module-rule-builder.d.ts +2 -2
  31. package/dist/builders/module-rule-builder.d.ts.map +1 -1
  32. package/dist/builders/module-rule-builder.js +16 -19
  33. package/dist/builders/module-rule-builder.js.map +1 -1
  34. package/dist/builders/scoped-function-rule-builder.d.ts +11 -0
  35. package/dist/builders/scoped-function-rule-builder.d.ts.map +1 -1
  36. package/dist/builders/scoped-function-rule-builder.js +12 -0
  37. package/dist/builders/scoped-function-rule-builder.js.map +1 -1
  38. package/dist/builders/slice-discovery-message.d.ts +36 -0
  39. package/dist/builders/slice-discovery-message.d.ts.map +1 -0
  40. package/dist/builders/slice-discovery-message.js +181 -0
  41. package/dist/builders/slice-discovery-message.js.map +1 -0
  42. package/dist/builders/slice-rule-builder.d.ts +151 -12
  43. package/dist/builders/slice-rule-builder.d.ts.map +1 -1
  44. package/dist/builders/slice-rule-builder.js +243 -35
  45. package/dist/builders/slice-rule-builder.js.map +1 -1
  46. package/dist/builders/type-rule-builder.d.ts +95 -24
  47. package/dist/builders/type-rule-builder.d.ts.map +1 -1
  48. package/dist/builders/type-rule-builder.js +109 -42
  49. package/dist/builders/type-rule-builder.js.map +1 -1
  50. package/dist/{helpers → builders}/within.d.ts +2 -2
  51. package/dist/builders/within.d.ts.map +1 -0
  52. package/dist/{helpers → builders}/within.js +1 -1
  53. package/dist/builders/within.js.map +1 -0
  54. package/dist/cli/commands/baseline.d.ts +8 -3
  55. package/dist/cli/commands/baseline.d.ts.map +1 -1
  56. package/dist/cli/commands/baseline.js +111 -7
  57. package/dist/cli/commands/baseline.js.map +1 -1
  58. package/dist/cli/commands/check.d.ts +7 -2
  59. package/dist/cli/commands/check.d.ts.map +1 -1
  60. package/dist/cli/commands/check.js +385 -47
  61. package/dist/cli/commands/check.js.map +1 -1
  62. package/dist/cli/commands/doctor.d.ts +29 -0
  63. package/dist/cli/commands/doctor.d.ts.map +1 -0
  64. package/dist/cli/commands/doctor.js +224 -0
  65. package/dist/cli/commands/doctor.js.map +1 -0
  66. package/dist/cli/commands/explain.d.ts +2 -1
  67. package/dist/cli/commands/explain.d.ts.map +1 -1
  68. package/dist/cli/commands/explain.js +19 -10
  69. package/dist/cli/commands/explain.js.map +1 -1
  70. package/dist/cli/commands/init.d.ts +9 -3
  71. package/dist/cli/commands/init.d.ts.map +1 -1
  72. package/dist/cli/commands/init.js +162 -137
  73. package/dist/cli/commands/init.js.map +1 -1
  74. package/dist/cli/import-rule-module.d.ts +29 -0
  75. package/dist/cli/import-rule-module.d.ts.map +1 -0
  76. package/dist/cli/import-rule-module.js +47 -0
  77. package/dist/cli/import-rule-module.js.map +1 -0
  78. package/dist/cli/index.d.ts +2 -2
  79. package/dist/cli/index.d.ts.map +1 -1
  80. package/dist/cli/index.js +101 -43
  81. package/dist/cli/index.js.map +1 -1
  82. package/dist/cli/load-rules.d.ts +6 -11
  83. package/dist/cli/load-rules.d.ts.map +1 -1
  84. package/dist/cli/load-rules.js +25 -32
  85. package/dist/cli/load-rules.js.map +1 -1
  86. package/dist/cli/resolve-config.d.ts +0 -8
  87. package/dist/cli/resolve-config.d.ts.map +1 -1
  88. package/dist/cli/resolve-config.js +25 -55
  89. package/dist/cli/resolve-config.js.map +1 -1
  90. package/dist/cli/rule-file-findings.d.ts +178 -0
  91. package/dist/cli/rule-file-findings.d.ts.map +1 -0
  92. package/dist/cli/rule-file-findings.js +351 -0
  93. package/dist/cli/rule-file-findings.js.map +1 -0
  94. package/dist/cli/watch.d.ts +17 -34
  95. package/dist/cli/watch.d.ts.map +1 -1
  96. package/dist/cli/watch.js +14 -129
  97. package/dist/cli/watch.js.map +1 -1
  98. package/dist/conditions/body-analysis-function.d.ts.map +1 -1
  99. package/dist/conditions/body-analysis-function.js +16 -7
  100. package/dist/conditions/body-analysis-function.js.map +1 -1
  101. package/dist/conditions/body-analysis-module.d.ts.map +1 -1
  102. package/dist/conditions/body-analysis-module.js +14 -9
  103. package/dist/conditions/body-analysis-module.js.map +1 -1
  104. package/dist/conditions/body-analysis.d.ts.map +1 -1
  105. package/dist/conditions/body-analysis.js +16 -7
  106. package/dist/conditions/body-analysis.js.map +1 -1
  107. package/dist/conditions/call.d.ts.map +1 -1
  108. package/dist/conditions/call.js +41 -19
  109. package/dist/conditions/call.js.map +1 -1
  110. package/dist/conditions/catch-analysis.d.ts +2 -1
  111. package/dist/conditions/catch-analysis.d.ts.map +1 -1
  112. package/dist/conditions/catch-analysis.js.map +1 -1
  113. package/dist/conditions/cross-layer.d.ts +2 -10
  114. package/dist/conditions/cross-layer.d.ts.map +1 -1
  115. package/dist/conditions/cross-layer.js +227 -23
  116. package/dist/conditions/cross-layer.js.map +1 -1
  117. package/dist/conditions/dependency.d.ts +35 -6
  118. package/dist/conditions/dependency.d.ts.map +1 -1
  119. package/dist/conditions/dependency.js +299 -45
  120. package/dist/conditions/dependency.js.map +1 -1
  121. package/dist/conditions/exports.d.ts.map +1 -1
  122. package/dist/conditions/exports.js +41 -37
  123. package/dist/conditions/exports.js.map +1 -1
  124. package/dist/conditions/function.d.ts.map +1 -1
  125. package/dist/conditions/function.js +19 -5
  126. package/dist/conditions/function.js.map +1 -1
  127. package/dist/conditions/helpers.d.ts +2 -1
  128. package/dist/conditions/helpers.d.ts.map +1 -1
  129. package/dist/conditions/helpers.js +4 -1
  130. package/dist/conditions/helpers.js.map +1 -1
  131. package/dist/conditions/jsx.d.ts.map +1 -1
  132. package/dist/conditions/jsx.js +7 -2
  133. package/dist/conditions/jsx.js.map +1 -1
  134. package/dist/conditions/match-identity.d.ts +43 -0
  135. package/dist/conditions/match-identity.d.ts.map +1 -0
  136. package/dist/conditions/match-identity.js +43 -0
  137. package/dist/conditions/match-identity.js.map +1 -0
  138. package/dist/conditions/members.d.ts.map +1 -1
  139. package/dist/conditions/members.js +20 -6
  140. package/dist/conditions/members.js.map +1 -1
  141. package/dist/conditions/reverse-dependency.d.ts +18 -5
  142. package/dist/conditions/reverse-dependency.d.ts.map +1 -1
  143. package/dist/conditions/reverse-dependency.js +155 -93
  144. package/dist/conditions/reverse-dependency.js.map +1 -1
  145. package/dist/conditions/slice.d.ts +86 -2
  146. package/dist/conditions/slice.d.ts.map +1 -1
  147. package/dist/conditions/slice.js +234 -53
  148. package/dist/conditions/slice.js.map +1 -1
  149. package/dist/conditions/structural.d.ts.map +1 -1
  150. package/dist/conditions/structural.js +16 -4
  151. package/dist/conditions/structural.js.map +1 -1
  152. package/dist/core/check-all.d.ts +19 -0
  153. package/dist/core/check-all.d.ts.map +1 -0
  154. package/dist/core/check-all.js +150 -0
  155. package/dist/core/check-all.js.map +1 -0
  156. package/dist/core/combinators.d.ts +48 -0
  157. package/dist/core/combinators.d.ts.map +1 -0
  158. package/dist/core/combinators.js +49 -0
  159. package/dist/core/combinators.js.map +1 -0
  160. package/dist/core/descendant-cache.d.ts +36 -0
  161. package/dist/core/descendant-cache.d.ts.map +1 -0
  162. package/dist/core/descendant-cache.js +117 -0
  163. package/dist/core/descendant-cache.js.map +1 -0
  164. package/dist/core/diagnose.d.ts +234 -0
  165. package/dist/core/diagnose.d.ts.map +1 -0
  166. package/dist/core/diagnose.js +390 -0
  167. package/dist/core/diagnose.js.map +1 -0
  168. package/dist/core/disk-set.d.ts +48 -0
  169. package/dist/core/disk-set.d.ts.map +1 -0
  170. package/dist/core/disk-set.js +218 -0
  171. package/dist/core/disk-set.js.map +1 -0
  172. package/dist/core/element-cache.d.ts +75 -0
  173. package/dist/core/element-cache.d.ts.map +1 -0
  174. package/dist/core/element-cache.js +32 -0
  175. package/dist/core/element-cache.js.map +1 -0
  176. package/dist/core/empty-project-advice.d.ts +75 -0
  177. package/dist/core/empty-project-advice.d.ts.map +1 -0
  178. package/dist/core/empty-project-advice.js +61 -0
  179. package/dist/core/empty-project-advice.js.map +1 -0
  180. package/dist/core/exclusion-comments.d.ts +19 -0
  181. package/dist/core/exclusion-comments.d.ts.map +1 -0
  182. package/dist/core/exclusion-comments.js +7 -0
  183. package/dist/core/exclusion-comments.js.map +1 -0
  184. package/dist/core/execute-rule.d.ts +158 -0
  185. package/dist/core/execute-rule.d.ts.map +1 -0
  186. package/dist/core/execute-rule.js +666 -0
  187. package/dist/core/execute-rule.js.map +1 -0
  188. package/dist/core/glob-diagnosis.d.ts +78 -0
  189. package/dist/core/glob-diagnosis.d.ts.map +1 -0
  190. package/dist/core/glob-diagnosis.js +134 -0
  191. package/dist/core/glob-diagnosis.js.map +1 -0
  192. package/dist/core/glob-evaluator.d.ts +48 -0
  193. package/dist/core/glob-evaluator.d.ts.map +1 -0
  194. package/dist/core/glob-evaluator.js +89 -0
  195. package/dist/core/glob-evaluator.js.map +1 -0
  196. package/dist/core/import-candidates.d.ts +60 -0
  197. package/dist/core/import-candidates.d.ts.map +1 -0
  198. package/dist/core/import-candidates.js +93 -0
  199. package/dist/core/import-candidates.js.map +1 -0
  200. package/dist/core/import-options.d.ts +55 -2
  201. package/dist/core/import-options.d.ts.map +1 -1
  202. package/dist/core/import-options.js +64 -0
  203. package/dist/core/import-options.js.map +1 -1
  204. package/dist/core/invalidate-on-modify.d.ts +20 -0
  205. package/dist/core/invalidate-on-modify.d.ts.map +1 -0
  206. package/dist/core/invalidate-on-modify.js +26 -0
  207. package/dist/core/invalidate-on-modify.js.map +1 -0
  208. package/dist/core/line-index.d.ts +3 -0
  209. package/dist/core/line-index.d.ts.map +1 -0
  210. package/dist/core/line-index.js +227 -0
  211. package/dist/core/line-index.js.map +1 -0
  212. package/dist/core/metric-violation.d.ts +86 -0
  213. package/dist/core/metric-violation.d.ts.map +1 -0
  214. package/dist/core/metric-violation.js +132 -0
  215. package/dist/core/metric-violation.js.map +1 -0
  216. package/dist/core/module-edges.d.ts +361 -0
  217. package/dist/core/module-edges.d.ts.map +1 -0
  218. package/dist/core/module-edges.js +593 -0
  219. package/dist/core/module-edges.js.map +1 -0
  220. package/dist/core/object-literal-functions.d.ts +42 -0
  221. package/dist/core/object-literal-functions.d.ts.map +1 -0
  222. package/dist/core/object-literal-functions.js +75 -0
  223. package/dist/core/object-literal-functions.js.map +1 -0
  224. package/dist/core/orphan-exclusions.d.ts +44 -0
  225. package/dist/core/orphan-exclusions.d.ts.map +1 -0
  226. package/dist/core/orphan-exclusions.js +224 -0
  227. package/dist/core/orphan-exclusions.js.map +1 -0
  228. package/dist/core/pair-condition.d.ts +32 -3
  229. package/dist/core/pair-condition.d.ts.map +1 -1
  230. package/dist/core/path-universe.d.ts +22 -0
  231. package/dist/core/path-universe.d.ts.map +1 -0
  232. package/dist/core/path-universe.js +45 -0
  233. package/dist/core/path-universe.js.map +1 -0
  234. package/dist/core/per-root-compiler-options.d.ts +20 -0
  235. package/dist/core/per-root-compiler-options.d.ts.map +1 -0
  236. package/dist/core/per-root-compiler-options.js +57 -0
  237. package/dist/core/per-root-compiler-options.js.map +1 -0
  238. package/dist/core/project-relative.d.ts +98 -0
  239. package/dist/core/project-relative.d.ts.map +1 -0
  240. package/dist/core/project-relative.js +185 -0
  241. package/dist/core/project-relative.js.map +1 -0
  242. package/dist/core/project.d.ts +7 -1
  243. package/dist/core/project.d.ts.map +1 -1
  244. package/dist/core/project.js +29 -4
  245. package/dist/core/project.js.map +1 -1
  246. package/dist/core/rule-builder.d.ts +303 -0
  247. package/dist/core/rule-builder.d.ts.map +1 -0
  248. package/dist/core/rule-builder.js +463 -0
  249. package/dist/core/rule-builder.js.map +1 -0
  250. package/dist/core/rule-declaration.d.ts +32 -0
  251. package/dist/core/rule-declaration.d.ts.map +1 -0
  252. package/dist/core/rule-declaration.js +88 -0
  253. package/dist/core/rule-declaration.js.map +1 -0
  254. package/dist/core/terminal-builder.d.ts +629 -0
  255. package/dist/core/terminal-builder.d.ts.map +1 -0
  256. package/dist/core/terminal-builder.js +807 -0
  257. package/dist/core/terminal-builder.js.map +1 -0
  258. package/dist/core/terminal-execution.d.ts +39 -0
  259. package/dist/core/terminal-execution.d.ts.map +1 -0
  260. package/dist/core/terminal-execution.js +103 -0
  261. package/dist/core/terminal-execution.js.map +1 -0
  262. package/dist/core/ts-comment-mask.d.ts +2 -0
  263. package/dist/core/ts-comment-mask.d.ts.map +1 -0
  264. package/dist/core/ts-comment-mask.js +102 -0
  265. package/dist/core/ts-comment-mask.js.map +1 -0
  266. package/dist/core/vacuity-diagnosis.d.ts +103 -0
  267. package/dist/core/vacuity-diagnosis.d.ts.map +1 -0
  268. package/dist/core/vacuity-diagnosis.js +415 -0
  269. package/dist/core/vacuity-diagnosis.js.map +1 -0
  270. package/dist/core/violation.d.ts +16 -11
  271. package/dist/core/violation.d.ts.map +1 -1
  272. package/dist/core/violation.js +33 -13
  273. package/dist/core/violation.js.map +1 -1
  274. package/dist/graphql/graphql-rule-builder.d.ts +95 -0
  275. package/dist/graphql/graphql-rule-builder.d.ts.map +1 -0
  276. package/dist/graphql/graphql-rule-builder.js +127 -0
  277. package/dist/graphql/graphql-rule-builder.js.map +1 -0
  278. package/dist/graphql/index.d.ts +1 -0
  279. package/dist/graphql/index.d.ts.map +1 -1
  280. package/dist/graphql/index.js +2 -1
  281. package/dist/graphql/index.js.map +1 -1
  282. package/dist/graphql/resolver-rule-builder.d.ts +44 -23
  283. package/dist/graphql/resolver-rule-builder.d.ts.map +1 -1
  284. package/dist/graphql/resolver-rule-builder.js +80 -72
  285. package/dist/graphql/resolver-rule-builder.js.map +1 -1
  286. package/dist/graphql/schema-loader.d.ts +21 -0
  287. package/dist/graphql/schema-loader.d.ts.map +1 -1
  288. package/dist/graphql/schema-loader.js +82 -12
  289. package/dist/graphql/schema-loader.js.map +1 -1
  290. package/dist/graphql/schema-predicates.d.ts.map +1 -1
  291. package/dist/graphql/schema-predicates.js +17 -12
  292. package/dist/graphql/schema-predicates.js.map +1 -1
  293. package/dist/graphql/schema-rule-builder.d.ts +15 -24
  294. package/dist/graphql/schema-rule-builder.d.ts.map +1 -1
  295. package/dist/graphql/schema-rule-builder.js +39 -77
  296. package/dist/graphql/schema-rule-builder.js.map +1 -1
  297. package/dist/helpers/baseline-diagnostics.d.ts +45 -0
  298. package/dist/helpers/baseline-diagnostics.d.ts.map +1 -0
  299. package/dist/helpers/baseline-diagnostics.js +168 -0
  300. package/dist/helpers/baseline-diagnostics.js.map +1 -0
  301. package/dist/helpers/baseline-generator.d.ts +28 -0
  302. package/dist/helpers/baseline-generator.d.ts.map +1 -0
  303. package/dist/helpers/baseline-generator.js +27 -0
  304. package/dist/helpers/baseline-generator.js.map +1 -0
  305. package/dist/helpers/baseline.d.ts +336 -0
  306. package/dist/helpers/baseline.d.ts.map +1 -0
  307. package/dist/helpers/baseline.js +664 -0
  308. package/dist/helpers/baseline.js.map +1 -0
  309. package/dist/helpers/body-traversal.d.ts +27 -2
  310. package/dist/helpers/body-traversal.d.ts.map +1 -1
  311. package/dist/helpers/body-traversal.js +135 -16
  312. package/dist/helpers/body-traversal.js.map +1 -1
  313. package/dist/helpers/callback-extractor.d.ts.map +1 -1
  314. package/dist/helpers/callback-extractor.js +53 -84
  315. package/dist/helpers/callback-extractor.js.map +1 -1
  316. package/dist/helpers/complexity.d.ts +13 -5
  317. package/dist/helpers/complexity.d.ts.map +1 -1
  318. package/dist/helpers/complexity.js +15 -6
  319. package/dist/helpers/complexity.js.map +1 -1
  320. package/dist/helpers/diff-aware.d.ts +16 -0
  321. package/dist/helpers/diff-aware.d.ts.map +1 -0
  322. package/dist/helpers/diff-aware.js +16 -0
  323. package/dist/helpers/diff-aware.js.map +1 -0
  324. package/dist/helpers/matchers.d.ts +68 -7
  325. package/dist/helpers/matchers.d.ts.map +1 -1
  326. package/dist/helpers/matchers.js +164 -86
  327. package/dist/helpers/matchers.js.map +1 -1
  328. package/dist/helpers/slice-graph.d.ts +100 -8
  329. package/dist/helpers/slice-graph.d.ts.map +1 -1
  330. package/dist/helpers/slice-graph.js +126 -34
  331. package/dist/helpers/slice-graph.js.map +1 -1
  332. package/dist/helpers/tarjan.d.ts +0 -14
  333. package/dist/helpers/tarjan.d.ts.map +1 -1
  334. package/dist/helpers/tarjan.js +29 -16
  335. package/dist/helpers/tarjan.js.map +1 -1
  336. package/dist/index.d.ts +55 -19
  337. package/dist/index.d.ts.map +1 -1
  338. package/dist/index.js +67 -15
  339. package/dist/index.js.map +1 -1
  340. package/dist/models/arch-call.d.ts +3 -2
  341. package/dist/models/arch-call.d.ts.map +1 -1
  342. package/dist/models/arch-call.js.map +1 -1
  343. package/dist/models/arch-function.d.ts +54 -8
  344. package/dist/models/arch-function.d.ts.map +1 -1
  345. package/dist/models/arch-function.js +145 -8
  346. package/dist/models/arch-function.js.map +1 -1
  347. package/dist/models/arch-jsx-element.d.ts.map +1 -1
  348. package/dist/models/arch-jsx-element.js +18 -19
  349. package/dist/models/arch-jsx-element.js.map +1 -1
  350. package/dist/models/slice.d.ts +33 -9
  351. package/dist/models/slice.d.ts.map +1 -1
  352. package/dist/models/slice.js +129 -19
  353. package/dist/models/slice.js.map +1 -1
  354. package/dist/predicates/call.d.ts.map +1 -1
  355. package/dist/predicates/call.js +5 -0
  356. package/dist/predicates/call.js.map +1 -1
  357. package/dist/predicates/identity.d.ts +21 -0
  358. package/dist/predicates/identity.d.ts.map +1 -1
  359. package/dist/predicates/identity.js +75 -3
  360. package/dist/predicates/identity.js.map +1 -1
  361. package/dist/predicates/index.d.ts +1 -1
  362. package/dist/predicates/index.d.ts.map +1 -1
  363. package/dist/predicates/index.js +1 -1
  364. package/dist/predicates/index.js.map +1 -1
  365. package/dist/predicates/jsx.d.ts.map +1 -1
  366. package/dist/predicates/jsx.js +2 -1
  367. package/dist/predicates/jsx.js.map +1 -1
  368. package/dist/predicates/metrics.d.ts +4 -2
  369. package/dist/predicates/metrics.d.ts.map +1 -1
  370. package/dist/predicates/metrics.js +4 -2
  371. package/dist/predicates/metrics.js.map +1 -1
  372. package/dist/predicates/module.d.ts +4 -12
  373. package/dist/predicates/module.d.ts.map +1 -1
  374. package/dist/predicates/module.js +57 -37
  375. package/dist/predicates/module.js.map +1 -1
  376. package/dist/presets/agent-guardrails.d.ts +49 -16
  377. package/dist/presets/agent-guardrails.d.ts.map +1 -1
  378. package/dist/presets/agent-guardrails.js +308 -19
  379. package/dist/presets/agent-guardrails.js.map +1 -1
  380. package/dist/presets/boundaries.d.ts +46 -3
  381. package/dist/presets/boundaries.d.ts.map +1 -1
  382. package/dist/presets/boundaries.js +132 -23
  383. package/dist/presets/boundaries.js.map +1 -1
  384. package/dist/presets/data-layer.d.ts +23 -3
  385. package/dist/presets/data-layer.d.ts.map +1 -1
  386. package/dist/presets/data-layer.js +58 -16
  387. package/dist/presets/data-layer.js.map +1 -1
  388. package/dist/presets/index.d.ts +10 -5
  389. package/dist/presets/index.d.ts.map +1 -1
  390. package/dist/presets/index.js +19 -3
  391. package/dist/presets/index.js.map +1 -1
  392. package/dist/presets/layered.d.ts +47 -3
  393. package/dist/presets/layered.d.ts.map +1 -1
  394. package/dist/presets/layered.js +66 -29
  395. package/dist/presets/layered.js.map +1 -1
  396. package/dist/presets/recommended.d.ts +81 -10
  397. package/dist/presets/recommended.d.ts.map +1 -1
  398. package/dist/presets/recommended.js +25 -23
  399. package/dist/presets/recommended.js.map +1 -1
  400. package/dist/presets/shared.d.ts +267 -2
  401. package/dist/presets/shared.d.ts.map +1 -1
  402. package/dist/presets/shared.js +367 -4
  403. package/dist/presets/shared.js.map +1 -1
  404. package/dist/rules/code-quality.d.ts.map +1 -1
  405. package/dist/rules/code-quality.js +15 -6
  406. package/dist/rules/code-quality.js.map +1 -1
  407. package/dist/rules/hygiene.d.ts +1 -1
  408. package/dist/rules/hygiene.js +1 -1
  409. package/dist/rules/metrics-function.d.ts +2 -1
  410. package/dist/rules/metrics-function.d.ts.map +1 -1
  411. package/dist/rules/metrics-function.js +63 -39
  412. package/dist/rules/metrics-function.js.map +1 -1
  413. package/dist/rules/metrics.d.ts +2 -1
  414. package/dist/rules/metrics.d.ts.map +1 -1
  415. package/dist/rules/metrics.js +80 -47
  416. package/dist/rules/metrics.js.map +1 -1
  417. package/dist/smells/clusters.d.ts +78 -0
  418. package/dist/smells/clusters.d.ts.map +1 -0
  419. package/dist/smells/clusters.js +124 -0
  420. package/dist/smells/clusters.js.map +1 -0
  421. package/dist/smells/duplicate-bodies.d.ts +92 -5
  422. package/dist/smells/duplicate-bodies.d.ts.map +1 -1
  423. package/dist/smells/duplicate-bodies.js +203 -73
  424. package/dist/smells/duplicate-bodies.js.map +1 -1
  425. package/dist/smells/duplicate-report.d.ts +90 -0
  426. package/dist/smells/duplicate-report.d.ts.map +1 -0
  427. package/dist/smells/duplicate-report.js +344 -0
  428. package/dist/smells/duplicate-report.js.map +1 -0
  429. package/dist/smells/fingerprint.d.ts +20 -1
  430. package/dist/smells/fingerprint.d.ts.map +1 -1
  431. package/dist/smells/fingerprint.js +27 -3
  432. package/dist/smells/fingerprint.js.map +1 -1
  433. package/dist/smells/inconsistent-siblings.d.ts +128 -7
  434. package/dist/smells/inconsistent-siblings.d.ts.map +1 -1
  435. package/dist/smells/inconsistent-siblings.js +288 -86
  436. package/dist/smells/inconsistent-siblings.js.map +1 -1
  437. package/dist/smells/sibling-files.d.ts +47 -0
  438. package/dist/smells/sibling-files.d.ts.map +1 -0
  439. package/dist/smells/sibling-files.js +104 -0
  440. package/dist/smells/sibling-files.js.map +1 -0
  441. package/dist/smells/similar-pairs.d.ts +28 -0
  442. package/dist/smells/similar-pairs.d.ts.map +1 -0
  443. package/dist/smells/similar-pairs.js +134 -0
  444. package/dist/smells/similar-pairs.js.map +1 -0
  445. package/dist/smells/smell-builder.d.ts +57 -12
  446. package/dist/smells/smell-builder.d.ts.map +1 -1
  447. package/dist/smells/smell-builder.js +112 -14
  448. package/dist/smells/smell-builder.js.map +1 -1
  449. package/dist/smells/variation.d.ts +47 -0
  450. package/dist/smells/variation.d.ts.map +1 -0
  451. package/dist/smells/variation.js +90 -0
  452. package/dist/smells/variation.js.map +1 -0
  453. package/dist/tsconfig/index.d.ts +19 -0
  454. package/dist/tsconfig/index.d.ts.map +1 -0
  455. package/dist/tsconfig/index.js +20 -0
  456. package/dist/tsconfig/index.js.map +1 -0
  457. package/dist/tsconfig/tsconfig-builder.d.ts +42 -9
  458. package/dist/tsconfig/tsconfig-builder.d.ts.map +1 -1
  459. package/dist/tsconfig/tsconfig-builder.js +65 -16
  460. package/dist/tsconfig/tsconfig-builder.js.map +1 -1
  461. package/package.json +4 -2
  462. package/dist/core/module-dependencies.d.ts +0 -28
  463. package/dist/core/module-dependencies.d.ts.map +0 -1
  464. package/dist/core/module-dependencies.js +0 -35
  465. package/dist/core/module-dependencies.js.map +0 -1
  466. package/dist/helpers/within.d.ts.map +0 -1
  467. package/dist/helpers/within.js.map +0 -1
@@ -0,0 +1,666 @@
1
+ import fs from 'node:fs';
2
+ import { severityFor, disambiguateIdentities, byCodepoint, violationsEmittedCount, } from '@nielspeter/eess/internal';
3
+ import { ArchRuleError } from '@nielspeter/eess';
4
+ import { formatViolations } from '@nielspeter/eess';
5
+ import { formatViolationsJson } from '@nielspeter/eess';
6
+ import { activeNotice } from '@nielspeter/eess/internal';
7
+ import { formatViolationsGitHub } from '@nielspeter/eess';
8
+ import { parseExclusionComments, isExcludedByComment } from './exclusion-comments.js';
9
+ import { UNSUPPRESSABLE } from '@nielspeter/eess/internal';
10
+ import { recordCommentSuppression } from '@nielspeter/eess/internal';
11
+ import { writeStderr } from '@nielspeter/eess/internal';
12
+ /**
13
+ * Complete each violation's identity, then apply `.excluding()` patterns and
14
+ * inline exclusion comments.
15
+ *
16
+ * Extracted to eliminate terminal-method duplication across builders.
17
+ *
18
+ * **Not** baseline or diff filtering, despite what this said for several
19
+ * releases — those run in `executeCheck` / `executeWarn`, after this returns.
20
+ *
21
+ * ## The invariant, for whoever adds the next filter
22
+ *
23
+ * **Enrichment runs first, so every filter sees a complete violation.** That is
24
+ * the whole reason for the ordering, and it is easy to undo by accident because
25
+ * enrichment looks like output formatting rather than identity. It is not: the
26
+ * comment filter matches on `ruleId`, and when enrichment ran last that filter
27
+ * saw `undefined` for every condition that did not stamp the field itself
28
+ * (bug 0041) — a documented feature that silently did nothing.
29
+ *
30
+ * Enrichment is pure, idempotent, and writes a **disjoint field set** from
31
+ * everything the filters read: it touches `ruleId`, `because`, `suggestion` and
32
+ * `docs`; `.excluding()` matches on `element`/`file`/`message`, and the
33
+ * `bypassFilters` refusal path reads a flag enrichment never writes. So
34
+ * "identity is complete before anything reads it" is a simpler invariant to hold
35
+ * than "each filter must know which fields exist yet". Add a filter that reads
36
+ * one of those four fields and it will work; add a mutation of them below a
37
+ * filter and you have reintroduced 0041.
38
+ */
39
+ export function applyFilters(violations, ctx) {
40
+ // Identity uniqueness, BEFORE the enrichment below and before every filter — see
41
+ // `disambiguateIdentities`. Two reasons for this position specifically:
42
+ //
43
+ // 1. This function's own contract is that identity is complete before anything reads it,
44
+ // and the baseline reads it after. A producer that emits two findings with one identity
45
+ // hands the baseline one entry for two findings, and accepting either accepts both —
46
+ // which is bugs 0028, 0063, 0064 and 0065, one per family that got reviewed.
47
+ // 2. Ahead of the filters rather than after them, so a finding's identity is a property of
48
+ // what the RULE found, not of what a `--changed` or `.excluding()` run happened to keep.
49
+ // Suffixing after filtering would give the same finding different identities in CI and
50
+ // on a laptop, which is the defect `identity` exists to prevent.
51
+ //
52
+ // Enrichment does not touch `element`, `message` or `identity`, so the order between this
53
+ // and the block below is free; it runs first to keep "identity is settled" the outermost
54
+ // statement about this function.
55
+ let result = disambiguateIdentities(violations);
56
+ // Enrich FIRST, because a filter cannot match on a field that is not set yet.
57
+ //
58
+ // [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):
59
+ // this block used to run LAST, after the inline-comment filter below. That
60
+ // filter's first statement is `if (!violation.ruleId) return false`
61
+ // (`exclusion-comments.ts:262`), so an exclusion comment matched only
62
+ // violations whose *producing condition* stamped `ruleId` itself. For every
63
+ // condition that left it to this enrichment — the dependency, exports, slice,
64
+ // reverse-dependency and module-body families, which is most of them — the
65
+ // comment was inert: no suppression, no error, no warning. Measured, with a
66
+ // documented comment and a rule carrying an id, `modules().notImportFrom()`
67
+ // returned the violation unsuppressed, and it carried the matching `ruleId`,
68
+ // stamped here a few lines too late.
69
+ //
70
+ // Ordering, not lookup, is the fix: giving `isExcludedByComment` a second
71
+ // source for the id would leave two places that decide what a rule is called.
72
+ //
73
+ // Safe ahead of `.excluding()` as well, which matches on `element`/`file`/
74
+ // `message` — none of which this touches. It costs a map over violations that
75
+ // may later be filtered out; correctness beats that.
76
+ const meta = ctx.metadata;
77
+ if (ctx.reason || meta?.id || meta?.because || meta?.suggestion || meta?.docs) {
78
+ result = result.map((v) => ({
79
+ ...v,
80
+ ruleId: v.ruleId ?? meta?.id,
81
+ because: v.because ?? ctx.reason ?? meta?.because,
82
+ // A `bypassFilters` finding reports that the rule enforces NOTHING, so the
83
+ // author's `suggestion` cannot be its remedy: that text describes how to fix
84
+ // a real violation of the rule, and the formatter renders `suggestion` under
85
+ // `Fix:` — the field an agent obeys. Pairing a configuration message with an
86
+ // unrelated `Fix:` is a false remedy by juxtaposition (bug 0021), and it is
87
+ // ADR-009 rule 2: a failure may not assert a cause it cannot verify.
88
+ //
89
+ // `SliceRuleBuilder.metaViolation` argued exactly this in a comment and
90
+ // omitted both fields — and was overridden here, one layer up, so the
91
+ // omission had no effect in any shipped version. Measured: a finding reading
92
+ // "resolved no slices" printed "Split the cycle by extracting a shared
93
+ // module." as its Fix:.
94
+ //
95
+ // This guard reaches only producers that LEAVE the fields unset. A producer
96
+ // that assigns `context.suggestion` itself defeats it — bug 0042, where
97
+ // `cross-layer.ts` did exactly that and shipped the author's remedy on an
98
+ // empty-layer finding. Such a producer owns the discipline itself.
99
+ //
100
+ // `ruleId` and `because` stay. Neither asserts a remedy: the id says WHICH
101
+ // rule enforces nothing, which is the first thing the reader needs, and
102
+ // `because` states why the rule exists, which is context rather than a
103
+ // claim about this finding's cause. A producer that wants a remedy sets its
104
+ // own — `metaViolation` sets `docs: GLOB_DOCS`, which is about the fault.
105
+ suggestion: v.bypassFilters ? v.suggestion : (v.suggestion ?? meta?.suggestion),
106
+ docs: v.bypassFilters ? v.docs : (v.docs ?? meta?.docs),
107
+ }));
108
+ }
109
+ // Apply .excluding() chain exclusions
110
+ const exclusions = ctx.exclusions ?? [];
111
+ if (exclusions.length > 0) {
112
+ const matchedPatterns = new Set();
113
+ /** Patterns that matched a meta-finding, which cannot be excluded. */
114
+ const refusedPatterns = new Set();
115
+ // Per exclusion-pattern index, the DISTINCT `element` values it matched among
116
+ // violations whose `identity` starts with `cycle-edge::` (plan 0104). A plain
117
+ // string-prefix read of a field every violation already carries — no cycle-specific
118
+ // import here, so this stays family-agnostic in spirit even though today only one
119
+ // family sets that prefix. One pattern matching more than one distinct cycle edge is
120
+ // the exact loophole a whole-component `.excluding()` waiver reopens: it silently
121
+ // absorbs any FUTURE edge the pattern also happens to match, which is the same
122
+ // fail-open shape plan 0104's `element`-per-edge change exists to close.
123
+ const matchedCycleEdges = new Map();
124
+ result = result.filter((v) => {
125
+ // Config-level meta-findings (empty selector / empty discovery) are never
126
+ // excludable: they report that the rule checks NOTHING, so silencing one
127
+ // silences the guard itself. Baseline and diff-aware already honor this
128
+ // flag; `.excluding()` must too, or a rule that enforces nothing can be
129
+ // made green — the exact false-green ADR-008 exists to prevent. This
130
+ // matters more now that meta-messages quote the user's own globs/paths,
131
+ // which an unrelated path exclusion can incidentally match.
132
+ if (v.bypassFilters) {
133
+ // Record a pattern that WOULD have matched, so the "unused exclusion" warning
134
+ // below doesn't tell the caller their exclusion is stale after a rename. It
135
+ // isn't stale — it is refused, which is a different instruction.
136
+ const wouldMatch = exclusions.findIndex((pattern) => typeof pattern === 'string'
137
+ ? [v.element, v.file, v.message].includes(pattern)
138
+ : [v.element, v.file, v.message].some((target) => pattern.test(target)));
139
+ if (wouldMatch >= 0)
140
+ refusedPatterns.add(wouldMatch);
141
+ return true;
142
+ }
143
+ // Match against element, file, or message — so that custom conditions
144
+ // using createViolation() can be excluded by file path or message content,
145
+ // not just by element name (which may be a generic AST node kind).
146
+ const targets = [v.element, v.file, v.message];
147
+ const matchIndex = exclusions.findIndex((pattern) => typeof pattern === 'string'
148
+ ? targets.some((t) => t === pattern)
149
+ : targets.some((t) => pattern.test(t)));
150
+ if (matchIndex >= 0) {
151
+ matchedPatterns.add(matchIndex);
152
+ if (v.identity?.startsWith('cycle-edge::') === true) {
153
+ const set = matchedCycleEdges.get(matchIndex) ?? new Set();
154
+ set.add(v.element);
155
+ matchedCycleEdges.set(matchIndex, set);
156
+ }
157
+ return false;
158
+ }
159
+ return true;
160
+ });
161
+ const ruleId = ctx.metadata?.id ?? 'unnamed';
162
+ const silentIndices = ctx.silentIndices ?? new Set();
163
+ exclusions.forEach((pattern, index) => {
164
+ if (refusedPatterns.has(index)) {
165
+ writeStderr(`[eess] Exclusion '${String(pattern)}' in rule '${ruleId}' matched a ` +
166
+ `configuration finding, which cannot be excluded — that finding reports the ` +
167
+ `rule enforces nothing. Fix the fault it names instead.`);
168
+ }
169
+ else if (!matchedPatterns.has(index) && !silentIndices.has(index)) {
170
+ writeStderr(`[eess] Unused exclusion '${String(pattern)}' in rule '${ruleId}'. ` +
171
+ `It matched zero violations — it may be stale after a rename.`);
172
+ }
173
+ else {
174
+ // A pattern matching more than one distinct cycle edge silently absorbs any
175
+ // future edge it also happens to match — the loose-regex loophole plan 0104's
176
+ // per-edge `element` reopens for anyone matching on `file`/`message` instead of an
177
+ // exact `element` string (an exact string can only equal one edge, by
178
+ // construction). Advisory (`writeStderr`), not a `DiagnosticFinding` or a
179
+ // second unsuppressable gate. Review flagged the asymmetry directly rather
180
+ // than let it pass as settled: "Unused exclusion" above it warns about a
181
+ // pattern that silences NOTHING (fail-closed — the finding still fires, CI is
182
+ // still red), while this warns about a pattern that silences MULTIPLE real
183
+ // findings and every future one on the same edges (fail-open — a green
184
+ // `check()`, one stderr line). ADR-008 states the primary consumer does not
185
+ // read warnings. Kept advisory here, matching plan 0104's own review
186
+ // resolution — but the asymmetry is real and worth re-litigating if this
187
+ // loophole is measured firing in practice, not settled by the precedent
188
+ // alone.
189
+ const edges = matchedCycleEdges.get(index);
190
+ if (edges !== undefined && edges.size > 1) {
191
+ // byCodepoint, not the default comparator — this is user-facing (the
192
+ // suggested `.excluding(...)` line) and the same determinism class
193
+ // bug 0010 named for every other sorted, filesystem-adjacent output
194
+ // in this family (review: architect).
195
+ const sorted = [...edges].sort(byCodepoint);
196
+ writeStderr(`[eess] Exclusion '${String(pattern)}' in rule '${ruleId}' matched ${String(sorted.length)} ` +
197
+ `distinct cycle edges (${sorted.join(', ')}). A pattern matching more than one edge silently ` +
198
+ `absorbs any future cycle among the edges it matches — name each edge separately: ` +
199
+ `.excluding(${sorted.map((e) => `'${e}'`).join(', ')}).`);
200
+ }
201
+ }
202
+ });
203
+ }
204
+ // Scan source files for inline exclusion comments.
205
+ //
206
+ // **Not gated on `ctx.metadata?.id` (bug 0255).** A comment matches by rule
207
+ // id, so a chain without `.rule({ id })` can never honour one — and while the
208
+ // scan was gated on the id, the file was not even parsed, so nothing could
209
+ // say so. This is the second of the two copies: the kernel's got the same
210
+ // change, and review caught that landing it in one and claiming "eess now
211
+ // prints…" was a false green for the dialect adopters actually install
212
+ // (plan 0188 tracks the duplication itself; this is not the commit that
213
+ // fixes it, but it is not the commit that widens it either).
214
+ if (result.length > 0) {
215
+ const undocumented = [];
216
+ const filePaths = new Set(result.map((v) => v.file));
217
+ const allComments = [...filePaths].flatMap((filePath) => {
218
+ // Two EXPECTED shapes never reach the read, so that the catch below is
219
+ // left holding only the surprising one.
220
+ //
221
+ // `file: ''` is a configuration finding — there is no source and no
222
+ // comment to find. This used to be expressed by letting `readFileSync('')`
223
+ // throw into the catch: control flow by exception, on a NORMAL path.
224
+ //
225
+ // A path not on disk is an in-memory ts-morph project or a test fixture's
226
+ // synthetic file. Same story — no file, so no exclusion comments. Measured:
227
+ // without this clause a warning fires once per file for every in-memory
228
+ // project (it broke two cases in `excluding-matching.test.ts`).
229
+ //
230
+ // Together these are what made the catch unreportable — it could not tell
231
+ // either expected case from a file we genuinely failed to read, so it had
232
+ // to stay silent about all three.
233
+ if (filePath === '' || !fs.existsSync(filePath))
234
+ return [];
235
+ try {
236
+ const sourceText = fs.readFileSync(filePath, 'utf-8');
237
+ const parseResult = parseExclusionComments(sourceText, filePath);
238
+ for (const warning of parseResult.warnings) {
239
+ // An undocumented exclusion is well-formed and APPLIES, so a stderr
240
+ // line is the wrong weight: it silences a real finding and the build
241
+ // goes green (bug 0039). It becomes a configuration finding below.
242
+ // The malformed shapes are different — two of the three decline to
243
+ // create the exclusion at all, so the original violation still fires
244
+ // and the build is already red. A line on stderr is right for those.
245
+ if (warning.kind === 'undocumented' && ctx.metadata?.id !== undefined) {
246
+ // The promotion below is keyed on the rule id; without one there is
247
+ // nothing to key it on, and the directive is inert anyway — which
248
+ // the inert report states, with a remedy. So it falls to stderr.
249
+ undocumented.push(warning);
250
+ continue;
251
+ }
252
+ writeStderr(`[eess] ${warning.message}`);
253
+ }
254
+ return parseResult.exclusions;
255
+ }
256
+ catch (err) {
257
+ // On disk, and still unreadable — a permission or I/O failure, not a
258
+ // shape eess expects. That is NOT silent: every `// eess-exclude` in
259
+ // this file stops applying, so a violation the author believes is
260
+ // waived fires again with no stated cause. Naming the file and the
261
+ // reason is the difference between "your waiver broke" and "eess
262
+ // started reporting something new" (ADR-009 rule 2).
263
+ writeStderr(`[eess] could not read ${filePath} to apply its exclusion comments ` +
264
+ `(${err instanceof Error ? err.message : String(err)}) — any ` +
265
+ `\`// eess-exclude\` directives in it were NOT applied.\n`);
266
+ return [];
267
+ }
268
+ });
269
+ const ruleIdForComments = ctx.metadata?.id;
270
+ if (allComments.length > 0 && ruleIdForComments !== undefined) {
271
+ // `v.bypassFilters` explicitly, not by accident. These findings are
272
+ // immune today only because they carry `file: ''`, which the guard above
273
+ // returns no exclusions for, so `comment.file === ''` can never hold.
274
+ // The moment one carries a real path this clause becomes the only
275
+ // protection — and bug 0026 is that moment: configuration findings now
276
+ // carry the rule file they came from, so this file IS read and its
277
+ // `// eess-exclude` comments ARE parsed. Without the first clause
278
+ // a comment in a rule file would silence the finding that says the rule
279
+ // enforces nothing. Pinned by tests/helpers/exclusion-comments.ts.
280
+ // Which comments actually did something, so the ones that did nothing can
281
+ // be named. Reuses `isExcludedByComment` one comment at a time rather than
282
+ // re-deriving the match, so it cannot drift from the real rule.
283
+ const spent = new Set();
284
+ result = result.filter((v) => {
285
+ if (v.bypassFilters === true)
286
+ return true;
287
+ if (!isExcludedByComment(v, allComments))
288
+ return true;
289
+ // Disclose it. Silently dropping is what made this the only filter in
290
+ // the pipeline a reader could not see — see `comment-suppression.ts`.
291
+ recordCommentSuppression(v.ruleId ?? ctx.metadata?.id ?? '(unnamed rule)', v.file);
292
+ for (const c of allComments)
293
+ if (isExcludedByComment(v, [c]))
294
+ spent.add(c);
295
+ return false;
296
+ });
297
+ // A directive naming THIS rule that suppressed nothing — the same shape
298
+ // and weight as the `.excluding()` "Unused exclusion" line above.
299
+ for (const c of allComments) {
300
+ if (c.ruleId !== ruleIdForComments || spent.has(c))
301
+ continue;
302
+ writeStderr(`[eess] Exclusion comment for '${ruleIdForComments}' at ${c.file}:${String(c.line)} ` +
303
+ `suppressed nothing. It may be stale, or out of reach: a single-line ` +
304
+ `directive covers only the NEXT line. eess-exclude-start/-end covers a ` +
305
+ `region instead.`);
306
+ }
307
+ }
308
+ else if (allComments.length > 0) {
309
+ // No rule id, so nothing here can match. It states the fact and names no
310
+ // id: a directive in this file may belong to another, working rule, and
311
+ // from inside one rule's run there is no way to tell (review reproduced
312
+ // the harm of guessing — it produced advice to collide two rules on one
313
+ // id). One line per file, not per comment.
314
+ const byFile = new Map();
315
+ for (const c of allComments) {
316
+ const lines = byFile.get(c.file);
317
+ if (lines)
318
+ lines.push(c.line);
319
+ else
320
+ byFile.set(c.file, [c.line]);
321
+ }
322
+ // Name the rule, preferring its own sentence over its reason (bug 0258).
323
+ //
324
+ // The first version used `.because()` alone, on the premise that no rule
325
+ // description was reachable here. Review measured that false: every
326
+ // builder implements `describeRule()`, `filterContext()` is a method on
327
+ // the class that has it, and the kernel already names an id-less rule this
328
+ // way for its assertion-less finding. `.because()` is optional prose — the
329
+ // chains this diagnostic targets often have neither an id nor a reason —
330
+ // so leaning on it alone was a weaker floor for the same cost.
331
+ //
332
+ // `'unnamed'` is `TerminalBuilder`'s own fallback for a rule with no id;
333
+ // naming a rule "unnamed" tells the reader nothing, so it counts as absent
334
+ // and the reason takes over. Whitespace is collapsed either way: both are
335
+ // prose that may wrap, and this report is one line per file.
336
+ // Collapse to one line, and escape the delimiter. A `.because()` reason is
337
+ // arbitrary prose, so one containing a quoted term rendered as
338
+ // `("ignore the "legacy" path")` — three quotes before the paren, and no
339
+ // way for a reader to see where the label ends. Escaping keeps the
340
+ // author's words exact where replacing them would not.
341
+ const oneLine = (t) => t.replace(/\s+/g, ' ').trim().replace(/"/g, '\\"');
342
+ const described = ctx.describe?.();
343
+ const label = described !== undefined && described !== 'unnamed' && described !== ''
344
+ ? oneLine(described)
345
+ : ctx.reason === undefined
346
+ ? undefined
347
+ : oneLine(ctx.reason);
348
+ const named = label === undefined || label === '' ? '' : ` ("${label}")`;
349
+ for (const [file, lines] of byFile) {
350
+ const where = lines.length === 1 ? `line ${String(lines[0])}` : `lines ${lines.join(', ')}`;
351
+ writeStderr(`[eess] This rule${named} declares no id, so no exclusion comment can apply to it — ` +
352
+ `a comment matches a violation by rule id. ${file} has a directive at ${where}. ` +
353
+ `If one was meant for this rule, give the rule an id with .rule({ id: '<your-id>' }); ` +
354
+ `directives naming other rules are not this rule's to honour.`);
355
+ }
356
+ }
357
+ // The exemption stands; what fails is the missing justification. That is
358
+ // deliberate and it is what makes the remedy remediable: add a reason and
359
+ // this finding clears while the exclusion keeps working. Refusing to apply
360
+ // the exclusion instead would make the remedy "add a reason" produce a
361
+ // DIFFERENT failure — the violation itself — which is a remedy that does
362
+ // not remediate (ADR-009 rule 2).
363
+ //
364
+ // Unsuppressable, because a suppression mechanism that can suppress the
365
+ // complaint about itself is not a mechanism.
366
+ //
367
+ // `undocumented` is only populated when the rule HAS an id (the push above
368
+ // is guarded on it), so this loop is unreachable without one — the local
369
+ // re-binding is what tells the compiler that, after bug 0255 removed the
370
+ // outer `ctx.metadata?.id` gate.
371
+ for (const warning of undocumented) {
372
+ if (ruleIdForComments === undefined)
373
+ continue;
374
+ result.push({
375
+ rule: ruleIdForComments,
376
+ ruleId: ruleIdForComments,
377
+ element: `${ruleIdForComments}@${warning.file}:${String(warning.line)}`,
378
+ file: warning.file,
379
+ line: warning.line,
380
+ message: `This exclusion states no reason, so nothing records why the rule is ` +
381
+ `waived here — and it is silently suppressing a real finding.`,
382
+ suggestion: `Add a reason: // eess-exclude ${ruleIdForComments}: <why>. ` +
383
+ `A reason is prose and nothing verifies it, so this raises the cost of a ` +
384
+ `suppression rather than preventing one — the audience is the reviewer ` +
385
+ `reading the diff. If the exemption is not justifiable, delete it and fix ` +
386
+ `the finding instead. ${UNSUPPRESSABLE}`,
387
+ // No explicit `severity`. `bypassFilters` already forces `error` through
388
+ // `severityFor`, which every consumer path runs — `violations()`
389
+ // (`terminal-builder.ts:229`), `executeCheck` and `executeWarn`. Setting
390
+ // it here was dead: a sabotage row that flipped it to `warn` left the
391
+ // suite green, because the flag overrode it downstream. A line that
392
+ // reads load-bearing and is not is worse than no line.
393
+ bypassFilters: true,
394
+ });
395
+ }
396
+ }
397
+ return result;
398
+ }
399
+ /**
400
+ * Stamp any un-stamped violation with a default severity (per-violation wins),
401
+ * except a configuration meta-finding, which is always `error`.
402
+ *
403
+ * This is the site that mattered most and read as the safest: `?? severity`
404
+ * looks conservative, but five of the six `bypassFilters` producers set no
405
+ * severity at all, so on the `executeWarn` path every one of them resolved to
406
+ * `warn` — a finding saying "this rule enforces nothing", reported as advice.
407
+ */
408
+ function stampSeverity(violations, severity) {
409
+ return violations.map((v) => ({ ...v, severity: severityFor(v, v.severity ?? severity) }));
410
+ }
411
+ /**
412
+ * Write a severity-aware, single-document report for the given format.
413
+ *
414
+ * Shared by the CLI runner and the throwing `check` terminal so the three
415
+ * format branches live in one place:
416
+ * - `json` ALWAYS emits one valid document (even with zero violations) so
417
+ * consumers/agents can parse a clean run.
418
+ * - `github` partitions by severity so warnings render as `::warning`, not
419
+ * `::error`.
420
+ * - terminal (default) writes the rich format to stderr.
421
+ *
422
+ * Terminal/github emit nothing when there are no violations.
423
+ */
424
+ export function writeReport(violations, format, reason,
425
+ /** Allowlist rules that tested no edges, for the JSON document (bug 0015). */
426
+ untested = []) {
427
+ // Counted at the top, before the json early-return, so every path that writes
428
+ // is counted once. See `violationsWritten()` for why this is an emission count
429
+ // and not a suppression count.
430
+ violationsWrittenHere += violations.length;
431
+ if (format === 'json') {
432
+ process.stdout.write(formatViolationsJson(violations, reason, untested) + '\n');
433
+ return;
434
+ }
435
+ if (violations.length === 0)
436
+ return;
437
+ if (format === 'github') {
438
+ const errors = violations.filter((v) => (v.severity ?? 'error') === 'error');
439
+ const warnings = violations.filter((v) => v.severity === 'warn');
440
+ const parts = [];
441
+ if (errors.length > 0)
442
+ parts.push(formatViolationsGitHub(errors, 'error'));
443
+ if (warnings.length > 0)
444
+ parts.push(formatViolationsGitHub(warnings, 'warning'));
445
+ process.stdout.write(parts.join('\n') + '\n');
446
+ }
447
+ else {
448
+ writeStderr(formatViolations(violations, reason));
449
+ }
450
+ }
451
+ /**
452
+ * Whether a caller aggregates and reports every finding itself.
453
+ *
454
+ * The CLI does: it collects across all rule files and calls `writeReport` once, so a
455
+ * per-rule terminal that also writes produces the finding twice — measured on the real
456
+ * CLI as two `Architecture Violation [1 of 1]` blocks with identical content while
457
+ * `--format json` said 1 (bug 0029).
458
+ *
459
+ * **Only the findings the aggregator can actually recover may be suppressed**, which
460
+ * means only the ones that travel on the thrown `ArchRuleError`. A `.warn()` whose
461
+ * violations are ordinary does not throw, and the CLI never calls `.violations()` on a
462
+ * self-executing rule file — so those exist nowhere else and must always be written or
463
+ * they are lost outright.
464
+ *
465
+ * Default false, because the in-test path has no aggregator: nothing catches the error
466
+ * and re-renders it, so `ArchRuleError.message` — a one-line summary by design — would
467
+ * be all a reader gets, losing the finding's message, its remedy and the sentence
468
+ * saying it cannot be suppressed.
469
+ */
470
+ let callerAggregatesReports = false;
471
+ let violationsWrittenHere = 0;
472
+ /**
473
+ * Run `fn` with report aggregation on, restoring the previous value afterwards.
474
+ *
475
+ * **A dynamic extent, not a latch.** This replaced an exported
476
+ * `setCallerAggregatesReports(on)` that the CLI called once and nothing ever set
477
+ * back. Aggregation is a property of a RUN, and while `executeCheck` was the only
478
+ * reader the difference was invisible — the CLI wanted suppression for its whole
479
+ * life. It stopped being invisible the moment `deliver()` and `checkAll()` read it
480
+ * too (bug 0203): measured, a preset called directly in a process that had already
481
+ * run `runCheck` once emitted **6 violation blocks before and 0 after**. It still
482
+ * threw, so nothing went falsely green — but the report, the `Why:` and the `Fix:`
483
+ * were gone, with no signal that anything had been swallowed.
484
+ *
485
+ * Not an option on `CheckOptions`: a self-executing rule file writes `.check()`
486
+ * with no arguments and a preset is called by the RULE FILE, not by the CLI, so
487
+ * there is no per-call site the CLI could set. This is a property of who is
488
+ * driving the run.
489
+ *
490
+ * Restores rather than clears, so nesting cannot switch a still-running outer
491
+ * aggregation off.
492
+ */
493
+ export async function withCallerAggregating(fn) {
494
+ const previous = callerAggregatesReports;
495
+ callerAggregatesReports = true;
496
+ try {
497
+ return await fn();
498
+ }
499
+ finally {
500
+ callerAggregatesReports = previous;
501
+ }
502
+ }
503
+ /**
504
+ * How many violations have actually been WRITTEN by either emitter, ever.
505
+ *
506
+ * Read as a delta across one rule file's evaluation, this answers the only
507
+ * question the CLI's "your output was not filtered" notice may assert: *did
508
+ * anything emit while that module was loading?*
509
+ *
510
+ * **This replaced an inverted signal, and the inversion was a measured defect.**
511
+ * The first version counted the writes `executeCheck` SUPPRESSED and concluded
512
+ * "then nothing leaked" from the absence of a suppression. That is a double
513
+ * negative, and a rule file that suppresses one terminal while leaking through
514
+ * another satisfies it while leaking — measured: a `report: 'warn'` preset plus a
515
+ * silenced `.check()` in one file leaked 7 violation blocks and the notice stayed
516
+ * silent. A silence built from a stale signal is worse than the false claim it was
517
+ * introduced to fix, because the run says nothing at all.
518
+ *
519
+ * **Three emitters are counted, not two, and an earlier version of this docblock
520
+ * got the list wrong.** It said `writeReport` was "used by `executeCheck`,
521
+ * `executeWarn` and `check-all.ts`" — `executeWarn` does not call it. It writes
522
+ * through `writeStderr` / `process.stdout.write` directly, with its own
523
+ * json/github/terminal branching, so it moved neither counter and the leak
524
+ * detector could not see it. Measured: a live `.warn()` beside a throwing
525
+ * `.check()` under `--baseline` leaked its advisory findings in silence.
526
+ *
527
+ * The three: this module's `writeReport` (`executeCheck`, `check-all.ts`), this
528
+ * module's `executeWarn` advisory write, and the kernel's `reportViolations`
529
+ * (`finishPreset`). Missing any one reproduces the blind spot on that path — which
530
+ * is exactly what happened, twice.
531
+ *
532
+ * That every emitter must be counted, and that nothing enforces it, is
533
+ * [bug 0205](../../../../work/bugs/0205-four-emitters-restate-the-suppression-rule-and-disagree.md).
534
+ */
535
+ /**
536
+ * Is a caller aggregating reports for this run?
537
+ *
538
+ * Exposed so the two OTHER ts-side emitters can honour the same contract
539
+ * `executeCheck` does: `presets/shared.ts`'s `deliver()` and `core/check-all.ts`.
540
+ * Both used to emit unconditionally, so a preset or a `checkAll()` at module scope
541
+ * printed its findings before the aggregating caller saw them — and the caller then
542
+ * reported the same violations again off the throw. Bug 0203.
543
+ */
544
+ export function callerAggregates() {
545
+ return callerAggregatesReports;
546
+ }
547
+ export function violationsWritten() {
548
+ return violationsWrittenHere + violationsEmittedCount();
549
+ }
550
+ /**
551
+ * Execute the terminal "check" action: apply options, format, throw on violations.
552
+ */
553
+ export function executeCheck(violations, ctx, options) {
554
+ let filtered = applyFilters(violations, ctx);
555
+ if (options?.baseline) {
556
+ filtered = options.baseline.filterNew(filtered);
557
+ }
558
+ if (options?.diff) {
559
+ // Per rule, so no run total exists here — state the configuration once per
560
+ // process instead of printing one line per rule (plan 0071,
561
+ // `core/diff-disclosure.ts`).
562
+ const before = filtered.length;
563
+ filtered = options.diff.filterToChanged(filtered);
564
+ const notice = activeNotice(before - filtered.length, options.diff.size, options.diff.baseBranch);
565
+ if (notice !== undefined)
566
+ writeStderr(notice);
567
+ }
568
+ if (filtered.length > 0) {
569
+ const stamped = stampSeverity(filtered, 'error');
570
+ // Bug 0201. `executeWarn` below has honoured this flag since it shipped;
571
+ // `executeCheck` never did, so a `.check()` at module scope printed its
572
+ // findings before the aggregating caller could filter them — which is the
573
+ // whole of bug 0199 on this path. Measured: the leak goes 4 violations → 0.
574
+ //
575
+ // Safe for every other caller because the flag defaults to `false` and only
576
+ // the CLI sets it: a `.check()` in a test file, where there is no aggregator,
577
+ // still prints exactly as before.
578
+ //
579
+ // The violations are NOT lost when we stay quiet — they ride the throw, which
580
+ // is the same reason `executeWarn` may suppress only its `bypassFilters`
581
+ // entries and must still write the rest.
582
+ if (!callerAggregatesReports)
583
+ writeReport(stamped, options?.format, ctx.reason);
584
+ throw new ArchRuleError(stamped, ctx.reason);
585
+ }
586
+ }
587
+ /**
588
+ * Execute the terminal "warn" action: apply options, format, log to stderr.
589
+ *
590
+ * Advisory for ordinary violations, which are logged exactly as before and
591
+ * never throw. **A `bypassFilters` configuration finding throws**, carrying
592
+ * only those findings.
593
+ *
594
+ * `.warn()` says "this rule's violations are advisory". A finding that the
595
+ * rule enforces nothing is not a violation of the rule — it reports that the
596
+ * rule cannot fire — and there is nothing advisory about that. Leaving the
597
+ * hole open would make `.warn()` the documented escape hatch for exactly the
598
+ * class of finding this release exists to surface, on exactly the
599
+ * gradual-adoption audience the docs point at it.
600
+ *
601
+ * The payload matters as much as the throw: an error carrying 200 warn-level
602
+ * violations plus one meta-finding would make "these findings are true" false
603
+ * for 200 of 201 entries. `.violations()` remains the non-throwing
604
+ * programmatic surface.
605
+ */
606
+ export function executeWarn(violations, ctx, options) {
607
+ let filtered = applyFilters(violations, ctx);
608
+ if (options?.baseline) {
609
+ filtered = options.baseline.filterNew(filtered);
610
+ }
611
+ if (options?.diff) {
612
+ // Per rule, so no run total exists here — state the configuration once per
613
+ // process instead of printing one line per rule (plan 0071,
614
+ // `core/diff-disclosure.ts`).
615
+ const before = filtered.length;
616
+ filtered = options.diff.filterToChanged(filtered);
617
+ const notice = activeNotice(before - filtered.length, options.diff.size, options.diff.baseBranch);
618
+ if (notice !== undefined)
619
+ writeStderr(notice);
620
+ }
621
+ if (filtered.length > 0) {
622
+ const stamped = stampSeverity(filtered, 'warn');
623
+ const configFindings = stamped.filter((v) => v.bypassFilters === true);
624
+ // Write only what the throw will NOT carry.
625
+ //
626
+ // This used to write `stamped` in full and then throw the configuration
627
+ // findings, so those were reported twice: once here and once by whoever
628
+ // caught the error and reported `error.violations`. Measured on the CLI —
629
+ // two `Architecture Violation [1 of 1]` blocks with identical content, while
630
+ // `--format json` said 1 (bug 0029).
631
+ //
632
+ // Splitting rather than skipping the write entirely, because the ordinary
633
+ // warn-level violations are NOT on the error — it deliberately carries only
634
+ // the configuration findings, so that "these findings are true" stays true
635
+ // for every entry. Dropping the write would lose them.
636
+ // The configuration findings ride on the throw, so an aggregator re-reports them
637
+ // and writing here would duplicate. Everything else exists only here.
638
+ const advisory = callerAggregatesReports
639
+ ? stamped.filter((v) => v.bypassFilters !== true)
640
+ : stamped;
641
+ if (advisory.length > 0) {
642
+ // Counted, because this is an EMISSION and an aggregating caller's leak
643
+ // detector reads emissions. This function does not go through `writeReport`
644
+ // — it has its own json/github/terminal branching, deliberately, so that a
645
+ // json run's stdout document stays the caller's alone — and that made it
646
+ // invisible: measured, a live `.warn()` beside a throwing `.check()` under
647
+ // `--baseline` printed its advisory findings unfiltered while
648
+ // `violationsWritten()` reported nothing written, so the "your filters did
649
+ // not reach this" notice stayed silent. Bug 0199's false negative, reopened
650
+ // through the one emitter this module's own docblock claimed was covered.
651
+ violationsWrittenHere += advisory.length;
652
+ if (options?.format === 'json') {
653
+ writeStderr(formatViolationsJson(advisory, ctx.reason));
654
+ }
655
+ else if (options?.format === 'github') {
656
+ process.stdout.write(formatViolationsGitHub(advisory, 'warning') + '\n');
657
+ }
658
+ else {
659
+ writeStderr(formatViolations(advisory, ctx.reason));
660
+ }
661
+ }
662
+ if (configFindings.length > 0)
663
+ throw new ArchRuleError(configFindings, ctx.reason);
664
+ }
665
+ }
666
+ //# sourceMappingURL=execute-rule.js.map