@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
package/dist/cli.js CHANGED
@@ -35,6 +35,7 @@ var __importStar = (this && this.__importStar) || (function () {
35
35
  Object.defineProperty(exports, "__esModule", { value: true });
36
36
  exports.run = run;
37
37
  const node_util_1 = require("node:util");
38
+ const vendored_advisor_1 = require("./analyzers/tools/vendored-advisor");
38
39
  const detect_1 = require("./detect");
39
40
  const generator_1 = require("./generator");
40
41
  const prompts_1 = require("./prompts");
@@ -43,8 +44,22 @@ const update_1 = require("./update");
43
44
  const doctor_1 = require("./doctor");
44
45
  const constants_1 = require("./constants");
45
46
  const logger = __importStar(require("./logger"));
47
+ const scoring_1 = require("./scoring");
48
+ const tools_unavailable_prose_1 = require("./analyzers/tools/tools-unavailable-prose");
49
+ const report_date_1 = require("./analyzers/tools/report-date");
46
50
  const fs = __importStar(require("fs"));
47
51
  const path = __importStar(require("path"));
52
+ // process.stdout.write returns false when the OS pipe buffer is full
53
+ // (typically 64KB on Linux). Without awaiting 'drain', the process exits
54
+ // and the tail of large payloads is silently lost on POSIX — manifests as
55
+ // 0-byte files when piping `--json` output through `cat > file` or similar.
56
+ // Tracked as D017.
57
+ async function emitJson(payload) {
58
+ const data = JSON.stringify(payload, null, 2) + '\n';
59
+ if (!process.stdout.write(data)) {
60
+ await new Promise((resolve) => process.stdout.once('drain', resolve));
61
+ }
62
+ }
48
63
  function printUsage() {
49
64
  console.log(`
50
65
  ${logger.bold('vyuh-dxkit')} v${constants_1.VERSION} — AI-native developer experience toolkit
@@ -60,6 +75,9 @@ function printUsage() {
60
75
  vyuh-dxkit dev-report [path] Developer activity analysis
61
76
  vyuh-dxkit licenses [path] Dependency license inventory
62
77
  vyuh-dxkit bom [path] Bill of Materials (licenses + vulnerabilities joined)
78
+ vyuh-dxkit coverage [path] Run per-pack test-with-coverage (side-effecting; materializes the coverage artifact health/test-gaps read)
79
+ vyuh-dxkit dashboard [path] Render .dxkit/reports/ into a single HTML dashboard
80
+ vyuh-dxkit report [path] Run every analyzer + dashboard in one shot (full audit)
63
81
  vyuh-dxkit to-xlsx <json> Convert a dxkit JSON report to 15-col XLSX
64
82
  vyuh-dxkit tools [path] Show required analysis tools status
65
83
  vyuh-dxkit tools install Interactively install missing tools
@@ -79,16 +97,16 @@ function printUsage() {
79
97
  --rescan Re-run codebase analysis
80
98
 
81
99
  ${logger.bold('Analyzer options (health, vulnerabilities, test-gaps, quality, dev-report, licenses, bom):')}
82
- --json Print report as JSON to stdout
83
- --verbose Print per-tool timing to stderr
84
- --no-save Skip writing the markdown report file
85
- --detailed Also write <name>-detailed.md + .json with evidence + ranked actions
86
- --xlsx Licenses/bom: also write 15-col BOM XLSX
87
- --since Dev-report: start date (YYYY-MM-DD)
88
- --filter Bom: 'all' (default) or 'top-level' (keeps only root manifest deps;
89
- advisory rollup under byTopLevelDep still reflects transitives)
90
- --no-nested Bom: disable nested-project aggregation (default scans the repo
91
- for all sub-projects with manifests and merges their BOMs)
100
+ --json Print report as JSON to stdout
101
+ --verbose Print per-tool timing to stderr
102
+ --no-save Skip writing the markdown report file
103
+ --detailed Also write <name>-detailed.md + .json with evidence + ranked actions
104
+ --xlsx Licenses/bom: also write 15-col BOM XLSX
105
+ --since Dev-report: start date (YYYY-MM-DD)
106
+ --filter Bom: 'all' (default) or 'top-level' (keeps only root manifest deps;
107
+ advisory rollup under byTopLevelDep still reflects transitives)
108
+ --with-coverage Health/test-gaps: materialize coverage artifacts via per-pack
109
+ runTests() before analysis (line-coverage truth vs filename match)
92
110
 
93
111
  ${logger.bold('Examples:')}
94
112
  npx vyuh-dxkit init # Interactive
@@ -121,8 +139,14 @@ async function run(argv) {
121
139
  output: { type: 'string', short: 'o' },
122
140
  xlsx: { type: 'boolean', default: false },
123
141
  filter: { type: 'string' },
124
- 'no-nested': { type: 'boolean', default: false },
125
142
  all: { type: 'boolean', default: false },
143
+ 'reports-dir': { type: 'string' },
144
+ 'json-dir': { type: 'string' },
145
+ 'project-name': { type: 'string' },
146
+ lang: { type: 'string' },
147
+ timeout: { type: 'string' },
148
+ 'no-fail-fast': { type: 'boolean', default: false },
149
+ 'with-coverage': { type: 'boolean', default: false },
126
150
  },
127
151
  allowPositionals: true,
128
152
  strict: false,
@@ -242,27 +266,57 @@ async function run(argv) {
242
266
  }
243
267
  case 'health': {
244
268
  const targetPath = resolveRepoPath(positionals[1]);
245
- const { analyzeHealth, analyzeHealthWithMetrics } = await Promise.resolve().then(() => __importStar(require('./analyzers/health')));
269
+ const { analyzeHealthWithMetrics } = await Promise.resolve().then(() => __importStar(require('./analyzers/health')));
246
270
  logger.header('vyuh-dxkit health');
247
271
  logger.info(`Analyzing ${targetPath}...`);
248
272
  const startTime = Date.now();
273
+ // D021 (2.4.7): --with-coverage materializes the coverage artifact
274
+ // BEFORE the analyzer runs, so the report reads line-coverage
275
+ // truth (`coverageFidelity: 'line-coverage'`) instead of falling
276
+ // back to the filename-match heuristic. Shares the same per-pack
277
+ // runner the `coverage` command uses; honors --lang to limit
278
+ // scope on polyglot repos.
279
+ if (values['with-coverage']) {
280
+ const { runCoverageAcrossPacks } = await Promise.resolve().then(() => __importStar(require('./analyzers/coverage-runner')));
281
+ const langFilter = values.lang;
282
+ logger.info('Running test-with-coverage across active packs...');
283
+ const { rows } = await runCoverageAcrossPacks(targetPath, {
284
+ langFilter,
285
+ failFast: !values['no-fail-fast'],
286
+ onPackStart: (id) => process.stderr.write(` → ${id}: running tests with coverage...\n`),
287
+ });
288
+ const successes = rows.filter((r) => r.status === 'success').length;
289
+ if (successes > 0) {
290
+ logger.success(`${successes}/${rows.length} packs produced coverage artifacts`);
291
+ }
292
+ else {
293
+ logger.warn(`0/${rows.length} packs produced coverage artifacts — falling back to heuristic`);
294
+ }
295
+ console.log(''); // slop-ok
296
+ }
249
297
  // Detailed mode needs HealthMetrics for remediation planning; pull both.
250
- const healthResult = values.detailed
251
- ? await analyzeHealthWithMetrics(targetPath, { verbose: !!values.verbose })
252
- : {
253
- report: await analyzeHealth(targetPath, { verbose: !!values.verbose }),
254
- metrics: null,
255
- };
298
+ // D032 (2.4.7): always gather the underlying metrics so the
299
+ // `-detailed.json` write below has the data it needs. Pre-fix
300
+ // the metrics-bearing path was gated on `--detailed`, so the
301
+ // dashboard's input JSON was only produced when the user opted
302
+ // into detailed reporting — making the dashboard headline numbers
303
+ // silently stale or zero on a default `dxkit health . && dxkit
304
+ // dashboard .` workflow. Both internal entry points share
305
+ // `analyzeHealthInternal`, so the only cost is keeping a metrics
306
+ // reference live (no extra compute).
307
+ const healthResult = await analyzeHealthWithMetrics(targetPath, {
308
+ verbose: !!values.verbose,
309
+ });
256
310
  const report = healthResult.report;
257
311
  const healthMetrics = healthResult.metrics;
258
312
  const elapsed = ((Date.now() - startTime) / 1000).toFixed(1);
259
313
  if (values.json) {
260
- console.log(JSON.stringify(report, null, 2));
314
+ await emitJson(report);
261
315
  }
262
316
  else {
263
317
  // Console output
264
318
  console.log('');
265
- console.log(` ${logger.bold('Overall:')} ${report.summary.overallScore}/100 (Grade: ${report.summary.grade})`);
319
+ console.log(` ${logger.bold('Overall:')} ${report.summary.overallScore}/100 (Rating: ${report.summary.rating})`);
266
320
  console.log('');
267
321
  const dims = report.dimensions;
268
322
  const order = [
@@ -275,7 +329,11 @@ async function run(argv) {
275
329
  ];
276
330
  for (const [name, dim] of order) {
277
331
  const bar = '█'.repeat(Math.round(dim.score / 5)) + '░'.repeat(20 - Math.round(dim.score / 5));
278
- console.log(` ${name.padEnd(22)} ${bar} ${dim.score.toString().padStart(3)}/100 ${dim.status}`);
332
+ console.log(` ${name.padEnd(22)} ${bar} ${dim.score.toString().padStart(3)}/100 ${dim.rating}`);
333
+ const topAction = (0, scoring_1.formatTopActionLine)(dim);
334
+ if (topAction) {
335
+ logger.dim(` ${' '.repeat(22)} → ${topAction}`);
336
+ }
279
337
  }
280
338
  console.log('');
281
339
  logger.dim('Tools: ' + report.toolsUsed.join(', '));
@@ -283,27 +341,42 @@ async function run(argv) {
283
341
  logger.dim('Unavailable: ' + report.toolsUnavailable.join(', '));
284
342
  }
285
343
  logger.dim(`Completed in ${elapsed}s`);
286
- // Save markdown report (unless --no-save)
287
- if (!values['no-save']) {
288
- const reportDir = path.join(targetPath, '.dxkit', 'reports');
289
- const date = new Date().toISOString().slice(0, 10);
290
- const reportPath = path.join(reportDir, `health-audit-${date}.md`);
291
- fs.mkdirSync(reportDir, { recursive: true });
292
- fs.writeFileSync(reportPath, formatMarkdownReport(report, elapsed));
293
- console.log('');
294
- logger.success(`Report saved to ${path.relative(targetPath, reportPath)}`);
295
- if (values.detailed && healthMetrics) {
296
- const { buildHealthDetailed, formatHealthDetailedMarkdown } = await Promise.resolve().then(() => __importStar(require('./analyzers/health/detailed')));
297
- const detailed = buildHealthDetailed(report, healthMetrics);
298
- const detailedMdPath = path.join(reportDir, `health-audit-${date}-detailed.md`);
299
- const detailedJsonPath = path.join(reportDir, `health-audit-${date}-detailed.json`);
300
- fs.writeFileSync(detailedMdPath, formatHealthDetailedMarkdown(detailed, elapsed));
301
- fs.writeFileSync(detailedJsonPath, JSON.stringify(detailed, null, 2));
302
- logger.success(`Detailed report saved to ${path.relative(targetPath, detailedMdPath)}`);
303
- logger.success(`Detailed JSON saved to ${path.relative(targetPath, detailedJsonPath)}`);
304
- }
344
+ }
345
+ // Disk side: orthogonal to --json so consumers don't need separate
346
+ // `--detailed` and `--detailed --json` invocations (closes D018).
347
+ // `logger.success` routes to stderr in --json mode, so it's safe to
348
+ // call unconditionally.
349
+ if (!values['no-save']) {
350
+ const reportDir = path.join(targetPath, '.dxkit', 'reports');
351
+ const date = (0, report_date_1.getReportDate)();
352
+ const reportPath = path.join(reportDir, `health-audit-${date}.md`);
353
+ fs.mkdirSync(reportDir, { recursive: true });
354
+ fs.writeFileSync(reportPath, formatMarkdownReport(report, elapsed));
355
+ if (!values.json)
356
+ console.log(''); // slop-ok
357
+ logger.success(`Report saved to ${path.relative(targetPath, reportPath)}`);
358
+ // D032 (2.4.7): always write BOTH `-detailed.json` AND
359
+ // `-detailed.md` so `vyuh-dxkit dashboard` finds fresh inputs
360
+ // on every run. The dashboard reads JSON for tile metrics and
361
+ // embeds the markdown for tab content (Language Breakdown +
362
+ // Plans live only in the detailed.md). Pre-fix gating these
363
+ // on `--detailed` meant a default `health → dashboard` workflow
364
+ // showed stale tile values + stale tab content from whichever
365
+ // run last passed `--detailed`. The `--detailed` flag now only
366
+ // controls the console success-log lines.
367
+ const { buildHealthDetailed, formatHealthDetailedMarkdown } = await Promise.resolve().then(() => __importStar(require('./analyzers/health/detailed')));
368
+ const detailed = buildHealthDetailed(report, healthMetrics);
369
+ const detailedJsonPath = path.join(reportDir, `health-audit-${date}-detailed.json`);
370
+ const detailedMdPath = path.join(reportDir, `health-audit-${date}-detailed.md`);
371
+ fs.writeFileSync(detailedJsonPath, JSON.stringify(detailed, null, 2));
372
+ fs.writeFileSync(detailedMdPath, formatHealthDetailedMarkdown(detailed, elapsed));
373
+ if (values.detailed) {
374
+ logger.success(`Detailed report saved to ${path.relative(targetPath, detailedMdPath)}`);
375
+ logger.success(`Detailed JSON saved to ${path.relative(targetPath, detailedJsonPath)}`);
305
376
  }
306
- // Hint about missing tools (exclude project-side config errors)
377
+ }
378
+ if (!values.json) {
379
+ // Hint about missing tools (exclude project-side config errors).
307
380
  const PROJECT_ISSUES = ['config error', 'legacy .eslintrc', 'no eslint config'];
308
381
  const trulyMissing = report.toolsUnavailable.filter((t) => !PROJECT_ISSUES.some((p) => t.includes(p)));
309
382
  if (trulyMissing.length > 0) {
@@ -363,7 +436,7 @@ async function run(argv) {
363
436
  const report = await analyzeSecurity(targetPath, { verbose: !!values.verbose });
364
437
  const elapsed = ((Date.now() - startTime) / 1000).toFixed(1);
365
438
  if (values.json) {
366
- console.log(JSON.stringify(report, null, 2));
439
+ await emitJson(report);
367
440
  }
368
441
  else {
369
442
  const s = report.summary.findings;
@@ -381,24 +454,27 @@ async function run(argv) {
381
454
  logger.dim('Unavailable: ' + report.toolsUnavailable.join(', '));
382
455
  }
383
456
  logger.dim(`Completed in ${elapsed}s`);
384
- if (!values['no-save']) {
385
- const reportDir = path.join(targetPath, '.dxkit', 'reports');
386
- const date = new Date().toISOString().slice(0, 10);
387
- const reportPath = path.join(reportDir, `vulnerability-scan-${date}.md`);
388
- fs.mkdirSync(reportDir, { recursive: true });
389
- fs.writeFileSync(reportPath, formatSecurityReport(report, elapsed));
390
- console.log('');
391
- logger.success(`Report saved to ${path.relative(targetPath, reportPath)}`);
392
- if (values.detailed) {
393
- const { buildSecurityDetailed, formatSecurityDetailedMarkdown } = await Promise.resolve().then(() => __importStar(require('./analyzers/security/detailed')));
394
- const detailed = buildSecurityDetailed(report);
395
- const detailedMdPath = path.join(reportDir, `vulnerability-scan-${date}-detailed.md`);
396
- const detailedJsonPath = path.join(reportDir, `vulnerability-scan-${date}-detailed.json`);
397
- fs.writeFileSync(detailedMdPath, formatSecurityDetailedMarkdown(detailed, elapsed));
398
- fs.writeFileSync(detailedJsonPath, JSON.stringify(detailed, null, 2));
399
- logger.success(`Detailed report saved to ${path.relative(targetPath, detailedMdPath)}`);
400
- logger.success(`Detailed JSON saved to ${path.relative(targetPath, detailedJsonPath)}`);
401
- }
457
+ }
458
+ // Disk side: orthogonal to --json (closes D018).
459
+ if (!values['no-save']) {
460
+ const reportDir = path.join(targetPath, '.dxkit', 'reports');
461
+ const date = (0, report_date_1.getReportDate)();
462
+ const reportPath = path.join(reportDir, `vulnerability-scan-${date}.md`);
463
+ fs.mkdirSync(reportDir, { recursive: true });
464
+ fs.writeFileSync(reportPath, formatSecurityReport(report, elapsed));
465
+ if (!values.json)
466
+ console.log(''); // slop-ok
467
+ logger.success(`Report saved to ${path.relative(targetPath, reportPath)}`);
468
+ // D032 (2.4.7): detailed JSON + MD always written so dashboard finds fresh inputs.
469
+ const { buildSecurityDetailed, formatSecurityDetailedMarkdown } = await Promise.resolve().then(() => __importStar(require('./analyzers/security/detailed')));
470
+ const securityDetailed = buildSecurityDetailed(report);
471
+ const securityDetailedJsonPath = path.join(reportDir, `vulnerability-scan-${date}-detailed.json`);
472
+ const securityDetailedMdPath = path.join(reportDir, `vulnerability-scan-${date}-detailed.md`);
473
+ fs.writeFileSync(securityDetailedJsonPath, JSON.stringify(securityDetailed, null, 2));
474
+ fs.writeFileSync(securityDetailedMdPath, formatSecurityDetailedMarkdown(securityDetailed, elapsed));
475
+ if (values.detailed) {
476
+ logger.success(`Detailed report saved to ${path.relative(targetPath, securityDetailedMdPath)}`);
477
+ logger.success(`Detailed JSON saved to ${path.relative(targetPath, securityDetailedJsonPath)}`);
402
478
  }
403
479
  }
404
480
  break;
@@ -409,10 +485,32 @@ async function run(argv) {
409
485
  logger.header('vyuh-dxkit test-gaps');
410
486
  logger.info(`Analyzing ${targetPath}...`);
411
487
  const startTime = Date.now();
488
+ // D021 (2.4.7): --with-coverage materializes the coverage artifact
489
+ // before analysis so the test-gaps report reads line-coverage
490
+ // truth instead of falling back to filename-match. Same runner
491
+ // health --with-coverage uses.
492
+ if (values['with-coverage']) {
493
+ const { runCoverageAcrossPacks } = await Promise.resolve().then(() => __importStar(require('./analyzers/coverage-runner')));
494
+ const langFilter = values.lang;
495
+ logger.info('Running test-with-coverage across active packs...');
496
+ const { rows } = await runCoverageAcrossPacks(targetPath, {
497
+ langFilter,
498
+ failFast: !values['no-fail-fast'],
499
+ onPackStart: (id) => process.stderr.write(` → ${id}: running tests with coverage...\n`),
500
+ });
501
+ const successes = rows.filter((r) => r.status === 'success').length;
502
+ if (successes > 0) {
503
+ logger.success(`${successes}/${rows.length} packs produced coverage artifacts`);
504
+ }
505
+ else {
506
+ logger.warn(`0/${rows.length} packs produced coverage artifacts — falling back to heuristic`);
507
+ }
508
+ console.log(''); // slop-ok
509
+ }
412
510
  const report = await analyzeTestGaps(targetPath, { verbose: !!values.verbose });
413
511
  const elapsed = ((Date.now() - startTime) / 1000).toFixed(1);
414
512
  if (values.json) {
415
- console.log(JSON.stringify(report, null, 2));
513
+ await emitJson(report);
416
514
  }
417
515
  else {
418
516
  const s = report.summary;
@@ -426,24 +524,27 @@ async function run(argv) {
426
524
  console.log('');
427
525
  logger.dim('Tools: ' + report.toolsUsed.join(', '));
428
526
  logger.dim(`Completed in ${elapsed}s`);
429
- if (!values['no-save']) {
430
- const reportDir = path.join(targetPath, '.dxkit', 'reports');
431
- const date = new Date().toISOString().slice(0, 10);
432
- const reportPath = path.join(reportDir, `test-gaps-${date}.md`);
433
- fs.mkdirSync(reportDir, { recursive: true });
434
- fs.writeFileSync(reportPath, formatTestGapsReport(report, elapsed));
435
- console.log('');
436
- logger.success(`Report saved to ${path.relative(targetPath, reportPath)}`);
437
- if (values.detailed) {
438
- const { buildTestGapsDetailed, formatTestGapsDetailedMarkdown } = await Promise.resolve().then(() => __importStar(require('./analyzers/tests/detailed')));
439
- const detailed = buildTestGapsDetailed(report);
440
- const detailedMdPath = path.join(reportDir, `test-gaps-${date}-detailed.md`);
441
- const detailedJsonPath = path.join(reportDir, `test-gaps-${date}-detailed.json`);
442
- fs.writeFileSync(detailedMdPath, formatTestGapsDetailedMarkdown(detailed, elapsed));
443
- fs.writeFileSync(detailedJsonPath, JSON.stringify(detailed, null, 2));
444
- logger.success(`Detailed report saved to ${path.relative(targetPath, detailedMdPath)}`);
445
- logger.success(`Detailed JSON saved to ${path.relative(targetPath, detailedJsonPath)}`);
446
- }
527
+ }
528
+ // Disk side: orthogonal to --json (closes D018).
529
+ if (!values['no-save']) {
530
+ const reportDir = path.join(targetPath, '.dxkit', 'reports');
531
+ const date = (0, report_date_1.getReportDate)();
532
+ const reportPath = path.join(reportDir, `test-gaps-${date}.md`);
533
+ fs.mkdirSync(reportDir, { recursive: true });
534
+ fs.writeFileSync(reportPath, formatTestGapsReport(report, elapsed));
535
+ if (!values.json)
536
+ console.log(''); // slop-ok
537
+ logger.success(`Report saved to ${path.relative(targetPath, reportPath)}`);
538
+ // D032 (2.4.7): detailed JSON + MD always written so dashboard finds fresh inputs.
539
+ const { buildTestGapsDetailed, formatTestGapsDetailedMarkdown } = await Promise.resolve().then(() => __importStar(require('./analyzers/tests/detailed')));
540
+ const testGapsDetailed = buildTestGapsDetailed(report);
541
+ const testGapsDetailedJsonPath = path.join(reportDir, `test-gaps-${date}-detailed.json`);
542
+ const testGapsDetailedMdPath = path.join(reportDir, `test-gaps-${date}-detailed.md`);
543
+ fs.writeFileSync(testGapsDetailedJsonPath, JSON.stringify(testGapsDetailed, null, 2));
544
+ fs.writeFileSync(testGapsDetailedMdPath, formatTestGapsDetailedMarkdown(testGapsDetailed, elapsed));
545
+ if (values.detailed) {
546
+ logger.success(`Detailed report saved to ${path.relative(targetPath, testGapsDetailedMdPath)}`);
547
+ logger.success(`Detailed JSON saved to ${path.relative(targetPath, testGapsDetailedJsonPath)}`);
447
548
  }
448
549
  }
449
550
  break;
@@ -460,7 +561,7 @@ async function run(argv) {
460
561
  });
461
562
  const elapsed = ((Date.now() - startTime) / 1000).toFixed(1);
462
563
  if (values.json) {
463
- console.log(JSON.stringify(report, null, 2));
564
+ await emitJson(report);
464
565
  }
465
566
  else {
466
567
  const m = report.metrics;
@@ -495,24 +596,27 @@ async function run(argv) {
495
596
  logger.dim('Unavailable: ' + report.toolsUnavailable.join(', '));
496
597
  }
497
598
  logger.dim(`Completed in ${elapsed}s`);
498
- if (!values['no-save']) {
499
- const reportDir = path.join(targetPath, '.dxkit', 'reports');
500
- const date = new Date().toISOString().slice(0, 10);
501
- const reportPath = path.join(reportDir, `quality-review-${date}.md`);
502
- fs.mkdirSync(reportDir, { recursive: true });
503
- fs.writeFileSync(reportPath, formatQualityReport(report, elapsed));
504
- console.log('');
505
- logger.success(`Report saved to ${path.relative(targetPath, reportPath)}`);
506
- if (values.detailed) {
507
- const { buildQualityDetailed, formatQualityDetailedMarkdown } = await Promise.resolve().then(() => __importStar(require('./analyzers/quality/detailed')));
508
- const detailed = buildQualityDetailed(report);
509
- const detailedMdPath = path.join(reportDir, `quality-review-${date}-detailed.md`);
510
- const detailedJsonPath = path.join(reportDir, `quality-review-${date}-detailed.json`);
511
- fs.writeFileSync(detailedMdPath, formatQualityDetailedMarkdown(detailed, elapsed));
512
- fs.writeFileSync(detailedJsonPath, JSON.stringify(detailed, null, 2));
513
- logger.success(`Detailed report saved to ${path.relative(targetPath, detailedMdPath)}`);
514
- logger.success(`Detailed JSON saved to ${path.relative(targetPath, detailedJsonPath)}`);
515
- }
599
+ }
600
+ // Disk side: orthogonal to --json (closes D018).
601
+ if (!values['no-save']) {
602
+ const reportDir = path.join(targetPath, '.dxkit', 'reports');
603
+ const date = (0, report_date_1.getReportDate)();
604
+ const reportPath = path.join(reportDir, `quality-review-${date}.md`);
605
+ fs.mkdirSync(reportDir, { recursive: true });
606
+ fs.writeFileSync(reportPath, formatQualityReport(report, elapsed));
607
+ if (!values.json)
608
+ console.log(''); // slop-ok
609
+ logger.success(`Report saved to ${path.relative(targetPath, reportPath)}`);
610
+ // D032 (2.4.7): detailed JSON + MD always written so dashboard finds fresh inputs.
611
+ const { buildQualityDetailed, formatQualityDetailedMarkdown } = await Promise.resolve().then(() => __importStar(require('./analyzers/quality/detailed')));
612
+ const qualityDetailed = buildQualityDetailed(report);
613
+ const qualityDetailedJsonPath = path.join(reportDir, `quality-review-${date}-detailed.json`);
614
+ const qualityDetailedMdPath = path.join(reportDir, `quality-review-${date}-detailed.md`);
615
+ fs.writeFileSync(qualityDetailedJsonPath, JSON.stringify(qualityDetailed, null, 2));
616
+ fs.writeFileSync(qualityDetailedMdPath, formatQualityDetailedMarkdown(qualityDetailed, elapsed));
617
+ if (values.detailed) {
618
+ logger.success(`Detailed report saved to ${path.relative(targetPath, qualityDetailedMdPath)}`);
619
+ logger.success(`Detailed JSON saved to ${path.relative(targetPath, qualityDetailedJsonPath)}`);
516
620
  }
517
621
  }
518
622
  break;
@@ -524,10 +628,12 @@ async function run(argv) {
524
628
  logger.header('vyuh-dxkit dev-report');
525
629
  logger.info(`Analyzing ${targetPath}...`);
526
630
  const startTime = Date.now();
527
- const report = analyzeDevActivity(targetPath, sinceFlag, { verbose: !!values.verbose });
631
+ const report = await analyzeDevActivity(targetPath, sinceFlag, {
632
+ verbose: !!values.verbose,
633
+ });
528
634
  const elapsed = ((Date.now() - startTime) / 1000).toFixed(1);
529
635
  if (values.json) {
530
- console.log(JSON.stringify(report, null, 2));
636
+ await emitJson(report);
531
637
  }
532
638
  else {
533
639
  const s = report.summary;
@@ -547,28 +653,30 @@ async function run(argv) {
547
653
  console.log('');
548
654
  logger.dim('Tools: ' + report.toolsUsed.join(', '));
549
655
  logger.dim(`Completed in ${elapsed}s`);
550
- if (!values['no-save']) {
551
- const reportDir = path.join(targetPath, '.dxkit', 'reports');
552
- const date = new Date().toISOString().slice(0, 10);
553
- const reportPath = path.join(reportDir, `developer-report-${date}.md`);
554
- fs.mkdirSync(reportDir, { recursive: true });
555
- fs.writeFileSync(reportPath, formatDevReport(report, elapsed));
556
- console.log('');
557
- logger.success(`Report saved to ${path.relative(targetPath, reportPath)}`);
558
- if (values.detailed) {
559
- const { buildDevDetailed, formatDevDetailedMarkdown } = await Promise.resolve().then(() => __importStar(require('./analyzers/developer/detailed')));
560
- const { gatherVagueCommitExamples } = await Promise.resolve().then(() => __importStar(require('./analyzers/developer/gather')));
561
- const sinceDate = sinceFlag ||
562
- new Date(Date.now() - 90 * 24 * 60 * 60 * 1000).toISOString().slice(0, 10);
563
- const vague = gatherVagueCommitExamples(targetPath, sinceDate);
564
- const detailed = buildDevDetailed(report, vague);
565
- const detailedMdPath = path.join(reportDir, `developer-report-${date}-detailed.md`);
566
- const detailedJsonPath = path.join(reportDir, `developer-report-${date}-detailed.json`);
567
- fs.writeFileSync(detailedMdPath, formatDevDetailedMarkdown(detailed, elapsed));
568
- fs.writeFileSync(detailedJsonPath, JSON.stringify(detailed, null, 2));
569
- logger.success(`Detailed report saved to ${path.relative(targetPath, detailedMdPath)}`);
570
- logger.success(`Detailed JSON saved to ${path.relative(targetPath, detailedJsonPath)}`);
571
- }
656
+ }
657
+ // Disk side: orthogonal to --json (closes D018).
658
+ if (!values['no-save']) {
659
+ const reportDir = path.join(targetPath, '.dxkit', 'reports');
660
+ const date = (0, report_date_1.getReportDate)();
661
+ const reportPath = path.join(reportDir, `developer-report-${date}.md`);
662
+ fs.mkdirSync(reportDir, { recursive: true });
663
+ fs.writeFileSync(reportPath, formatDevReport(report, elapsed));
664
+ if (!values.json)
665
+ console.log(''); // slop-ok
666
+ logger.success(`Report saved to ${path.relative(targetPath, reportPath)}`);
667
+ // D032 (2.4.7): detailed JSON + MD always written so dashboard finds fresh inputs.
668
+ const { buildDevDetailed, formatDevDetailedMarkdown } = await Promise.resolve().then(() => __importStar(require('./analyzers/developer/detailed')));
669
+ const { gatherVagueCommitExamples } = await Promise.resolve().then(() => __importStar(require('./analyzers/developer/gather')));
670
+ const sinceDate = sinceFlag || new Date(Date.now() - 90 * 24 * 60 * 60 * 1000).toISOString().slice(0, 10);
671
+ const vague = gatherVagueCommitExamples(targetPath, sinceDate);
672
+ const devDetailed = buildDevDetailed(report, vague);
673
+ const devDetailedJsonPath = path.join(reportDir, `developer-report-${date}-detailed.json`);
674
+ const devDetailedMdPath = path.join(reportDir, `developer-report-${date}-detailed.md`);
675
+ fs.writeFileSync(devDetailedJsonPath, JSON.stringify(devDetailed, null, 2));
676
+ fs.writeFileSync(devDetailedMdPath, formatDevDetailedMarkdown(devDetailed, elapsed));
677
+ if (values.detailed) {
678
+ logger.success(`Detailed report saved to ${path.relative(targetPath, devDetailedMdPath)}`);
679
+ logger.success(`Detailed JSON saved to ${path.relative(targetPath, devDetailedJsonPath)}`);
572
680
  }
573
681
  }
574
682
  break;
@@ -582,7 +690,7 @@ async function run(argv) {
582
690
  const report = await analyzeLicenses(targetPath, { verbose: !!values.verbose });
583
691
  const elapsed = ((Date.now() - startTime) / 1000).toFixed(1);
584
692
  if (values.json) {
585
- console.log(JSON.stringify(report, null, 2)); // slop-ok
693
+ await emitJson(report); // slop-ok
586
694
  }
587
695
  else {
588
696
  const s = report.summary;
@@ -606,35 +714,36 @@ async function run(argv) {
606
714
  console.log(''); // slop-ok
607
715
  logger.dim('Tools: ' + (report.toolsUsed.join(', ') || '(none)'));
608
716
  logger.dim(`Completed in ${elapsed}s`);
609
- if (!values['no-save']) {
610
- const reportDir = path.join(targetPath, '.dxkit', 'reports');
611
- const date = new Date().toISOString().slice(0, 10);
612
- const reportPath = path.join(reportDir, `licenses-${date}.md`);
613
- fs.mkdirSync(reportDir, { recursive: true });
614
- fs.writeFileSync(reportPath, formatLicensesReport(report, elapsed));
717
+ }
718
+ // Disk side: orthogonal to --json (closes D018).
719
+ if (!values['no-save']) {
720
+ const reportDir = path.join(targetPath, '.dxkit', 'reports');
721
+ const date = (0, report_date_1.getReportDate)();
722
+ const reportPath = path.join(reportDir, `licenses-${date}.md`);
723
+ fs.mkdirSync(reportDir, { recursive: true });
724
+ fs.writeFileSync(reportPath, formatLicensesReport(report, elapsed));
725
+ if (!values.json)
615
726
  console.log(''); // slop-ok
616
- logger.success(`Report saved to ${path.relative(targetPath, reportPath)}`);
617
- if (values.detailed || values.xlsx) {
618
- const { buildLicensesDetailed, formatLicensesDetailedMarkdown } = await Promise.resolve().then(() => __importStar(require('./analyzers/licenses/detailed')));
619
- const detailed = buildLicensesDetailed(report);
620
- if (values.detailed) {
621
- const detailedMdPath = path.join(reportDir, `licenses-${date}-detailed.md`);
622
- const detailedJsonPath = path.join(reportDir, `licenses-${date}-detailed.json`);
623
- fs.writeFileSync(detailedMdPath, formatLicensesDetailedMarkdown(detailed, elapsed));
624
- fs.writeFileSync(detailedJsonPath, JSON.stringify(detailed, null, 2));
625
- logger.success(`Detailed report saved to ${path.relative(targetPath, detailedMdPath)}`);
626
- logger.success(`Detailed JSON saved to ${path.relative(targetPath, detailedJsonPath)}`);
627
- }
628
- if (values.xlsx) {
629
- const { toLicensesXlsx } = await Promise.resolve().then(() => __importStar(require('./analyzers/xlsx')));
630
- const xlsxPath = values.output
631
- ? path.resolve(values.output)
632
- : path.join(reportDir, `licenses-${date}.xlsx`);
633
- const buf = await toLicensesXlsx(detailed);
634
- fs.writeFileSync(xlsxPath, buf);
635
- logger.success(`XLSX saved to ${path.relative(targetPath, xlsxPath)}`);
636
- }
637
- }
727
+ logger.success(`Report saved to ${path.relative(targetPath, reportPath)}`);
728
+ // D032 (2.4.7): detailed JSON + MD always written so dashboard finds fresh inputs.
729
+ const { buildLicensesDetailed, formatLicensesDetailedMarkdown } = await Promise.resolve().then(() => __importStar(require('./analyzers/licenses/detailed')));
730
+ const licensesDetailed = buildLicensesDetailed(report);
731
+ const licensesDetailedJsonPath = path.join(reportDir, `licenses-${date}-detailed.json`);
732
+ const licensesDetailedMdPath = path.join(reportDir, `licenses-${date}-detailed.md`);
733
+ fs.writeFileSync(licensesDetailedJsonPath, JSON.stringify(licensesDetailed, null, 2));
734
+ fs.writeFileSync(licensesDetailedMdPath, formatLicensesDetailedMarkdown(licensesDetailed, elapsed));
735
+ if (values.detailed) {
736
+ logger.success(`Detailed report saved to ${path.relative(targetPath, licensesDetailedMdPath)}`);
737
+ logger.success(`Detailed JSON saved to ${path.relative(targetPath, licensesDetailedJsonPath)}`);
738
+ }
739
+ if (values.xlsx) {
740
+ const { toLicensesXlsx } = await Promise.resolve().then(() => __importStar(require('./analyzers/xlsx')));
741
+ const xlsxPath = values.output
742
+ ? path.resolve(values.output)
743
+ : path.join(reportDir, `licenses-${date}.xlsx`);
744
+ const buf = await toLicensesXlsx(licensesDetailed);
745
+ fs.writeFileSync(xlsxPath, buf);
746
+ logger.success(`XLSX saved to ${path.relative(targetPath, xlsxPath)}`);
638
747
  }
639
748
  }
640
749
  break;
@@ -650,16 +759,14 @@ async function run(argv) {
650
759
  process.exit(1);
651
760
  }
652
761
  const filter = rawFilter;
653
- const nested = !values['no-nested'];
654
762
  const startTime = Date.now();
655
763
  const report = await analyzeBom(targetPath, {
656
764
  verbose: !!values.verbose,
657
765
  filter,
658
- nested,
659
766
  });
660
767
  const elapsed = ((Date.now() - startTime) / 1000).toFixed(1);
661
768
  if (values.json) {
662
- console.log(JSON.stringify(report, null, 2)); // slop-ok
769
+ await emitJson(report); // slop-ok
663
770
  }
664
771
  else {
665
772
  const s = report.summary;
@@ -696,36 +803,283 @@ async function run(argv) {
696
803
  console.log(''); // slop-ok
697
804
  logger.dim('Tools: ' + (report.toolsUsed.join(', ') || '(none)'));
698
805
  logger.dim(`Completed in ${elapsed}s`);
699
- if (!values['no-save']) {
700
- const reportDir = path.join(targetPath, '.dxkit', 'reports');
701
- const date = new Date().toISOString().slice(0, 10);
702
- const reportPath = path.join(reportDir, `bom-${date}.md`);
703
- fs.mkdirSync(reportDir, { recursive: true });
704
- fs.writeFileSync(reportPath, formatBomReport(report, elapsed));
806
+ }
807
+ // Disk side: orthogonal to --json (closes D018).
808
+ if (!values['no-save']) {
809
+ const reportDir = path.join(targetPath, '.dxkit', 'reports');
810
+ const date = (0, report_date_1.getReportDate)();
811
+ const reportPath = path.join(reportDir, `bom-${date}.md`);
812
+ fs.mkdirSync(reportDir, { recursive: true });
813
+ fs.writeFileSync(reportPath, formatBomReport(report, elapsed));
814
+ if (!values.json)
705
815
  console.log(''); // slop-ok
706
- logger.success(`Report saved to ${path.relative(targetPath, reportPath)}`);
707
- if (values.detailed || values.xlsx) {
708
- const { buildBomDetailed, formatBomDetailedMarkdown } = await Promise.resolve().then(() => __importStar(require('./analyzers/bom/detailed')));
709
- const detailed = buildBomDetailed(report);
710
- if (values.detailed) {
711
- const detailedMdPath = path.join(reportDir, `bom-${date}-detailed.md`);
712
- const detailedJsonPath = path.join(reportDir, `bom-${date}-detailed.json`);
713
- fs.writeFileSync(detailedMdPath, formatBomDetailedMarkdown(detailed, elapsed));
714
- fs.writeFileSync(detailedJsonPath, JSON.stringify(detailed, null, 2));
715
- logger.success(`Detailed report saved to ${path.relative(targetPath, detailedMdPath)}`);
716
- logger.success(`Detailed JSON saved to ${path.relative(targetPath, detailedJsonPath)}`);
717
- }
718
- if (values.xlsx) {
719
- const { toBomXlsx } = await Promise.resolve().then(() => __importStar(require('./analyzers/xlsx')));
720
- const xlsxPath = values.output
721
- ? path.resolve(values.output)
722
- : path.join(reportDir, `bom-${date}.xlsx`);
723
- const buf = await toBomXlsx(report);
724
- fs.writeFileSync(xlsxPath, buf);
725
- logger.success(`XLSX saved to ${path.relative(targetPath, xlsxPath)}`);
726
- }
816
+ logger.success(`Report saved to ${path.relative(targetPath, reportPath)}`);
817
+ // D032 (2.4.7): detailed JSON + MD always written so dashboard finds fresh inputs.
818
+ const { buildBomDetailed, formatBomDetailedMarkdown } = await Promise.resolve().then(() => __importStar(require('./analyzers/bom/detailed')));
819
+ const bomDetailed = buildBomDetailed(report);
820
+ const bomDetailedJsonPath = path.join(reportDir, `bom-${date}-detailed.json`);
821
+ const bomDetailedMdPath = path.join(reportDir, `bom-${date}-detailed.md`);
822
+ fs.writeFileSync(bomDetailedJsonPath, JSON.stringify(bomDetailed, null, 2));
823
+ fs.writeFileSync(bomDetailedMdPath, formatBomDetailedMarkdown(bomDetailed, elapsed));
824
+ if (values.detailed) {
825
+ logger.success(`Detailed report saved to ${path.relative(targetPath, bomDetailedMdPath)}`);
826
+ logger.success(`Detailed JSON saved to ${path.relative(targetPath, bomDetailedJsonPath)}`);
827
+ }
828
+ if (values.xlsx) {
829
+ const { toBomXlsx } = await Promise.resolve().then(() => __importStar(require('./analyzers/xlsx')));
830
+ const xlsxPath = values.output
831
+ ? path.resolve(values.output)
832
+ : path.join(reportDir, `bom-${date}.xlsx`);
833
+ const buf = await toBomXlsx(report);
834
+ fs.writeFileSync(xlsxPath, buf);
835
+ logger.success(`XLSX saved to ${path.relative(targetPath, xlsxPath)}`);
836
+ }
837
+ }
838
+ break;
839
+ }
840
+ case 'dashboard': {
841
+ const targetPath = resolveRepoPath(positionals[1]);
842
+ const { analyzeDashboard } = await Promise.resolve().then(() => __importStar(require('./analyzers/dashboard')));
843
+ logger.header('vyuh-dxkit dashboard');
844
+ const reportsDir = values['reports-dir']
845
+ ? path.resolve(values['reports-dir'])
846
+ : path.join(targetPath, '.dxkit', 'reports');
847
+ const jsonDir = values['json-dir'] ? path.resolve(values['json-dir']) : undefined;
848
+ const projectName = values['project-name'] ?? undefined;
849
+ const outputPath = values.output
850
+ ? path.resolve(values.output)
851
+ : path.join(reportsDir, 'dashboard.html');
852
+ let result;
853
+ try {
854
+ result = analyzeDashboard(targetPath, { reportsDir, jsonDir, projectName });
855
+ }
856
+ catch (err) {
857
+ const msg = err instanceof Error ? err.message : String(err);
858
+ logger.fail(msg);
859
+ process.exit(1);
860
+ }
861
+ if (result.reportCount === 0) {
862
+ logger.fail(`No report markdowns found in ${path.relative(targetPath, reportsDir) || reportsDir}.\n` +
863
+ `Run 'vyuh-dxkit health .' (or any other report command) first to populate the directory.`);
864
+ process.exit(1);
865
+ }
866
+ if (values['no-save']) {
867
+ // Drain-aware HTML emission to stdout. Mirrors emitJson() for
868
+ // payloads that can exceed the 64KB pipe buffer (a dashboard
869
+ // with all reports embedded routinely runs 300-500KB).
870
+ if (!process.stdout.write(result.html)) {
871
+ await new Promise((resolve) => process.stdout.once('drain', resolve));
872
+ }
873
+ }
874
+ else {
875
+ fs.mkdirSync(path.dirname(outputPath), { recursive: true });
876
+ fs.writeFileSync(outputPath, result.html);
877
+ logger.success(`Dashboard written to ${path.relative(targetPath, outputPath) || outputPath}`);
878
+ logger.dim(`${result.reportCount} reports · ${result.summary.healthScore !== null ? `health ${result.summary.healthScore}/100` : 'no health data'} · ` +
879
+ `${result.summary.vulnCount} vulns · ${result.summary.gapCount} test gaps · ` +
880
+ `${result.summary.advisoryCount} BoM advisories · ${result.criticalIssueCount} critical-issue tiles`);
881
+ }
882
+ break;
883
+ }
884
+ case 'coverage': {
885
+ const targetPath = resolveRepoPath(positionals[1]);
886
+ const { runCoverageAcrossPacks } = await Promise.resolve().then(() => __importStar(require('./analyzers/coverage-runner')));
887
+ const { detectActiveLanguages } = await Promise.resolve().then(() => __importStar(require('./languages')));
888
+ logger.header('vyuh-dxkit coverage');
889
+ const active = detectActiveLanguages(targetPath);
890
+ const langFilter = values.lang;
891
+ const failFast = !values['no-fail-fast'];
892
+ const candidates = active.filter((p) => !langFilter || p.id === langFilter);
893
+ if (candidates.length === 0) {
894
+ logger.fail(langFilter
895
+ ? `No active language pack matches --lang ${langFilter}. Active packs: ${active.map((p) => p.id).join(', ') || '(none)'}`
896
+ : `No active language packs detected in ${targetPath}. Nothing to run.`);
897
+ process.exit(2);
898
+ }
899
+ logger.info(`Stack: ${candidates.map((p) => p.id).join(', ')}`);
900
+ console.log(''); // slop-ok
901
+ const { rows } = await runCoverageAcrossPacks(targetPath, {
902
+ langFilter,
903
+ failFast,
904
+ onPackStart: (id) => process.stderr.write(` → ${id}: running tests with coverage...\n`),
905
+ });
906
+ // Render summary table via the same drain-aware stdout primitive
907
+ // emitJson uses — wide table rows would otherwise trip the
908
+ // no-bare-console-statements slop gate.
909
+ const writeRow = (s) => {
910
+ process.stdout.write(s + '\n');
911
+ };
912
+ writeRow('');
913
+ writeRow(` ${logger.bold('Pack'.padEnd(12))} ${logger.bold('Status'.padEnd(12))} ${logger.bold('Duration'.padEnd(10))} ${logger.bold('Artifact')}`);
914
+ writeRow(` ${'─'.repeat(12)} ${'─'.repeat(12)} ${'─'.repeat(10)} ${'─'.repeat(40)}`);
915
+ for (const r of rows) {
916
+ const icon = r.status === 'success'
917
+ ? '\x1b[32m✓\x1b[0m'
918
+ : r.status === 'unavailable' || r.status === 'skipped'
919
+ ? '\x1b[2m·\x1b[0m'
920
+ : '\x1b[31m✗\x1b[0m';
921
+ const duration = r.durationMs > 0 ? `${(r.durationMs / 1000).toFixed(1)}s`.padStart(10) : '—'.padStart(10);
922
+ const right = r.artifact ?? r.reason ?? '';
923
+ writeRow(` ${icon} ${r.pack.padEnd(10)} ${r.status.padEnd(12)} ${duration} ${right}`);
924
+ }
925
+ const successes = rows.filter((r) => r.status === 'success').length;
926
+ const failures = rows.filter((r) => r.status === 'failed').length;
927
+ const unavailable = rows.filter((r) => r.status === 'unavailable' || r.status === 'skipped').length;
928
+ console.log(''); // slop-ok
929
+ if (failures > 0) {
930
+ logger.fail(`${successes}/${rows.length} packs produced coverage. ${failures} failed.`);
931
+ process.exit(1);
932
+ }
933
+ else if (successes === 0) {
934
+ logger.fail(`0/${rows.length} packs produced coverage (${unavailable} unavailable / skipped).`);
935
+ process.exit(2);
936
+ }
937
+ else {
938
+ logger.success(`${successes}/${rows.length} packs produced coverage. ` +
939
+ `Run \`vyuh-dxkit health\` or \`vyuh-dxkit test-gaps\` to consume.`);
940
+ }
941
+ break;
942
+ }
943
+ case 'report': {
944
+ // D021 (2.4.7 sub-piece 3): single orchestrator that runs every
945
+ // analyzer in sequence and produces a fully-populated dashboard.
946
+ // Child-process model rather than direct function calls: each
947
+ // analyzer command already owns its file-write flow (D032 made
948
+ // the detailed JSON + MD unconditional), so spawning preserves
949
+ // every side effect without duplicating code. The ~7 extra Node
950
+ // startups add ~10-15s on top of 5-10 minutes of real analysis —
951
+ // acceptable for a "press one button, get a complete audit"
952
+ // command. Direct function refactoring is recipe-v4 candidate
953
+ // territory (would touch every analyzer's CLI wiring).
954
+ const targetPath = resolveRepoPath(positionals[1]);
955
+ const { spawnSync } = await Promise.resolve().then(() => __importStar(require('child_process')));
956
+ logger.header('vyuh-dxkit report');
957
+ logger.info(`Generating full audit for ${targetPath}...`);
958
+ console.log(''); // slop-ok
959
+ // Which analyzers run, in dependency order. `health` runs first
960
+ // so its detailed JSON exists when later commands or the
961
+ // dashboard look for it; `dashboard` runs last so every report
962
+ // it embeds is fresh.
963
+ const analyzerSteps = [
964
+ { label: 'Health', cmd: 'health', reportPrefix: 'health-audit' },
965
+ { label: 'Vulnerabilities', cmd: 'vulnerabilities', reportPrefix: 'vulnerability-scan' },
966
+ { label: 'Test gaps', cmd: 'test-gaps', reportPrefix: 'test-gaps' },
967
+ { label: 'Code quality', cmd: 'quality', reportPrefix: 'quality-review' },
968
+ { label: 'Developer report', cmd: 'dev-report', reportPrefix: 'developer-report' },
969
+ { label: 'BoM', cmd: 'bom', reportPrefix: 'bom' },
970
+ { label: 'Licenses', cmd: 'licenses', reportPrefix: 'licenses' },
971
+ ];
972
+ // Forward common analyzer flags to each child so the orchestrator
973
+ // honors the same options the user would pass to a single command.
974
+ const passthroughFlags = [];
975
+ if (values.detailed)
976
+ passthroughFlags.push('--detailed');
977
+ if (values.xlsx)
978
+ passthroughFlags.push('--xlsx');
979
+ if (values.verbose)
980
+ passthroughFlags.push('--verbose');
981
+ if (values.since)
982
+ passthroughFlags.push('--since', values.since);
983
+ if (values.filter)
984
+ passthroughFlags.push('--filter', values.filter);
985
+ if (values['no-nested'])
986
+ passthroughFlags.push('--no-nested');
987
+ // --with-coverage handled ONCE upfront via `vyuh-dxkit coverage`;
988
+ // health + test-gaps then read the materialized artifact via
989
+ // `loadCoverage()` without re-running the test suite per command.
990
+ // Pre-fix `report --with-coverage` (had it existed) would have
991
+ // double-run tests for health and again for test-gaps.
992
+ const runStartedAt = Date.now();
993
+ const stepDurations = [];
994
+ if (values['with-coverage']) {
995
+ logger.info('[setup] Materializing coverage artifacts (one run, shared)...');
996
+ const t0 = Date.now();
997
+ const rc = spawnSync(process.execPath, [
998
+ process.argv[1],
999
+ 'coverage',
1000
+ targetPath,
1001
+ ...(values['no-fail-fast'] ? ['--no-fail-fast'] : []),
1002
+ ], { stdio: 'inherit' }).status;
1003
+ stepDurations.push({ label: 'Coverage', ms: Date.now() - t0, rc: rc ?? -1 });
1004
+ console.log(''); // slop-ok
1005
+ }
1006
+ const reportDir = path.join(targetPath, '.dxkit', 'reports');
1007
+ // Snapshot the date once at orchestrator startup so every
1008
+ // spawned subcommand writes filenames against the same date —
1009
+ // long runs crossing UTC midnight otherwise produce a mix of
1010
+ // pre- and post-midnight suffixes, and the post-step file-
1011
+ // existence checks below miss the rolled-forward files.
1012
+ const dateStr = (0, report_date_1.getReportDate)();
1013
+ const childEnv = { ...process.env, DXKIT_REPORT_DATE: dateStr };
1014
+ for (const step of analyzerSteps) {
1015
+ logger.info(`[${stepDurations.length + 1}/${analyzerSteps.length + 1}] ${step.label}...`);
1016
+ const t0 = Date.now();
1017
+ const rc = spawnSync(process.execPath, [process.argv[1], step.cmd, targetPath, ...passthroughFlags, ...(step.extraFlags ?? [])], { stdio: 'inherit', env: childEnv }).status;
1018
+ let effectiveRc = rc ?? -1;
1019
+ // Post-step assertion: the child returned rc=0 BUT did the
1020
+ // expected markdown actually land on disk? On heavy polyglot
1021
+ // repos (a JS-heavy customer frontend; 13K+ graphify nodes,
1022
+ // jscpd timeout exhaustion) the health child was observed to silently exit
1023
+ // 0 without writing its markdown — the dashboard then renders
1024
+ // "no <X> data" and the customer never learns their report
1025
+ // is missing. The orchestrator owns the "did the report
1026
+ // actually ship" assertion; analyzer subcommands keep their
1027
+ // own write logic unchanged.
1028
+ if (effectiveRc === 0) {
1029
+ const expectedReport = path.join(reportDir, `${step.reportPrefix}-${dateStr}.md`);
1030
+ if (!fs.existsSync(expectedReport)) {
1031
+ logger.warn(`${step.label} returned exit 0 but did NOT write ${path.relative(targetPath, expectedReport)}. ` +
1032
+ `Treating as failure so the final summary surfaces it.`);
1033
+ effectiveRc = -1;
727
1034
  }
728
1035
  }
1036
+ stepDurations.push({ label: step.label, ms: Date.now() - t0, rc: effectiveRc });
1037
+ // When the FIRST step (Health) fails, the AnalysisResult cache
1038
+ // didn't get built — every downstream step then re-runs the
1039
+ // full detect + Layer 0 + Layer 2 gather from scratch. On a
1040
+ // heavy polyglot frontend this added ~86 s of redundant Layer
1041
+ // 2 work to Step 2 (Vulnerabilities) alone, and ~10× that
1042
+ // across the remaining 6 steps. Surface the cascade so the
1043
+ // user understands why subsequent steps feel slower; the
1044
+ // alternative path (build the cache directly from the failed
1045
+ // gather) is a structural fix tracked for a later release.
1046
+ if (step.cmd === 'health' && effectiveRc !== 0) {
1047
+ logger.warn('Health failed before the analysis cache could be built. ' +
1048
+ 'The remaining steps will re-detect the stack and re-gather ' +
1049
+ 'shared metrics from scratch (expect each to be measurably ' +
1050
+ 'slower than usual). Their reports will still be written ' +
1051
+ 'when they succeed individually.');
1052
+ }
1053
+ console.log(''); // slop-ok
1054
+ }
1055
+ logger.info(`[${stepDurations.length + 1}/${analyzerSteps.length + 1}] Dashboard...`);
1056
+ const dashT0 = Date.now();
1057
+ const dashRc = spawnSync(process.execPath, [process.argv[1], 'dashboard', targetPath], {
1058
+ stdio: 'inherit',
1059
+ env: childEnv,
1060
+ }).status;
1061
+ stepDurations.push({ label: 'Dashboard', ms: Date.now() - dashT0, rc: dashRc ?? -1 });
1062
+ // Final summary. Always emit it so the user sees the dashboard
1063
+ // location without scrolling through per-step output.
1064
+ const totalElapsed = ((Date.now() - runStartedAt) / 1000).toFixed(1);
1065
+ const failed = stepDurations.filter((s) => s.rc !== 0);
1066
+ console.log(''); // slop-ok
1067
+ logger.dim('─'.repeat(60));
1068
+ for (const s of stepDurations) {
1069
+ const status = s.rc === 0 ? '\x1b[32m✓\x1b[0m' : '\x1b[31m✗\x1b[0m';
1070
+ const duration = `${(s.ms / 1000).toFixed(1)}s`.padStart(8);
1071
+ process.stdout.write(` ${status} ${s.label.padEnd(20)} ${duration}\n`);
1072
+ }
1073
+ logger.dim('─'.repeat(60));
1074
+ console.log(''); // slop-ok
1075
+ if (failed.length === 0) {
1076
+ logger.success(`All ${stepDurations.length} steps completed in ${totalElapsed}s. ` +
1077
+ `Open .dxkit/reports/dashboard.html for the full picture.`);
1078
+ }
1079
+ else {
1080
+ logger.warn(`${stepDurations.length - failed.length}/${stepDurations.length} steps completed (${failed.length} failed: ${failed.map((s) => s.label).join(', ')}). ` +
1081
+ `Partial dashboard at .dxkit/reports/dashboard.html.`);
1082
+ process.exit(1);
729
1083
  }
730
1084
  break;
731
1085
  }
@@ -781,7 +1135,7 @@ function formatMarkdownReport(report, elapsed) {
781
1135
  lines.push('');
782
1136
  lines.push('---');
783
1137
  lines.push('');
784
- lines.push(`## Overall Health Score: ${report.summary.overallScore}/100 (Grade: ${report.summary.grade})`);
1138
+ lines.push(`## Overall Health Score: ${report.summary.overallScore}/100 (Rating: ${report.summary.rating})`);
785
1139
  lines.push('');
786
1140
  lines.push('| Dimension | Score | Status |');
787
1141
  lines.push('|---|---|---|');
@@ -795,7 +1149,7 @@ function formatMarkdownReport(report, elapsed) {
795
1149
  };
796
1150
  for (const [key, dim] of Object.entries(report.dimensions)) {
797
1151
  const name = dimNames[key] || key;
798
- lines.push(`| ${name} | ${dim.score}/100 | ${dim.status.charAt(0).toUpperCase() + dim.status.slice(1)} |`);
1152
+ lines.push(`| ${name} | ${dim.score}/100 | ${dim.rating} |`);
799
1153
  }
800
1154
  lines.push('');
801
1155
  lines.push('---');
@@ -803,10 +1157,13 @@ function formatMarkdownReport(report, elapsed) {
803
1157
  // Dimension details
804
1158
  for (const [key, dim] of Object.entries(report.dimensions)) {
805
1159
  const name = dimNames[key] || key;
806
- lines.push(`## ${name} (${dim.score}/100) -- ${dim.status.charAt(0).toUpperCase() + dim.status.slice(1)}`);
1160
+ lines.push(`## ${name} (${dim.score}/100) -- ${dim.rating}`);
807
1161
  lines.push('');
808
1162
  lines.push(dim.details);
809
1163
  lines.push('');
1164
+ const topActions = (0, scoring_1.formatTopActionsBlock)(dim);
1165
+ for (const line of topActions)
1166
+ lines.push(line);
810
1167
  lines.push('| Metric | Value |');
811
1168
  lines.push('|---|---|');
812
1169
  for (const [mk, mv] of Object.entries(dim.metrics)) {
@@ -818,6 +1175,37 @@ function formatMarkdownReport(report, elapsed) {
818
1175
  lines.push('---');
819
1176
  lines.push('');
820
1177
  }
1178
+ // 2.4.7: top-N largest files. Surfaces the file-size distribution
1179
+ // beyond the single "largest" callout in the Code Quality /
1180
+ // Maintainability dimensions. Skipped when the array is empty
1181
+ // (no source files counted or autogen excluded everything).
1182
+ if (report.largestFiles && report.largestFiles.length > 0) {
1183
+ lines.push('## Top Files by Size');
1184
+ lines.push('');
1185
+ lines.push('| Rank | File | Lines |');
1186
+ lines.push('|-----:|------|------:|');
1187
+ report.largestFiles.forEach((f, i) => {
1188
+ lines.push(`| ${i + 1} | \`${f.path}\` | ${f.lines.toLocaleString()} |`);
1189
+ });
1190
+ lines.push('');
1191
+ // Advisory: when largest-files contain paths matching a known
1192
+ // vendored-code convention not already in the customer's
1193
+ // exclusion chain, surface a single tip pointing at the
1194
+ // `.dxkit-ignore` escape hatch. Bundled defaults already cover
1195
+ // `vendor/`, `third_party/`, `playground/`, `lexical-playground/`,
1196
+ // etc.; the remaining cases (most commonly `/libs/`) live in
1197
+ // customer-specific paths that can't be defaulted-away without
1198
+ // false-positives on first-party monorepo layouts.
1199
+ const suspects = (0, vendored_advisor_1.suspectVendoredEntries)(report.largestFiles);
1200
+ if (suspects.length > 0) {
1201
+ lines.push(`> **Tip — possibly vendored:** ${suspects
1202
+ .map((s) => `\`${s.path}\``)
1203
+ .join(', ')} match path conventions for external / vendored code. If these aren't authored by your team, add them (or their parent directory) to \`.dxkit-ignore\` to keep largest-files, Maintainability scoring, and the densest-file metric focused on first-party code.`);
1204
+ lines.push('');
1205
+ }
1206
+ lines.push('---');
1207
+ lines.push('');
1208
+ }
821
1209
  // Score calculation table
822
1210
  lines.push('## Score Calculation');
823
1211
  lines.push('');
@@ -841,14 +1229,18 @@ function formatMarkdownReport(report, elapsed) {
841
1229
  // Footer
842
1230
  lines.push('---');
843
1231
  lines.push('');
844
- if (report.languages.length > 0) {
845
- lines.push('**Languages:** ' + report.languages.map((l) => `${l.name} (${l.percentage}%)`).join(', '));
1232
+ // Drop languages that round to 0% — a single .py file alongside a
1233
+ // 300K-LOC C# codebase shouldn't surface as "Python (0%)" in the
1234
+ // header. Filter at the renderer rather than the detector so the
1235
+ // raw HealthReport.languages still carries everything for
1236
+ // programmatic consumers.
1237
+ const visibleLanguages = report.languages.filter((l) => l.percentage >= 1);
1238
+ if (visibleLanguages.length > 0) {
1239
+ lines.push('**Languages:** ' + visibleLanguages.map((l) => `${l.name} (${l.percentage}%)`).join(', '));
846
1240
  lines.push('');
847
1241
  }
848
1242
  lines.push(`**Tools used:** ${report.toolsUsed.join(', ')}`);
849
- if (report.toolsUnavailable.length > 0) {
850
- lines.push(`**Tools unavailable:** ${report.toolsUnavailable.join(', ')}`);
851
- }
1243
+ lines.push(...(0, tools_unavailable_prose_1.renderToolsUnavailableLines)(report.toolsUnavailable));
852
1244
  lines.push(`**Analysis time:** ${elapsed}s`);
853
1245
  lines.push('');
854
1246
  lines.push('*Generated by [VyuhLabs DXKit](https://www.npmjs.com/package/@vyuhlabs/dxkit)*');