@nielspeter/eess-ts 0.2.1 → 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 +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 +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 -24
  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
@@ -1,37 +1,101 @@
1
1
  import picomatch from 'picomatch';
2
2
  import path from 'node:path';
3
+ import { selectionMemo } from '@nielspeter/eess';
3
4
  import { SmellBuilder } from './smell-builder.js';
4
5
  import { collectFunctions } from '../models/arch-function.js';
5
- import { buildFingerprint, computeSimilarity } from './fingerprint.js';
6
+ import { fingerprintAll, findSimilarPairs } from './similar-pairs.js';
7
+ import { relativeToRoot } from '../core/project-relative.js';
6
8
  /** Test file patterns for ignoreTests(). */
7
9
  const TEST_PATTERNS = ['**/*.test.ts', '**/*.spec.ts', '**/__tests__/**'];
10
+ const selectionOf = selectionMemo();
8
11
  export class DuplicateBodiesBuilder extends SmellBuilder {
9
12
  _minSimilarity = 0.85;
13
+ _minDistinctVocabulary = 8;
10
14
  constructor(project) {
11
15
  super(project);
12
16
  }
13
17
  /** Set the AST similarity threshold. Default: 0.85. */
14
18
  withMinSimilarity(threshold) {
15
- this._minSimilarity = threshold;
16
- return this;
19
+ const next = this.copy();
20
+ next._minSimilarity = threshold;
21
+ return next;
22
+ }
23
+ /**
24
+ * Minimum count of distinct identifier/literal text either body must carry
25
+ * before a pair is even compared — not raw line count, and not similarity.
26
+ * Two bodies can share a syntactic shape for no reason other than the shape
27
+ * being mandated (a wither, a getter, a boilerplate skeleton); below this
28
+ * threshold a "match" carries no information about what the code actually
29
+ * does. Default: 8 — see plan 0103's Phase 0 triage for how it was chosen.
30
+ * Tune down for a codebase with terser naming than this default assumes;
31
+ * tune up if short, low-vocabulary bodies keep surfacing as noise.
32
+ */
33
+ minDistinctVocabulary(n) {
34
+ const next = this.copy();
35
+ next._minDistinctVocabulary = n;
36
+ return next;
37
+ }
38
+ /**
39
+ * Named by id or by what the detector checks — the inherited version returns
40
+ * the sentinel `'unnamed'`, and `inconsistent-siblings.ts` already carries this
41
+ * override for that reason.
42
+ *
43
+ * It became load-bearing in plan 0099: the floor makes an over-filtered
44
+ * detector FAIL, and `dedupeConfigFindings` keys on `(file, ruleId ?? rule,
45
+ * element)`. With `file: ''` and `'unnamed'` in both other slots, three
46
+ * genuinely different detectors — different similarity, different
47
+ * `ignorePaths` — collapsed into one finding claiming they were "one option"
48
+ * and "one edit". Two were silently discarded and the survivor said
49
+ * `Rule: unnamed`.
50
+ */
51
+ describeRule() {
52
+ return {
53
+ ...super.describeRule(),
54
+ rule: this._metadata?.id ?? this.describe(),
55
+ };
17
56
  }
18
57
  detect() {
19
- const functions = this.collectFilteredFunctions();
20
- const fingerprinted = this.fingerprintAll(functions);
21
- const pairs = this.findSimilarPairs(fingerprinted);
58
+ const functions = this.selected();
59
+ const fingerprinted = fingerprintAll(functions);
60
+ const pairs = findSimilarPairs(fingerprinted, this._minSimilarity, this._minDistinctVocabulary);
22
61
  return this.buildViolations(pairs);
23
62
  }
24
63
  describe() {
25
64
  const scope = this._folders.length > 0 ? this._folders.join(', ') : 'all files';
26
- return `No duplicate function bodies in ${scope} (similarity >= ${String(this._minSimilarity)})`;
65
+ // EVERY narrowing that distinguishes two instances, not just similarity.
66
+ //
67
+ // This is the `Rule:` line a reader sees, and since plan 0099 it is also the
68
+ // dedupe identity for a detector with no `.rule({ id })`. Rendering only
69
+ // folders and similarity meant three detectors differing in `minLines` shared
70
+ // one key: measured, 3 findings collapsed to 1 claiming they were "one edit",
71
+ // and fixing the surviving threshold left the other two dead and green.
72
+ const filters = [
73
+ `minLines >= ${String(this._minLines)}`,
74
+ `minDistinctVocabulary >= ${String(this._minDistinctVocabulary)}`,
75
+ ];
76
+ if (this._ignorePaths.length > 0)
77
+ filters.push(`ignoring ${this._ignorePaths.join(', ')}`);
78
+ if (this._ignoreTests)
79
+ filters.push('ignoring tests');
80
+ return (`No duplicate function bodies in ${scope} ` +
81
+ `(similarity >= ${String(this._minSimilarity)}, ${filters.join(', ')})`);
27
82
  }
28
83
  /** Check if a file path passes all glob-based filters. */
29
- passesFileFilters(filePath, folderMatchers, ignoreMatchers, testMatchers) {
30
- if (folderMatchers.length > 0 && !folderMatchers.some((m) => m(filePath)))
84
+ /**
85
+ * The three matcher sets travel together as one `filters` — they are one
86
+ * configuration, and four positional `picomatch.Matcher[]` arguments in a row
87
+ * are exactly the transposition risk the parameter cap exists to prevent.
88
+ */
89
+ passesFileFilters(filePath, filters, fromRoot) {
90
+ const { folderMatchers, ignoreMatchers, testMatchers } = filters;
91
+ // Both forms, for every set — bug 0036. A project-relative `inFolder()` or
92
+ // `ignorePaths()` glob could never match an absolute path.
93
+ const hits = (ms) => ms.some((m) => m(filePath)) || (fromRoot !== undefined && ms.some((m) => m(fromRoot)));
94
+ if (folderMatchers.length > 0 && !hits(folderMatchers))
31
95
  return false;
32
- if (ignoreMatchers.some((m) => m(filePath)))
96
+ if (hits(ignoreMatchers))
33
97
  return false;
34
- if (testMatchers.some((m) => m(filePath)))
98
+ if (hits(testMatchers))
35
99
  return false;
36
100
  return true;
37
101
  }
@@ -44,6 +108,27 @@ export class DuplicateBodiesBuilder extends SmellBuilder {
44
108
  return lineCount >= this._minLines;
45
109
  }
46
110
  /** Collect all functions matching folder/path/test filters. */
111
+ /**
112
+ * Bodies entering pairwise comparison — plan 0096, and the ONE method both
113
+ * readers call.
114
+ *
115
+ * The seam is AFTER `minLines` and the path filters: a project can load 300
116
+ * files while every body sits under the threshold, and the count that notices
117
+ * has to be taken where the comparison receives its input, not where the files
118
+ * were read.
119
+ *
120
+ * No fingerprinting to count. `meetsMinLines` already returns false for a
121
+ * bodyless function, so `fingerprintAll(xs).length === xs.length` **always** —
122
+ * fingerprinting to answer "is it zero" is a full AST pass of dead work on
123
+ * what becomes the hot path of every consumer's `diagnose()` call.
124
+ */
125
+ selected() {
126
+ return selectionOf(this, () => this.collectFilteredFunctions());
127
+ }
128
+ /** Units this detector examined — plan 0096. */
129
+ examinedUnits() {
130
+ return this.selected().length;
131
+ }
47
132
  collectFilteredFunctions() {
48
133
  const sourceFiles = this.project.getSourceFiles();
49
134
  const folderMatchers = this._folders.map((g) => picomatch(g));
@@ -51,10 +136,16 @@ export class DuplicateBodiesBuilder extends SmellBuilder {
51
136
  const testMatchers = this._ignoreTests ? TEST_PATTERNS.map((g) => picomatch(g)) : [];
52
137
  const allFunctions = [];
53
138
  for (const sf of sourceFiles) {
54
- if (!this.passesFileFilters(sf.getFilePath(), folderMatchers, ignoreMatchers, testMatchers)) {
139
+ if (!this.passesFileFilters(sf.getFilePath(), { folderMatchers, ignoreMatchers, testMatchers }, relativeToRoot(sf, sf.getFilePath(), this.project.tsConfigPath))) {
55
140
  continue;
56
141
  }
57
- for (const fn of collectFunctions(sf)) {
142
+ // Detectors scan for a property of the code, not a user-declared subject
143
+ // set, so they always include object-literal functions. `functions()`
144
+ // keeps that opt-in because widening a selector silently changes every
145
+ // existing rule; a detector has no such contract to break, and a
146
+ // duplicated arrow under an object key — a resolver, a route handler, a
147
+ // reducer case — is exactly the copy-paste rot this exists to find.
148
+ for (const fn of collectFunctions(sf, { includeObjectLiteralFunctions: true })) {
58
149
  if (this.meetsMinLines(fn)) {
59
150
  allFunctions.push(fn);
60
151
  }
@@ -62,53 +153,27 @@ export class DuplicateBodiesBuilder extends SmellBuilder {
62
153
  }
63
154
  return allFunctions;
64
155
  }
65
- /** Build fingerprints for all collected functions. */
66
- fingerprintAll(functions) {
67
- const result = [];
68
- for (const fn of functions) {
69
- const body = fn.getBody();
70
- if (!body)
71
- continue;
72
- result.push({ fn, fingerprint: buildFingerprint(body) });
73
- }
74
- return result;
75
- }
76
- /** Compare all pairs of fingerprints, collect those above threshold. */
77
- findSimilarPairs(items) {
78
- const pairs = [];
79
- for (let i = 0; i < items.length; i++) {
80
- for (let j = i + 1; j < items.length; j++) {
81
- const a = items[i];
82
- const b = items[j];
83
- if (!a || !b)
84
- continue;
85
- // Fast rejection: if node counts differ too much, similarity cannot reach threshold
86
- const maxCount = Math.max(a.fingerprint.nodeCount, b.fingerprint.nodeCount);
87
- const minCount = Math.min(a.fingerprint.nodeCount, b.fingerprint.nodeCount);
88
- if (maxCount > 0 && minCount / maxCount < this._minSimilarity) {
89
- continue;
90
- }
91
- const similarity = computeSimilarity(a.fingerprint, b.fingerprint);
92
- if (similarity >= this._minSimilarity) {
93
- pairs.push({ a: a.fn, b: b.fn, similarity });
94
- }
95
- }
96
- }
97
- return pairs;
98
- }
99
156
  /** Build violations from similar pairs. */
157
+ /**
158
+ * Report order: by folder when `.groupByFolder()` asked for it, otherwise the
159
+ * order the pairs were found in.
160
+ *
161
+ * Extracted from {@link buildViolations} so that method is the violation
162
+ * shape and nothing else — presentation order is a separate decision.
163
+ */
164
+ orderedPairs(pairs) {
165
+ if (!this._groupByFolder)
166
+ return pairs;
167
+ return [...pairs].sort((x, y) => {
168
+ const folderA = path.dirname(x.a.getSourceFile().getFilePath());
169
+ const folderB = path.dirname(y.a.getSourceFile().getFilePath());
170
+ return folderA.localeCompare(folderB);
171
+ });
172
+ }
100
173
  buildViolations(pairs) {
101
174
  const ruleDescription = this.describe();
102
175
  const violations = [];
103
- // Optionally sort pairs by folder for grouped output
104
- const sortedPairs = this._groupByFolder
105
- ? [...pairs].sort((x, y) => {
106
- const folderA = path.dirname(x.a.getSourceFile().getFilePath());
107
- const folderB = path.dirname(y.a.getSourceFile().getFilePath());
108
- return folderA.localeCompare(folderB);
109
- })
110
- : pairs;
111
- for (const pair of sortedPairs) {
176
+ for (const pair of this.orderedPairs(pairs)) {
112
177
  const nameA = pair.a.getName() ?? '<anonymous>';
113
178
  const fileA = pair.a.getSourceFile().getFilePath();
114
179
  const lineA = pair.a.getStartLineNumber();
@@ -122,6 +187,22 @@ export class DuplicateBodiesBuilder extends SmellBuilder {
122
187
  file: fileA,
123
188
  line: lineA,
124
189
  message: `${nameA} (${fileA}:${String(lineA)}) is ${String(pct)}% similar to ${nameB} (${fileB}:${String(lineB)})`,
190
+ // Which endpoint is "a" comes from the source-file walk order, which is
191
+ // a property of the filesystem: the same pair reports A→B on one
192
+ // machine and B→A on another, and the reported message alone would
193
+ // give them different identities. Sort the endpoints so the pair reads
194
+ // the same either way. Qualified by path — a bare function name is not
195
+ // unique across files — and without the similarity percentage, which
196
+ // drifts as either body is edited.
197
+ //
198
+ // Limitation: two anonymous functions in one file share an endpoint
199
+ // (`<file>#<anonymous>`) and so share an identity. Nothing stable
200
+ // distinguishes them — a line number would, and that is the coordinate
201
+ // dependence being removed. Measured at 0 collisions over 1006 findings
202
+ // on a real codebase; the collision guard in
203
+ // `tests/integration/baseline-portability.test.ts` is what would catch
204
+ // it becoming common.
205
+ identity: `duplicate-pair::${[`${fileA}#${nameA}`, `${fileB}#${nameB}`].sort().join('::')}`,
125
206
  because: this._reason,
126
207
  });
127
208
  }
@@ -1 +1 @@
1
- {"version":3,"file":"duplicate-bodies.js","sourceRoot":"","sources":["../../src/smells/duplicate-bodies.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"duplicate-bodies.js","sourceRoot":"","sources":["../../src/smells/duplicate-bodies.ts"],"names":[],"mappings":"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"}
@@ -1,4 +1,4 @@
1
- import { type SyntaxKind, type Node } from 'ts-morph';
1
+ import { SyntaxKind, type Node } from 'ts-morph';
2
2
  /**
3
3
  * Structural fingerprint of a function body.
4
4
  * Captures the shape (node kinds, call targets) while ignoring
@@ -11,6 +11,13 @@ export interface Fingerprint {
11
11
  readonly calls: readonly string[];
12
12
  /** Total AST node count (for line-count filtering) */
13
13
  readonly nodeCount: number;
14
+ /**
15
+ * Count of DISTINCT identifier/literal texts in the body — the vocabulary
16
+ * a body actually carries, as opposed to its punctuation/keyword shape.
17
+ * Plan 0103's floor reads this; `computeSimilarity()` does not — see that
18
+ * function's own docs for why.
19
+ */
20
+ readonly distinctVocabulary: number;
14
21
  }
15
22
  /**
16
23
  * Build a structural fingerprint from a function body AST node.
@@ -1 +1 @@
1
- {"version":3,"file":"fingerprint.d.ts","sourceRoot":"","sources":["../../src/smells/fingerprint.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,UAAU,EAAE,KAAK,IAAI,EAAqB,MAAM,UAAU,CAAA;AAExE;;;;GAIG;AACH,MAAM,WAAW,WAAW;IAC1B,wDAAwD;IACxD,QAAQ,CAAC,KAAK,EAAE,SAAS,UAAU,EAAE,CAAA;IACrC,uEAAuE;IACvE,QAAQ,CAAC,KAAK,EAAE,SAAS,MAAM,EAAE,CAAA;IACjC,sDAAsD;IACtD,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAA;CAC3B;AAED;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,IAAI,GAAG,WAAW,CAYxD;AAED;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,CAAC,EAAE,WAAW,EAAE,CAAC,EAAE,WAAW,GAAG,MAAM,CAMxE"}
1
+ {"version":3,"file":"fingerprint.d.ts","sourceRoot":"","sources":["../../src/smells/fingerprint.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,KAAK,IAAI,EAAqB,MAAM,UAAU,CAAA;AAgBnE;;;;GAIG;AACH,MAAM,WAAW,WAAW;IAC1B,wDAAwD;IACxD,QAAQ,CAAC,KAAK,EAAE,SAAS,UAAU,EAAE,CAAA;IACrC,uEAAuE;IACvE,QAAQ,CAAC,KAAK,EAAE,SAAS,MAAM,EAAE,CAAA;IACjC,sDAAsD;IACtD,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAA;IAC1B;;;;;OAKG;IACH,QAAQ,CAAC,kBAAkB,EAAE,MAAM,CAAA;CACpC;AAED;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,IAAI,GAAG,WAAW,CAiBxD;AAED;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,CAAC,EAAE,WAAW,EAAE,CAAC,EAAE,WAAW,GAAG,MAAM,CAMxE"}
@@ -1,4 +1,17 @@
1
- import { Node as NodeClass } from 'ts-morph';
1
+ import { SyntaxKind, Node as NodeClass } from 'ts-morph';
2
+ // Deliberately not exhaustive over every text-bearing kind — `TemplateHead`/
3
+ // `TemplateMiddle`/`TemplateTail` (interpolated templates), `BigIntLiteral` and
4
+ // `RegularExpressionLiteral` are omitted. Every omission UNDERcounts vocabulary,
5
+ // which only makes the floor (plan 0103) more conservative — fewer pairs
6
+ // compared, never a false positive from a body that reads as emptier than it
7
+ // is. Widen this set if a real interpolated-template-heavy corpus needs it.
8
+ const TEXT_KINDS = new Set([
9
+ SyntaxKind.Identifier,
10
+ SyntaxKind.PrivateIdentifier,
11
+ SyntaxKind.StringLiteral,
12
+ SyntaxKind.NoSubstitutionTemplateLiteral,
13
+ SyntaxKind.NumericLiteral,
14
+ ]);
2
15
  /**
3
16
  * Build a structural fingerprint from a function body AST node.
4
17
  * Walks all descendants, records their SyntaxKind in order,
@@ -7,13 +20,18 @@ import { Node as NodeClass } from 'ts-morph';
7
20
  export function buildFingerprint(body) {
8
21
  const kinds = [];
9
22
  const calls = [];
23
+ const distinct = new Set();
10
24
  for (const node of body.getDescendants()) {
11
- kinds.push(node.getKind());
25
+ const kind = node.getKind();
26
+ kinds.push(kind);
12
27
  if (NodeClass.isCallExpression(node)) {
13
28
  calls.push(node.getExpression().getText().replace(/\?\./g, '.'));
14
29
  }
30
+ if (TEXT_KINDS.has(kind)) {
31
+ distinct.add(node.getText());
32
+ }
15
33
  }
16
- return { kinds, calls, nodeCount: kinds.length };
34
+ return { kinds, calls, nodeCount: kinds.length, distinctVocabulary: distinct.size };
17
35
  }
18
36
  /**
19
37
  * Compute similarity between two fingerprints.
@@ -1 +1 @@
1
- {"version":3,"file":"fingerprint.js","sourceRoot":"","sources":["../../src/smells/fingerprint.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"fingerprint.js","sourceRoot":"","sources":["../../src/smells/fingerprint.ts"],"names":[],"mappings":"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"}
@@ -1,21 +1,142 @@
1
+ import type { RuleDescription } from '@nielspeter/eess';
1
2
  import { SmellBuilder } from './smell-builder.js';
2
3
  import type { ExpressionMatcher } from '../helpers/matchers.js';
3
- import type { ArchViolation } from '../core/violation.js';
4
+ import type { ArchViolation } from '@nielspeter/eess';
4
5
  import type { ArchProject } from '../core/project.js';
5
6
  export declare class InconsistentSiblingsBuilder extends SmellBuilder {
6
7
  private _pattern?;
7
8
  constructor(project: ArchProject);
8
9
  /** The pattern that most siblings should follow. */
9
10
  forPattern(matcher: ExpressionMatcher): this;
10
- /** Check if a source file contains any function matching the pattern. */
11
- private fileMatchesPattern;
12
- /** Partition files into matching and non-matching based on the pattern. */
13
- private partitionByPattern;
11
+ /**
12
+ * Sibling files in folders large enough to be compared — plan 0096, and the
13
+ * ONE method both readers call.
14
+ *
15
+ * The `>= 2` threshold lives HERE rather than in `detect()`'s loop, which is
16
+ * what makes this a shared derivation instead of two that agree by luck.
17
+ * Filtering before the caller's sort is safe: `groupFilesByFolder()` returns
18
+ * insertion order and `detect()` sorts by folder name, a total order, so
19
+ * removing entries cannot reorder the survivors.
20
+ */
21
+ private selected;
22
+ /**
23
+ * Units this detector examined — plan 0096: sibling files in folders large
24
+ * enough to be compared.
25
+ *
26
+ * Counted in units ITERATED, never in pattern matches: a tripwire that
27
+ * examines every sibling and matches none has non-empty evidence, which is the
28
+ * 0.34.0 carve-out this must not break.
29
+ */
30
+ examinedUnits(): number;
31
+ /**
32
+ * Pure recomputation of the corpus split — never accumulated on `this`. It is
33
+ * the SINGLE source the finding's message, the gate, and the diagnostic preview
34
+ * all read, so the preview and the finding cannot diverge, and calling it twice
35
+ * cannot double-count (each call recomputes). Mirrors `examinedUnits()`: one
36
+ * derivation, not an instance field.
37
+ *
38
+ * `canFireSoon` is true when any folder is at or within ONE edit of a majority —
39
+ * such a folder can fire, so it is not inert even though it does not fire today.
40
+ *
41
+ * The GUARD lives here, not at the emit site. A preview read through this
42
+ * accessor can then never report on a rule that can fire: a healthy control
43
+ * (majority present, `editsToMajority <= 1`) gets `canFireSoon = true`, and the
44
+ * hook below returns the empty string for it. The check-time emit and the
45
+ * diagnostic preview share this one predicate by construction.
46
+ */
47
+ private inertAssessment;
14
48
  /** Build violations for non-matching files in a folder where the majority matches. */
49
+ /**
50
+ * The two description strings travel together as one `describe` — adjacent
51
+ * positional strings are the transposition the parameter cap guards against,
52
+ * and these two read alike.
53
+ */
15
54
  private buildFolderViolations;
55
+ /**
56
+ * Whether this rule states an assertion at all — the assertion gate's question.
57
+ *
58
+ * True once a condition has been added.
59
+ *
60
+ * Overrides the `TerminalBuilder` default (`true`), whose JSDoc carries the
61
+ * contract and the reason this is public rather than protected.
62
+ */
63
+ assertsSomething(): boolean;
64
+ /**
65
+ * Named by id or by what the detector checks — the inherited version says
66
+ * 'unnamed'. Uses `describe()` rather than a call-site locator for the same
67
+ * reason as `SliceRuleBuilder`: `explain --format agent` reads this field.
68
+ */
69
+ /**
70
+ * This detector counts sibling FILES, not function bodies — plan 0099.
71
+ *
72
+ * It inherited `SmellBuilder`'s noun while `examinedUnits()` sums
73
+ * `files.length`, so the message read "examined 0 function bodies" for a
74
+ * file-counting detector: the category error this plan exists to remove, in the
75
+ * family sitting directly under the override.
76
+ */
77
+ protected examinedUnitNoun(): string;
78
+ /**
79
+ * A structural description of this rule without executing it — what `explain`
80
+ * prints and what the ADR/citation gates read.
81
+ */
82
+ describeRule(): RuleDescription;
83
+ /**
84
+ * The remedy for this builder's assertion-less state, as one string.
85
+ *
86
+ * One channel, so `diagnose()`'s advice and the finding's own message cannot
87
+ * disagree. Overrides `TerminalBuilder`'s generic text with wording specific to
88
+ * what this builder is missing.
89
+ */
90
+ assertionAdvice(): string;
91
+ /** The file-selection facts `sibling-files.ts` needs, stated as a value. */
92
+ private scope;
16
93
  protected detect(): ArchViolation[];
94
+ /**
95
+ * The `DiagnosableRule` hook — public, no-arg, exactly the shape the
96
+ * interface requires. Recomputes `inertAssessment()` fresh, so `diagnose()`
97
+ * on a builder that never ran `check()` still gets truthful numbers.
98
+ */
99
+ inertAdvice(): string;
100
+ /**
101
+ * THE single derivation for both surfaces, and the guard. Returns the empty
102
+ * string unless the rule is genuinely inert (`matching > 0 && !canFireSoon &&
103
+ * !declaresEmpty()`), so a healthy control (majority present, within one edit
104
+ * of one, or declared empty) gets `''` and diagnose() reports nothing for it.
105
+ * Both `inertAdvice()` above and `detect()`'s emit call this with an
106
+ * `Assessment` — the SAME guard and the SAME message either way, so the
107
+ * preview and the finding cannot diverge.
108
+ *
109
+ * `!this.declaresEmpty()` lives HERE, not only in `inertEmitEnabled()` (review:
110
+ * architect) — a declared-empty rule reports its expiry, not the inert finding,
111
+ * on BOTH surfaces, so `diagnose()`'s preview and `check()`'s eventual failure
112
+ * name the same cause for the same rule state. Before this fix the two clauses
113
+ * were split across two functions and only the emit path read the declaration,
114
+ * so a rule carrying `.expectEmpty()` could preview one cause via `diagnose()`
115
+ * and fail with a different one via `check()` — the exact drift this shared-
116
+ * derivation design exists to make impossible "by construction".
117
+ */
118
+ private inertAdviceFor;
119
+ private inertViolation;
120
+ /**
121
+ * The pure version gate — nothing else. The `_expectEmpty` precedence
122
+ * (`!this.declaresEmpty()`) moved into `inertAdviceFor()` above, so it applies
123
+ * identically to `diagnose()`'s preview and to this emit path; this function's
124
+ * only job is "has the N+1 release happened yet."
125
+ *
126
+ * `INERT_FINDING_EMIT` is deliberately a bare, non-exported module `const` —
127
+ * not an env var, constructor option, or per-project override. The
128
+ * `bypassFilters: true` / unsuppressable design above depends on there being
129
+ * no configuration surface a future contributor could "helpfully" expose,
130
+ * which would undermine it silently.
131
+ *
132
+ * `protected`, not `private` — a test-only subclass overrides this to exercise
133
+ * the emit path (`inertViolation()`, `inertElement()`, the `detect()` branch
134
+ * that pushes them) before the N+1 flip ships, so that path is not shipped
135
+ * with zero test coverage merely because the real gate is off today (review:
136
+ * testing). See `tests/smells/inconsistent-siblings.test.ts`'s
137
+ * `EmittingSiblings` subclass.
138
+ */
139
+ protected inertEmitEnabled(): boolean;
17
140
  protected describe(): string;
18
- /** Group source files by parent folder, applying all filters. */
19
- private groupFilesByFolder;
20
141
  }
21
142
  //# sourceMappingURL=inconsistent-siblings.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"inconsistent-siblings.d.ts","sourceRoot":"","sources":["../../src/smells/inconsistent-siblings.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAA;AAGjD,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAA;AAC/D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAA;AACzD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAA;AAQrD,qBAAa,2BAA4B,SAAQ,YAAY;IAC3D,OAAO,CAAC,QAAQ,CAAC,CAAmB;gBAExB,OAAO,EAAE,WAAW;IAIhC,oDAAoD;IACpD,UAAU,CAAC,OAAO,EAAE,iBAAiB,GAAG,IAAI;IAK5C,yEAAyE;IACzE,OAAO,CAAC,kBAAkB;IAa1B,2EAA2E;IAC3E,OAAO,CAAC,kBAAkB;IAgB1B,sFAAsF;IACtF,OAAO,CAAC,qBAAqB;IAyB7B,SAAS,CAAC,MAAM,IAAI,aAAa,EAAE;IAqCnC,SAAS,CAAC,QAAQ,IAAI,MAAM;IAK5B,iEAAiE;IACjE,OAAO,CAAC,kBAAkB;CAqC3B"}
1
+ {"version":3,"file":"inconsistent-siblings.d.ts","sourceRoot":"","sources":["../../src/smells/inconsistent-siblings.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAA;AAGvD,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAA;AACjD,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAA;AAC/D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAA;AACrD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAA;AAyBrD,qBAAa,2BAA4B,SAAQ,YAAY;IAC3D,OAAO,CAAC,QAAQ,CAAC,CAAmB;gBAExB,OAAO,EAAE,WAAW;IAIhC,oDAAoD;IACpD,UAAU,CAAC,OAAO,EAAE,iBAAiB,GAAG,IAAI;IAM5C;;;;;;;;;OASG;IACH,OAAO,CAAC,QAAQ;IAMhB;;;;;;;OAOG;IACH,aAAa,IAAI,MAAM;IAIvB;;;;;;;;;;;;;;;OAeG;IACH,OAAO,CAAC,eAAe;IAuCvB,sFAAsF;IACtF;;;;OAIG;IACH,OAAO,CAAC,qBAAqB;IA8B7B;;;;;;;OAOG;IACM,gBAAgB,IAAI,OAAO;IAIpC;;;;OAIG;IACH;;;;;;;OAOG;cACgB,gBAAgB,IAAI,MAAM;IAI7C;;;OAGG;IACM,YAAY,IAAI,eAAe;IAOxC;;;;;;OAMG;IACM,eAAe,IAAI,MAAM;IAOlC,4EAA4E;IAC5E,OAAO,CAAC,KAAK;IAWb,SAAS,CAAC,MAAM,IAAI,aAAa,EAAE;IA8DnC;;;;OAIG;IACH,WAAW,IAAI,MAAM;IAMrB;;;;;;;;;;;;;;;;;OAiBG;IACH,OAAO,CAAC,cAAc;IAKtB,OAAO,CAAC,cAAc;IA2BtB;;;;;;;;;;;;;;;;;;OAkBG;IACH,SAAS,CAAC,gBAAgB,IAAI,OAAO;IAIrC,SAAS,CAAC,QAAQ,IAAI,MAAM;CAI7B"}