@vyuhlabs/dxkit 2.4.6 โ†’ 2.4.8

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 (357) hide show
  1. package/CHANGELOG.md +1076 -0
  2. package/README.md +132 -27
  3. package/dist/analysis-result.d.ts +112 -0
  4. package/dist/analysis-result.d.ts.map +1 -0
  5. package/dist/analysis-result.js +52 -0
  6. package/dist/analysis-result.js.map +1 -0
  7. package/dist/analyzers/bom/detailed.d.ts.map +1 -1
  8. package/dist/analyzers/bom/detailed.js +19 -0
  9. package/dist/analyzers/bom/detailed.js.map +1 -1
  10. package/dist/analyzers/bom/gather.d.ts +27 -26
  11. package/dist/analyzers/bom/gather.d.ts.map +1 -1
  12. package/dist/analyzers/bom/gather.js +26 -87
  13. package/dist/analyzers/bom/gather.js.map +1 -1
  14. package/dist/analyzers/bom/index.d.ts +0 -7
  15. package/dist/analyzers/bom/index.d.ts.map +1 -1
  16. package/dist/analyzers/bom/index.js +98 -48
  17. package/dist/analyzers/bom/index.js.map +1 -1
  18. package/dist/analyzers/bom/types.d.ts +11 -13
  19. package/dist/analyzers/bom/types.d.ts.map +1 -1
  20. package/dist/analyzers/cache.d.ts +95 -0
  21. package/dist/analyzers/cache.d.ts.map +1 -0
  22. package/dist/analyzers/cache.js +309 -0
  23. package/dist/analyzers/cache.js.map +1 -0
  24. package/dist/analyzers/coverage-runner.d.ts +56 -0
  25. package/dist/analyzers/coverage-runner.d.ts.map +1 -0
  26. package/dist/analyzers/coverage-runner.js +72 -0
  27. package/dist/analyzers/coverage-runner.js.map +1 -0
  28. package/dist/analyzers/dashboard/index.d.ts +24 -0
  29. package/dist/analyzers/dashboard/index.d.ts.map +1 -0
  30. package/dist/analyzers/dashboard/index.js +667 -0
  31. package/dist/analyzers/dashboard/index.js.map +1 -0
  32. package/dist/analyzers/developer/gather.d.ts.map +1 -1
  33. package/dist/analyzers/developer/gather.js +205 -37
  34. package/dist/analyzers/developer/gather.js.map +1 -1
  35. package/dist/analyzers/developer/index.d.ts +1 -1
  36. package/dist/analyzers/developer/index.d.ts.map +1 -1
  37. package/dist/analyzers/developer/index.js +21 -9
  38. package/dist/analyzers/developer/index.js.map +1 -1
  39. package/dist/analyzers/dispatcher.d.ts +52 -0
  40. package/dist/analyzers/dispatcher.d.ts.map +1 -1
  41. package/dist/analyzers/dispatcher.js +92 -9
  42. package/dist/analyzers/dispatcher.js.map +1 -1
  43. package/dist/analyzers/docs/shallow.d.ts +17 -5
  44. package/dist/analyzers/docs/shallow.d.ts.map +1 -1
  45. package/dist/analyzers/docs/shallow.js +65 -2
  46. package/dist/analyzers/docs/shallow.js.map +1 -1
  47. package/dist/analyzers/dx/shallow.d.ts +17 -5
  48. package/dist/analyzers/dx/shallow.d.ts.map +1 -1
  49. package/dist/analyzers/dx/shallow.js +66 -2
  50. package/dist/analyzers/dx/shallow.js.map +1 -1
  51. package/dist/analyzers/health/actions.d.ts +1 -1
  52. package/dist/analyzers/health/actions.d.ts.map +1 -1
  53. package/dist/analyzers/health/actions.js +27 -9
  54. package/dist/analyzers/health/actions.js.map +1 -1
  55. package/dist/analyzers/health/detailed.d.ts +2 -1
  56. package/dist/analyzers/health/detailed.d.ts.map +1 -1
  57. package/dist/analyzers/health/detailed.js +11 -7
  58. package/dist/analyzers/health/detailed.js.map +1 -1
  59. package/dist/analyzers/health.d.ts +27 -0
  60. package/dist/analyzers/health.d.ts.map +1 -1
  61. package/dist/analyzers/health.js +282 -34
  62. package/dist/analyzers/health.js.map +1 -1
  63. package/dist/analyzers/licenses/gather.d.ts +35 -8
  64. package/dist/analyzers/licenses/gather.d.ts.map +1 -1
  65. package/dist/analyzers/licenses/gather.js +86 -13
  66. package/dist/analyzers/licenses/gather.js.map +1 -1
  67. package/dist/analyzers/licenses/index.d.ts +1 -1
  68. package/dist/analyzers/licenses/index.d.ts.map +1 -1
  69. package/dist/analyzers/licenses/index.js +52 -11
  70. package/dist/analyzers/licenses/index.js.map +1 -1
  71. package/dist/analyzers/licenses/types.d.ts +15 -0
  72. package/dist/analyzers/licenses/types.d.ts.map +1 -1
  73. package/dist/analyzers/maintainability/shallow.d.ts +17 -5
  74. package/dist/analyzers/maintainability/shallow.d.ts.map +1 -1
  75. package/dist/analyzers/maintainability/shallow.js +80 -2
  76. package/dist/analyzers/maintainability/shallow.js.map +1 -1
  77. package/dist/analyzers/quality/detailed.d.ts.map +1 -1
  78. package/dist/analyzers/quality/detailed.js +4 -6
  79. package/dist/analyzers/quality/detailed.js.map +1 -1
  80. package/dist/analyzers/quality/gather.d.ts +1 -14
  81. package/dist/analyzers/quality/gather.d.ts.map +1 -1
  82. package/dist/analyzers/quality/gather.js +48 -137
  83. package/dist/analyzers/quality/gather.js.map +1 -1
  84. package/dist/analyzers/quality/index.d.ts +9 -2
  85. package/dist/analyzers/quality/index.d.ts.map +1 -1
  86. package/dist/analyzers/quality/index.js +197 -117
  87. package/dist/analyzers/quality/index.js.map +1 -1
  88. package/dist/analyzers/quality/shallow.d.ts +50 -5
  89. package/dist/analyzers/quality/shallow.d.ts.map +1 -1
  90. package/dist/analyzers/quality/shallow.js +155 -2
  91. package/dist/analyzers/quality/shallow.js.map +1 -1
  92. package/dist/analyzers/quality/types.d.ts +14 -0
  93. package/dist/analyzers/quality/types.d.ts.map +1 -1
  94. package/dist/analyzers/security/actions.d.ts +11 -4
  95. package/dist/analyzers/security/actions.d.ts.map +1 -1
  96. package/dist/analyzers/security/actions.js +87 -37
  97. package/dist/analyzers/security/actions.js.map +1 -1
  98. package/dist/analyzers/security/aggregator.d.ts +236 -0
  99. package/dist/analyzers/security/aggregator.d.ts.map +1 -0
  100. package/dist/analyzers/security/aggregator.js +349 -0
  101. package/dist/analyzers/security/aggregator.js.map +1 -0
  102. package/dist/analyzers/security/detailed.d.ts +2 -2
  103. package/dist/analyzers/security/detailed.d.ts.map +1 -1
  104. package/dist/analyzers/security/detailed.js +10 -9
  105. package/dist/analyzers/security/detailed.js.map +1 -1
  106. package/dist/analyzers/security/gather.d.ts +104 -1
  107. package/dist/analyzers/security/gather.d.ts.map +1 -1
  108. package/dist/analyzers/security/gather.js +299 -9
  109. package/dist/analyzers/security/gather.js.map +1 -1
  110. package/dist/analyzers/security/index.d.ts +15 -0
  111. package/dist/analyzers/security/index.d.ts.map +1 -1
  112. package/dist/analyzers/security/index.js +463 -50
  113. package/dist/analyzers/security/index.js.map +1 -1
  114. package/dist/analyzers/security/shallow.d.ts +50 -6
  115. package/dist/analyzers/security/shallow.d.ts.map +1 -1
  116. package/dist/analyzers/security/shallow.js +154 -2
  117. package/dist/analyzers/security/shallow.js.map +1 -1
  118. package/dist/analyzers/security/types.d.ts +51 -0
  119. package/dist/analyzers/security/types.d.ts.map +1 -1
  120. package/dist/analyzers/tests/detailed.d.ts.map +1 -1
  121. package/dist/analyzers/tests/detailed.js +2 -3
  122. package/dist/analyzers/tests/detailed.js.map +1 -1
  123. package/dist/analyzers/tests/gather.d.ts +2 -1
  124. package/dist/analyzers/tests/gather.d.ts.map +1 -1
  125. package/dist/analyzers/tests/gather.js +98 -69
  126. package/dist/analyzers/tests/gather.js.map +1 -1
  127. package/dist/analyzers/tests/index.d.ts +11 -2
  128. package/dist/analyzers/tests/index.d.ts.map +1 -1
  129. package/dist/analyzers/tests/index.js +83 -18
  130. package/dist/analyzers/tests/index.js.map +1 -1
  131. package/dist/analyzers/tests/shallow.d.ts +19 -5
  132. package/dist/analyzers/tests/shallow.d.ts.map +1 -1
  133. package/dist/analyzers/tests/shallow.js +89 -2
  134. package/dist/analyzers/tests/shallow.js.map +1 -1
  135. package/dist/analyzers/tests/types.d.ts +41 -1
  136. package/dist/analyzers/tests/types.d.ts.map +1 -1
  137. package/dist/analyzers/tools/autogen-header.d.ts +8 -0
  138. package/dist/analyzers/tools/autogen-header.d.ts.map +1 -0
  139. package/dist/analyzers/tools/autogen-header.js +107 -0
  140. package/dist/analyzers/tools/autogen-header.js.map +1 -0
  141. package/dist/analyzers/tools/cloc.d.ts.map +1 -1
  142. package/dist/analyzers/tools/cloc.js +36 -5
  143. package/dist/analyzers/tools/cloc.js.map +1 -1
  144. package/dist/analyzers/tools/deadline.d.ts +67 -0
  145. package/dist/analyzers/tools/deadline.d.ts.map +1 -0
  146. package/dist/analyzers/tools/deadline.js +81 -0
  147. package/dist/analyzers/tools/deadline.js.map +1 -0
  148. package/dist/analyzers/tools/debug-statements.d.ts +17 -0
  149. package/dist/analyzers/tools/debug-statements.d.ts.map +1 -0
  150. package/dist/analyzers/tools/debug-statements.js +58 -0
  151. package/dist/analyzers/tools/debug-statements.js.map +1 -0
  152. package/dist/analyzers/tools/default-exclusions.gitignore +28 -0
  153. package/dist/analyzers/tools/exclusions.d.ts +33 -6
  154. package/dist/analyzers/tools/exclusions.d.ts.map +1 -1
  155. package/dist/analyzers/tools/exclusions.js +95 -26
  156. package/dist/analyzers/tools/exclusions.js.map +1 -1
  157. package/dist/analyzers/tools/generic.d.ts +17 -2
  158. package/dist/analyzers/tools/generic.d.ts.map +1 -1
  159. package/dist/analyzers/tools/generic.js +206 -109
  160. package/dist/analyzers/tools/generic.js.map +1 -1
  161. package/dist/analyzers/tools/gitleaks.d.ts.map +1 -1
  162. package/dist/analyzers/tools/gitleaks.js +48 -1
  163. package/dist/analyzers/tools/gitleaks.js.map +1 -1
  164. package/dist/analyzers/tools/graphify.d.ts +30 -2
  165. package/dist/analyzers/tools/graphify.d.ts.map +1 -1
  166. package/dist/analyzers/tools/graphify.js +131 -15
  167. package/dist/analyzers/tools/graphify.js.map +1 -1
  168. package/dist/analyzers/tools/jscpd.d.ts +12 -2
  169. package/dist/analyzers/tools/jscpd.d.ts.map +1 -1
  170. package/dist/analyzers/tools/jscpd.js +129 -6
  171. package/dist/analyzers/tools/jscpd.js.map +1 -1
  172. package/dist/analyzers/tools/lint-label.d.ts +29 -0
  173. package/dist/analyzers/tools/lint-label.d.ts.map +1 -0
  174. package/dist/analyzers/tools/lint-label.js +23 -0
  175. package/dist/analyzers/tools/lint-label.js.map +1 -0
  176. package/dist/analyzers/tools/minified-detection.d.ts +9 -0
  177. package/dist/analyzers/tools/minified-detection.d.ts.map +1 -0
  178. package/dist/analyzers/tools/minified-detection.js +147 -0
  179. package/dist/analyzers/tools/minified-detection.js.map +1 -0
  180. package/dist/analyzers/tools/nuget-package-reference.d.ts +133 -0
  181. package/dist/analyzers/tools/nuget-package-reference.d.ts.map +1 -0
  182. package/dist/analyzers/tools/nuget-package-reference.js +177 -0
  183. package/dist/analyzers/tools/nuget-package-reference.js.map +1 -0
  184. package/dist/analyzers/tools/osv-scanner-deps.d.ts +3 -2
  185. package/dist/analyzers/tools/osv-scanner-deps.d.ts.map +1 -1
  186. package/dist/analyzers/tools/osv-scanner-deps.js +32 -14
  187. package/dist/analyzers/tools/osv-scanner-deps.js.map +1 -1
  188. package/dist/analyzers/tools/osv.d.ts +36 -0
  189. package/dist/analyzers/tools/osv.d.ts.map +1 -1
  190. package/dist/analyzers/tools/osv.js +26 -0
  191. package/dist/analyzers/tools/osv.js.map +1 -1
  192. package/dist/analyzers/tools/parallel.d.ts +1 -1
  193. package/dist/analyzers/tools/parallel.d.ts.map +1 -1
  194. package/dist/analyzers/tools/parallel.js +2 -2
  195. package/dist/analyzers/tools/parallel.js.map +1 -1
  196. package/dist/analyzers/tools/report-date.d.ts +17 -0
  197. package/dist/analyzers/tools/report-date.d.ts.map +1 -0
  198. package/dist/analyzers/tools/report-date.js +26 -0
  199. package/dist/analyzers/tools/report-date.js.map +1 -0
  200. package/dist/analyzers/tools/risk-score.d.ts +7 -0
  201. package/dist/analyzers/tools/risk-score.d.ts.map +1 -1
  202. package/dist/analyzers/tools/risk-score.js +9 -2
  203. package/dist/analyzers/tools/risk-score.js.map +1 -1
  204. package/dist/analyzers/tools/run-tests-helper.d.ts +43 -0
  205. package/dist/analyzers/tools/run-tests-helper.d.ts.map +1 -0
  206. package/dist/analyzers/tools/run-tests-helper.js +156 -0
  207. package/dist/analyzers/tools/run-tests-helper.js.map +1 -0
  208. package/dist/analyzers/tools/runner.d.ts.map +1 -1
  209. package/dist/analyzers/tools/runner.js +75 -12
  210. package/dist/analyzers/tools/runner.js.map +1 -1
  211. package/dist/analyzers/tools/semgrep.d.ts +39 -2
  212. package/dist/analyzers/tools/semgrep.d.ts.map +1 -1
  213. package/dist/analyzers/tools/semgrep.js +131 -9
  214. package/dist/analyzers/tools/semgrep.js.map +1 -1
  215. package/dist/analyzers/tools/timing.d.ts +17 -3
  216. package/dist/analyzers/tools/timing.d.ts.map +1 -1
  217. package/dist/analyzers/tools/timing.js +36 -14
  218. package/dist/analyzers/tools/timing.js.map +1 -1
  219. package/dist/analyzers/tools/tool-registry.d.ts.map +1 -1
  220. package/dist/analyzers/tools/tool-registry.js +11 -1
  221. package/dist/analyzers/tools/tool-registry.js.map +1 -1
  222. package/dist/analyzers/tools/tools-unavailable-prose.d.ts +18 -0
  223. package/dist/analyzers/tools/tools-unavailable-prose.d.ts.map +1 -0
  224. package/dist/analyzers/tools/tools-unavailable-prose.js +69 -0
  225. package/dist/analyzers/tools/tools-unavailable-prose.js.map +1 -0
  226. package/dist/analyzers/tools/upgrade-plan-resolver.d.ts.map +1 -1
  227. package/dist/analyzers/tools/upgrade-plan-resolver.js +7 -0
  228. package/dist/analyzers/tools/upgrade-plan-resolver.js.map +1 -1
  229. package/dist/analyzers/tools/vendored-advisor.d.ts +43 -0
  230. package/dist/analyzers/tools/vendored-advisor.d.ts.map +1 -0
  231. package/dist/analyzers/tools/vendored-advisor.js +107 -0
  232. package/dist/analyzers/tools/vendored-advisor.js.map +1 -0
  233. package/dist/analyzers/tools/walk-paths.d.ts +78 -0
  234. package/dist/analyzers/tools/walk-paths.d.ts.map +1 -0
  235. package/dist/analyzers/tools/walk-paths.js +150 -0
  236. package/dist/analyzers/tools/walk-paths.js.map +1 -0
  237. package/dist/analyzers/tools/walk-source-files.d.ts +70 -0
  238. package/dist/analyzers/tools/walk-source-files.d.ts.map +1 -0
  239. package/dist/analyzers/tools/walk-source-files.js +369 -0
  240. package/dist/analyzers/tools/walk-source-files.js.map +1 -0
  241. package/dist/analyzers/types.d.ts +204 -4
  242. package/dist/analyzers/types.d.ts.map +1 -1
  243. package/dist/analyzers/xlsx/bom.d.ts.map +1 -1
  244. package/dist/analyzers/xlsx/bom.js +8 -1
  245. package/dist/analyzers/xlsx/bom.js.map +1 -1
  246. package/dist/cli.d.ts.map +1 -1
  247. package/dist/cli.js +581 -189
  248. package/dist/cli.js.map +1 -1
  249. package/dist/detect.d.ts.map +1 -1
  250. package/dist/detect.js +24 -7
  251. package/dist/detect.js.map +1 -1
  252. package/dist/doctor.d.ts.map +1 -1
  253. package/dist/doctor.js +103 -53
  254. package/dist/doctor.js.map +1 -1
  255. package/dist/languages/capabilities/provider.d.ts +130 -1
  256. package/dist/languages/capabilities/provider.d.ts.map +1 -1
  257. package/dist/languages/capabilities/types.d.ts +68 -7
  258. package/dist/languages/capabilities/types.d.ts.map +1 -1
  259. package/dist/languages/csharp.d.ts +15 -1
  260. package/dist/languages/csharp.d.ts.map +1 -1
  261. package/dist/languages/csharp.js +624 -146
  262. package/dist/languages/csharp.js.map +1 -1
  263. package/dist/languages/go.d.ts.map +1 -1
  264. package/dist/languages/go.js +89 -11
  265. package/dist/languages/go.js.map +1 -1
  266. package/dist/languages/index.d.ts +132 -2
  267. package/dist/languages/index.d.ts.map +1 -1
  268. package/dist/languages/index.js +207 -0
  269. package/dist/languages/index.js.map +1 -1
  270. package/dist/languages/java.d.ts.map +1 -1
  271. package/dist/languages/java.js +113 -26
  272. package/dist/languages/java.js.map +1 -1
  273. package/dist/languages/kotlin.d.ts.map +1 -1
  274. package/dist/languages/kotlin.js +132 -26
  275. package/dist/languages/kotlin.js.map +1 -1
  276. package/dist/languages/python.d.ts.map +1 -1
  277. package/dist/languages/python.js +149 -44
  278. package/dist/languages/python.js.map +1 -1
  279. package/dist/languages/ruby.d.ts +39 -1
  280. package/dist/languages/ruby.d.ts.map +1 -1
  281. package/dist/languages/ruby.js +178 -44
  282. package/dist/languages/ruby.js.map +1 -1
  283. package/dist/languages/rust.d.ts.map +1 -1
  284. package/dist/languages/rust.js +103 -16
  285. package/dist/languages/rust.js.map +1 -1
  286. package/dist/languages/types.d.ts +228 -5
  287. package/dist/languages/types.d.ts.map +1 -1
  288. package/dist/languages/typescript.d.ts.map +1 -1
  289. package/dist/languages/typescript.js +201 -14
  290. package/dist/languages/typescript.js.map +1 -1
  291. package/dist/scoring/dimensions/documentation.d.ts +53 -0
  292. package/dist/scoring/dimensions/documentation.d.ts.map +1 -0
  293. package/dist/scoring/dimensions/documentation.js +106 -0
  294. package/dist/scoring/dimensions/documentation.js.map +1 -0
  295. package/dist/scoring/dimensions/dx.d.ts +53 -0
  296. package/dist/scoring/dimensions/dx.d.ts.map +1 -0
  297. package/dist/scoring/dimensions/dx.js +105 -0
  298. package/dist/scoring/dimensions/dx.js.map +1 -0
  299. package/dist/scoring/dimensions/maintainability.d.ts +53 -0
  300. package/dist/scoring/dimensions/maintainability.d.ts.map +1 -0
  301. package/dist/scoring/dimensions/maintainability.js +101 -0
  302. package/dist/scoring/dimensions/maintainability.js.map +1 -0
  303. package/dist/scoring/dimensions/quality.d.ts +108 -0
  304. package/dist/scoring/dimensions/quality.d.ts.map +1 -0
  305. package/dist/scoring/dimensions/quality.js +174 -0
  306. package/dist/scoring/dimensions/quality.js.map +1 -0
  307. package/dist/scoring/dimensions/security.d.ts +84 -0
  308. package/dist/scoring/dimensions/security.d.ts.map +1 -0
  309. package/dist/scoring/dimensions/security.js +135 -0
  310. package/dist/scoring/dimensions/security.js.map +1 -0
  311. package/dist/scoring/dimensions/testing.d.ts +56 -0
  312. package/dist/scoring/dimensions/testing.d.ts.map +1 -0
  313. package/dist/scoring/dimensions/testing.js +98 -0
  314. package/dist/scoring/dimensions/testing.js.map +1 -0
  315. package/dist/scoring/evaluator.d.ts +27 -0
  316. package/dist/scoring/evaluator.d.ts.map +1 -0
  317. package/dist/scoring/evaluator.js +124 -0
  318. package/dist/scoring/evaluator.js.map +1 -0
  319. package/dist/scoring/format.d.ts +34 -0
  320. package/dist/scoring/format.d.ts.map +1 -0
  321. package/dist/scoring/format.js +63 -0
  322. package/dist/scoring/format.js.map +1 -0
  323. package/dist/scoring/index.d.ts +37 -0
  324. package/dist/scoring/index.d.ts.map +1 -0
  325. package/dist/scoring/index.js +57 -0
  326. package/dist/scoring/index.js.map +1 -0
  327. package/dist/scoring/overall.d.ts +54 -0
  328. package/dist/scoring/overall.d.ts.map +1 -0
  329. package/dist/scoring/overall.js +76 -0
  330. package/dist/scoring/overall.js.map +1 -0
  331. package/dist/scoring/result.d.ts +111 -0
  332. package/dist/scoring/result.d.ts.map +1 -0
  333. package/dist/scoring/result.js +14 -0
  334. package/dist/scoring/result.js.map +1 -0
  335. package/dist/scoring/spec.d.ts +76 -0
  336. package/dist/scoring/spec.d.ts.map +1 -0
  337. package/dist/scoring/spec.js +22 -0
  338. package/dist/scoring/spec.js.map +1 -0
  339. package/dist/scoring/thresholds.d.ts +56 -0
  340. package/dist/scoring/thresholds.d.ts.map +1 -0
  341. package/dist/scoring/thresholds.js +75 -0
  342. package/dist/scoring/thresholds.js.map +1 -0
  343. package/dist/tools-cli.d.ts.map +1 -1
  344. package/dist/tools-cli.js +21 -2
  345. package/dist/tools-cli.js.map +1 -1
  346. package/dist/types.d.ts +16 -0
  347. package/dist/types.d.ts.map +1 -1
  348. package/package.json +1 -1
  349. package/templates/.claude/commands/dashboard.md +17 -9
  350. package/dist/analyzers/scoring.d.ts +0 -49
  351. package/dist/analyzers/scoring.d.ts.map +0 -1
  352. package/dist/analyzers/scoring.js +0 -422
  353. package/dist/analyzers/scoring.js.map +0 -1
  354. package/dist/analyzers/security/scoring.d.ts +0 -29
  355. package/dist/analyzers/security/scoring.d.ts.map +0 -1
  356. package/dist/analyzers/security/scoring.js +0 -40
  357. package/dist/analyzers/security/scoring.js.map +0 -1
