@opensip-tools/fitness 1.0.4

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 (461) hide show
  1. package/.turbo/turbo-build.log +4 -0
  2. package/.turbo/turbo-typecheck.log +4 -0
  3. package/LICENSE +21 -0
  4. package/dist/__tests__/gate.test.d.ts +13 -0
  5. package/dist/__tests__/gate.test.d.ts.map +1 -0
  6. package/dist/__tests__/gate.test.js +422 -0
  7. package/dist/__tests__/gate.test.js.map +1 -0
  8. package/dist/__tests__/sarif.test.d.ts +2 -0
  9. package/dist/__tests__/sarif.test.d.ts.map +1 -0
  10. package/dist/__tests__/sarif.test.js +169 -0
  11. package/dist/__tests__/sarif.test.js.map +1 -0
  12. package/dist/cli/dashboard.d.ts +6 -0
  13. package/dist/cli/dashboard.d.ts.map +1 -0
  14. package/dist/cli/dashboard.js +77 -0
  15. package/dist/cli/dashboard.js.map +1 -0
  16. package/dist/cli/fit.d.ts +37 -0
  17. package/dist/cli/fit.d.ts.map +1 -0
  18. package/dist/cli/fit.js +539 -0
  19. package/dist/cli/fit.js.map +1 -0
  20. package/dist/cli/list-checks.d.ts +6 -0
  21. package/dist/cli/list-checks.d.ts.map +1 -0
  22. package/dist/cli/list-checks.js +23 -0
  23. package/dist/cli/list-checks.js.map +1 -0
  24. package/dist/cli/list-recipes.d.ts +6 -0
  25. package/dist/cli/list-recipes.d.ts.map +1 -0
  26. package/dist/cli/list-recipes.js +31 -0
  27. package/dist/cli/list-recipes.js.map +1 -0
  28. package/dist/framework/__tests__/ast-utilities.test.d.ts +2 -0
  29. package/dist/framework/__tests__/ast-utilities.test.d.ts.map +1 -0
  30. package/dist/framework/__tests__/ast-utilities.test.js +153 -0
  31. package/dist/framework/__tests__/ast-utilities.test.js.map +1 -0
  32. package/dist/framework/__tests__/check-config.test.d.ts +2 -0
  33. package/dist/framework/__tests__/check-config.test.d.ts.map +1 -0
  34. package/dist/framework/__tests__/check-config.test.js +56 -0
  35. package/dist/framework/__tests__/check-config.test.js.map +1 -0
  36. package/dist/framework/__tests__/command-executor.test.d.ts +2 -0
  37. package/dist/framework/__tests__/command-executor.test.d.ts.map +1 -0
  38. package/dist/framework/__tests__/command-executor.test.js +71 -0
  39. package/dist/framework/__tests__/command-executor.test.js.map +1 -0
  40. package/dist/framework/__tests__/content-filter-dispatch.test.d.ts +2 -0
  41. package/dist/framework/__tests__/content-filter-dispatch.test.d.ts.map +1 -0
  42. package/dist/framework/__tests__/content-filter-dispatch.test.js +104 -0
  43. package/dist/framework/__tests__/content-filter-dispatch.test.js.map +1 -0
  44. package/dist/framework/__tests__/content-filter.test.d.ts +2 -0
  45. package/dist/framework/__tests__/content-filter.test.d.ts.map +1 -0
  46. package/dist/framework/__tests__/content-filter.test.js +126 -0
  47. package/dist/framework/__tests__/content-filter.test.js.map +1 -0
  48. package/dist/framework/__tests__/define-check.test.d.ts +2 -0
  49. package/dist/framework/__tests__/define-check.test.d.ts.map +1 -0
  50. package/dist/framework/__tests__/define-check.test.js +155 -0
  51. package/dist/framework/__tests__/define-check.test.js.map +1 -0
  52. package/dist/framework/__tests__/directive-inventory.test.d.ts +2 -0
  53. package/dist/framework/__tests__/directive-inventory.test.d.ts.map +1 -0
  54. package/dist/framework/__tests__/directive-inventory.test.js +44 -0
  55. package/dist/framework/__tests__/directive-inventory.test.js.map +1 -0
  56. package/dist/framework/__tests__/execution-context.test.d.ts +2 -0
  57. package/dist/framework/__tests__/execution-context.test.d.ts.map +1 -0
  58. package/dist/framework/__tests__/execution-context.test.js +62 -0
  59. package/dist/framework/__tests__/execution-context.test.js.map +1 -0
  60. package/dist/framework/__tests__/file-accessor.test.d.ts +2 -0
  61. package/dist/framework/__tests__/file-accessor.test.d.ts.map +1 -0
  62. package/dist/framework/__tests__/file-accessor.test.js +106 -0
  63. package/dist/framework/__tests__/file-accessor.test.js.map +1 -0
  64. package/dist/framework/__tests__/file-cache.test.d.ts +2 -0
  65. package/dist/framework/__tests__/file-cache.test.d.ts.map +1 -0
  66. package/dist/framework/__tests__/file-cache.test.js +122 -0
  67. package/dist/framework/__tests__/file-cache.test.js.map +1 -0
  68. package/dist/framework/__tests__/import-graph.test.d.ts +15 -0
  69. package/dist/framework/__tests__/import-graph.test.d.ts.map +1 -0
  70. package/dist/framework/__tests__/import-graph.test.js +164 -0
  71. package/dist/framework/__tests__/import-graph.test.js.map +1 -0
  72. package/dist/framework/__tests__/path-matcher.test.d.ts +2 -0
  73. package/dist/framework/__tests__/path-matcher.test.d.ts.map +1 -0
  74. package/dist/framework/__tests__/path-matcher.test.js +113 -0
  75. package/dist/framework/__tests__/path-matcher.test.js.map +1 -0
  76. package/dist/framework/__tests__/register-helpers.test.d.ts +2 -0
  77. package/dist/framework/__tests__/register-helpers.test.d.ts.map +1 -0
  78. package/dist/framework/__tests__/register-helpers.test.js +42 -0
  79. package/dist/framework/__tests__/register-helpers.test.js.map +1 -0
  80. package/dist/framework/__tests__/registry.test.d.ts +2 -0
  81. package/dist/framework/__tests__/registry.test.d.ts.map +1 -0
  82. package/dist/framework/__tests__/registry.test.js +208 -0
  83. package/dist/framework/__tests__/registry.test.js.map +1 -0
  84. package/dist/framework/__tests__/result-builder.test.d.ts +2 -0
  85. package/dist/framework/__tests__/result-builder.test.d.ts.map +1 -0
  86. package/dist/framework/__tests__/result-builder.test.js +153 -0
  87. package/dist/framework/__tests__/result-builder.test.js.map +1 -0
  88. package/dist/framework/__tests__/scope-resolver.test.d.ts +2 -0
  89. package/dist/framework/__tests__/scope-resolver.test.d.ts.map +1 -0
  90. package/dist/framework/__tests__/scope-resolver.test.js +140 -0
  91. package/dist/framework/__tests__/scope-resolver.test.js.map +1 -0
  92. package/dist/framework/__tests__/severity-mapping.test.d.ts +2 -0
  93. package/dist/framework/__tests__/severity-mapping.test.d.ts.map +1 -0
  94. package/dist/framework/__tests__/severity-mapping.test.js +42 -0
  95. package/dist/framework/__tests__/severity-mapping.test.js.map +1 -0
  96. package/dist/framework/__tests__/strip-literals.test.d.ts +2 -0
  97. package/dist/framework/__tests__/strip-literals.test.d.ts.map +1 -0
  98. package/dist/framework/__tests__/strip-literals.test.js +87 -0
  99. package/dist/framework/__tests__/strip-literals.test.js.map +1 -0
  100. package/dist/framework/abortable-exec.d.ts +34 -0
  101. package/dist/framework/abortable-exec.d.ts.map +1 -0
  102. package/dist/framework/abortable-exec.js +136 -0
  103. package/dist/framework/abortable-exec.js.map +1 -0
  104. package/dist/framework/ast-utilities.d.ts +41 -0
  105. package/dist/framework/ast-utilities.d.ts.map +1 -0
  106. package/dist/framework/ast-utilities.js +106 -0
  107. package/dist/framework/ast-utilities.js.map +1 -0
  108. package/dist/framework/check-config.d.ts +171 -0
  109. package/dist/framework/check-config.d.ts.map +1 -0
  110. package/dist/framework/check-config.js +114 -0
  111. package/dist/framework/check-config.js.map +1 -0
  112. package/dist/framework/check-types.d.ts +57 -0
  113. package/dist/framework/check-types.d.ts.map +1 -0
  114. package/dist/framework/check-types.js +35 -0
  115. package/dist/framework/check-types.js.map +1 -0
  116. package/dist/framework/command-executor.d.ts +25 -0
  117. package/dist/framework/command-executor.d.ts.map +1 -0
  118. package/dist/framework/command-executor.js +63 -0
  119. package/dist/framework/command-executor.js.map +1 -0
  120. package/dist/framework/constants.d.ts +9 -0
  121. package/dist/framework/constants.d.ts.map +1 -0
  122. package/dist/framework/constants.js +16 -0
  123. package/dist/framework/constants.js.map +1 -0
  124. package/dist/framework/content-filter.d.ts +33 -0
  125. package/dist/framework/content-filter.d.ts.map +1 -0
  126. package/dist/framework/content-filter.js +236 -0
  127. package/dist/framework/content-filter.js.map +1 -0
  128. package/dist/framework/define-check.d.ts +38 -0
  129. package/dist/framework/define-check.d.ts.map +1 -0
  130. package/dist/framework/define-check.js +252 -0
  131. package/dist/framework/define-check.js.map +1 -0
  132. package/dist/framework/directive-inventory.d.ts +34 -0
  133. package/dist/framework/directive-inventory.d.ts.map +1 -0
  134. package/dist/framework/directive-inventory.js +77 -0
  135. package/dist/framework/directive-inventory.js.map +1 -0
  136. package/dist/framework/directive-parsing.d.ts +20 -0
  137. package/dist/framework/directive-parsing.d.ts.map +1 -0
  138. package/dist/framework/directive-parsing.js +121 -0
  139. package/dist/framework/directive-parsing.js.map +1 -0
  140. package/dist/framework/execution-context.d.ts +95 -0
  141. package/dist/framework/execution-context.d.ts.map +1 -0
  142. package/dist/framework/execution-context.js +122 -0
  143. package/dist/framework/execution-context.js.map +1 -0
  144. package/dist/framework/file-accessor.d.ts +20 -0
  145. package/dist/framework/file-accessor.d.ts.map +1 -0
  146. package/dist/framework/file-accessor.js +122 -0
  147. package/dist/framework/file-accessor.js.map +1 -0
  148. package/dist/framework/file-cache.d.ts +70 -0
  149. package/dist/framework/file-cache.d.ts.map +1 -0
  150. package/dist/framework/file-cache.js +178 -0
  151. package/dist/framework/file-cache.js.map +1 -0
  152. package/dist/framework/file-type-filter.d.ts +11 -0
  153. package/dist/framework/file-type-filter.d.ts.map +1 -0
  154. package/dist/framework/file-type-filter.js +21 -0
  155. package/dist/framework/file-type-filter.js.map +1 -0
  156. package/dist/framework/ignore-processing.d.ts +22 -0
  157. package/dist/framework/ignore-processing.d.ts.map +1 -0
  158. package/dist/framework/ignore-processing.js +241 -0
  159. package/dist/framework/ignore-processing.js.map +1 -0
  160. package/dist/framework/import-graph.d.ts +51 -0
  161. package/dist/framework/import-graph.d.ts.map +1 -0
  162. package/dist/framework/import-graph.js +216 -0
  163. package/dist/framework/import-graph.js.map +1 -0
  164. package/dist/framework/memory-profiler.d.ts +53 -0
  165. package/dist/framework/memory-profiler.d.ts.map +1 -0
  166. package/dist/framework/memory-profiler.js +92 -0
  167. package/dist/framework/memory-profiler.js.map +1 -0
  168. package/dist/framework/parse-cache.d.ts +23 -0
  169. package/dist/framework/parse-cache.d.ts.map +1 -0
  170. package/dist/framework/parse-cache.js +37 -0
  171. package/dist/framework/parse-cache.js.map +1 -0
  172. package/dist/framework/path-matcher.d.ts +86 -0
  173. package/dist/framework/path-matcher.d.ts.map +1 -0
  174. package/dist/framework/path-matcher.js +138 -0
  175. package/dist/framework/path-matcher.js.map +1 -0
  176. package/dist/framework/register-helpers.d.ts +10 -0
  177. package/dist/framework/register-helpers.d.ts.map +1 -0
  178. package/dist/framework/register-helpers.js +17 -0
  179. package/dist/framework/register-helpers.js.map +1 -0
  180. package/dist/framework/registry.d.ts +41 -0
  181. package/dist/framework/registry.d.ts.map +1 -0
  182. package/dist/framework/registry.js +103 -0
  183. package/dist/framework/registry.js.map +1 -0
  184. package/dist/framework/result-builder.d.ts +74 -0
  185. package/dist/framework/result-builder.d.ts.map +1 -0
  186. package/dist/framework/result-builder.js +154 -0
  187. package/dist/framework/result-builder.js.map +1 -0
  188. package/dist/framework/scope-resolver.d.ts +23 -0
  189. package/dist/framework/scope-resolver.d.ts.map +1 -0
  190. package/dist/framework/scope-resolver.js +201 -0
  191. package/dist/framework/scope-resolver.js.map +1 -0
  192. package/dist/framework/severity-mapping.d.ts +13 -0
  193. package/dist/framework/severity-mapping.d.ts.map +1 -0
  194. package/dist/framework/severity-mapping.js +51 -0
  195. package/dist/framework/severity-mapping.js.map +1 -0
  196. package/dist/framework/strip-literals.d.ts +48 -0
  197. package/dist/framework/strip-literals.d.ts.map +1 -0
  198. package/dist/framework/strip-literals.js +188 -0
  199. package/dist/framework/strip-literals.js.map +1 -0
  200. package/dist/gate.d.ts +74 -0
  201. package/dist/gate.d.ts.map +1 -0
  202. package/dist/gate.js +257 -0
  203. package/dist/gate.js.map +1 -0
  204. package/dist/index.d.ts +47 -0
  205. package/dist/index.d.ts.map +1 -0
  206. package/dist/index.js +51 -0
  207. package/dist/index.js.map +1 -0
  208. package/dist/plugins/__tests__/check-package-discovery.test.d.ts +2 -0
  209. package/dist/plugins/__tests__/check-package-discovery.test.d.ts.map +1 -0
  210. package/dist/plugins/__tests__/check-package-discovery.test.js +170 -0
  211. package/dist/plugins/__tests__/check-package-discovery.test.js.map +1 -0
  212. package/dist/plugins/__tests__/lang-domain.test.d.ts +2 -0
  213. package/dist/plugins/__tests__/lang-domain.test.d.ts.map +1 -0
  214. package/dist/plugins/__tests__/lang-domain.test.js +171 -0
  215. package/dist/plugins/__tests__/lang-domain.test.js.map +1 -0
  216. package/dist/plugins/__tests__/loader.test.d.ts +2 -0
  217. package/dist/plugins/__tests__/loader.test.d.ts.map +1 -0
  218. package/dist/plugins/__tests__/loader.test.js +194 -0
  219. package/dist/plugins/__tests__/loader.test.js.map +1 -0
  220. package/dist/plugins/check-package-discovery.d.ts +73 -0
  221. package/dist/plugins/check-package-discovery.d.ts.map +1 -0
  222. package/dist/plugins/check-package-discovery.js +212 -0
  223. package/dist/plugins/check-package-discovery.js.map +1 -0
  224. package/dist/plugins/loader.d.ts +31 -0
  225. package/dist/plugins/loader.d.ts.map +1 -0
  226. package/dist/plugins/loader.js +290 -0
  227. package/dist/plugins/loader.js.map +1 -0
  228. package/dist/plugins/types.d.ts +23 -0
  229. package/dist/plugins/types.d.ts.map +1 -0
  230. package/dist/plugins/types.js +9 -0
  231. package/dist/plugins/types.js.map +1 -0
  232. package/dist/recipes/__tests__/built-in-recipes.test.d.ts +2 -0
  233. package/dist/recipes/__tests__/built-in-recipes.test.d.ts.map +1 -0
  234. package/dist/recipes/__tests__/built-in-recipes.test.js +93 -0
  235. package/dist/recipes/__tests__/built-in-recipes.test.js.map +1 -0
  236. package/dist/recipes/__tests__/check-config.test.d.ts +5 -0
  237. package/dist/recipes/__tests__/check-config.test.d.ts.map +1 -0
  238. package/dist/recipes/__tests__/check-config.test.js +37 -0
  239. package/dist/recipes/__tests__/check-config.test.js.map +1 -0
  240. package/dist/recipes/__tests__/check-resolution.test.d.ts +2 -0
  241. package/dist/recipes/__tests__/check-resolution.test.d.ts.map +1 -0
  242. package/dist/recipes/__tests__/check-resolution.test.js +135 -0
  243. package/dist/recipes/__tests__/check-resolution.test.js.map +1 -0
  244. package/dist/recipes/__tests__/registry.test.d.ts +2 -0
  245. package/dist/recipes/__tests__/registry.test.d.ts.map +1 -0
  246. package/dist/recipes/__tests__/registry.test.js +97 -0
  247. package/dist/recipes/__tests__/registry.test.js.map +1 -0
  248. package/dist/recipes/__tests__/retry.test.d.ts +2 -0
  249. package/dist/recipes/__tests__/retry.test.d.ts.map +1 -0
  250. package/dist/recipes/__tests__/retry.test.js +75 -0
  251. package/dist/recipes/__tests__/retry.test.js.map +1 -0
  252. package/dist/recipes/__tests__/service.test.d.ts +11 -0
  253. package/dist/recipes/__tests__/service.test.d.ts.map +1 -0
  254. package/dist/recipes/__tests__/service.test.js +482 -0
  255. package/dist/recipes/__tests__/service.test.js.map +1 -0
  256. package/dist/recipes/built-in-recipes.d.ts +14 -0
  257. package/dist/recipes/built-in-recipes.d.ts.map +1 -0
  258. package/dist/recipes/built-in-recipes.js +247 -0
  259. package/dist/recipes/built-in-recipes.js.map +1 -0
  260. package/dist/recipes/check-config.d.ts +40 -0
  261. package/dist/recipes/check-config.d.ts.map +1 -0
  262. package/dist/recipes/check-config.js +61 -0
  263. package/dist/recipes/check-config.js.map +1 -0
  264. package/dist/recipes/check-resolution.d.ts +21 -0
  265. package/dist/recipes/check-resolution.d.ts.map +1 -0
  266. package/dist/recipes/check-resolution.js +121 -0
  267. package/dist/recipes/check-resolution.js.map +1 -0
  268. package/dist/recipes/check-result-processor.d.ts +51 -0
  269. package/dist/recipes/check-result-processor.d.ts.map +1 -0
  270. package/dist/recipes/check-result-processor.js +158 -0
  271. package/dist/recipes/check-result-processor.js.map +1 -0
  272. package/dist/recipes/parallel-execution.d.ts +33 -0
  273. package/dist/recipes/parallel-execution.d.ts.map +1 -0
  274. package/dist/recipes/parallel-execution.js +142 -0
  275. package/dist/recipes/parallel-execution.js.map +1 -0
  276. package/dist/recipes/registry.d.ts +81 -0
  277. package/dist/recipes/registry.d.ts.map +1 -0
  278. package/dist/recipes/registry.js +131 -0
  279. package/dist/recipes/registry.js.map +1 -0
  280. package/dist/recipes/retry.d.ts +25 -0
  281. package/dist/recipes/retry.d.ts.map +1 -0
  282. package/dist/recipes/retry.js +44 -0
  283. package/dist/recipes/retry.js.map +1 -0
  284. package/dist/recipes/sequential-execution.d.ts +10 -0
  285. package/dist/recipes/sequential-execution.d.ts.map +1 -0
  286. package/dist/recipes/sequential-execution.js +122 -0
  287. package/dist/recipes/sequential-execution.js.map +1 -0
  288. package/dist/recipes/service-types.d.ts +84 -0
  289. package/dist/recipes/service-types.d.ts.map +1 -0
  290. package/dist/recipes/service-types.js +8 -0
  291. package/dist/recipes/service-types.js.map +1 -0
  292. package/dist/recipes/service.d.ts +71 -0
  293. package/dist/recipes/service.d.ts.map +1 -0
  294. package/dist/recipes/service.js +331 -0
  295. package/dist/recipes/service.js.map +1 -0
  296. package/dist/recipes/types.d.ts +154 -0
  297. package/dist/recipes/types.d.ts.map +1 -0
  298. package/dist/recipes/types.js +54 -0
  299. package/dist/recipes/types.js.map +1 -0
  300. package/dist/sarif.d.ts +34 -0
  301. package/dist/sarif.d.ts.map +1 -0
  302. package/dist/sarif.js +192 -0
  303. package/dist/sarif.js.map +1 -0
  304. package/dist/signalers/__tests__/loader.test.d.ts +2 -0
  305. package/dist/signalers/__tests__/loader.test.d.ts.map +1 -0
  306. package/dist/signalers/__tests__/loader.test.js +74 -0
  307. package/dist/signalers/__tests__/loader.test.js.map +1 -0
  308. package/dist/signalers/index.d.ts +8 -0
  309. package/dist/signalers/index.d.ts.map +1 -0
  310. package/dist/signalers/index.js +9 -0
  311. package/dist/signalers/index.js.map +1 -0
  312. package/dist/signalers/loader.d.ts +24 -0
  313. package/dist/signalers/loader.d.ts.map +1 -0
  314. package/dist/signalers/loader.js +108 -0
  315. package/dist/signalers/loader.js.map +1 -0
  316. package/dist/signalers/schema.d.ts +288 -0
  317. package/dist/signalers/schema.d.ts.map +1 -0
  318. package/dist/signalers/schema.js +99 -0
  319. package/dist/signalers/schema.js.map +1 -0
  320. package/dist/signalers/types.d.ts +13 -0
  321. package/dist/signalers/types.d.ts.map +1 -0
  322. package/dist/signalers/types.js +5 -0
  323. package/dist/signalers/types.js.map +1 -0
  324. package/dist/targets/__tests__/loader.test.d.ts +2 -0
  325. package/dist/targets/__tests__/loader.test.d.ts.map +1 -0
  326. package/dist/targets/__tests__/loader.test.js +127 -0
  327. package/dist/targets/__tests__/loader.test.js.map +1 -0
  328. package/dist/targets/__tests__/resolver.test.d.ts +2 -0
  329. package/dist/targets/__tests__/resolver.test.d.ts.map +1 -0
  330. package/dist/targets/__tests__/resolver.test.js +54 -0
  331. package/dist/targets/__tests__/resolver.test.js.map +1 -0
  332. package/dist/targets/__tests__/target-registry.test.d.ts +2 -0
  333. package/dist/targets/__tests__/target-registry.test.d.ts.map +1 -0
  334. package/dist/targets/__tests__/target-registry.test.js +89 -0
  335. package/dist/targets/__tests__/target-registry.test.js.map +1 -0
  336. package/dist/targets/index.d.ts +10 -0
  337. package/dist/targets/index.d.ts.map +1 -0
  338. package/dist/targets/index.js +12 -0
  339. package/dist/targets/index.js.map +1 -0
  340. package/dist/targets/loader.d.ts +19 -0
  341. package/dist/targets/loader.d.ts.map +1 -0
  342. package/dist/targets/loader.js +159 -0
  343. package/dist/targets/loader.js.map +1 -0
  344. package/dist/targets/resolver.d.ts +19 -0
  345. package/dist/targets/resolver.d.ts.map +1 -0
  346. package/dist/targets/resolver.js +37 -0
  347. package/dist/targets/resolver.js.map +1 -0
  348. package/dist/targets/target-registry.d.ts +61 -0
  349. package/dist/targets/target-registry.d.ts.map +1 -0
  350. package/dist/targets/target-registry.js +93 -0
  351. package/dist/targets/target-registry.js.map +1 -0
  352. package/dist/targets/types.d.ts +85 -0
  353. package/dist/targets/types.d.ts.map +1 -0
  354. package/dist/targets/types.js +5 -0
  355. package/dist/targets/types.js.map +1 -0
  356. package/dist/tool.d.ts +17 -0
  357. package/dist/tool.d.ts.map +1 -0
  358. package/dist/tool.js +282 -0
  359. package/dist/tool.js.map +1 -0
  360. package/dist/types/findings.d.ts +117 -0
  361. package/dist/types/findings.d.ts.map +1 -0
  362. package/dist/types/findings.js +93 -0
  363. package/dist/types/findings.js.map +1 -0
  364. package/dist/types/severity.d.ts +15 -0
  365. package/dist/types/severity.d.ts.map +1 -0
  366. package/dist/types/severity.js +36 -0
  367. package/dist/types/severity.js.map +1 -0
  368. package/package.json +45 -0
  369. package/src/__tests__/gate.test.ts +537 -0
  370. package/src/__tests__/sarif.test.ts +201 -0
  371. package/src/cli/dashboard.ts +93 -0
  372. package/src/cli/fit.ts +612 -0
  373. package/src/cli/list-checks.ts +32 -0
  374. package/src/cli/list-recipes.ts +38 -0
  375. package/src/framework/__tests__/ast-utilities.test.ts +157 -0
  376. package/src/framework/__tests__/check-config.test.ts +65 -0
  377. package/src/framework/__tests__/command-executor.test.ts +79 -0
  378. package/src/framework/__tests__/content-filter-dispatch.test.ts +132 -0
  379. package/src/framework/__tests__/content-filter.test.ts +136 -0
  380. package/src/framework/__tests__/define-check.test.ts +180 -0
  381. package/src/framework/__tests__/directive-inventory.test.ts +53 -0
  382. package/src/framework/__tests__/execution-context.test.ts +80 -0
  383. package/src/framework/__tests__/file-accessor.test.ts +121 -0
  384. package/src/framework/__tests__/file-cache.test.ts +142 -0
  385. package/src/framework/__tests__/import-graph.test.ts +282 -0
  386. package/src/framework/__tests__/path-matcher.test.ts +130 -0
  387. package/src/framework/__tests__/register-helpers.test.ts +51 -0
  388. package/src/framework/__tests__/registry.test.ts +243 -0
  389. package/src/framework/__tests__/result-builder.test.ts +178 -0
  390. package/src/framework/__tests__/scope-resolver.test.ts +208 -0
  391. package/src/framework/__tests__/severity-mapping.test.ts +50 -0
  392. package/src/framework/__tests__/strip-literals.test.ts +109 -0
  393. package/src/framework/abortable-exec.ts +177 -0
  394. package/src/framework/ast-utilities.ts +112 -0
  395. package/src/framework/check-config.ts +339 -0
  396. package/src/framework/check-types.ts +77 -0
  397. package/src/framework/command-executor.ts +100 -0
  398. package/src/framework/constants.ts +16 -0
  399. package/src/framework/content-filter.ts +288 -0
  400. package/src/framework/define-check.ts +336 -0
  401. package/src/framework/directive-inventory.ts +110 -0
  402. package/src/framework/directive-parsing.ts +152 -0
  403. package/src/framework/execution-context.ts +247 -0
  404. package/src/framework/file-accessor.ts +171 -0
  405. package/src/framework/file-cache.ts +220 -0
  406. package/src/framework/file-type-filter.ts +25 -0
  407. package/src/framework/ignore-processing.ts +350 -0
  408. package/src/framework/import-graph.ts +280 -0
  409. package/src/framework/memory-profiler.ts +145 -0
  410. package/src/framework/parse-cache.ts +38 -0
  411. package/src/framework/path-matcher.ts +191 -0
  412. package/src/framework/register-helpers.ts +20 -0
  413. package/src/framework/registry.ts +125 -0
  414. package/src/framework/result-builder.ts +225 -0
  415. package/src/framework/scope-resolver.ts +262 -0
  416. package/src/framework/severity-mapping.ts +56 -0
  417. package/src/framework/strip-literals.ts +200 -0
  418. package/src/gate.ts +337 -0
  419. package/src/index.ts +110 -0
  420. package/src/plugins/__tests__/check-package-discovery.test.ts +204 -0
  421. package/src/plugins/__tests__/lang-domain.test.ts +198 -0
  422. package/src/plugins/__tests__/loader.test.ts +226 -0
  423. package/src/plugins/check-package-discovery.ts +242 -0
  424. package/src/plugins/loader.ts +327 -0
  425. package/src/plugins/types.ts +25 -0
  426. package/src/recipes/__tests__/built-in-recipes.test.ts +107 -0
  427. package/src/recipes/__tests__/check-config.test.ts +51 -0
  428. package/src/recipes/__tests__/check-resolution.test.ts +185 -0
  429. package/src/recipes/__tests__/registry.test.ts +115 -0
  430. package/src/recipes/__tests__/retry.test.ts +83 -0
  431. package/src/recipes/__tests__/service.test.ts +572 -0
  432. package/src/recipes/built-in-recipes.ts +273 -0
  433. package/src/recipes/check-config.ts +64 -0
  434. package/src/recipes/check-resolution.ts +169 -0
  435. package/src/recipes/check-result-processor.ts +258 -0
  436. package/src/recipes/parallel-execution.ts +220 -0
  437. package/src/recipes/registry.ts +192 -0
  438. package/src/recipes/retry.ts +69 -0
  439. package/src/recipes/sequential-execution.ts +139 -0
  440. package/src/recipes/service-types.ts +105 -0
  441. package/src/recipes/service.ts +400 -0
  442. package/src/recipes/types.ts +247 -0
  443. package/src/sarif.ts +232 -0
  444. package/src/signalers/__tests__/loader.test.ts +99 -0
  445. package/src/signalers/index.ts +9 -0
  446. package/src/signalers/loader.ts +141 -0
  447. package/src/signalers/schema.ts +117 -0
  448. package/src/signalers/types.ts +15 -0
  449. package/src/targets/__tests__/loader.test.ts +170 -0
  450. package/src/targets/__tests__/resolver.test.ts +74 -0
  451. package/src/targets/__tests__/target-registry.test.ts +103 -0
  452. package/src/targets/index.ts +13 -0
  453. package/src/targets/loader.ts +214 -0
  454. package/src/targets/resolver.ts +44 -0
  455. package/src/targets/target-registry.ts +111 -0
  456. package/src/targets/types.ts +89 -0
  457. package/src/tool.ts +302 -0
  458. package/src/types/findings.ts +239 -0
  459. package/src/types/severity.ts +39 -0
  460. package/tsconfig.json +8 -0
  461. package/vitest.config.ts +33 -0
