@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
package/src/cli/fit.ts ADDED
@@ -0,0 +1,612 @@
1
+ /**
2
+ * fit command — run fitness checks
3
+ */
4
+
5
+ import { dirname } from 'node:path';
6
+ import { fileURLToPath, pathToFileURL } from 'node:url';
7
+
8
+ import {
9
+ EXIT_CODES,
10
+ saveSession,
11
+ generateSessionId,
12
+ type CliArgs,
13
+ type CliOutput,
14
+ type TableRow,
15
+ type SummaryOptions,
16
+ type FitDoneResult,
17
+ type ErrorResult,
18
+ } from '@opensip-tools/contracts';
19
+ import { logger, type CheckDisplayEntry } from '@opensip-tools/core';
20
+
21
+ import { isCheck } from '../framework/check-types.js';
22
+ import { defaultRegistry } from '../framework/registry.js';
23
+ import { buildScopeBasedFileMap } from '../framework/scope-resolver.js';
24
+ import {
25
+ discoverCheckPackages,
26
+ readCheckPackageMetadata,
27
+ readCheckPackagePreferences,
28
+ } from '../plugins/check-package-discovery.js';
29
+ import { loadAllPlugins } from '../plugins/loader.js';
30
+ import { defaultRecipeRegistry } from '../recipes/registry.js';
31
+ import { FitnessRecipeService } from '../recipes/service.js';
32
+ import { loadSignalersConfig } from '../signalers/index.js';
33
+ import { loadTargetsConfig } from '../targets/index.js';
34
+
35
+ import type { FitnessRecipeServiceCallbacks, CheckSummary } from '../recipes/service-types.js';
36
+ import type { FitnessRecipeResult } from '../recipes/types.js';
37
+ import type { SignalersConfig } from '../signalers/types.js';
38
+
39
+
40
+ // ---------------------------------------------------------------------------
41
+ // Lazy-load fitness checks
42
+ // ---------------------------------------------------------------------------
43
+
44
+ let checksLoaded = false;
45
+ let pluginLoadErrors: readonly string[] = [];
46
+
47
+ /**
48
+ * Merged display map contributed by every loaded check package via the
49
+ * FitPluginExports.checkDisplay field. Each package owns the slugs it
50
+ * registers; on collision the last package loaded wins (no package is
51
+ * privileged). Slugs without an entry fall back to kebab-to-title-case.
52
+ */
53
+ const mergedCheckDisplay = new Map<string, CheckDisplayEntry>();
54
+ let getCheckDisplayName: (slug: string) => string = defaultDisplayName;
55
+ let getCheckIcon: (slug: string) => string = (_slug: string) => '\uD83D\uDD0D';
56
+
57
+ function defaultDisplayName(slug: string): string {
58
+ return slug
59
+ .split('-')
60
+ .map(w => w.charAt(0).toUpperCase() + w.slice(1))
61
+ .join(' ');
62
+ }
63
+
64
+ function rowStatus(cr: { timedOut?: boolean; passed: boolean }): 'TIMEOUT' | 'PASS' | 'FAIL' {
65
+ if (cr.timedOut) return 'TIMEOUT';
66
+ return cr.passed ? 'PASS' : 'FAIL';
67
+ }
68
+
69
+ function rebuildDisplayLookups(): void {
70
+ getCheckDisplayName = (slug) => {
71
+ const entry = mergedCheckDisplay.get(slug);
72
+ return entry ? entry[1] : defaultDisplayName(slug);
73
+ };
74
+ getCheckIcon = (slug) => {
75
+ const entry = mergedCheckDisplay.get(slug);
76
+ return entry ? entry[0] : '\uD83D\uDD0D';
77
+ };
78
+ }
79
+
80
+ /**
81
+ * Plugin load errors recorded during the most recent ensureChecksLoaded() call.
82
+ * Read by runFit to fail the run if any plugin failed to import \u2014 otherwise a
83
+ * malicious or broken plugin could silently suppress its own checks while the
84
+ * CLI exits 0, masking a compliance failure or a supply-chain compromise.
85
+ */
86
+ export function getPluginLoadErrors(): readonly string[] {
87
+ return pluginLoadErrors;
88
+ }
89
+
90
+ /**
91
+ * Pre-load hook the CLI registers via setPreLoadHook(). Lets the CLI
92
+ * inject CLI-only behavior (e.g. project-plugin auto-sync) without
93
+ * fitness needing to import CLI internals. Called once before the
94
+ * first ensureChecksLoaded() in this process.
95
+ */
96
+ export type PreLoadHook = (projectDir: string) => Promise<void>;
97
+
98
+ let preLoadHook: PreLoadHook | undefined;
99
+
100
+ /** Register a hook the CLI runs before fitness loads checks. */
101
+ export function setPreLoadHook(hook: PreLoadHook | undefined): void {
102
+ preLoadHook = hook;
103
+ }
104
+
105
+ export async function ensureChecksLoaded(projectDir?: string): Promise<void> {
106
+ if (checksLoaded) return;
107
+
108
+ // 0. CLI-injected pre-load hook (auto-sync project plugins, etc).
109
+ // Skipped when no hook is registered (e.g. running fitness via the
110
+ // Tool API outside the CLI).
111
+ if (projectDir && preLoadHook) {
112
+ await preLoadHook(projectDir);
113
+ }
114
+
115
+ // 1. Load fit plugins — discovers .mjs files in
116
+ // <projectDir>/opensip-tools/fit/{checks,recipes}/ and any
117
+ // npm packages declared in plugins.fit in the project config.
118
+ //
119
+ // Bundled language adapters (TypeScript, Rust, Python, etc.)
120
+ // are registered separately by the CLI bootstrap; fitness
121
+ // doesn't take direct deps on @opensip-tools/lang-* packages,
122
+ // and there's no project-local 'lang' plugin discovery path
123
+ // (the lang adapter set is fixed and shipped with the CLI).
124
+ const pluginResult = await loadAllPlugins('fit', projectDir);
125
+ pluginLoadErrors = pluginResult.errors;
126
+ if (pluginResult.errors.length > 0) {
127
+ // Surface plugin load errors to the user. The logger is silenced in
128
+ // normal CLI runs, so a structured-log-only failure was invisible
129
+ // before. Print one line per failure to stderr — short, actionable,
130
+ // and doesn't clobber stdout (which carries results + --json).
131
+ for (const err of pluginResult.errors) {
132
+ process.stderr.write(`opensip-tools: plugin failed to load — ${err}\n`);
133
+ logger.warn({ evt: 'cli.plugin.warning', module: 'cli:fit', message: err });
134
+ }
135
+ }
136
+
137
+ // 3. Discover and load every @opensip-tools/checks-* package installed
138
+ // in node_modules. No package is privileged — what used to be a
139
+ // hardcoded `import('@opensip-tools/checks-builtin')` is now an
140
+ // ordinary npm dependency declared by @opensip-tools/cli and
141
+ // discovered via discoverCheckPackages() like every other pack.
142
+ // Project config can override (plugins.checkPackages: [...]) or
143
+ // opt out (plugins.autoDiscoverChecks: false).
144
+ //
145
+ // `projectDir` is the discovery anchor — discoverCheckPackages
146
+ // walks up to ancestor node_modules from there. When called
147
+ // without one (e.g. ad-hoc `opensip-tools fit` in an unconfigured
148
+ // dir) we fall back to the CLI's own install dir so the bundled
149
+ // deps still resolve.
150
+ const discoveryAnchor = projectDir ?? cliInstallDir();
151
+ const checksRegistered = await loadDiscoveredCheckPackages(discoveryAnchor);
152
+
153
+ // 4. No-checks-loaded guard. Silent zero-checks would let a misconfig
154
+ // or missing dep produce a green run that scanned nothing — the
155
+ // exact failure mode the CLI exists to prevent. Warn loudly.
156
+ if (checksRegistered === 0) {
157
+ const msg =
158
+ 'opensip-tools: no check packages were loaded. ' +
159
+ 'Install at least one @opensip-tools/checks-* package, ' +
160
+ 'or declare plugins.checkPackages in opensip-tools.config.yml.\n';
161
+ process.stderr.write(msg);
162
+ logger.warn({
163
+ evt: 'cli.check_packages.empty',
164
+ module: 'cli:fit',
165
+ msg: 'no check packages loaded',
166
+ });
167
+ }
168
+
169
+ rebuildDisplayLookups();
170
+ checksLoaded = true;
171
+ }
172
+
173
+ /**
174
+ * Resolve the directory the CLI was installed into, used as a discovery
175
+ * fallback when no projectDir is supplied. Walks up from this module's
176
+ * URL to the @opensip-tools/cli package root so node_modules lookup
177
+ * sees the CLI's own dependency tree (which now contains checks-builtin
178
+ * and any other check packages declared in cli/package.json).
179
+ */
180
+ function cliInstallDir(): string {
181
+ // import.meta.url points at this file inside the CLI's dist/. The CLI
182
+ // package root is two levels up from dist/commands/. Resolve via the
183
+ // Node URL → path bridge to keep this OS-agnostic.
184
+ const thisFile = fileURLToPath(import.meta.url);
185
+ return dirname(dirname(dirname(thisFile)));
186
+ }
187
+
188
+ /**
189
+ * Load every check package returned by discoverCheckPackages(). Each
190
+ * package's main entry should follow the FitPluginExports contract:
191
+ *
192
+ * - `checks`: readonly Check[] (required)
193
+ * - `checkDisplay`: { [slug]: [icon, name] } (optional)
194
+ *
195
+ * Errors loading any one package don't fail the others — they surface
196
+ * to stderr the same way fit-domain plugin failures do.
197
+ *
198
+ * Returns the total number of checks registered across all loaded
199
+ * packages, so the caller can warn when zero packages contributed
200
+ * anything (a silent green run scanning nothing is the failure mode
201
+ * we want to make impossible).
202
+ */
203
+ async function loadDiscoveredCheckPackages(projectDir: string): Promise<number> {
204
+ const prefs = readCheckPackagePreferences(projectDir);
205
+ const discovered = discoverCheckPackages({
206
+ projectDir,
207
+ explicitPackages: prefs.checkPackages,
208
+ autoDiscover: prefs.autoDiscoverChecks,
209
+ });
210
+ let totalRegistered = 0;
211
+ for (const pkg of discovered) {
212
+ const meta = readCheckPackageMetadata(pkg.packageDir);
213
+ if (!meta) {
214
+ process.stderr.write(`opensip-tools: check package ${pkg.name} has no readable package.json — skipping\n`);
215
+ continue;
216
+ }
217
+ try {
218
+ const moduleUrl = pathToFileURL(meta.mainEntry).href;
219
+ const mod = (await import(moduleUrl)) as {
220
+ checks?: unknown;
221
+ checkDisplay?: unknown;
222
+ };
223
+ const checks = mod.checks;
224
+ if (!Array.isArray(checks)) {
225
+ process.stderr.write(`opensip-tools: check package ${pkg.name} does not export a "checks" array — skipping\n`);
226
+ continue;
227
+ }
228
+ let registered = 0;
229
+ for (const check of checks) {
230
+ if (isCheck(check)) {
231
+ defaultRegistry.register(check, pkg.name);
232
+ registered++;
233
+ }
234
+ }
235
+ totalRegistered += registered;
236
+ mergeCheckDisplay(pkg.name, mod.checkDisplay);
237
+ logger.info({
238
+ evt: 'cli.check_package.loaded',
239
+ module: 'cli:fit',
240
+ name: pkg.name,
241
+ checksRegistered: registered,
242
+ });
243
+ } catch (error) {
244
+ const msg = error instanceof Error ? error.message : String(error);
245
+ process.stderr.write(`opensip-tools: failed to load check package ${pkg.name}: ${msg}\n`);
246
+ logger.warn({
247
+ evt: 'cli.check_package.load_failed',
248
+ module: 'cli:fit',
249
+ name: pkg.name,
250
+ error: msg,
251
+ });
252
+ }
253
+ }
254
+ return totalRegistered;
255
+ }
256
+
257
+ /**
258
+ * Merge a check package's display map into the CLI-wide registry.
259
+ *
260
+ * Validates each entry is a `[icon, name]` tuple before accepting it —
261
+ * a malformed `checkDisplay` export from a third-party package shouldn't
262
+ * crash the run. Bad entries are dropped silently with a debug log so
263
+ * the user still gets a (worse-formatted) result rather than a hang.
264
+ *
265
+ * On collision, last package loaded wins. That's intentional: it lets
266
+ * a downstream package override a base package's display name without
267
+ * having to touch the original.
268
+ */
269
+ function mergeCheckDisplay(packageName: string, raw: unknown): void {
270
+ if (!raw || typeof raw !== 'object') return;
271
+ for (const [slug, entry] of Object.entries(raw as Record<string, unknown>)) {
272
+ if (
273
+ Array.isArray(entry) &&
274
+ entry.length === 2 &&
275
+ typeof entry[0] === 'string' &&
276
+ typeof entry[1] === 'string'
277
+ ) {
278
+ mergedCheckDisplay.set(slug, [entry[0], entry[1]] as const);
279
+ } else {
280
+ logger.debug({
281
+ evt: 'cli.check_package.bad_display_entry',
282
+ module: 'cli:fit',
283
+ packageName,
284
+ slug,
285
+ });
286
+ }
287
+ }
288
+ }
289
+
290
+ /** Get display name for a check slug (available after ensureChecksLoaded) */
291
+ export function getDisplayName(slug: string): string {
292
+ return getCheckDisplayName(slug);
293
+ }
294
+
295
+ /** Get the number of enabled checks (available after ensureChecksLoaded) */
296
+ export function getEnabledCheckCount(): number {
297
+ return defaultRegistry.listEnabled().length;
298
+ }
299
+
300
+ /** Get icon for a check slug (available after ensureChecksLoaded) */
301
+ export function getIcon(slug: string): string {
302
+ return getCheckIcon(slug);
303
+ }
304
+
305
+ // ---------------------------------------------------------------------------
306
+ // Formatting helpers (used to build TableRow data)
307
+ // ---------------------------------------------------------------------------
308
+
309
+ export function formatDuration(ms: number): string {
310
+ if (ms < 1000) return `${ms}ms`;
311
+ return `${(ms / 1000).toFixed(1)}s`;
312
+ }
313
+
314
+ export function formatValidatedColumn(totalItems: number | undefined, itemType = 'items'): string {
315
+ // No meaningful count: external tool checks, errored checks, or checks with no file scanning
316
+ if (!totalItems) return '—';
317
+ // Use singular for count of 1, plural otherwise (e.g., "1 file", "450 files", "13 packages")
318
+ const singular = itemType.endsWith('s') ? itemType.slice(0, -1) : itemType;
319
+ return totalItems === 1 ? `${totalItems} ${singular}` : `${totalItems} ${itemType}`;
320
+ }
321
+
322
+ // ---------------------------------------------------------------------------
323
+ // executeFit — main fit command (returns data, no console output)
324
+ // ---------------------------------------------------------------------------
325
+
326
+ // eslint-disable-next-line sonarjs/cognitive-complexity -- top-level CLI command flow: validates args, resolves config, runs recipes, persists results — distinct phases that read better inline
327
+ export async function executeFit(
328
+ args: CliArgs,
329
+ onProgress?: (completed: number, total: number) => void,
330
+ ): Promise<{ result: FitDoneResult; output: CliOutput } | { result: ErrorResult; output?: undefined }> {
331
+ logger.info({ evt: 'cli.checks.loading', module: 'cli:fit' });
332
+ await ensureChecksLoaded(args.cwd);
333
+ logger.info({ evt: 'cli.checks.loaded', module: 'cli:fit', checkCount: defaultRegistry.listEnabled().length });
334
+
335
+ // Determine recipe: --check and --tags each create an ad-hoc recipe;
336
+ // otherwise use a named recipe. --check takes precedence over --recipe
337
+ // so "opensip-tools fit --check <slug>" runs just that slug.
338
+ const useAdHoc = args.check != null || args.tags != null;
339
+ const recipeName = useAdHoc ? undefined : (args.recipe ?? 'default');
340
+ if (recipeName && !defaultRecipeRegistry.has(recipeName)) {
341
+ return {
342
+ result: {
343
+ type: 'error',
344
+ message: `Unknown recipe '${recipeName}'.`,
345
+ suggestion: 'Run opensip-tools fit --recipes to see available recipes.',
346
+ exitCode: EXIT_CODES.CONFIGURATION_ERROR,
347
+ },
348
+ };
349
+ }
350
+
351
+ // -- Config resolution --
352
+ // Both loaders share the same project config file. A missing file is a
353
+ // HARD error: file-based checks would silently produce zero findings,
354
+ // making the scan look green when it actually never ran. The resolver
355
+ // throws with a message that enumerates every path it attempted.
356
+ let signalersConfig: SignalersConfig;
357
+ let targetsResult: ReturnType<typeof loadTargetsConfig>;
358
+ try {
359
+ signalersConfig = loadSignalersConfig(args.cwd, args.config);
360
+ targetsResult = loadTargetsConfig(args.cwd, args.config);
361
+ } catch (error) {
362
+ const message = error instanceof Error ? error.message : String(error);
363
+ logger.warn({ evt: 'cli.config.load_failed', module: 'cli:fit', message });
364
+ return {
365
+ result: {
366
+ type: 'error',
367
+ message,
368
+ suggestion: "Run 'opensip-tools init' to scaffold a config, or pass --config <path> to point at an existing one.",
369
+ exitCode: EXIT_CODES.CONFIGURATION_ERROR,
370
+ },
371
+ };
372
+ }
373
+
374
+ const disabledChecks = signalersConfig.fitness.disabledChecks;
375
+ const { registry: targetRegistry, config: targetsConfig } = targetsResult;
376
+ const configFound = true;
377
+
378
+ // Phase 9: validate that every language declared in the targets config
379
+ // has a registered LanguageAdapter. Warn loudly when a target asks for
380
+ // a language we don't know how to handle — silent acceptance would let
381
+ // users ship configs that scan files but produce wrong results.
382
+ {
383
+ const { defaultLanguageRegistry: langRegistry } = await import('@opensip-tools/core');
384
+ const knownLanguages = new Set<string>(langRegistry.list().flatMap((a) => [a.id, ...(a.aliases ?? [])]));
385
+ const unknownLanguages = new Set<string>()
386
+ for (const target of targetRegistry.getAll()) {
387
+ const langs = target.config.languages ?? []
388
+ for (const lang of langs) {
389
+ if (!knownLanguages.has(lang)) unknownLanguages.add(lang)
390
+ }
391
+ }
392
+ if (unknownLanguages.size > 0) {
393
+ const list = [...unknownLanguages].sort().join(', ')
394
+ const known = [...knownLanguages].filter((l) => !l.startsWith('rs') && !l.startsWith('py')).slice(0, 8).join(', ')
395
+ process.stderr.write(
396
+ `opensip-tools: target config declares unknown language(s): ${list}. ` +
397
+ `Known languages: ${[...knownLanguages].sort().join(', ')}. ` +
398
+ `Files in unknown languages will scan with no string/comment filtering.\n`,
399
+ );
400
+ logger.warn({
401
+ evt: 'cli.config.unknown_languages',
402
+ module: 'cli:fit',
403
+ unknown: [...unknownLanguages],
404
+ known: [...knownLanguages],
405
+ });
406
+ // Reference `known` to avoid unused-var lint without changing the user-facing message.
407
+ void known
408
+ }
409
+ }
410
+
411
+ const allChecks = defaultRegistry.listSlugs().map((key) => {
412
+ const check = defaultRegistry.getBySlug(key);
413
+ return { slug: check?.config.slug ?? key, scope: check?.config.checkScope };
414
+ });
415
+ const scopeMap = buildScopeBasedFileMap(allChecks, targetRegistry, targetsConfig, args.cwd);
416
+ const checkTargetFiles = scopeMap.size > 0 ? scopeMap : undefined;
417
+
418
+ const label = args.tags ? `tags: ${args.tags}` : `recipe ${recipeName ?? 'default'}`;
419
+
420
+ // -- Progress callbacks --
421
+ //
422
+ // Monotonic completed-count:
423
+ //
424
+ // The service fires onCheckStart(slug, displayIndex, total) when a check
425
+ // STARTS and onCheckComplete(slug, summary, displayIndex, total) when it
426
+ // FINISHES. Under parallel execution `displayIndex` is the check's
427
+ // position in the queue (1..total), not "how many have completed" — so
428
+ // the last started check's index hops above the current completion
429
+ // tally and then "resets" down when an earlier check finishes. The UI
430
+ // showed `147/148 → 121/148 → 78/148` because each event fired with a
431
+ // different in-flight check's queue position.
432
+ //
433
+ // The progress bar wants a monotonic counter. Track completed locally,
434
+ // increment only on onCheckComplete, ignore onCheckStart's index. The
435
+ // counter is strictly non-decreasing and always reflects "N of M
436
+ // checks done."
437
+ let completedCount = 0;
438
+ const callbacks: FitnessRecipeServiceCallbacks = {
439
+ onCheckStart(checkSlug: string, index: number, total: number) {
440
+ logger.debug({ evt: 'cli.check.start', module: 'cli:fit', checkSlug, index, total });
441
+ // Emit current completed count so the UI shows activity without
442
+ // moving the bar backward on start events.
443
+ onProgress?.(completedCount, total);
444
+ },
445
+ onCheckComplete(checkSlug: string, summary: CheckSummary, index: number, total: number) {
446
+ logger.debug({ evt: 'cli.check.complete', module: 'cli:fit', checkSlug, passed: summary.passed, errors: summary.errors, warnings: summary.warnings, durationMs: summary.durationMs });
447
+ completedCount++;
448
+ onProgress?.(completedCount, total);
449
+ },
450
+ };
451
+
452
+ // -- Execute via FitnessRecipeService --
453
+ // Forward globalExcludes from the project config so the matchFiles()
454
+ // fileCache fallback honors them. Without this, scope-empty checks
455
+ // (e.g. file-length-limit) scan every prewarmed file regardless of
456
+ // whether the project config told us to exclude it.
457
+ const service = new FitnessRecipeService({
458
+ cwd: args.cwd,
459
+ checkTargetFiles,
460
+ callbacks,
461
+ disabledChecks,
462
+ includeViolations: true,
463
+ globalExcludes: targetsConfig.globalExcludes,
464
+ });
465
+
466
+ let fitnessResult: FitnessRecipeResult;
467
+ try {
468
+ if (args.check) {
469
+ fitnessResult = await service.start(FitnessRecipeService.createAdHocRecipe({ check: args.check }));
470
+ } else if (args.tags) {
471
+ const tagFilters = args.tags.split(',').map(t => t.trim()).filter(Boolean);
472
+ fitnessResult = await service.start(FitnessRecipeService.createAdHocRecipe({ tagFilters }));
473
+ } else {
474
+ fitnessResult = await service.start(recipeName!);
475
+ }
476
+ } catch (error) {
477
+ const msg = error instanceof Error ? error.message : String(error);
478
+ return {
479
+ result: {
480
+ type: 'error',
481
+ message: `Fitness run failed: ${msg}`,
482
+ exitCode: EXIT_CODES.RUNTIME_ERROR,
483
+ },
484
+ };
485
+ }
486
+
487
+ // -- Format output from recipe result --
488
+ const { summary, checkResults, durationMs } = fitnessResult;
489
+ const score = summary.totalChecks > 0
490
+ ? Math.round((summary.passedChecks / summary.totalChecks) * 100)
491
+ : 0;
492
+
493
+ // Build structured output
494
+ const output: CliOutput = {
495
+ version: '1.0',
496
+ tool: 'fit',
497
+ timestamp: new Date().toISOString(),
498
+ recipe: recipeName,
499
+ score,
500
+ passed: summary.failedChecks === 0 && pluginLoadErrors.length === 0,
501
+ summary: {
502
+ total: summary.totalChecks,
503
+ passed: summary.passedChecks,
504
+ failed: summary.failedChecks,
505
+ errors: summary.totalErrors,
506
+ warnings: summary.totalWarnings,
507
+ },
508
+ checks: checkResults.map(cr => ({
509
+ checkSlug: cr.checkSlug,
510
+ passed: cr.passed,
511
+ violationCount: cr.violationCount,
512
+ findings: (cr.violations ?? []).map(v => ({
513
+ ruleId: cr.checkSlug,
514
+ message: v.message,
515
+ severity: v.severity,
516
+ filePath: v.file,
517
+ line: v.line,
518
+ column: v.column,
519
+ suggestion: v.suggestion,
520
+ })),
521
+ durationMs: cr.durationMs,
522
+ })),
523
+ durationMs,
524
+ };
525
+
526
+ // Persist session for history and dashboard
527
+ try {
528
+ saveSession({
529
+ id: generateSessionId(),
530
+ tool: 'fit',
531
+ timestamp: output.timestamp,
532
+ cwd: args.cwd,
533
+ recipe: recipeName,
534
+ score,
535
+ passed: output.passed,
536
+ summary: output.summary,
537
+ checks: output.checks,
538
+ durationMs,
539
+ });
540
+ } catch {
541
+ // Best effort — don't fail the run if persistence fails
542
+ }
543
+
544
+ // Build table rows
545
+ const tableRows: TableRow[] = checkResults.map(cr => ({
546
+ check: getCheckDisplayName(cr.checkSlug),
547
+ status: rowStatus(cr),
548
+ errors: cr.errorCount,
549
+ warnings: cr.warningCount,
550
+ validated: formatValidatedColumn(cr.totalItems, cr.itemType),
551
+ ignored: cr.ignoredCount,
552
+ duration: formatDuration(cr.durationMs),
553
+ durationMs: cr.durationMs,
554
+ }));
555
+
556
+ // Build summary
557
+ const summaryOpts: SummaryOptions = {
558
+ passed: summary.passedChecks,
559
+ failed: summary.failedChecks,
560
+ totalErrors: summary.totalErrors,
561
+ totalWarnings: summary.totalWarnings,
562
+ totalIgnored: summary.totalIgnored,
563
+ durationMs,
564
+ };
565
+
566
+ // Determine exit code from config thresholds
567
+ // failOnErrors: fail if total errors >= this value (default: 1, 0 = never fail on errors)
568
+ // failOnWarnings: fail if total warnings >= this value (default: 0 = never fail on warnings)
569
+ const failOnErrors = signalersConfig?.fitness.failOnErrors ?? 1;
570
+ const failOnWarnings = signalersConfig?.fitness.failOnWarnings ?? 0;
571
+ const shouldFail =
572
+ pluginLoadErrors.length > 0 ||
573
+ (failOnErrors > 0 && summary.totalErrors >= failOnErrors) ||
574
+ (failOnWarnings > 0 && summary.totalWarnings >= failOnWarnings);
575
+
576
+ // Build findings if requested
577
+ let findings: FitDoneResult['findings'];
578
+ if ((args.findings || args.verbose) && (summary.totalErrors + summary.totalWarnings) > 0) {
579
+ findings = {
580
+ checks: checkResults
581
+ .filter(cr => cr.errorCount > 0 || cr.warningCount > 0 || cr.error)
582
+ .map(cr => ({
583
+ checkSlug: cr.checkSlug,
584
+ errorCount: cr.errorCount,
585
+ warningCount: cr.warningCount,
586
+ error: cr.error,
587
+ violations: cr.violations?.map(v => ({
588
+ severity: v.severity,
589
+ message: v.message,
590
+ file: v.file,
591
+ line: v.line,
592
+ suggestion: v.suggestion,
593
+ })),
594
+ })),
595
+ };
596
+ }
597
+
598
+ const result: FitDoneResult = {
599
+ type: 'fit-done',
600
+ rows: tableRows,
601
+ summary: summaryOpts,
602
+ label,
603
+ cwd: args.cwd,
604
+ findings,
605
+ shouldFail,
606
+ configFound,
607
+ };
608
+
609
+ logger.info({ evt: 'cli.fit.complete', module: 'cli:fit', score, passed: fitnessResult.success, totalChecks: summary.totalChecks, durationMs });
610
+
611
+ return { result, output };
612
+ }
@@ -0,0 +1,32 @@
1
+ /**
2
+ * list-checks command — list all available fitness checks
3
+ */
4
+
5
+
6
+ import { defaultRegistry } from '../framework/registry.js';
7
+
8
+ import { ensureChecksLoaded } from './fit.js';
9
+
10
+ import type { ListChecksResult } from '@opensip-tools/contracts';
11
+
12
+ // ---------------------------------------------------------------------------
13
+ // listChecks
14
+ // ---------------------------------------------------------------------------
15
+
16
+ export async function listChecks(projectDir?: string): Promise<ListChecksResult> {
17
+ await ensureChecksLoaded(projectDir);
18
+ const checks = defaultRegistry.listEnabled();
19
+
20
+ const entries = checks.map((check) => ({
21
+ slug: check.config.slug,
22
+ description: check.config.description,
23
+ tags: [...(check.config.tags ?? ['untagged'])],
24
+ }));
25
+
26
+ return {
27
+ type: 'list-checks',
28
+ checks: entries,
29
+ totalCount: checks.length,
30
+ };
31
+ }
32
+
@@ -0,0 +1,38 @@
1
+ /**
2
+ * list-recipes command — list all available fitness recipes
3
+ */
4
+
5
+
6
+ import { defaultRecipeRegistry } from '../recipes/registry.js';
7
+
8
+ import { ensureChecksLoaded } from './fit.js';
9
+
10
+ import type { ListRecipesResult } from '@opensip-tools/contracts';
11
+
12
+ // ---------------------------------------------------------------------------
13
+ // listRecipes
14
+ // ---------------------------------------------------------------------------
15
+
16
+ export async function listRecipes(projectDir?: string): Promise<ListRecipesResult> {
17
+ // Load plugins so user-defined recipes (e.g. ~/.opensip-tools/fit/*.mjs) appear.
18
+ await ensureChecksLoaded(projectDir);
19
+
20
+ const recipes = defaultRecipeRegistry.getAllRecipes().map((recipe) => {
21
+ const selector = recipe.checks;
22
+ let checkCount: string;
23
+ if (selector.type === 'all') {
24
+ checkCount = 'all checks';
25
+ } else if (selector.type === 'explicit') {
26
+ checkCount = `${selector.checkIds.length} checks`;
27
+ } else {
28
+ checkCount = 'pattern-based';
29
+ }
30
+ return { name: recipe.name, description: recipe.description, checkCount };
31
+ });
32
+
33
+ return {
34
+ type: 'list-recipes',
35
+ recipes,
36
+ };
37
+ }
38
+