@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,807 @@
1
+ import { collectWithAssertionGuard as runWithGuard } from './terminal-execution.js';
2
+ import { zeroSubjectsAdviceOf, zeroSubjectsViolationOf } from './vacuity-diagnosis.js';
3
+ import { collectResult } from '@nielspeter/eess';
4
+ import { severityFor, subjectOf } from '@nielspeter/eess/internal';
5
+ import { recordExclusions } from '@nielspeter/eess/internal';
6
+ import { executeCheck, executeWarn, applyFilters } from './execute-rule.js';
7
+ import { shallowClone } from '@nielspeter/eess/internal';
8
+ import { ruleDescriptionFrom } from '@nielspeter/eess/internal';
9
+ /**
10
+ * Declaring both emptiness assertions is a contradiction, and 0069's appendix
11
+ * says it "must fail at build time, not silently pick one".
12
+ *
13
+ * Thrown when the chain is built rather than reported as a finding: this is not
14
+ * a property of the codebase under test, it is a rule that cannot be evaluated,
15
+ * and the author is standing right there.
16
+ *
17
+ * `TypeError`, following `combinators.ts`'s "cannot mix Predicate objects and
18
+ * TypeMatcher functions" — the same class of build-time misuse. A bare `Error`
19
+ * was written first and this project's own `quality/typed-errors` rule rejected
20
+ * it, which is the third time in this plan's implementation that the dogfooding
21
+ * caught the new code.
22
+ */
23
+ const CONTRADICTION = '.expectEmpty() and .expectNonEmpty() on the same rule contradict each other — ' +
24
+ 'a selection cannot be required to be both empty and non-empty. Keep the one you mean.';
25
+ /**
26
+ * True when two or more of this batch's RAW violations would share one
27
+ * `subjectOf()` before `disambiguateIdentities()` (`applyFilters()`'s own
28
+ * first step) repairs the collision with a positional `#1`/`#2` suffix —
29
+ * plan 0090's own review found this, reproduced: a rule whose FIXED violation
30
+ * held the bare subject and whose NEW violation lands on the same position a
31
+ * PRIOR run's `#1` occupied gets the identical accepted-list identity the
32
+ * fixed one held — a genuinely new finding silently absorbed as accepted,
33
+ * because the identity `accepted` was built from was never stable, only
34
+ * positionally stable. That is bug 0084's exact swap-blindness, reintroduced
35
+ * through the identity primitive `accepted` is built on rather than through a
36
+ * bare count.
37
+ *
38
+ * Deliberately coarse: this does not try to isolate which violations
39
+ * collided and escalate only those — `violations()` escalates the WHOLE
40
+ * batch once this is true, because a batch whose identities are not reliably
41
+ * unique is a batch `accepted` cannot safely reason about AT ALL, and
42
+ * silently trusting the non-colliding remainder risks the same false
43
+ * confidence in a smaller blast radius. The remedy is on the rule, not the
44
+ * mechanism: narrow the selector or qualify the condition's message so
45
+ * violations do not collide (`ArchViolation.identity`'s own doc comment
46
+ * states this contract), not to make the escalation more surgical.
47
+ *
48
+ * Computed independently of `identityCollisions()`/`resetIdentityCollisions()`
49
+ * (`violation.ts`) — that channel is global, mutable, disclosure-only state
50
+ * for a different consumer (a test asserting a specific producer collided);
51
+ * reading or resetting it from inside `violations()`, a widely-called,
52
+ * side-effect-free accessor, would couple this to a different feature's
53
+ * instrumentation. This is a pure recomputation instead, using the same
54
+ * `rule::subject` grouping key `disambiguateIdentities()` itself groups on.
55
+ */
56
+ function hasIdentityCollision(violations) {
57
+ const seen = new Set();
58
+ for (const v of violations) {
59
+ const key = `${v.rule}::${subjectOf(v)}`;
60
+ if (seen.has(key))
61
+ return true;
62
+ seen.add(key);
63
+ }
64
+ return false;
65
+ }
66
+ /**
67
+ * The declaration half of every rule builder: what the author states.
68
+ *
69
+ * Owns `because`, `rule`, `excluding`, `describeRule`, `asSeverity`, the two
70
+ * emptiness assertions and the filter state — everything a chain accumulates
71
+ * BEFORE anything runs. {@link TerminalBuilder} extends this with the terminals
72
+ * and the execution behind them; a builder subclasses that one and inherits both
73
+ * halves, so the split is in this file rather than in their contracts.
74
+ *
75
+ * `RuleBuilder<T>` extends this too, as of plan 0069. It used not to, and the
76
+ * old docstring said so plainly: "RuleBuilder does NOT extend this because it
77
+ * predates it." The cost was not the duplicated methods, it was that every
78
+ * safety feature added to one root silently did not reach the builders on the
79
+ * other — which is what bug 0013 cost. Anything that must hold for all thirteen
80
+ * builders now has exactly one place to live.
81
+ */
82
+ class RuleDeclaration {
83
+ _reason;
84
+ _metadata;
85
+ _severity;
86
+ /**
87
+ * Plan 0090 — the debt half of a `warn`. `undefined` means an ADVISORY
88
+ * warning: permanent, no ceiling, exactly today's `.asSeverity('warn')`.
89
+ * Set means DEFERRED: a violation whose `subjectOf()` is in this list stays
90
+ * `warn`; anything not in it escalates to `error` in `violations()` — a new
91
+ * finding this list did not accept. See `asSeverity()`'s own doc comment.
92
+ */
93
+ _acceptedWarnings;
94
+ // `protected`, not `private`. `RuleBuilder` declared these `protected` before
95
+ // the single-root refactor and both classes are public exports, so narrowing
96
+ // them is a compile break for an external subclass — the same argument that
97
+ // kept `globs()` concrete rather than abstract.
98
+ _exclusions = [];
99
+ _silentIndices = new Set();
100
+ /**
101
+ * The declared-empty grammar — plan 0097, and [ts-archunit ADR-010](https://github.com/nielspeter/ts-archunit/blob/main/adr/010-the-extension-surface-is-a-contract.md)
102
+ * rule 3(a).
103
+ *
104
+ * These lived on `RuleBuilder<T>`, so the smell family — the one bug 0066 is
105
+ * filed against — could not reach them: that bug listed "is `.expectEmpty()`
106
+ * reachable on a smell builder at all?" under Not measured, and the answer was
107
+ * no. ADR-010 part 3 requires every family's grammar to expose a declaration
108
+ * path, so they belong on the root every family shares.
109
+ *
110
+ * Booleans rather than registry membership, deliberately. `shallowClone` in
111
+ * `copy()` carries an own property for free on every chain step, where a
112
+ * `WeakSet` keyed on the builder would be lost at the first `copy()` — and the
113
+ * threat model differs from the one that forced the cardinality registry,
114
+ * which guards user-CONSTRUCTIBLE condition objects. These sit behind a
115
+ * sanctioned method on a class whose only other audience is ADR-010's
116
+ * contract.
117
+ */
118
+ _requireNonEmpty = false;
119
+ _expectEmpty = false;
120
+ /**
121
+ * Assert that the predicate chain matches at least one subject. If the
122
+ * filtered subject set is empty, the rule FAILS with a config-level
123
+ * meta-finding instead of passing vacuously — the "0 === 0" false-green
124
+ * ADR-008 forbids. Opt-in: legitimately-empty selections (e.g. "no
125
+ * repositories yet") stay green without it. Built on the materialized
126
+ * subject set (plan 0064); the finding bypasses diff/baseline (plan 0067).
127
+ */
128
+ expectNonEmpty() {
129
+ if (this._expectEmpty)
130
+ throw new TypeError(CONTRADICTION);
131
+ const next = this.copy();
132
+ next._requireNonEmpty = true;
133
+ return next;
134
+ }
135
+ /**
136
+ * Assert that this selector matches **nothing**, and fail the day it matches
137
+ * something.
138
+ *
139
+ * Plan 0074 (R3b). Since an empty selection is now a failure by default, a
140
+ * rule whose selection is legitimately empty needs a way to say so — and
141
+ * 0069's appendix rejected `.allowEmpty()` for being "one word, silent
142
+ * forever, typo or not, and nothing revisits it". This is the shape that
143
+ * survived review, and the difference is that it is an **assertion**:
144
+ *
145
+ * ```ts
146
+ * classes(p).that().haveDecorator('Deprecated')
147
+ * .expectEmpty() // nothing is deprecated yet
148
+ * .should().beExported()
149
+ *
150
+ * // the day someone deprecates a class:
151
+ * // FAIL: .expectEmpty() asserted 0 subjects, found 1
152
+ * ```
153
+ *
154
+ * An agent that reaches for this to silence a real typo gets a different
155
+ * failure the moment the typo is fixed, and until then the intent is stated
156
+ * in the rule where a reader sees it — rather than in a baseline, or nowhere.
157
+ *
158
+ * Symmetric with {@link expectNonEmpty} for the rule builders. A family may **refuse** the zero-arg form where a whole-rule notion of empty has no meaning — `CrossProjectBuilder` throws, because it declares per side — so a consumer walking `TerminalBuilder[]` must not call it unguarded. Exactly symmetric, and the two together mean
159
+ * the empty/non-empty question is always answerable from the rule text.
160
+ * Declaring both is a contradiction and throws here rather than silently
161
+ * picking one.
162
+ */
163
+ expectEmpty() {
164
+ if (this._requireNonEmpty)
165
+ throw new TypeError(CONTRADICTION);
166
+ const next = this.copy();
167
+ next._expectEmpty = true;
168
+ return next;
169
+ }
170
+ /**
171
+ * True when this rule's emptiness is DECLARED — plan 0097, consumed by 0098.
172
+ *
173
+ * The base answer is the whole-rule flag. It is `protected` and overridable
174
+ * because a family whose declaration is not whole-rule must be able to say so:
175
+ * `CrossProjectBuilder` declares per SIDE and refuses the zero-arg form
176
+ * entirely, so `_expectEmpty` is unreachable there and this default would
177
+ * answer `false` for a rule whose every side the author declared — reporting a
178
+ * finding that tells them to declare what they declared, which is ADR-008
179
+ * rule 2's loop.
180
+ *
181
+ * PUBLIC, and forced rather than chosen — the same reason `assertsSomething()`
182
+ * is: `DiagnosableRule` is a structural interface, and a protected member
183
+ * cannot satisfy it. Plan 0096's preview is the first reader, and a preview
184
+ * that ignored the declaration would report a finding on a rule the gate will
185
+ * accept — over-reporting against the very thing it previews, which is the
186
+ * rule 5 violation inside the migration that plan warns about.
187
+ *
188
+ * It exists now, ahead of the floor that reads it, for a reason worth stating:
189
+ * a private version of this lived on `CrossProjectBuilder` and was deleted
190
+ * as dead code, correctly — but the deletion also removed the only expression
191
+ * of the concept, so the override 0098 needs would have gone missing SILENTLY
192
+ * rather than as the compile error a narrowed-visibility clash would have
193
+ * produced. Declaring it here makes the coupling loud again.
194
+ */
195
+ declaresEmpty() {
196
+ return this._expectEmpty;
197
+ }
198
+ /**
199
+ * How THIS family spells the declaration, for a remedy that names a real call.
200
+ *
201
+ * The sibling of `assertionAdvice()`, and it exists for the same reason: a
202
+ * remedy is only verified to remediate if following it works, and the generic
203
+ * `.expectEmpty()` is a `TypeError` on `CrossProjectBuilder`, which declares
204
+ * per side. Advice that names the one form the reader cannot call is ADR-008
205
+ * rule 2's failure with extra confidence.
206
+ *
207
+ * Overriding `expectEmpty()` and not this leaves a remedy that throws, so the
208
+ * classification census in `evidence-at-every-seam.test.ts` requires both
209
+ * together rather than trusting the next author to remember.
210
+ */
211
+ emptyDeclarationAdvice() {
212
+ // The AUTHOR states it; core does not guess. This branched on
213
+ // `id.startsWith('preset/')`, which core cannot verify — false for a
214
+ // hand-written `.rule({ id: 'preset/...' })`, for a third-party preset that
215
+ // never extended `PresetBaseOptions`, and for one that forwards `overrides`
216
+ // but not `expectEmpty`. The method exists precisely to stop advice naming a
217
+ // call the reader cannot make, so deriving it from a string prefix gave up
218
+ // the discipline it was added to enforce.
219
+ return this._metadata?.declarationSpelling ?? '.expectEmpty()';
220
+ }
221
+ /**
222
+ * Attach a human-readable rationale to the rule.
223
+ * Included in violation messages when `.check()` throws.
224
+ */
225
+ because(reason) {
226
+ const next = this.copy();
227
+ next._reason = reason;
228
+ return next;
229
+ }
230
+ /**
231
+ * Attach rich metadata to the rule.
232
+ * Provides educational context in violation output: why, how to fix, docs link.
233
+ *
234
+ * If `metadata.because` is set, it also sets the reason (same as `.because()`).
235
+ */
236
+ rule(metadata) {
237
+ const next = this.copy();
238
+ next._metadata = metadata;
239
+ if (metadata.because) {
240
+ next._reason = metadata.because;
241
+ }
242
+ return next;
243
+ }
244
+ /**
245
+ * Exclude specific violations from reporting by matching against
246
+ * the violation's `element`, `file`, or `message` fields.
247
+ *
248
+ * Matched violations are silently suppressed. Use for permanent,
249
+ * intentional exceptions — not for temporary violations (use baseline for those).
250
+ *
251
+ * Patterns are matched against all three fields. Prefer anchored regexes
252
+ * or full string matches over short substrings, especially for `message`
253
+ * matching, to avoid accidentally suppressing unrelated violations whose
254
+ * messages happen to contain the same text.
255
+ *
256
+ * Emits a warning if an exclusion matches zero violations — so renamed
257
+ * or deleted exceptions don't silently stay in the rule.
258
+ *
259
+ * A configuration meta-finding — one that reports the rule enforces
260
+ * nothing — is never excludable, and an exclusion that would have matched
261
+ * one is reported as refused rather than as stale.
262
+ *
263
+ * For narrowing a rule's scope at the predicate phase (so the rule never
264
+ * evaluates the excluded element), use `satisfy(not(<predicate>))` instead.
265
+ * See the "Excluding a file from a rule's scope" recipe in docs/recipes.md.
266
+ *
267
+ * @example
268
+ * // Exclude by element name (fully qualified)
269
+ * .excluding('Asset.getImageUrl')
270
+ *
271
+ * @example
272
+ * // Exclude by file path (regex anchored to suffix)
273
+ * .excluding(/repositories\/index\.ts$/)
274
+ *
275
+ * @example
276
+ * // Multiple exclusions, mixed forms
277
+ * .excluding('Asset.getImageUrl', /\/legacy\//, /generated/)
278
+ */
279
+ excluding(...patterns) {
280
+ const next = this.copy();
281
+ // The kernel's — this was byte-identical to its `excluding`, and the two
282
+ // builders cannot share it by inheritance because the hierarchies differ.
283
+ recordExclusions(patterns, next._exclusions, next._silentIndices);
284
+ return next;
285
+ }
286
+ /**
287
+ * Return a structured description of this rule without executing it.
288
+ * Used by the `explain` CLI subcommand.
289
+ */
290
+ describeRule() {
291
+ return ruleDescriptionFrom({
292
+ metadata: this._metadata,
293
+ reason: this._reason,
294
+ rule: this._metadata?.id ?? 'unnamed',
295
+ imperativeFallback: this._reason,
296
+ });
297
+ }
298
+ /**
299
+ * Set this rule's severity, returning a COPY — the held builder is never
300
+ * mutated.
301
+ *
302
+ * `options.accepted` applies only to `'warn'` and names the violations already
303
+ * accepted at that level; it is cleared when the level is `'error'`, so a rule
304
+ * escalated back to error cannot carry a stale allowlist that would silently
305
+ * excuse the very findings the escalation was for.
306
+ *
307
+ * A configuration finding ignores this entirely (`severityFor`): its severity
308
+ * is not the author's to lower.
309
+ */
310
+ asSeverity(level, options) {
311
+ const next = this.copy();
312
+ next._severity = level;
313
+ next._acceptedWarnings = level === 'warn' ? options?.accepted : undefined;
314
+ return next;
315
+ }
316
+ /**
317
+ * Whether this rule asserts anything about what it selects (plan 0070).
318
+ *
319
+ * `false` means the rule can never fail, and as of 0.23.0
320
+ * `collectWithAssertionGuard()` turns that into an unsuppressable
321
+ * configuration finding on every terminal; `diagnose()` / `doctor` report it
322
+ * without running the rule. 0.22.0 shipped this hook with nothing at runtime
323
+ * reading it, because the gate drafted for that release wrote through a
324
+ * bespoke stderr channel that bypassed the formatter, the JSON payload, the
325
+ * annotation path and the exit code — a review found a defect at each of those
326
+ * seams, so the channel was withdrawn and the hook shipped on its own. The
327
+ * finding form reaches all four surfaces by construction, which is what the
328
+ * withdrawal bought.
329
+ *
330
+ * Concrete with a `true` default rather than abstract: both roots are public
331
+ * exports, so an abstract member is a compile break for an external subclass
332
+ * (the `globs()` argument). The default makes a new builder EXEMPT by
333
+ * default — the opposite polarity from `globs()`'s empty default, which only
334
+ * makes a builder invisible. The classification test in
335
+ * `tests/core/assertion-gate.test.ts` is what forces the decision for every
336
+ * exported builder.
337
+ *
338
+ * Public, not protected — `diagnose()` duck-types it through
339
+ * `DiagnosableRule`, and a protected member cannot satisfy a structural
340
+ * interface. Same forcing as `assertsSomething` on `RuleBuilder`, which was
341
+ * already shipped public.
342
+ */
343
+ assertsSomething() {
344
+ return true;
345
+ }
346
+ /**
347
+ * The remedy for this builder's assertion-less state, as one string.
348
+ *
349
+ * The "one string, one place" channel: `diagnose()`'s advice and the
350
+ * finding's message and `suggestion` all read this method, so the diagnostic
351
+ * a consumer runs before upgrading and the failure they get after cannot
352
+ * disagree — plan 0070 round 2 measured an earlier design shipping two
353
+ * diverging texts for one state.
354
+ *
355
+ * Public for the same `DiagnosableRule` duck-typing reason as
356
+ * `assertsSomething` — plan 0070 drafted this `protected`, and a protected
357
+ * member cannot satisfy the structural interface `diagnose()` consumes.
358
+ */
359
+ assertionAdvice() {
360
+ return 'this rule asserts nothing, so it can never fail. Add an assertion, or delete the rule.';
361
+ }
362
+ /**
363
+ * Every glob declaration this rule makes, as independent trees.
364
+ *
365
+ * One entry per independent declaration, because each one dies on its own:
366
+ * a rule whose selector is satisfiable and whose discovery glob is not has
367
+ * exactly one fault, and reporting them as one tree would lose that.
368
+ *
369
+ * Concrete with an empty default rather than abstract. Making it abstract
370
+ * would enumerate every builder at compile time — genuinely attractive, and
371
+ * how the census refactor did it — but `RuleBuilder` and `TerminalBuilder`
372
+ * are both public exports, so an abstract member is a compile break for
373
+ * anyone who has subclassed them. R2a is the release people install in order
374
+ * to MEASURE before R3 flips anything; it cannot be the one that fails to
375
+ * compile. The vacuity that `abstract` would have caught is caught instead
376
+ * by a test that reflects over both entry points and fails a `return []`
377
+ * stub, which the compiler could not have done anyway.
378
+ */
379
+ globs() {
380
+ return [];
381
+ }
382
+ /**
383
+ * The project this rule was built against, when it has one.
384
+ *
385
+ * Concrete returning `undefined` rather than abstract, for the reason
386
+ * `globs()` above records: both roots are public exports, so an abstract
387
+ * member is a compile break for an external subclass. `RuleBuilder`
388
+ * overrides it with the non-optional form.
389
+ *
390
+ * The glob gate needs it, and a builder that cannot name its project simply
391
+ * has its globs left unchecked — the same fallback `diagnose()` takes, and
392
+ * the honest one: satisfiability is meaningless without a path universe to
393
+ * take it against.
394
+ */
395
+ getProject() {
396
+ return undefined;
397
+ }
398
+ /**
399
+ * Selector globs that can never match — plan 0074 (R3b), the flip.
400
+ *
401
+ * 0069's decision table, not reopened here: a **selector** glob that is
402
+ * unsatisfiable means the rule can never have subjects, so it certifies
403
+ * nothing and passing is a lie. `discovery` already fails (0067-D, and the
404
+ * slice builders own their own message). `condition` and `exclusion` are
405
+ * **never** faults — a condition glob matching nothing is indistinguishable
406
+ * from an armed tripwire that has not fired, and plan 0072 got that wrong
407
+ * twice before it stayed written down.
408
+ *
409
+ * Negative polarity is excluded by `isDeadGlobTree` itself: `not(dead)`
410
+ * over-selects rather than under-selecting, so it cannot be dead.
411
+ *
412
+ * This is the same computation `diagnose()` performs — deliberately, and
413
+ * from the same functions, so the pre-flight and the gate can never disagree
414
+ * about what is dead. `doctor` told adopters what R3b would fail on; if the
415
+ * two used separate logic that promise would be worth nothing.
416
+ */
417
+ /**
418
+ * Whether any condition on this rule declares emptiness as its passing state.
419
+ *
420
+ * Concrete `false` default for the reason `globs()` records — an abstract
421
+ * member breaks external subclasses. `RuleBuilder` overrides it by reading
422
+ * its own conditions; the builders that take their condition as a
423
+ * constructor argument have no cardinality condition to declare.
424
+ *
425
+ * **PUBLIC since plan 0098**, for the same structural reason as
426
+ * `assertsSomething()` and `declaresEmpty()`: `diagnose()` reads it through a
427
+ * structural interface and a protected member cannot satisfy one. It became a
428
+ * reader the moment the rule-builder family started reporting evidence —
429
+ * `.should().notExist()` examines zero subjects *because that is what it
430
+ * asserts*, and previewing it as a fault tells the author their working rule
431
+ * is broken. Caught by this repo's own 53-rule suite, on the one rule in it
432
+ * that ends in `.notExist()`.
433
+ *
434
+ * Deliberately NOT folded into `declaresEmpty()`, though both suppress the
435
+ * same finding. They are different facts and plan 0099 needs them apart: its
436
+ * expiry branch reads the declaration flag alone, because `.notExist()` over a
437
+ * selection that has grown is the condition doing its job, never a declaration
438
+ * that has expired.
439
+ */
440
+ assertsCardinality() {
441
+ return false;
442
+ }
443
+ /**
444
+ * The narrowing THIS family applied, when it can name it — plan 0099.
445
+ *
446
+ * ADR-010 part 4 wants the zero-examined remedy to name the **actual excluder,
447
+ * including internal defaults**, because "fix your filters" to a user who wrote
448
+ * none sends an agent looking for filters that do not exist.
449
+ *
450
+ * The root cannot know this: only the family holds its own thresholds. Review
451
+ * measured the cost of leaving it out — `agentGuardrails({ src: '**\/lib/**',
452
+ * noCopyPaste: true })` over two one-line functions, a **correctly configured**
453
+ * rule pointing at real code, hard-failed with "widen it until it matches
454
+ * something" while the true cause was `minLines(5)`, a default the author never
455
+ * wrote and which that preset exposes no knob for.
456
+ *
457
+ * Returns `undefined` when a family has nothing specific to say, and the caller
458
+ * falls back to naming the possibility rather than asserting a cause it cannot
459
+ * verify.
460
+ */
461
+ narrowingHint() {
462
+ return undefined;
463
+ }
464
+ /**
465
+ * What this family counts in `examined` — plan 0099.
466
+ *
467
+ * `CollectResult.examined` is unit-typed per family (subjects, bodies, pairs,
468
+ * keys), so a message that prints a FILE count and then says the rule examined
469
+ * "0 of them" commits a category error. Measured on this repo: a rule whose
470
+ * glob matched essentially all 616 files and whose NAME predicate matched none
471
+ * printed "loaded 616 files, and this rule examined 0 of them" — sending the
472
+ * reader to widen a glob that was already maximal.
473
+ */
474
+ examinedUnitNoun() {
475
+ return 'subjects';
476
+ }
477
+ /**
478
+ * An independent copy of this builder.
479
+ *
480
+ * **A held selection is immutable** (bug 0016). Every method that adds to a
481
+ * builder returns a copy instead of mutating `this`, so
482
+ *
483
+ * ```ts
484
+ * const repositories = classes(p).that().extend('BaseRepository')
485
+ * repositories.that().haveNameEndingWith('Legacy').should().notExist().check()
486
+ * repositories.should().beExported().check() // still ALL repositories
487
+ * ```
488
+ *
489
+ * works. Before this, narrowing a held selection edited it in place: the
490
+ * second rule silently inherited `Legacy` and reported on a subset — or on
491
+ * nothing, and then passed. Same for `.excluding()`, which leaked a
492
+ * suppression into every later rule off the same selection, and `.rule()`,
493
+ * which leaked the id that `// eess-exclude <id>` comments are matched
494
+ * against (`execute-rule.ts` gates the whole comment scan on `metadata.id`)
495
+ * and that a preset's severity `overrides` are keyed on — so a later rule
496
+ * inherited a suppression channel it never opted into.
497
+ *
498
+ * Not baselines: `hashViolation` keys on the rule *description*, never on
499
+ * `metadata.id`. An earlier version of this docstring said baselines and a
500
+ * `--rule` filter were keyed on the id. Neither is true, and `--rule` does
501
+ * not exist — the only occurrences of it in the repo were three copies of
502
+ * this sentence. ADR-009 rule 2: a failure may not assert a cause it cannot
503
+ * verify, and that includes naming a flag the CLI does not have.
504
+ *
505
+ * Cost is one object per chain link, against a ts-morph walk. Irrelevant.
506
+ */
507
+ copy() {
508
+ const clone = shallowClone(this);
509
+ clone.adoptFilterState(this);
510
+ clone._metadata = this._metadata ? { ...this._metadata } : undefined;
511
+ return clone;
512
+ }
513
+ /**
514
+ * Give this builder independent copies of another's filter state.
515
+ *
516
+ * `copy()` shallow-copies every field, so without this a copy would share
517
+ * its parent's exclusion array by reference and `.excluding()` on one would
518
+ * silently mutate the other. The copy lives here rather than inline in
519
+ * `copy()` because the knowledge of what needs duplicating belongs with the
520
+ * fields — the same reason every other state-holding class overrides
521
+ * `copy()` instead of `copy()` knowing about their fields.
522
+ */
523
+ adoptFilterState(source) {
524
+ this._exclusions = [...source._exclusions];
525
+ this._silentIndices = new Set(source._silentIndices);
526
+ }
527
+ }
528
+ /**
529
+ * The terminals — `check()`, `warn()`, `violations()` — and the execution behind
530
+ * them.
531
+ *
532
+ * Split from {@link RuleDeclaration} because a builder does two jobs: it collects
533
+ * a declaration, and it runs it. Both halves were one class of 372 code lines,
534
+ * and the gate that measures class size was right about it.
535
+ *
536
+ * A subclass still extends `TerminalBuilder` and inherits both halves, so the ten
537
+ * builders that do are untouched — the split is in this file, not in their
538
+ * contracts.
539
+ */
540
+ export class TerminalBuilder extends RuleDeclaration {
541
+ /**
542
+ * Does this builder diagnose its own empty discovery?
543
+ *
544
+ * `false` — the gate reports a dead `discovery` glob, which is the fix for
545
+ * [ts-archunit bug 0040](https://github.com/nielspeter/ts-archunit/blob/main/bugs/fixed/0040-a-crosslayer-rule-reports-nothing-when-its-layer-resolves-nothing.md)'s
546
+ * silence half: two of three cross-layer conditions reported **nothing** when a
547
+ * layer resolved to no files.
548
+ *
549
+ * `true` — the builder owns it, and the gate stays out. Two builders override,
550
+ * and for different reasons, which is the thing to understand before adding a
551
+ * third:
552
+ *
553
+ * - `SliceRuleBuilder` returns a constant `true`, because its discovery
554
+ * semantics are **not** per-tree and cannot be expressed by a position filter
555
+ * (below). Nothing about that varies by condition.
556
+ * - `PairFinalBuilder` **asks its condition**, via the registry in
557
+ * `owns-empty-discovery.ts` (plan 0081). There, ownership does vary by
558
+ * condition, and a blanket `true` once suppressed the gate for the one
559
+ * condition that did not self-report — which is why this used to say
560
+ * "`SliceRuleBuilder` is the one that does" and was wrong.
561
+ *
562
+ * A hand-maintained roster of which builders override is the defect class plan
563
+ * 0081 was filed to remove, so this names the two shapes rather than the two
564
+ * classes:
565
+ *
566
+ * | builder | one dead tree among live ones |
567
+ * | --- | --- |
568
+ * | `crossLayer` | a **fault** — that layer's pairs are unchecked |
569
+ * | slice `assignedFrom` | **legitimate** — a slice with no files yet is a real shape |
570
+ *
571
+ * `slice-rule-builder.ts` records that second guard being withdrawn before
572
+ * release for firing on real projects: "a layer not created yet, and the
573
+ * `strict-boundaries` scaffold itself". Slice already handles the all-empty case
574
+ * with a better message than the gate's, so it owns both halves.
575
+ *
576
+ * Declared, never named from the outside. A list of exceptions in the gate is
577
+ * an unchecked claim about who owns what — which is exactly the comment this
578
+ * plan was filed to correct.
579
+ */
580
+ ownsDiscoveryDiagnosis() {
581
+ return false;
582
+ }
583
+ /**
584
+ * Execute the rule with the given severity.
585
+ * `.severity('error')` is equivalent to `.check()`.
586
+ * `.severity('warn')` is equivalent to `.warn()`.
587
+ */
588
+ severity(level) {
589
+ if (level === 'error') {
590
+ this.check();
591
+ }
592
+ else {
593
+ this.warn();
594
+ }
595
+ }
596
+ /**
597
+ * A rule that asserts nothing about what it selects cannot fail, so it is
598
+ * reported as a configuration finding (bug 0019, plan 0070).
599
+ *
600
+ * **Gate-first**, ahead of `collectViolations()`, for three measured reasons:
601
+ * an assertion-less rule cannot produce a legitimate finding, so running it
602
+ * buys nothing but a full AST walk; `CrossProjectBuilder.collectViolations`
603
+ * throws before returning, so a gate placed after it would never run for that
604
+ * builder and its `RangeError` would escape the CLI's `ArchRuleError`-only
605
+ * catch, dropping every remaining rule file; and the alternative ordering —
606
+ * let an existing `bypassFilters` finding win — only functions for rules that
607
+ * opted into `.expectNonEmpty()`, which is the opt-in this whole plan exists
608
+ * because nobody uses.
609
+ *
610
+ * The consequence, accepted: a rule with a dead glob AND no condition reports
611
+ * the missing assertion only. That is the right root cause — no selector
612
+ * makes an assertion-less rule capable of failing — and the selector fault
613
+ * resurfaces on the next run, once there is something to assert.
614
+ *
615
+ * `bypassFilters` makes it a configuration finding: `error` severity
616
+ * regardless of `.asSeverity('warn')`, refused by `.excluding()`, and skipped
617
+ * by diff and baseline. See `severityFor` and ADR-009 rule 1.
618
+ */
619
+ collectWithAssertionGuard() {
620
+ return runWithGuard(this.asRun());
621
+ }
622
+ /** This rule, reduced to what `terminal-execution.ts` needs to run it. */
623
+ asRun() {
624
+ return {
625
+ facts: this.facts(),
626
+ expectsEmpty: this._expectEmpty,
627
+ assertsSomething: () => this.assertsSomething(),
628
+ ownsDiscoveryDiagnosis: () => this.ownsDiscoveryDiagnosis(),
629
+ assertsCardinality: () => this.assertsCardinality(),
630
+ collectViolations: () => this.collectViolations(),
631
+ };
632
+ }
633
+ /**
634
+ * What `applyFilters` needs, in one place.
635
+ *
636
+ * `violations()`, `check()` and `warn()` each rebuilt this object literally —
637
+ * three copies of the same four fields, which is three places to forget one.
638
+ */
639
+ filterContext() {
640
+ return {
641
+ reason: this._reason,
642
+ metadata: this._metadata,
643
+ exclusions: this._exclusions,
644
+ silentIndices: this._silentIndices,
645
+ // Bug 0258 round 2: an id-less rule still has a sentence, and every
646
+ // builder already implements `describeRule()`. Deferred, because this
647
+ // runs on every terminal call and is read only when an id-less rule meets
648
+ // an exclusion comment it cannot honour.
649
+ describe: () => this.describeRule().rule,
650
+ };
651
+ }
652
+ /** This rule's own account of itself, for `vacuity-diagnosis.ts`. */
653
+ facts() {
654
+ return {
655
+ ruleClass: { name: this.constructor.name },
656
+ describeRule: () => this.describeRule(),
657
+ assertionAdvice: () => this.assertionAdvice(),
658
+ emptyDeclarationAdvice: () => this.emptyDeclarationAdvice(),
659
+ examinedUnitNoun: () => this.examinedUnitNoun(),
660
+ narrowingHint: () => this.narrowingHint(),
661
+ getProject: () => this.getProject(),
662
+ globs: () => this.globs(),
663
+ assertsCardinality: () => this.assertsCardinality(),
664
+ declaresEmpty: () => this.declaresEmpty(),
665
+ zeroSubjectsAdvice: () => this.zeroSubjectsAdvice(),
666
+ zeroSubjectsViolation: (project) => this.zeroSubjectsViolation(project),
667
+ };
668
+ }
669
+ /**
670
+ * Execute the rule and return violations after exclusion filtering.
671
+ * Does not throw — use for programmatic access (presets, aggregation).
672
+ */
673
+ violations() {
674
+ // The evidence comes from `collectViolations()` directly: the assertion
675
+ // guard returns a bare array of findings (it can ADD a gate finding to a
676
+ // rule that collected nothing), so the denominator has to be read from the
677
+ // rule's own receipt rather than from what survived the gates.
678
+ //
679
+ // **The declaration travels with it — D4.** "D4's declaration goes on both
680
+ // terminals' receipts, or the flagship dialect's presets arrive undeclared."
681
+ // Unifying the TYPE across the two terminals is not enough; this is the line
682
+ // that makes an `eess-ts` rule's declaration reach the emitter at all, and
683
+ // without it every `.expectEmpty()` in the flagship dialect was silently
684
+ // undeclared. Caught by a test asserting `declaredEmpty` directly.
685
+ //
686
+ // `assertsCardinality()` only declares over ZERO examined — ADR-010 §3:
687
+ // `.notExist()` over zero subjects is a declaration by construction, but
688
+ // over a live selection it is an ordinary assertion.
689
+ const raw = this.collectWithAssertionGuard();
690
+ const evidence = {
691
+ examined: raw.examined,
692
+ sourceEmpty: raw.sourceEmpty,
693
+ declaredEmpty: raw.declaredEmpty,
694
+ };
695
+ const filtered = applyFilters(raw, this.filterContext());
696
+ const sev = this._severity ?? 'error';
697
+ // Computed from `raw`, not `filtered`: `applyFilters()` already ran
698
+ // `disambiguateIdentities()` by this point, which REPAIRS a colliding
699
+ // subject with a positional `#1`/`#2` suffix — see `hasIdentityCollision`'s
700
+ // own doc comment for why checking post-repair identities would miss
701
+ // exactly the case this exists to catch. Only computed when it can matter.
702
+ const unsafe = sev === 'warn' && this._acceptedWarnings !== undefined && hasIdentityCollision(raw);
703
+ return collectResult(filtered.map((v) => ({
704
+ ...v,
705
+ severity: severityFor(v, unsafe ? 'error' : this.fallbackSeverityFor(v, sev)),
706
+ })), evidence);
707
+ }
708
+ /**
709
+ * Plan 0090's escalation, per violation. A DEFERRED warning (`_acceptedWarnings`
710
+ * set) stays `warn` only for a violation whose `subjectOf()` is in the accepted
711
+ * list; anything else — a genuinely new finding — escalates to `error`, which is
712
+ * the whole point: an accepted list that never fails on something new is
713
+ * indistinguishable from an advisory warning, and bug 0084 is what that costs.
714
+ * An ADVISORY warning (`_acceptedWarnings` `undefined`) is unaffected — `sev`
715
+ * passes through unchanged, exactly today's behaviour.
716
+ */
717
+ fallbackSeverityFor(v, sev) {
718
+ if (sev !== 'warn' || this._acceptedWarnings === undefined)
719
+ return sev;
720
+ return this._acceptedWarnings.includes(subjectOf(v)) ? 'warn' : 'error';
721
+ }
722
+ /**
723
+ * Execute the rule and throw `ArchRuleError` if any violations are found.
724
+ * This is the primary terminal method — use in test assertions.
725
+ *
726
+ * @param options - Optional baseline, diff filtering, and output format
727
+ */
728
+ check(options) {
729
+ const violations = this.collectWithAssertionGuard();
730
+ executeCheck(violations, this.filterContext(), options);
731
+ }
732
+ /**
733
+ * Execute the rule and log violations to stderr. Does not throw.
734
+ * Use for rules that should warn but not fail CI.
735
+ *
736
+ * @param options - Optional baseline, diff filtering, and output format
737
+ */
738
+ warn(options) {
739
+ const violations = this.collectWithAssertionGuard();
740
+ executeWarn(violations, this.filterContext(), options);
741
+ }
742
+ /**
743
+ * The advice for a DEFERRED warning whose accepted list no longer covers
744
+ * everything it currently finds — plan 0090.
745
+ *
746
+ * PUBLIC and zero-arg, following `inertAdvice()`/`zeroSubjectsAdvice()`'s
747
+ * precedent exactly: read structurally by `diagnose()`, so `doctor`'s preview
748
+ * and `checkAll()`/CLI `check`'s eventual failure carry the same evidence by
749
+ * construction. Returns `''` for an advisory warning (`_acceptedWarnings`
750
+ * `undefined`), for a rule at `'error'` severity, and for a deferred warning
751
+ * whose current findings are all still within `accepted` — a working, healthy
752
+ * deferral is not a problem `doctor` should report, only a breached one is.
753
+ *
754
+ * Calls `this.violations()` rather than re-deriving "is this accepted" a
755
+ * second time: that already applies the exact escalation this reports on, so
756
+ * the two can never disagree about which violations breached the list.
757
+ *
758
+ * Also checks `hasIdentityCollision()` separately, on its own
759
+ * `collectWithAssertionGuard()` pass — not to decide WHETHER to report (that
760
+ * is already folded into `.violations()`'s own escalation, which forces the
761
+ * whole batch to `error` when unsafe), but to choose the RIGHT message: a
762
+ * collision means `accepted` cannot be trusted at all here, which is a
763
+ * different, more urgent fact than "this specific finding is new".
764
+ */
765
+ deferredWarningAdvice() {
766
+ if (this._severity !== 'warn' || this._acceptedWarnings === undefined)
767
+ return '';
768
+ const breaching = this.violations().filter((v) => v.severity === 'error');
769
+ if (breaching.length === 0)
770
+ return '';
771
+ const described = this.describeRule();
772
+ const name = described.id || described.rule || this.constructor.name;
773
+ if (hasIdentityCollision(this.collectWithAssertionGuard())) {
774
+ return (`"${name}" is a deferred warning, but its findings are not reliably identifiable: two or ` +
775
+ `more share one subject (rule + element + message, with no producer-set \`identity\`), so ` +
776
+ `the repair that keeps them distinct assigns a POSITIONAL "#1"/"#2" suffix — not stable ` +
777
+ `across runs, so a fixed finding and a genuinely new one can land on the same suffix and ` +
778
+ `\`accepted\` would silently treat the new one as already-known debt. Every finding here is ` +
779
+ `escalated to error until this is fixed: qualify the condition's message, or set ` +
780
+ `ArchViolation.identity explicitly, so each finding's subject is unique on its own.`);
781
+ }
782
+ const subjects = breaching.map((v) => subjectOf(v));
783
+ return (`"${name}" is a deferred warning (accepted: ${String(this._acceptedWarnings.length)} finding` +
784
+ `${this._acceptedWarnings.length === 1 ? '' : 's'}), and ${String(breaching.length)} current ` +
785
+ `finding${breaching.length === 1 ? '' : 's'} ${breaching.length === 1 ? 'is' : 'are'} not in ` +
786
+ `that list — a new finding this deferral did not accept, which will fail at check() time: ` +
787
+ `${subjects.join(', ')}. Either fix it, or extend \`accepted\` if it is genuinely more debt of ` +
788
+ `the same kind you already deferred.`);
789
+ }
790
+ /**
791
+ * The advice for a rule that examined zero units — plan 0099.
792
+ *
793
+ * PUBLIC and zero-arg, following `assertionAdvice()`'s precedent exactly: it is
794
+ * read structurally by `diagnose()` so `doctor` and the gate carry the same
795
+ * string **by construction**. Plan 0070 added that seam after the two were
796
+ * measured diverging; review measured this pair diverging the same way, with
797
+ * `diagnose()` still closing "A later release makes this state fail at check
798
+ * time" in the release that makes it fail.
799
+ */
800
+ zeroSubjectsAdvice() {
801
+ return zeroSubjectsAdviceOf(this.facts());
802
+ }
803
+ zeroSubjectsViolation(project) {
804
+ return zeroSubjectsViolationOf(this.facts(), project);
805
+ }
806
+ }
807
+ //# sourceMappingURL=terminal-builder.js.map