@@ -0,0 +1,288 @@
1
+ // @fitness-ignore-file file-length-limits -- framework/content-filter complexity requires single-file cohesion
2
+ /**
3
+ * @fileoverview TypeScript scanner-based content filtering
4
+ *
5
+ * Uses the TypeScript scanner (not full AST parser) to identify string literal
6
+ * and comment regions. String content is replaced with spaces of equal length,
7
+ * preserving line/column positions for accurate violation reporting.
8
+ */
9
+
10
+ import { logger } from '@opensip-tools/core'
11
+ import ts from 'typescript'
12
+
13
+
14
+ // =============================================================================
15
+ // TYPES
16
+ // =============================================================================
17
+
18
+ /** Content processed by the TypeScript scanner with string/comment region tracking */
19
+ export interface FilteredContent {
20
+ /** Content with string literals replaced by whitespace of equal length */
21
+ readonly code: string
22
+ /**
23
+ * Content with both string literals AND comments replaced by whitespace
24
+ * of equal length. Use when a check pattern-matches identifiers via regex
25
+ * and would otherwise false-positive on banned-call references that
26
+ * appear in JSDoc / line / block comments documenting the rule
27
+ * (e.g. ``"Replace getDatabase() with the constructor StoreDeps"`` inside
28
+ * a doc string).
29
+ */
30
+ readonly codeNoComments: string
31
+ /** Original content (unchanged) */
32
+ readonly raw: string
33
+ /** Set of line numbers (1-based) that are entirely inside comments */
34
+ readonly commentLines: ReadonlySet<number>
35
+ /** Check if a (1-based line, 0-based column) position is inside a string literal */
36
+ readonly isInString: (line: number, column: number) => boolean
37
+ /** Check if a (1-based line, 0-based column) position is inside a comment */
38
+ readonly isInComment: (line: number, column: number) => boolean
39
+ }
40
+
41
+ /** A region in the source text defined by byte offsets */
42
+ interface Region {
43
+ readonly start: number
44
+ readonly end: number
45
+ }
46
+
47
+ // =============================================================================
48
+ // HELPERS
49
+ // =============================================================================
50
+
51
+ /** Build an array of byte offsets where each line starts. */
52
+ function buildLineStarts(content: string): number[] {
53
+ const lineStarts: number[] = [0]
54
+ // eslint-disable-next-line unicorn/no-for-loop -- offset-bearing scan: stores UTF-16 indexes
55
+ for (let i = 0; i < content.length; i++) {
56
+ if (content[i] === '\n') lineStarts.push(i + 1)
57
+ }
58
+ return lineStarts
59
+ }
60
+
61
+ /**
62
+ * Build a set of 1-based line numbers from a list of regions.
63
+ * A line is included if any part of it falls within a region.
64
+ */
65
+ function linesToSet(content: string, regions: readonly Region[]): ReadonlySet<number> {
66
+ if (regions.length === 0) return new Set()
67
+
68
+ const lineStarts = buildLineStarts(content)
69
+
70
+ const result = new Set<number>()
71
+ for (const region of regions) {
72
+ for (let lineIdx = 0; lineIdx < lineStarts.length; lineIdx++) {
73
+ const lineStart = lineStarts[lineIdx]
74
+ const lineEnd = lineIdx + 1 < lineStarts.length ? lineStarts[lineIdx + 1] - 1 : content.length
75
+ if (lineStart > region.end) break
76
+ if (lineEnd >= region.start) {
77
+ result.add(lineIdx + 1) // 1-based
78
+ }
79
+ }
80
+ }
81
+ return result
82
+ }
83
+
84
+ /**
85
+ * Check if a (1-based line, 0-based column) offset falls within any region.
86
+ */
87
+ function isInRegions(content: string, regions: readonly Region[], line: number, column: number): boolean {
88
+ if (regions.length === 0) return false
89
+
90
+ // Convert line/column to byte offset
91
+ let currentLine = 1
92
+ let lineStart = 0
93
+ // eslint-disable-next-line unicorn/no-for-loop -- offset-bearing scan: captures UTF-16 line start
94
+ for (let i = 0; i < content.length; i++) {
95
+ if (currentLine === line) {
96
+ lineStart = i
97
+ break
98
+ }
99
+ if (content[i] === '\n') currentLine++
100
+ }
101
+ if (currentLine !== line) return false
102
+
103
+ const offset = lineStart + column
104
+ for (const region of regions) {
105
+ if (offset >= region.start && offset < region.end) return true
106
+ }
107
+ return false
108
+ }
109
+
110
+ /**
111
+ * Replace characters in the given range with spaces, preserving newlines.
112
+ * Records the range as a string region.
113
+ */
114
+ function replaceCharsInRange(chars: string[], start: number, end: number, stringRegions: Region[]): void {
115
+ stringRegions.push({ start, end })
116
+ for (let i = start; i < end; i++) {
117
+ if (chars[i] !== '\n') chars[i] = ' '
118
+ }
119
+ }
120
+
121
+ // =============================================================================
122
+ // MAIN
123
+ // =============================================================================
124
+
125
+ /**
126
+ * Scan content using TypeScript's scanner to identify string and comment regions.
127
+ *
128
+ * String literals are replaced with spaces of equal length, preserving
129
+ * line/column positions. Comments are tracked but not removed (directives
130
+ * live in comments and must be preserved).
131
+ */
132
+ // Module-level cache to avoid re-running the TS scanner on the same content.
133
+ // Bounded by an idle timer (10 min, matching parse-cache.ts) so long-lived
134
+ // embedders don't accumulate cached filter results across runs forever. The
135
+ // timer resets each time filterContent runs, so an active session never
136
+ // loses its cache.
137
+ const FILTER_CACHE_IDLE_TIMEOUT_MS = 10 * 60 * 1000
138
+ const filterCache = new Map<string, FilteredContent>()
139
+ let filterCacheIdleTimer: ReturnType<typeof setTimeout> | null = null
140
+
141
+ function scheduleFilterCacheClear(): void {
142
+ if (filterCacheIdleTimer) clearTimeout(filterCacheIdleTimer)
143
+ filterCacheIdleTimer = setTimeout(() => {
144
+ filterCache.clear()
145
+ filterCacheIdleTimer = null
146
+ }, FILTER_CACHE_IDLE_TIMEOUT_MS)
147
+ filterCacheIdleTimer.unref()
148
+ }
149
+
150
+ /** Clear the filter cache (call between runs or on memory pressure) */
151
+ export function clearFilterCache(): void {
152
+ filterCache.clear()
153
+ if (filterCacheIdleTimer) {
154
+ clearTimeout(filterCacheIdleTimer)
155
+ filterCacheIdleTimer = null
156
+ }
157
+ }
158
+
159
+ export function filterContent(content: string): FilteredContent {
160
+ const cached = filterCache.get(content)
161
+ if (cached) {
162
+ scheduleFilterCacheClear()
163
+ return cached
164
+ }
165
+
166
+ try {
167
+ const result = filterContentImpl(content)
168
+ filterCache.set(content, result)
169
+ scheduleFilterCacheClear()
170
+ return result
171
+ } catch {
172
+ // Graceful degradation — return raw content if scanner fails
173
+ logger.debug('Content filter fell back to raw content', { evt: 'fitness.content_filter.fallback', module: 'fitness:framework' })
174
+ const fallback: FilteredContent = {
175
+ code: content,
176
+ codeNoComments: content,
177
+ raw: content,
178
+ commentLines: new Set(),
179
+ isInString: () => false,
180
+ isInComment: () => false,
181
+ }
182
+ filterCache.set(content, fallback)
183
+ return fallback
184
+ }
185
+ }
186
+
187
+ // eslint-disable-next-line sonarjs/cognitive-complexity -- TS scanner driver: token-by-token loop with per-kind handling; flatter shape would scatter token classification
188
+ function filterContentImpl(content: string): FilteredContent {
189
+ const scanner = ts.createScanner(ts.ScriptTarget.Latest, false, ts.LanguageVariant.Standard, content)
190
+
191
+ const stringRegions: Region[] = []
192
+ const commentRegions: Region[] = []
193
+ const chars = [...content]
194
+
195
+ // Depth counter, not a boolean — a `${ `inner` }` construct nests two templates
196
+ // and each `}` that closes a template-expression must be rescanned. A plain
197
+ // boolean flipped off by the inner TemplateTail would leave the outer unrescanned
198
+ // and desync the scanner for the rest of the file (which silently wipes real
199
+ // code to whitespace). Incremented at TemplateHead, decremented at TemplateTail.
200
+ let templateDepth = 0
201
+
202
+
203
+ while (true) {
204
+ let token = scanner.scan()
205
+ // @fitness-ignore-next-line unsafe-secret-comparison -- comparing TypeScript SyntaxKind enum, not a secret
206
+ if (token === ts.SyntaxKind.EndOfFileToken) break
207
+
208
+ // After a CloseBraceToken inside ANY template expression, rescan to get TemplateMiddle/TemplateTail
209
+ // @fitness-ignore-next-line unsafe-secret-comparison -- comparing TypeScript SyntaxKind enum, not a secret
210
+ if (token === ts.SyntaxKind.CloseBraceToken && templateDepth > 0) {
211
+ token = scanner.reScanTemplateToken(false)
212
+ }
213
+
214
+ const start = scanner.getTokenStart()
215
+ const end = scanner.getTokenEnd()
216
+
217
+ switch (token) {
218
+ case ts.SyntaxKind.StringLiteral:
219
+ case ts.SyntaxKind.NoSubstitutionTemplateLiteral: {
220
+ // Replace content inside quotes/backticks (keep delimiters)
221
+ replaceCharsInRange(chars, start + 1, end - 1, stringRegions)
222
+ break
223
+ }
224
+
225
+ case ts.SyntaxKind.TemplateHead: {
226
+ // `text ${ — replace text between ` and ${
227
+ templateDepth++
228
+ replaceCharsInRange(chars, start + 1, end - 2, stringRegions)
229
+ break
230
+ }
231
+
232
+ case ts.SyntaxKind.TemplateMiddle: {
233
+ // }text ${ — replace text between } and ${
234
+ replaceCharsInRange(chars, start + 1, end - 2, stringRegions)
235
+ break
236
+ }
237
+
238
+ case ts.SyntaxKind.TemplateTail: {
239
+ // }text` — replace text between } and `
240
+ templateDepth--
241
+ replaceCharsInRange(chars, start + 1, end - 1, stringRegions)
242
+ break
243
+ }
244
+
245
+ case ts.SyntaxKind.SingleLineCommentTrivia:
246
+ case ts.SyntaxKind.MultiLineCommentTrivia: {
247
+ // Track comment regions but don't modify content
248
+ commentRegions.push({ start, end })
249
+ break
250
+ }
251
+
252
+ // RegularExpressionLiteral — leave unchanged, regex is code
253
+ default: {
254
+ break
255
+ }
256
+ }
257
+ }
258
+
259
+ const code = chars.join('')
260
+ const commentLines = linesToSet(content, commentRegions)
261
+
262
+ // Compute `codeNoComments` by additionally replacing comment regions
263
+ // with whitespace. Done as a second pass on a fresh array so `code`
264
+ // (strings-stripped only) and `codeNoComments` (strings + comments
265
+ // stripped) remain available — most checks want one or the other,
266
+ // not both.
267
+ const charsNoComments = [...content]
268
+ for (const region of stringRegions) {
269
+ for (let i = region.start; i < region.end; i++) {
270
+ if (charsNoComments[i] !== '\n') charsNoComments[i] = ' '
271
+ }
272
+ }
273
+ for (const region of commentRegions) {
274
+ for (let i = region.start; i < region.end; i++) {
275
+ if (charsNoComments[i] !== '\n') charsNoComments[i] = ' '
276
+ }
277
+ }
278
+ const codeNoComments = charsNoComments.join('')
279
+
280
+ return {
281
+ code,
282
+ codeNoComments,
283
+ raw: content,
284
+ commentLines,
285
+ isInString: (line, column) => isInRegions(content, stringRegions, line, column),
286
+ isInComment: (line, column) => isInRegions(content, commentRegions, line, column),
287
+ }
288
+ }
@@ -0,0 +1,336 @@
1
+ // @fitness-ignore-file module-coupling-metrics -- central orchestration module with necessary coupling
2
+ // @fitness-ignore-file null-safety -- ResultBuilder.create() returns a fluent builder; .totalItems().filesScanned() chain is always safe
3
+ /**
4
+ * @fileoverview defineCheck - Unified check definition API
5
+ *
6
+ * The main API for creating fitness checks. Supports three modes:
7
+ * - analyze: Per-file analysis with content and path
8
+ * - analyzeAll: Multi-file analysis with lazy loading FileAccessor
9
+ * - command: External tool execution with output parsing
10
+ *
11
+ * Check authors return CheckViolation[]. The framework converts each
12
+ * CheckViolation into a universal Signal via createSignal().
13
+ */
14
+
15
+ import { logger , SystemError , createSignal , applyContentFilter } from '@opensip-tools/core'
16
+
17
+
18
+
19
+ import {
20
+ getAnalysisMode,
21
+ isAnalyzeConfig,
22
+ isAnalyzeAllConfig,
23
+ isCommandConfig,
24
+ validateCheckConfig,
25
+ } from './check-config.js'
26
+ import { executeCommand } from './command-executor.js'
27
+ import { CheckAbortedError, createExecutionContext } from './execution-context.js'
28
+ import { createFileAccessor } from './file-accessor.js'
29
+ import { filterFilesByType } from './file-type-filter.js'
30
+ import { filterSignalsByDirectives, buildFilteredResult } from './ignore-processing.js'
31
+ import { PathMatcher } from './path-matcher.js'
32
+ import { ResultBuilder } from './result-builder.js'
33
+ import { mapFindingSeverity, mapTagsToSignalCategory } from './severity-mapping.js'
34
+
35
+ import type {
36
+ UnifiedCheckConfig,
37
+ CheckViolation,
38
+ AnalyzeCheckConfig,
39
+ AnalyzeAllCheckConfig,
40
+ CommandCheckConfig,
41
+ } from './check-config.js'
42
+ import type { Check } from './check-types.js'
43
+ import type { ExecutionContext, RunOptions } from './execution-context.js'
44
+ import type { CheckResult } from '../types/findings.js'
45
+ import type { Signal } from '@opensip-tools/core'
46
+
47
+ // =============================================================================
48
+ // VIOLATION → SIGNAL CONVERSION
49
+ // =============================================================================
50
+
51
+ function toSignal(
52
+ violation: CheckViolation,
53
+ checkSlug: string,
54
+ checkTags: readonly string[],
55
+ defaultFilePath?: string,
56
+ provider = 'opensip',
57
+ ): Signal {
58
+ const filePath = violation.filePath ?? defaultFilePath ?? ''
59
+ return createSignal({
60
+ source: 'fitness',
61
+ provider,
62
+ severity: mapFindingSeverity(violation.severity),
63
+ category: mapTagsToSignalCategory(checkTags),
64
+ ruleId: `fit:${checkSlug}`,
65
+ message: violation.message,
66
+ suggestion: violation.suggestion,
67
+ code: { file: filePath, line: violation.line, column: violation.column },
68
+ fix: violation.fix
69
+ ?? (violation.suggestion ? { action: 'refactor' as const, confidence: 0.5 } : undefined),
70
+ metadata: Object.fromEntries(
71
+ Object.entries({
72
+ match: violation.match,
73
+ type: violation.type,
74
+ checkSlug,
75
+ checkTags: checkTags.length > 0 ? checkTags.join(',') : undefined,
76
+ }).filter(([, v]) => v != null && v !== ''),
77
+ ),
78
+ })
79
+ }
80
+
81
+ // =============================================================================
82
+ // ANALYSIS MODE EXECUTORS
83
+ // =============================================================================
84
+
85
+ /** @throws {CheckAbortedError} When the check is aborted via AbortSignal */
86
+ async function executeAnalyzeMode(
87
+ config: AnalyzeCheckConfig,
88
+ files: readonly string[],
89
+ ctx: ExecutionContext,
90
+ ): Promise<CheckResult> {
91
+ const builder = ResultBuilder.create({
92
+ checkId: config.id,
93
+ itemType: config.itemType ?? 'files',
94
+ })
95
+ .totalItems(files.length)
96
+ .filesScanned(files.length)
97
+
98
+ for (const filePath of files) {
99
+ if (ctx.signal?.aborted) {
100
+ throw new CheckAbortedError(config.slug)
101
+ }
102
+
103
+ try {
104
+ const rawContent = await ctx.readFile(filePath)
105
+ // Dispatch the content filter through the LanguageAdapter for the
106
+ // file's extension. Falls back to raw content when no adapter is
107
+ // registered. See languages/content-filter-dispatch.ts.
108
+ const content = applyContentFilter(filePath, rawContent, config.contentFilter ?? 'none')
109
+ const violations = config.analyze(content, filePath)
110
+
111
+ for (const violation of violations) {
112
+ void builder.addSignal(toSignal(violation, config.slug, config.tags ?? [], filePath, config.provider))
113
+ }
114
+ } catch (error) {
115
+ if (error instanceof CheckAbortedError) throw error
116
+ logger.debug('Skipping unreadable file', { evt: 'fitness.check.file.skip', module: 'fitness:framework', filePath, checkSlug: config.slug })
117
+ }
118
+ }
119
+
120
+ return builder.build()
121
+ }
122
+
123
+ /** @throws {CheckAbortedError} When the check is aborted via AbortSignal */
124
+ async function executeAnalyzeAllMode(
125
+ config: AnalyzeAllCheckConfig,
126
+ files: readonly string[],
127
+ ctx: ExecutionContext,
128
+ ): Promise<CheckResult> {
129
+ if (ctx.signal?.aborted) {
130
+ throw new CheckAbortedError(config.slug)
131
+ }
132
+
133
+ const fileAccessor = createFileAccessor(files, { signal: ctx.signal, contentFilter: config.contentFilter })
134
+ const violations = await config.analyzeAll(fileAccessor)
135
+
136
+ if (ctx.signal?.aborted) {
137
+ throw new CheckAbortedError(config.slug)
138
+ }
139
+
140
+ const builder = ResultBuilder.create({
141
+ checkId: config.id,
142
+ itemType: config.itemType ?? 'files',
143
+ })
144
+ .totalItems(files.length)
145
+ .filesScanned(files.length)
146
+
147
+ for (const violation of violations) {
148
+ if (!violation.filePath) {
149
+ ctx.log(`Warning: violation missing filePath in analyzeAll mode`)
150
+ }
151
+ void builder.addSignal(toSignal(violation, config.slug, config.tags ?? [], undefined, config.provider))
152
+ }
153
+
154
+ return builder.build()
155
+ }
156
+
157
+ /** @throws {CheckAbortedError} When the check is aborted via AbortSignal */
158
+ async function executeCommandMode(
159
+ config: CommandCheckConfig,
160
+ files: readonly string[],
161
+ ctx: ExecutionContext,
162
+ ): Promise<CheckResult> {
163
+ const result = await executeCommand(config.command, files, {
164
+ cwd: ctx.cwd,
165
+ signal: ctx.signal,
166
+ timeout: config.timeout,
167
+ })
168
+
169
+ if (result.aborted) {
170
+ throw new CheckAbortedError(config.slug)
171
+ }
172
+
173
+ const builder = ResultBuilder.create({
174
+ checkId: config.id,
175
+ itemType: config.itemType ?? 'files',
176
+ })
177
+ .totalItems(files.length)
178
+ .filesScanned(0)
179
+
180
+ if (result.error) {
181
+ return builder.buildError(result.error)
182
+ }
183
+
184
+ for (const violation of result.violations) {
185
+ void builder.addSignal(toSignal(violation, config.slug, config.tags ?? [], undefined, config.provider))
186
+ }
187
+
188
+ return builder.build()
189
+ }
190
+
191
+ // =============================================================================
192
+ // MAIN EXPORT
193
+ // =============================================================================
194
+
195
+ /**
196
+ * Define a fitness check using the unified API.
197
+ *
198
+ * @example
199
+ * ```typescript
200
+ * export const noConsoleLog = defineCheck({
201
+ * id: 'a1b2c3d4-e5f6-7890-abcd-ef1234567890',
202
+ * slug: 'no-console-log',
203
+ * category: 'quality',
204
+ * description: 'Disallow console.log in production code',
205
+ * analyze: (content, filePath) => {
206
+ * const violations: CheckViolation[] = [];
207
+ * content.split('\n').forEach((line, idx) => {
208
+ * if (line.includes('console.log')) {
209
+ * violations.push({ line: idx + 1, message: 'No console.log', severity: 'error' });
210
+ * }
211
+ * });
212
+ * return violations;
213
+ * },
214
+ * });
215
+ * ```
216
+ * @throws {ValidationError} When the check config is invalid
217
+ */
218
+ export function defineCheck(config: UnifiedCheckConfig): Check {
219
+ validateCheckConfig(config)
220
+
221
+ const check: Check = {
222
+ config: {
223
+ id: config.id,
224
+ slug: config.slug,
225
+ tags: config.tags ? [...config.tags] : [],
226
+ description: config.description,
227
+ longDescription: config.longDescription,
228
+ analysisMode: getAnalysisMode(config),
229
+ scope: { include: [], exclude: [], description: '' },
230
+ itemType: config.itemType ?? 'files',
231
+ docs: config.docs,
232
+ disabled: config.disabled,
233
+ confidence: config.confidence,
234
+ timeout: config.timeout,
235
+ scansFiles: !isCommandConfig(config),
236
+ fileTypes: config.fileTypes ? [...config.fileTypes] : undefined,
237
+ checkScope: config.scope ? { languages: [...config.scope.languages], concerns: [...config.scope.concerns] } : undefined,
238
+ // @fitness-ignore-next-line concurrency-safety -- async arrow delegates to executeUnifiedCheck which is async; needed for type compatibility
239
+ execute: async (ctx) => executeUnifiedCheck(config, ctx),
240
+ },
241
+
242
+ getScope() {
243
+ return { include: [], exclude: [], description: 'target-based scope' }
244
+ },
245
+
246
+ getMatcher(cwd: string): PathMatcher {
247
+ return PathMatcher.create({
248
+ include: [],
249
+ exclude: [],
250
+ cwd,
251
+ })
252
+ },
253
+
254
+ async run(cwd: string, options?: RunOptions): Promise<CheckResult> {
255
+ const start = Date.now()
256
+
257
+ const matcher = PathMatcher.create({
258
+ include: [],
259
+ exclude: [],
260
+ cwd,
261
+ })
262
+
263
+ const legacyConfig = {
264
+ id: config.id,
265
+ slug: config.slug,
266
+ tags: config.tags ? [...config.tags] : [],
267
+ description: config.description,
268
+ scope: { include: [] as readonly string[], exclude: [] as readonly string[], description: '' },
269
+ itemType: (config.itemType ?? 'files'),
270
+ docs: config.docs,
271
+ disabled: config.disabled,
272
+ timeout: config.timeout,
273
+ scansFiles: !isCommandConfig(config),
274
+ // @fitness-ignore-next-line concurrency-safety -- async arrow delegates to executeUnifiedCheck which is async; needed for type compatibility
275
+ execute: async (ctx: ExecutionContext) => executeUnifiedCheck(config, ctx),
276
+ }
277
+
278
+ const ctx = createExecutionContext(legacyConfig, cwd, matcher, options)
279
+
280
+ try {
281
+ const result = await executeUnifiedCheck(config, ctx)
282
+
283
+ const { filteredSignals, ignoredCount, appliedDirectives } = await filterSignalsByDirectives(
284
+ result.signals,
285
+ config.slug,
286
+ result.ignoredCount ?? 0,
287
+ )
288
+
289
+ const filtered = buildFilteredResult(result, filteredSignals, ignoredCount, start)
290
+ return appliedDirectives.length > 0 ? { ...filtered, appliedDirectives } : filtered
291
+ } catch (error) {
292
+ if (error instanceof CheckAbortedError) throw error
293
+
294
+ const builder = ResultBuilder.create({
295
+ checkId: config.id,
296
+ itemType: config.itemType ?? 'files',
297
+ })
298
+ return builder.buildError(
299
+ `Check ${config.slug} threw an error: ${error instanceof Error ? error.message : String(error)}`,
300
+ error instanceof Error ? error : undefined,
301
+ )
302
+ }
303
+ },
304
+ }
305
+
306
+ return check
307
+ }
308
+
309
+ /**
310
+ * Internal: Execute a check based on its analysis mode (analyze /
311
+ * analyzeAll / command).
312
+ * @throws {CheckAbortedError} When the check is aborted via AbortSignal
313
+ * @throws {SystemError} When an unknown analysis mode is encountered
314
+ */
315
+ async function executeUnifiedCheck(
316
+ config: UnifiedCheckConfig,
317
+ ctx: ExecutionContext,
318
+ ): Promise<CheckResult> {
319
+ const matchedFiles = await ctx.matchFiles()
320
+
321
+ // Filter by check's declared file types
322
+ const files = filterFilesByType(matchedFiles, config.fileTypes)
323
+
324
+ ctx.log(`Matched ${files.length} files`)
325
+
326
+ if (isAnalyzeConfig(config)) {
327
+ return executeAnalyzeMode(config, files, ctx)
328
+ } else if (isAnalyzeAllConfig(config)) {
329
+ return executeAnalyzeAllMode(config, files, ctx)
330
+ } else if (isCommandConfig(config)) {
331
+ return executeCommandMode(config, files, ctx)
332
+ }
333
+
334
+ const _exhaustiveCheck: never = config
335
+ throw new SystemError(`Unknown analysis mode: ${JSON.stringify(_exhaustiveCheck)}`, { code: 'SYSTEM.FITNESS.UNKNOWN_MODE' })
336
+ }