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