@@ -33,45 +33,116 @@ var __importStar = (this && this.__importStar) || (function () {
33
33
  };
34
34
  })();
35
35
  Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.formatDepActionTitle = formatDepActionTitle;
36
37
  exports.analyzeSecurity = analyzeSecurity;
37
38
  exports.formatSecurityReport = formatSecurityReport;
38
39
  /**
39
40
  * Security analyzer โ€” public API.
40
41
  */
41
42
  const path = __importStar(require("path"));
42
- const detect_1 = require("../../detect");
43
- const runner_1 = require("../tools/runner");
44
43
  const timing_1 = require("../tools/timing");
45
44
  const gather_1 = require("./gather");
46
- function countBySeverity(findings) {
47
- const counts = { critical: 0, high: 0, medium: 0, low: 0 };
48
- for (const f of findings)
49
- counts[f.severity]++;
50
- return counts;
45
+ const scoring_1 = require("../../scoring");
46
+ const cache_1 = require("../cache");
47
+ const health_1 = require("../health");
48
+ const languages_1 = require("../../languages");
49
+ const tools_unavailable_prose_1 = require("../tools/tools-unavailable-prose");
50
+ /**
51
+ * G_v4_4 (2.4.7): build the "Remediation Commands" entry for one
52
+ * dep-vuln finding by dispatching through the producing pack's
53
+ * `LanguageSupport.upgradeCommand`. Replaces the pre-G_v4_4 switch on
54
+ * `tool` (D062 โ€” switch keyed on `osv-scanner-nuget-direct` but generic
55
+ * osv-scanner findings carried `tool: 'osv-scanner'`, so dotnet-NuGet
56
+ * advisories shipped as bare prose comments). Each pack now owns its
57
+ * own template; non-pack code stays language-agnostic per CLAUDE.md
58
+ * rule 6.
59
+ *
60
+ * Dispatch order:
61
+ * 1. `f.packId` set โ†’ call pack's `upgradeCommand` (cardinal path).
62
+ * 2. No `packId` (legacy / non-pack producers) โ†’ generic prose fallback.
63
+ * 3. `upgradeCommand` returns `null` โ†’ generic prose fallback.
64
+ *
65
+ * **Contract (G_v4_10 / D111 root fix):** caller MUST pre-filter to
66
+ * findings with `fixedVersion` set. The D090 renderer splits findings
67
+ * into Actionable (has fixedVersion โ†’ bash block via this helper) and
68
+ * Mitigation (no fixedVersion โ†’ markdown list, never enters this
69
+ * helper). Pre-D111 this function had a "no patched version
70
+ * available โ€” review references" prose fallback for the no-fixedVersion
71
+ * case; that branch became dead after D090's split and gave the
72
+ * misleading impression that the helper handled both states. It does
73
+ * not โ€” returning null here signals a contract violation (caller did
74
+ * not filter), and consumers should treat null as a skip.
75
+ */
76
+ function buildUpgradeCommand(f) {
77
+ if (!f.fixedVersion)
78
+ return null;
79
+ if (f.packId) {
80
+ const pack = (0, languages_1.getLanguage)(f.packId);
81
+ if (pack && pack.upgradeCommand) {
82
+ const cmd = pack.upgradeCommand(f.package, f.fixedVersion);
83
+ if (cmd)
84
+ return cmd;
85
+ }
86
+ }
87
+ return `# Upgrade ${f.package} to ${f.fixedVersion} (source tool: ${f.tool ?? 'unknown'})`;
88
+ }
89
+ /**
90
+ * G_v4_10 / D111 (2.4.7 Phase C3): canonical UI title for a dep-vuln
91
+ * action. Branches on `fixedVersion` because "upgrade" and "mitigate"
92
+ * are linguistically different actions โ€” squashing them into one
93
+ * template with a `?? '(no patch)'` literal produced the grammatically
94
+ * broken "Upgrade `SharpCompress` to (no patch)" on the .NET WinForms benchmark when
95
+ * D108 sparse-tier fallback floated mitigation-only items into Top 5.
96
+ *
97
+ * This is the ONLY authorized site for phrasing "(no patch)" / "no
98
+ * patch available" in code; `scripts/check-architecture.sh` enforces
99
+ * G_v4_10 by banning the literal `'(no patch)'` outside this helper.
100
+ * Consumers (Top 5, future risk-prioritized lists, etc.) call this
101
+ * instead of templating inline.
102
+ */
103
+ function formatDepActionTitle(pkg, fixedVersion) {
104
+ return fixedVersion
105
+ ? `Upgrade \`${pkg}\` to ${fixedVersion}`
106
+ : `Review advisory for \`${pkg}\` โ€” no patch available`;
51
107
  }
52
108
  async function analyzeSecurity(repoPath, options = {}) {
53
109
  const verbose = !!options.verbose;
54
- const stack = (0, detect_1.detect)(repoPath);
110
+ // Code-side findings + the canonical SecurityAggregate come from the
111
+ // cross-process cache. Same gather that `health` reads โ€” two
112
+ // consumers, ONE source of truth for "code findings by severity,"
113
+ // "secrets by severity," "tls-bypass dedup." Closes the cross-process
114
+ // drift class for code-side numbers by construction.
115
+ const result = await (0, cache_1.readOrBuildAnalysisResult)({
116
+ cwd: repoPath,
117
+ build: (cwd) => (0, health_1.gatherAnalysisResultBody)(cwd, { verbose }),
118
+ });
119
+ const { stack, capabilities } = result;
120
+ const aggregate = capabilities.securityAggregate;
121
+ if (!aggregate) {
122
+ throw new Error('analyzeSecurity: cached AnalysisResult missing securityAggregate');
123
+ }
124
+ // Dependency CVEs need the full enrichment pass (EPSS / KEV /
125
+ // reachability / risk) that vuln-scan surfaces and `health` does
126
+ // not. Run it fresh against the same repo state the cache captured.
127
+ // Severity buckets + unique-fingerprint counts still come from the
128
+ // cached aggregate (severity is a raw advisory property; both basic
129
+ // and enriched paths produce the same buckets), so the two
130
+ // subcommands report identical totals.
131
+ const deps = await (0, timing_1.timedAsync)('dep-audit (enriched)', verbose, () => (0, gather_1.gatherDepVulns)(repoPath));
132
+ // toolsUsed/toolsUnavailable are derived from the cached aggregate's
133
+ // provenance (the canonical "what scanners ran" record), the
134
+ // enriched dep gather, and the cached file-walker capabilities
135
+ // (`find`, `git` are always-available builtins).
55
136
  const toolsUsed = ['find', 'git'];
56
137
  const toolsUnavailable = [];
57
- // 1. Secrets (gitleaks) โ€” dispatcher-driven via the SECRETS capability.
58
- const secrets = await (0, timing_1.timedAsync)('gitleaks', verbose, () => (0, gather_1.gatherSecrets)(repoPath));
59
- if (secrets.toolUsed)
60
- toolsUsed.push(secrets.toolUsed);
138
+ if (aggregate.provenance.secrets.tool)
139
+ toolsUsed.push(aggregate.provenance.secrets.tool);
61
140
  else
62
141
  toolsUnavailable.push('gitleaks');
63
- // 2. File findings (private keys, .env)
64
- const files = (0, timing_1.timed)('file-findings', verbose, () => (0, gather_1.gatherFileFindings)(repoPath));
65
- // 3. Code patterns (semgrep) โ€” dispatcher-driven via CODE_PATTERNS.
66
- const code = await (0, timing_1.timedAsync)('semgrep', verbose, () => (0, gather_1.gatherCodePatterns)(repoPath));
67
- if (code.toolUsed)
68
- toolsUsed.push(code.toolUsed);
142
+ if (aggregate.provenance.codePatterns.tool)
143
+ toolsUsed.push(aggregate.provenance.codePatterns.tool);
69
144
  else
70
145
  toolsUnavailable.push('semgrep');
71
- // 4. Dependency CVEs โ€” capability dispatcher across every active language
72
- // pack. The envelope's tool field already joins multiple sources
73
- // ('pip-audit, npm-audit'); split it back out for toolsUsed.
74
- const deps = await (0, timing_1.timedAsync)('dep-audit', verbose, () => (0, gather_1.gatherDepVulns)(repoPath));
75
146
  if (deps.tool) {
76
147
  for (const t of deps.tool.split(', '))
77
148
  toolsUsed.push(t);
@@ -79,18 +150,71 @@ async function analyzeSecurity(repoPath, options = {}) {
79
150
  else {
80
151
  toolsUnavailable.push('dep-audit');
81
152
  }
82
- const allFindings = [...secrets.findings, ...files, ...code.findings];
83
- const counts = countBySeverity(allFindings);
153
+ if (aggregate.findingsByCategory.code.some((f) => f.tool === 'tls-bypass-registry')) {
154
+ toolsUsed.push('tls-bypass-registry');
155
+ }
156
+ // Combined code-side severity counts for the existing "Code Findings"
157
+ // table (which renders secrets+files+code+config under one heading).
158
+ // Derived from the dedup'd aggregate, NOT from raw envelope arrays โ€”
159
+ // that's the D086/D091 closure. Sum of unique findings across the
160
+ // code/secret/config categories.
161
+ const codeFindings = [
162
+ ...aggregate.findingsByCategory.secret,
163
+ ...aggregate.findingsByCategory.code,
164
+ ...aggregate.findingsByCategory.config,
165
+ ];
166
+ const codeSummary = {
167
+ critical: aggregate.codeBySeverity.critical + aggregate.secretsBySeverity.critical,
168
+ high: aggregate.codeBySeverity.high + aggregate.secretsBySeverity.high,
169
+ medium: aggregate.codeBySeverity.medium + aggregate.secretsBySeverity.medium,
170
+ low: aggregate.codeBySeverity.low + aggregate.secretsBySeverity.low,
171
+ total: codeFindings.length,
172
+ };
173
+ // C2.1 (perception-D086 closure): code-only + secrets-only severity
174
+ // breakdowns surfaced as siblings of `summary.findings`. Renderer
175
+ // splits the executive-summary "Code Findings" table into two
176
+ // labeled sections so a reader scanning health + vuln-scan sees
177
+ // the SAME number under the SAME label.
178
+ const codeOnlySummary = {
179
+ critical: aggregate.codeBySeverity.critical,
180
+ high: aggregate.codeBySeverity.high,
181
+ medium: aggregate.codeBySeverity.medium,
182
+ low: aggregate.codeBySeverity.low,
183
+ total: aggregate.findingsByCategory.code.length,
184
+ };
185
+ const secretsOnlySummary = {
186
+ critical: aggregate.secretsBySeverity.critical,
187
+ high: aggregate.secretsBySeverity.high,
188
+ medium: aggregate.secretsBySeverity.medium,
189
+ low: aggregate.secretsBySeverity.low,
190
+ total: aggregate.findingsByCategory.secret.length + aggregate.findingsByCategory.config.length,
191
+ };
192
+ // D087 closure: dependency totals now read the canonical
193
+ // unique-by-fingerprint count from the aggregate, matching BoM's
194
+ // semantics. critical/high/medium/low are derived from the unique
195
+ // set, so the bucket sum always equals the unique total โ€” no more
196
+ // 70 vs 81 on the same page.
197
+ const depSummary = {
198
+ ...deps,
199
+ critical: aggregate.depBySeverity.critical,
200
+ high: aggregate.depBySeverity.high,
201
+ medium: aggregate.depBySeverity.medium,
202
+ low: aggregate.depBySeverity.low,
203
+ total: aggregate.dependencyAdvisoryUniqueCount,
204
+ findings: [...aggregate.findingsByCategory.dependency],
205
+ };
84
206
  return {
85
- repo: stack.projectName || path.basename(repoPath),
86
- analyzedAt: new Date().toISOString(),
87
- commitSha: (0, runner_1.run)('git rev-parse --short HEAD 2>/dev/null', repoPath),
88
- branch: (0, runner_1.run)('git rev-parse --abbrev-ref HEAD 2>/dev/null', repoPath),
207
+ repo: stack.projectName || path.basename(result.cwd),
208
+ analyzedAt: result.builtAt,
209
+ commitSha: result.commitSha,
210
+ branch: result.branch,
89
211
  summary: {
90
- findings: { ...counts, total: allFindings.length },
91
- dependencies: deps,
212
+ findings: codeSummary,
213
+ codeOnly: codeOnlySummary,
214
+ secretsOnly: secretsOnlySummary,
215
+ dependencies: depSummary,
92
216
  },
93
- findings: allFindings,
217
+ findings: codeFindings,
94
218
  toolsUsed,
95
219
  toolsUnavailable,
96
220
  };
@@ -105,30 +229,60 @@ function formatSecurityReport(report, elapsed) {
105
229
  L.push('');
106
230
  L.push('---');
107
231
  L.push('');
108
- // Executive summary โ€” two independent axes that must NOT be summed
109
- // naรฏvely. Code findings are issues your team owns and patches in
110
- // source; dependency vulns are issues in third-party packages that
111
- // upgrade-bumps resolve. Reporting them separately keeps the
112
- // remediation owner unambiguous.
232
+ // C2.1 (perception D086 closure): three independent axes, each with
233
+ // its own labeled table. Pre-C2.1 the executive summary had a single
234
+ // "Code Findings" table that combined code+secret+config under one
235
+ // label โ€” health's "code findings" prose meant code-only, so the
236
+ // two surfaces showed different numbers under the same name. Now
237
+ // each surface reads its own named field and the labels match.
113
238
  const s = report.summary.findings;
239
+ const c = report.summary.codeOnly;
240
+ const k = report.summary.secretsOnly;
114
241
  const d = report.summary.dependencies;
115
242
  L.push('## Executive Summary');
116
243
  L.push('');
117
- L.push('Security signals split across two independent axes:');
118
- L.push('- **Code findings** โ€” vulnerabilities in source your team owns. Fix by patching code.');
244
+ L.push('Security signals split across three independent axes:');
245
+ L.push('- **Code findings** โ€” code-pattern vulnerabilities your team owns (semgrep + TLS-bypass-registry). Fix by patching code.');
246
+ L.push('- **Secret & config findings** โ€” hardcoded secrets, private-key files, `.env` tracked in git. Fix by rotating + removing from history.');
119
247
  L.push('- **Dependency vulnerabilities** โ€” vulnerabilities in third-party packages. Fix by upgrading the dep.');
120
248
  L.push('');
249
+ // Code-only severity table. Reads `summary.codeOnly` directly from
250
+ // the canonical aggregator field `codeBySeverity`. Health's
251
+ // `Xc Yh Zm Wl code findings` prose comes from the same field โ€”
252
+ // numbers match by construction.
253
+ const codeSources = [
254
+ ...new Set(report.findings.filter((f) => f.category === 'code').map((f) => f.tool)),
255
+ ].sort();
121
256
  L.push('### Code Findings');
122
257
  L.push('');
123
- L.push(`_Sources: ${[...new Set(report.findings.map((f) => f.tool))].sort().join(', ') || '(none)'}_`);
258
+ L.push(`_Sources: ${codeSources.join(', ') || '(none)'}_`);
124
259
  L.push('');
125
260
  L.push('| Severity | Count |');
126
261
  L.push('|----------|------:|');
127
- L.push(`| CRITICAL | ${s.critical} |`);
128
- L.push(`| HIGH | ${s.high} |`);
129
- L.push(`| MEDIUM | ${s.medium} |`);
130
- L.push(`| LOW | ${s.low} |`);
131
- L.push(`| **Subtotal** | **${s.total}** |`);
262
+ L.push(`| CRITICAL | ${c.critical} |`);
263
+ L.push(`| HIGH | ${c.high} |`);
264
+ L.push(`| MEDIUM | ${c.medium} |`);
265
+ L.push(`| LOW | ${c.low} |`);
266
+ L.push(`| **Subtotal** | **${c.total}** |`);
267
+ L.push('');
268
+ // Secret + config severity table. Reads `summary.secretsOnly` from
269
+ // the aggregator's `secretsBySeverity` axis.
270
+ const secretSources = [
271
+ ...new Set(report.findings
272
+ .filter((f) => f.category === 'secret' || f.category === 'config')
273
+ .map((f) => f.tool)),
274
+ ].sort();
275
+ L.push('### Secret & Config Findings');
276
+ L.push('');
277
+ L.push(`_Sources: ${secretSources.join(', ') || '(none)'}_`);
278
+ L.push('');
279
+ L.push('| Severity | Count |');
280
+ L.push('|----------|------:|');
281
+ L.push(`| CRITICAL | ${k.critical} |`);
282
+ L.push(`| HIGH | ${k.high} |`);
283
+ L.push(`| MEDIUM | ${k.medium} |`);
284
+ L.push(`| LOW | ${k.low} |`);
285
+ L.push(`| **Subtotal** | **${k.total}** |`);
132
286
  L.push('');
133
287
  L.push('### Dependency Vulnerabilities');
134
288
  L.push('');
@@ -143,16 +297,206 @@ function formatSecurityReport(report, elapsed) {
143
297
  L.push(`| LOW | ${d.low} |`);
144
298
  L.push(`| **Subtotal** | **${d.total}** |`);
145
299
  L.push('');
146
- L.push(`**Total signals:** ${s.total + d.total} (${s.total} code + ${d.total} dependency)`);
300
+ // D025e: if at least one pack scanned successfully (tool set) but
301
+ // another active pack returned unavailable, the totals are partial.
302
+ // Surface this rather than letting the customer assume the table is
303
+ // exhaustive across their stack.
304
+ if (!d.available) {
305
+ L.push(`> โš  **Partial scan**: ${d.unavailableReason}. The table above`);
306
+ L.push(`> reflects only the packs whose dep-vuln tooling succeeded;`);
307
+ L.push(`> findings in the unscanned pack may be present but not listed.`);
308
+ L.push('');
309
+ }
310
+ L.push(`**Total signals:** ${s.total + d.total} (${c.total} code + ${k.total} secret/config + ${d.total} dependency)`);
311
+ }
312
+ else if (!d.available) {
313
+ // D025e: scan was attempted but couldn't run for any active pack.
314
+ // Pre-D025e this case shared the "no language pack with a depVulns
315
+ // provider was active" string with the genuinely-inactive case โ€” a
316
+ // factually-wrong framing because pack WAS active, just blocked.
317
+ L.push(`> โš  **Dependency vulnerability scan unavailable**: ${d.unavailableReason}.`);
318
+ L.push(`>`);
319
+ L.push(`> The dep-audit tool didn't run on this repo, so the count below`);
320
+ L.push(`> is not "0 vulnerabilities found" โ€” it's "0 vulnerabilities`);
321
+ L.push(`> reported because the scan didn't complete." The Security`);
322
+ L.push(`> dimension score is capped at ${scoring_1.CAP_TIERS.uncertainty}/100 until the underlying tool`);
323
+ L.push(`> becomes available or a fallback path produces real data.`);
324
+ L.push('');
325
+ L.push(`**Total signals:** ${s.total} (code only โ€” dep-audit incomplete)`);
147
326
  }
148
327
  else {
149
- L.push('_No dependency audit data โ€” no language pack with a depVulns provider was active._');
328
+ // D025e: genuinely-inactive case. No active language pack exposes a
329
+ // depVulns provider โ€” either the repo is non-code (docs/assets only)
330
+ // or every active pack legitimately reported `no-manifest` (a
331
+ // polyglot repo where the pack activates but has nothing to scan).
332
+ L.push('_No dependency audit data โ€” no active language pack reported a manifest to scan._');
150
333
  L.push('');
151
334
  L.push(`**Total signals:** ${s.total} (code only)`);
152
335
  }
153
336
  L.push('');
154
337
  L.push('---');
155
338
  L.push('');
339
+ const topActions = [];
340
+ const findings = report.findings;
341
+ const envInGit = findings.filter((f) => f.category === 'config' && f.rule === 'env-in-git');
342
+ // 1. KEV-listed deps
343
+ if (topActions.length < 5) {
344
+ const kev = (d.findings ?? [])
345
+ .filter((f) => f.kev)
346
+ .sort((a, b) => (b.riskScore ?? 0) - (a.riskScore ?? 0));
347
+ for (const f of kev) {
348
+ if (topActions.length >= 5)
349
+ break;
350
+ topActions.push({
351
+ title: formatDepActionTitle(f.package, f.fixedVersion),
352
+ location: `${f.package}@${f.installedVersion ?? '?'} ยท ${f.id}`,
353
+ impact: `**KEV (active exploitation)** ยท ${f.severity.toUpperCase()}`,
354
+ });
355
+ }
356
+ }
357
+ // 2. Hardcoded secrets (gitleaks)
358
+ if (topActions.length < 5) {
359
+ const secrets = findings.filter((f) => f.category === 'secret' && f.rule !== 'private-key-file');
360
+ for (const f of secrets) {
361
+ if (topActions.length >= 5)
362
+ break;
363
+ topActions.push({
364
+ title: `Rotate exposed credential (\`${f.rule}\`)`,
365
+ location: `${f.file}${f.line ? ':' + f.line : ''}`,
366
+ impact: `${f.severity.toUpperCase()} ยท committed credential โ€” presumed compromised`,
367
+ });
368
+ }
369
+ }
370
+ // 3. .env in git โ€” one action covering all .env files
371
+ if (topActions.length < 5 && envInGit.length > 0) {
372
+ topActions.push({
373
+ title: `Remove \`.env\` from git tracking + rotate credentials`,
374
+ location: envInGit.map((f) => f.file).join(', '),
375
+ impact: `HIGH ยท ${envInGit.length} file${envInGit.length === 1 ? '' : 's'} โ€” see callout below for full procedure`,
376
+ });
377
+ }
378
+ // 4. Private-key files on disk
379
+ if (topActions.length < 5) {
380
+ const keys = findings.filter((f) => f.rule === 'private-key-file');
381
+ for (const f of keys) {
382
+ if (topActions.length >= 5)
383
+ break;
384
+ topActions.push({
385
+ title: `Remove private-key file from repo + rotate`,
386
+ location: f.file,
387
+ impact: `CRITICAL ยท key file on disk`,
388
+ });
389
+ }
390
+ }
391
+ // 5. Top non-KEV dep vulns. D108 closure: graceful tiering for
392
+ // sparse repos. Pre-D108 the filter `riskScore >= 25` excluded
393
+ // the "watch" tier (10-25); on the .NET WinForms benchmark with MongoDB.Driver
394
+ // risk 19 + SharpCompress risk 15, no deps surfaced in Top 5
395
+ // even though both have unpatched advisories. Fix: iterate
396
+ // risk-score tiers and stop only when Top 5 is full.
397
+ //
398
+ // Tier order matches risk-score.ts:
399
+ // patch-now (โ‰ฅ 50) โ†’ plan-and-patch (25-50) โ†’ watch (10-25)
400
+ // โ†’ deprioritized (< 10)
401
+ // Within each tier, sort by risk score desc.
402
+ if (topActions.length < 5) {
403
+ const SCORE_TIERS = [50, 25, 10, 0];
404
+ const usedFingerprints = new Set();
405
+ for (const minScore of SCORE_TIERS) {
406
+ if (topActions.length >= 5)
407
+ break;
408
+ const tier = (d.findings ?? [])
409
+ .filter((f) => !f.kev)
410
+ .filter((f) => !usedFingerprints.has(f.fingerprint ?? ''))
411
+ .filter((f) => {
412
+ const score = f.riskScore ?? 0;
413
+ // For the lowest tier (>= 0), include findings without a
414
+ // scored risk too (some packs don't compute risk yet).
415
+ return minScore === 0 ? true : score >= minScore;
416
+ })
417
+ .sort((a, b) => (b.riskScore ?? 0) - (a.riskScore ?? 0));
418
+ for (const f of tier) {
419
+ if (topActions.length >= 5)
420
+ break;
421
+ topActions.push({
422
+ title: formatDepActionTitle(f.package, f.fixedVersion),
423
+ location: `${f.package}@${f.installedVersion ?? '?'} ยท ${f.id}`,
424
+ impact: typeof f.riskScore === 'number'
425
+ ? `${f.severity.toUpperCase()} ยท risk score ${f.riskScore.toFixed(0)}`
426
+ : `${f.severity.toUpperCase()} ยท ${f.id}`,
427
+ });
428
+ usedFingerprints.add(f.fingerprint ?? '');
429
+ }
430
+ }
431
+ }
432
+ // 6. Code-pattern HIGH findings (TLS bypass, eval, SSRF, etc.)
433
+ if (topActions.length < 5) {
434
+ const codeHigh = findings
435
+ .filter((f) => f.category === 'code' && (f.severity === 'critical' || f.severity === 'high'))
436
+ .sort((a, b) => (a.severity === 'critical' ? -1 : 1) - (b.severity === 'critical' ? -1 : 1));
437
+ for (const f of codeHigh) {
438
+ if (topActions.length >= 5)
439
+ break;
440
+ topActions.push({
441
+ title: `Fix ${f.rule}`,
442
+ location: `${f.file}${f.line ? ':' + f.line : ''}`,
443
+ impact: `${f.severity.toUpperCase()} ยท ${f.cwe || 'see source'}`,
444
+ });
445
+ }
446
+ }
447
+ if (topActions.length > 0) {
448
+ L.push('## ๐ŸŽฏ Top 5 Priority Actions');
449
+ L.push('');
450
+ L.push('Ranked by remediation leverage (active exploitation > committed credentials > high-risk dep upgrades > code patterns). Full inventory in the sections below.');
451
+ L.push('');
452
+ L.push('| # | Action | Location | Impact |');
453
+ L.push('|---|--------|----------|--------|');
454
+ topActions.forEach((a, i) => {
455
+ L.push(`| ${i + 1} | ${a.title} | \`${a.location}\` | ${a.impact} |`);
456
+ });
457
+ L.push('');
458
+ L.push('---');
459
+ L.push('');
460
+ }
461
+ // C2.3 / D099: `.env` files tracked in git get a dedicated callout
462
+ // block with the specific remediation command. Pre-C2.3 these
463
+ // findings appeared in the per-category list with no actionable
464
+ // command, drowning in the noise. A leaked `.env` is high-leverage
465
+ // remediation: one `git rm --cached` per file + a history-rewrite
466
+ // caveat covers the surface.
467
+ const envFindings = report.findings.filter((f) => f.category === 'config' && f.rule === 'env-in-git');
468
+ if (envFindings.length > 0) {
469
+ L.push('## ๐Ÿšจ `.env` files tracked in git');
470
+ L.push('');
471
+ L.push(`**${envFindings.length} \`.env\` file${envFindings.length === 1 ? '' : 's'} committed to source control.** Even if the file has been deleted in HEAD, the secrets remain in git history and are presumed compromised โ€” rotate ALL credentials in these files immediately.`);
472
+ L.push('');
473
+ L.push('Remove the file(s) from the working tree (history rewrite is separate):');
474
+ L.push('');
475
+ L.push('```bash');
476
+ for (const f of envFindings) {
477
+ L.push(`git rm --cached ${f.file}`);
478
+ }
479
+ L.push('echo ".env" >> .gitignore # if not already gitignored');
480
+ L.push('git commit -m "remove .env files from tracking"');
481
+ L.push('```');
482
+ L.push('');
483
+ L.push('**To purge from history** (rewrites SHAs โ€” coordinate with team before pushing):');
484
+ L.push('');
485
+ L.push('```bash');
486
+ L.push('# Option 1: git filter-repo (preferred โ€” fast, safe)');
487
+ for (const f of envFindings) {
488
+ L.push(`git filter-repo --path ${f.file} --invert-paths`);
489
+ }
490
+ L.push('');
491
+ L.push('# Option 2: BFG Repo-Cleaner (alternative)');
492
+ L.push('# bfg --delete-files .env');
493
+ L.push('');
494
+ L.push('# After EITHER option, every collaborator must re-clone.');
495
+ L.push('```');
496
+ L.push('');
497
+ L.push('---');
498
+ L.push('');
499
+ }
156
500
  // Findings grouped by category. Section numbers are assigned dynamically โ€”
157
501
  // empty categories are skipped entirely, so the rendered document never
158
502
  // jumps from "## 1. ..." to "## 4. ..." when middle sections have no
@@ -210,7 +554,11 @@ function formatSecurityReport(report, elapsed) {
210
554
  for (const f of shown) {
211
555
  const risk = typeof f.riskScore === 'number' ? `**${f.riskScore.toFixed(0)}**` : 'โ€”';
212
556
  const kev = f.kev ? 'โš ' : '';
213
- const reach = f.reachable === true ? 'โœ“' : f.reachable === false ? 'ยท' : '';
557
+ // D044 (2.4.7): three-state reachability rendering. Pre-D044
558
+ // `reachable === false` rendered as a mid-dot `ยท` which customers
559
+ // misread as "unknown/not-checked." Use โœ“/โœ—/โ€” for clarity and
560
+ // pair with the legend below the table.
561
+ const reach = f.reachable === true ? 'โœ“' : f.reachable === false ? 'โœ—' : 'โ€”';
214
562
  const epss = typeof f.epssScore === 'number' ? `${(f.epssScore * 100).toFixed(2)}%` : 'โ€”';
215
563
  L.push(`| ${risk} | ${f.severity.toUpperCase()} | ${kev} | ${reach} | \`${f.package}@${f.installedVersion ?? '?'}\` | \`${f.id}\` | ${f.fixedVersion ?? 'โ€”'} | ${epss} | ${f.tool} |`);
216
564
  }
@@ -218,15 +566,80 @@ function formatSecurityReport(report, elapsed) {
218
566
  L.push('');
219
567
  L.push(`_Showing ${cap} of ${sorted.length} advisories ranked by risk score. Run with \`--detailed\` for the full inventory + CVSS column._`);
220
568
  }
569
+ // D043 + D044 (2.4.7): column legends. Customers shouldn't have to
570
+ // infer what `ยท` / `โœ“` / `**19**` mean. Brief explanations keep the
571
+ // table interpretable without external docs.
572
+ L.push('');
573
+ L.push('**Column legend**:');
574
+ L.push('');
575
+ L.push(`- **Risk**: composite score combining CVSS base score, KEV-listing, EPSS exploitation probability, and source-code reachability. Higher is worse. Tiers (post-D023 / risk-score.ts): \`< 10\` deprioritized ยท \`10-25\` watch ยท \`25-50\` plan-and-patch ยท \`> 50\` patch-now.`);
576
+ L.push(`- **KEV**: \`โš \` means the CVE appears in CISA's Known Exploited Vulnerabilities catalog (active in-the-wild exploitation). Blank = not-KEV (verified, not omitted).`);
577
+ L.push(`- **Reach**: \`โœ“\` = an active language-pack's imports capability found this package in source (reachable). \`โœ—\` = imports walked but this package is declared in manifest only, not imported in code. \`โ€”\` = imports capability didn't run (no active pack, no source files, etc.) โ€” unknown reachability.`);
578
+ L.push(`- **Fix**: minimum upgrade version that clears the advisory (extracted from OSV's \`affected.ranges.events.fixed\`). \`โ€”\` = no patch released yet (consider mitigations) OR the source tool didn't surface fix info.`);
579
+ L.push(`- **EPSS**: probability the CVE is exploited within the next 30 days (FIRST.org's exploit-prediction scoring system). Blank/\`โ€”\` = no EPSS data (typically GHSA without a CVE alias).`);
221
580
  L.push('');
222
581
  L.push('---');
223
582
  L.push('');
583
+ // C3.2 / D090 (2.4.7): "Remediation Commands" splits into two
584
+ // subsections so customers can distinguish patch-now upgrades from
585
+ // advisories that need manual mitigation. Pre-fix all entries went
586
+ // into one bash block which made `# no patched version available`
587
+ // prose dominate (platform: 84 prose lines next to 1 actual
588
+ // `npm install` command), drowning out the actionable subset.
589
+ //
590
+ // - "Actionable upgrades" โ†’ findings with `fixedVersion` set.
591
+ // Pack's `upgradeCommand` emits a
592
+ // copy-pasteable install command.
593
+ // - "Mitigation required" โ†’ findings with NO `fixedVersion`.
594
+ // Rendered as a markdown list with
595
+ // the advisory link (first reference)
596
+ // so the customer has a one-click
597
+ // path to upstream guidance.
598
+ const actionable = report.summary.dependencies.findings.filter((f) => !!f.fixedVersion);
599
+ const mitigation = report.summary.dependencies.findings.filter((f) => !f.fixedVersion);
600
+ if (actionable.length > 0 || mitigation.length > 0) {
601
+ L.push('## Remediation Commands');
602
+ L.push('');
603
+ if (actionable.length > 0) {
604
+ L.push(`### Actionable upgrades (${actionable.length})`);
605
+ L.push('');
606
+ L.push('Copy-paste to upgrade each vulnerable package (run from the project root):');
607
+ L.push('');
608
+ L.push('```bash');
609
+ for (const f of actionable) {
610
+ // `actionable` is pre-filtered by `!!fixedVersion`; the `if`
611
+ // is a type-narrowing guard, not a runtime branch.
612
+ if (!f.fixedVersion)
613
+ continue;
614
+ const cmd = buildUpgradeCommand(f);
615
+ if (cmd === null)
616
+ continue;
617
+ L.push(`# ${f.package}@${f.installedVersion ?? '?'} โ†’ ${f.fixedVersion} (${f.id})`);
618
+ L.push(cmd);
619
+ L.push('');
620
+ }
621
+ L.push('```');
622
+ L.push('');
623
+ }
624
+ if (mitigation.length > 0) {
625
+ L.push(`### Mitigation required โ€” no patch available (${mitigation.length})`);
626
+ L.push('');
627
+ L.push('These advisories have no fixed version released. Review each upstream advisory for workarounds, then reduce exposure by upgrading the dependent package, pinning to a non-vulnerable transitive range, or removing the affected code path.');
628
+ L.push('');
629
+ for (const f of mitigation) {
630
+ const advisory = f.references?.[0];
631
+ const idCell = advisory ? `[${f.id}](${advisory})` : f.id;
632
+ L.push(`- \`${f.package}@${f.installedVersion ?? '?'}\` โ€” ${idCell}`);
633
+ }
634
+ L.push('');
635
+ }
636
+ L.push('---');
637
+ L.push('');
638
+ }
224
639
  }
225
640
  // Footer
226
641
  L.push(`**Tools used:** ${report.toolsUsed.join(', ')}`);
227
- if (report.toolsUnavailable.length > 0) {
228
- L.push(`**Tools unavailable:** ${report.toolsUnavailable.join(', ')}`);
229
- }
642
+ L.push(...(0, tools_unavailable_prose_1.renderToolsUnavailableLines)(report.toolsUnavailable));
230
643
  L.push(`**Analysis time:** ${elapsed}s`);
231
644
  L.push('');
232
645
  L.push('*Generated by [VyuhLabs DXKit](https://www.npmjs.com/package/@vyuhlabs/dxkit)*');