@vyuhlabs/dxkit 2.4.6 → 2.4.7

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 (345) hide show
  1. package/CHANGELOG.md +885 -0
  2. package/README.md +131 -26
  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 +666 -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 +19 -8
  38. package/dist/analyzers/developer/index.js.map +1 -1
  39. package/dist/analyzers/dispatcher.d.ts +37 -0
  40. package/dist/analyzers/dispatcher.d.ts.map +1 -1
  41. package/dist/analyzers/dispatcher.js +56 -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 +271 -33
  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 +70 -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 +189 -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 +347 -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 +103 -1
  107. package/dist/analyzers/security/gather.d.ts.map +1 -1
  108. package/dist/analyzers/security/gather.js +281 -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/debug-statements.d.ts +17 -0
  145. package/dist/analyzers/tools/debug-statements.d.ts.map +1 -0
  146. package/dist/analyzers/tools/debug-statements.js +58 -0
  147. package/dist/analyzers/tools/debug-statements.js.map +1 -0
  148. package/dist/analyzers/tools/default-exclusions.gitignore +28 -0
  149. package/dist/analyzers/tools/exclusions.d.ts +33 -6
  150. package/dist/analyzers/tools/exclusions.d.ts.map +1 -1
  151. package/dist/analyzers/tools/exclusions.js +95 -26
  152. package/dist/analyzers/tools/exclusions.js.map +1 -1
  153. package/dist/analyzers/tools/generic.d.ts +17 -2
  154. package/dist/analyzers/tools/generic.d.ts.map +1 -1
  155. package/dist/analyzers/tools/generic.js +206 -109
  156. package/dist/analyzers/tools/generic.js.map +1 -1
  157. package/dist/analyzers/tools/gitleaks.d.ts.map +1 -1
  158. package/dist/analyzers/tools/gitleaks.js +48 -1
  159. package/dist/analyzers/tools/gitleaks.js.map +1 -1
  160. package/dist/analyzers/tools/graphify.d.ts +30 -2
  161. package/dist/analyzers/tools/graphify.d.ts.map +1 -1
  162. package/dist/analyzers/tools/graphify.js +131 -15
  163. package/dist/analyzers/tools/graphify.js.map +1 -1
  164. package/dist/analyzers/tools/jscpd.d.ts +12 -2
  165. package/dist/analyzers/tools/jscpd.d.ts.map +1 -1
  166. package/dist/analyzers/tools/jscpd.js +129 -6
  167. package/dist/analyzers/tools/jscpd.js.map +1 -1
  168. package/dist/analyzers/tools/minified-detection.d.ts +9 -0
  169. package/dist/analyzers/tools/minified-detection.d.ts.map +1 -0
  170. package/dist/analyzers/tools/minified-detection.js +147 -0
  171. package/dist/analyzers/tools/minified-detection.js.map +1 -0
  172. package/dist/analyzers/tools/nuget-package-reference.d.ts +131 -0
  173. package/dist/analyzers/tools/nuget-package-reference.d.ts.map +1 -0
  174. package/dist/analyzers/tools/nuget-package-reference.js +175 -0
  175. package/dist/analyzers/tools/nuget-package-reference.js.map +1 -0
  176. package/dist/analyzers/tools/osv-scanner-deps.d.ts +3 -2
  177. package/dist/analyzers/tools/osv-scanner-deps.d.ts.map +1 -1
  178. package/dist/analyzers/tools/osv-scanner-deps.js +32 -14
  179. package/dist/analyzers/tools/osv-scanner-deps.js.map +1 -1
  180. package/dist/analyzers/tools/osv.d.ts +36 -0
  181. package/dist/analyzers/tools/osv.d.ts.map +1 -1
  182. package/dist/analyzers/tools/osv.js +26 -0
  183. package/dist/analyzers/tools/osv.js.map +1 -1
  184. package/dist/analyzers/tools/parallel.d.ts +1 -1
  185. package/dist/analyzers/tools/parallel.d.ts.map +1 -1
  186. package/dist/analyzers/tools/parallel.js +2 -2
  187. package/dist/analyzers/tools/parallel.js.map +1 -1
  188. package/dist/analyzers/tools/risk-score.d.ts +7 -0
  189. package/dist/analyzers/tools/risk-score.d.ts.map +1 -1
  190. package/dist/analyzers/tools/risk-score.js +9 -2
  191. package/dist/analyzers/tools/risk-score.js.map +1 -1
  192. package/dist/analyzers/tools/run-tests-helper.d.ts +43 -0
  193. package/dist/analyzers/tools/run-tests-helper.d.ts.map +1 -0
  194. package/dist/analyzers/tools/run-tests-helper.js +156 -0
  195. package/dist/analyzers/tools/run-tests-helper.js.map +1 -0
  196. package/dist/analyzers/tools/runner.d.ts.map +1 -1
  197. package/dist/analyzers/tools/runner.js +75 -12
  198. package/dist/analyzers/tools/runner.js.map +1 -1
  199. package/dist/analyzers/tools/semgrep.d.ts +39 -2
  200. package/dist/analyzers/tools/semgrep.d.ts.map +1 -1
  201. package/dist/analyzers/tools/semgrep.js +131 -9
  202. package/dist/analyzers/tools/semgrep.js.map +1 -1
  203. package/dist/analyzers/tools/timing.d.ts +17 -3
  204. package/dist/analyzers/tools/timing.d.ts.map +1 -1
  205. package/dist/analyzers/tools/timing.js +36 -14
  206. package/dist/analyzers/tools/timing.js.map +1 -1
  207. package/dist/analyzers/tools/tool-registry.d.ts.map +1 -1
  208. package/dist/analyzers/tools/tool-registry.js +11 -1
  209. package/dist/analyzers/tools/tool-registry.js.map +1 -1
  210. package/dist/analyzers/tools/tools-unavailable-prose.d.ts +18 -0
  211. package/dist/analyzers/tools/tools-unavailable-prose.d.ts.map +1 -0
  212. package/dist/analyzers/tools/tools-unavailable-prose.js +69 -0
  213. package/dist/analyzers/tools/tools-unavailable-prose.js.map +1 -0
  214. package/dist/analyzers/tools/upgrade-plan-resolver.d.ts.map +1 -1
  215. package/dist/analyzers/tools/upgrade-plan-resolver.js +7 -0
  216. package/dist/analyzers/tools/upgrade-plan-resolver.js.map +1 -1
  217. package/dist/analyzers/tools/vendored-advisor.d.ts +43 -0
  218. package/dist/analyzers/tools/vendored-advisor.d.ts.map +1 -0
  219. package/dist/analyzers/tools/vendored-advisor.js +107 -0
  220. package/dist/analyzers/tools/vendored-advisor.js.map +1 -0
  221. package/dist/analyzers/tools/walk-paths.d.ts +78 -0
  222. package/dist/analyzers/tools/walk-paths.d.ts.map +1 -0
  223. package/dist/analyzers/tools/walk-paths.js +150 -0
  224. package/dist/analyzers/tools/walk-paths.js.map +1 -0
  225. package/dist/analyzers/tools/walk-source-files.d.ts +70 -0
  226. package/dist/analyzers/tools/walk-source-files.d.ts.map +1 -0
  227. package/dist/analyzers/tools/walk-source-files.js +369 -0
  228. package/dist/analyzers/tools/walk-source-files.js.map +1 -0
  229. package/dist/analyzers/types.d.ts +204 -4
  230. package/dist/analyzers/types.d.ts.map +1 -1
  231. package/dist/analyzers/xlsx/bom.d.ts.map +1 -1
  232. package/dist/analyzers/xlsx/bom.js +8 -1
  233. package/dist/analyzers/xlsx/bom.js.map +1 -1
  234. package/dist/cli.d.ts.map +1 -1
  235. package/dist/cli.js +557 -189
  236. package/dist/cli.js.map +1 -1
  237. package/dist/detect.d.ts.map +1 -1
  238. package/dist/detect.js +24 -7
  239. package/dist/detect.js.map +1 -1
  240. package/dist/doctor.d.ts.map +1 -1
  241. package/dist/doctor.js +103 -53
  242. package/dist/doctor.js.map +1 -1
  243. package/dist/languages/capabilities/provider.d.ts +130 -1
  244. package/dist/languages/capabilities/provider.d.ts.map +1 -1
  245. package/dist/languages/capabilities/types.d.ts +68 -7
  246. package/dist/languages/capabilities/types.d.ts.map +1 -1
  247. package/dist/languages/csharp.d.ts +15 -1
  248. package/dist/languages/csharp.d.ts.map +1 -1
  249. package/dist/languages/csharp.js +624 -146
  250. package/dist/languages/csharp.js.map +1 -1
  251. package/dist/languages/go.d.ts.map +1 -1
  252. package/dist/languages/go.js +89 -11
  253. package/dist/languages/go.js.map +1 -1
  254. package/dist/languages/index.d.ts +131 -2
  255. package/dist/languages/index.d.ts.map +1 -1
  256. package/dist/languages/index.js +206 -0
  257. package/dist/languages/index.js.map +1 -1
  258. package/dist/languages/java.d.ts.map +1 -1
  259. package/dist/languages/java.js +113 -26
  260. package/dist/languages/java.js.map +1 -1
  261. package/dist/languages/kotlin.d.ts.map +1 -1
  262. package/dist/languages/kotlin.js +132 -26
  263. package/dist/languages/kotlin.js.map +1 -1
  264. package/dist/languages/python.d.ts.map +1 -1
  265. package/dist/languages/python.js +149 -44
  266. package/dist/languages/python.js.map +1 -1
  267. package/dist/languages/ruby.d.ts +39 -1
  268. package/dist/languages/ruby.d.ts.map +1 -1
  269. package/dist/languages/ruby.js +178 -44
  270. package/dist/languages/ruby.js.map +1 -1
  271. package/dist/languages/rust.d.ts.map +1 -1
  272. package/dist/languages/rust.js +103 -16
  273. package/dist/languages/rust.js.map +1 -1
  274. package/dist/languages/types.d.ts +228 -5
  275. package/dist/languages/types.d.ts.map +1 -1
  276. package/dist/languages/typescript.d.ts.map +1 -1
  277. package/dist/languages/typescript.js +201 -14
  278. package/dist/languages/typescript.js.map +1 -1
  279. package/dist/scoring/dimensions/documentation.d.ts +53 -0
  280. package/dist/scoring/dimensions/documentation.d.ts.map +1 -0
  281. package/dist/scoring/dimensions/documentation.js +106 -0
  282. package/dist/scoring/dimensions/documentation.js.map +1 -0
  283. package/dist/scoring/dimensions/dx.d.ts +53 -0
  284. package/dist/scoring/dimensions/dx.d.ts.map +1 -0
  285. package/dist/scoring/dimensions/dx.js +105 -0
  286. package/dist/scoring/dimensions/dx.js.map +1 -0
  287. package/dist/scoring/dimensions/maintainability.d.ts +53 -0
  288. package/dist/scoring/dimensions/maintainability.d.ts.map +1 -0
  289. package/dist/scoring/dimensions/maintainability.js +101 -0
  290. package/dist/scoring/dimensions/maintainability.js.map +1 -0
  291. package/dist/scoring/dimensions/quality.d.ts +108 -0
  292. package/dist/scoring/dimensions/quality.d.ts.map +1 -0
  293. package/dist/scoring/dimensions/quality.js +174 -0
  294. package/dist/scoring/dimensions/quality.js.map +1 -0
  295. package/dist/scoring/dimensions/security.d.ts +84 -0
  296. package/dist/scoring/dimensions/security.d.ts.map +1 -0
  297. package/dist/scoring/dimensions/security.js +135 -0
  298. package/dist/scoring/dimensions/security.js.map +1 -0
  299. package/dist/scoring/dimensions/testing.d.ts +56 -0
  300. package/dist/scoring/dimensions/testing.d.ts.map +1 -0
  301. package/dist/scoring/dimensions/testing.js +98 -0
  302. package/dist/scoring/dimensions/testing.js.map +1 -0
  303. package/dist/scoring/evaluator.d.ts +27 -0
  304. package/dist/scoring/evaluator.d.ts.map +1 -0
  305. package/dist/scoring/evaluator.js +124 -0
  306. package/dist/scoring/evaluator.js.map +1 -0
  307. package/dist/scoring/format.d.ts +34 -0
  308. package/dist/scoring/format.d.ts.map +1 -0
  309. package/dist/scoring/format.js +63 -0
  310. package/dist/scoring/format.js.map +1 -0
  311. package/dist/scoring/index.d.ts +37 -0
  312. package/dist/scoring/index.d.ts.map +1 -0
  313. package/dist/scoring/index.js +57 -0
  314. package/dist/scoring/index.js.map +1 -0
  315. package/dist/scoring/overall.d.ts +54 -0
  316. package/dist/scoring/overall.d.ts.map +1 -0
  317. package/dist/scoring/overall.js +76 -0
  318. package/dist/scoring/overall.js.map +1 -0
  319. package/dist/scoring/result.d.ts +111 -0
  320. package/dist/scoring/result.d.ts.map +1 -0
  321. package/dist/scoring/result.js +14 -0
  322. package/dist/scoring/result.js.map +1 -0
  323. package/dist/scoring/spec.d.ts +76 -0
  324. package/dist/scoring/spec.d.ts.map +1 -0
  325. package/dist/scoring/spec.js +22 -0
  326. package/dist/scoring/spec.js.map +1 -0
  327. package/dist/scoring/thresholds.d.ts +56 -0
  328. package/dist/scoring/thresholds.d.ts.map +1 -0
  329. package/dist/scoring/thresholds.js +75 -0
  330. package/dist/scoring/thresholds.js.map +1 -0
  331. package/dist/tools-cli.d.ts.map +1 -1
  332. package/dist/tools-cli.js +21 -2
  333. package/dist/tools-cli.js.map +1 -1
  334. package/dist/types.d.ts +16 -0
  335. package/dist/types.d.ts.map +1 -1
  336. package/package.json +1 -1
  337. package/templates/.claude/commands/dashboard.md +17 -9
  338. package/dist/analyzers/scoring.d.ts +0 -49
  339. package/dist/analyzers/scoring.d.ts.map +0 -1
  340. package/dist/analyzers/scoring.js +0 -422
  341. package/dist/analyzers/scoring.js.map +0 -1
  342. package/dist/analyzers/security/scoring.d.ts +0 -29
  343. package/dist/analyzers/security/scoring.d.ts.map +0 -1
  344. package/dist/analyzers/security/scoring.js +0 -40
  345. 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,21 @@ 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");
46
49
  const fs = __importStar(require("fs"));
47
50
  const path = __importStar(require("path"));
51
+ // process.stdout.write returns false when the OS pipe buffer is full
52
+ // (typically 64KB on Linux). Without awaiting 'drain', the process exits
53
+ // and the tail of large payloads is silently lost on POSIX — manifests as
54
+ // 0-byte files when piping `--json` output through `cat > file` or similar.
55
+ // Tracked as D017.
56
+ async function emitJson(payload) {
57
+ const data = JSON.stringify(payload, null, 2) + '\n';
58
+ if (!process.stdout.write(data)) {
59
+ await new Promise((resolve) => process.stdout.once('drain', resolve));
60
+ }
61
+ }
48
62
  function printUsage() {
49
63
  console.log(`
50
64
  ${logger.bold('vyuh-dxkit')} v${constants_1.VERSION} — AI-native developer experience toolkit
@@ -60,6 +74,9 @@ function printUsage() {
60
74
  vyuh-dxkit dev-report [path] Developer activity analysis
61
75
  vyuh-dxkit licenses [path] Dependency license inventory
62
76
  vyuh-dxkit bom [path] Bill of Materials (licenses + vulnerabilities joined)
77
+ vyuh-dxkit coverage [path] Run per-pack test-with-coverage (side-effecting; materializes the coverage artifact health/test-gaps read)
78
+ vyuh-dxkit dashboard [path] Render .dxkit/reports/ into a single HTML dashboard
79
+ vyuh-dxkit report [path] Run every analyzer + dashboard in one shot (full audit)
63
80
  vyuh-dxkit to-xlsx <json> Convert a dxkit JSON report to 15-col XLSX
64
81
  vyuh-dxkit tools [path] Show required analysis tools status
65
82
  vyuh-dxkit tools install Interactively install missing tools
@@ -79,16 +96,16 @@ function printUsage() {
79
96
  --rescan Re-run codebase analysis
80
97
 
81
98
  ${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)
99
+ --json Print report as JSON to stdout
100
+ --verbose Print per-tool timing to stderr
101
+ --no-save Skip writing the markdown report file
102
+ --detailed Also write <name>-detailed.md + .json with evidence + ranked actions
103
+ --xlsx Licenses/bom: also write 15-col BOM XLSX
104
+ --since Dev-report: start date (YYYY-MM-DD)
105
+ --filter Bom: 'all' (default) or 'top-level' (keeps only root manifest deps;
106
+ advisory rollup under byTopLevelDep still reflects transitives)
107
+ --with-coverage Health/test-gaps: materialize coverage artifacts via per-pack
108
+ runTests() before analysis (line-coverage truth vs filename match)
92
109
 
93
110
  ${logger.bold('Examples:')}
94
111
  npx vyuh-dxkit init # Interactive
@@ -121,8 +138,14 @@ async function run(argv) {
121
138
  output: { type: 'string', short: 'o' },
122
139
  xlsx: { type: 'boolean', default: false },
123
140
  filter: { type: 'string' },
124
- 'no-nested': { type: 'boolean', default: false },
125
141
  all: { type: 'boolean', default: false },
142
+ 'reports-dir': { type: 'string' },
143
+ 'json-dir': { type: 'string' },
144
+ 'project-name': { type: 'string' },
145
+ lang: { type: 'string' },
146
+ timeout: { type: 'string' },
147
+ 'no-fail-fast': { type: 'boolean', default: false },
148
+ 'with-coverage': { type: 'boolean', default: false },
126
149
  },
127
150
  allowPositionals: true,
128
151
  strict: false,
@@ -242,27 +265,57 @@ async function run(argv) {
242
265
  }
243
266
  case 'health': {
244
267
  const targetPath = resolveRepoPath(positionals[1]);
245
- const { analyzeHealth, analyzeHealthWithMetrics } = await Promise.resolve().then(() => __importStar(require('./analyzers/health')));
268
+ const { analyzeHealthWithMetrics } = await Promise.resolve().then(() => __importStar(require('./analyzers/health')));
246
269
  logger.header('vyuh-dxkit health');
247
270
  logger.info(`Analyzing ${targetPath}...`);
248
271
  const startTime = Date.now();
272
+ // D021 (2.4.7): --with-coverage materializes the coverage artifact
273
+ // BEFORE the analyzer runs, so the report reads line-coverage
274
+ // truth (`coverageFidelity: 'line-coverage'`) instead of falling
275
+ // back to the filename-match heuristic. Shares the same per-pack
276
+ // runner the `coverage` command uses; honors --lang to limit
277
+ // scope on polyglot repos.
278
+ if (values['with-coverage']) {
279
+ const { runCoverageAcrossPacks } = await Promise.resolve().then(() => __importStar(require('./analyzers/coverage-runner')));
280
+ const langFilter = values.lang;
281
+ logger.info('Running test-with-coverage across active packs...');
282
+ const { rows } = await runCoverageAcrossPacks(targetPath, {
283
+ langFilter,
284
+ failFast: !values['no-fail-fast'],
285
+ onPackStart: (id) => process.stderr.write(` → ${id}: running tests with coverage...\n`),
286
+ });
287
+ const successes = rows.filter((r) => r.status === 'success').length;
288
+ if (successes > 0) {
289
+ logger.success(`${successes}/${rows.length} packs produced coverage artifacts`);
290
+ }
291
+ else {
292
+ logger.warn(`0/${rows.length} packs produced coverage artifacts — falling back to heuristic`);
293
+ }
294
+ console.log(''); // slop-ok
295
+ }
249
296
  // 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
- };
297
+ // D032 (2.4.7): always gather the underlying metrics so the
298
+ // `-detailed.json` write below has the data it needs. Pre-fix
299
+ // the metrics-bearing path was gated on `--detailed`, so the
300
+ // dashboard's input JSON was only produced when the user opted
301
+ // into detailed reporting — making the dashboard headline numbers
302
+ // silently stale or zero on a default `dxkit health . && dxkit
303
+ // dashboard .` workflow. Both internal entry points share
304
+ // `analyzeHealthInternal`, so the only cost is keeping a metrics
305
+ // reference live (no extra compute).
306
+ const healthResult = await analyzeHealthWithMetrics(targetPath, {
307
+ verbose: !!values.verbose,
308
+ });
256
309
  const report = healthResult.report;
257
310
  const healthMetrics = healthResult.metrics;
258
311
  const elapsed = ((Date.now() - startTime) / 1000).toFixed(1);
259
312
  if (values.json) {
260
- console.log(JSON.stringify(report, null, 2));
313
+ await emitJson(report);
261
314
  }
262
315
  else {
263
316
  // Console output
264
317
  console.log('');
265
- console.log(` ${logger.bold('Overall:')} ${report.summary.overallScore}/100 (Grade: ${report.summary.grade})`);
318
+ console.log(` ${logger.bold('Overall:')} ${report.summary.overallScore}/100 (Rating: ${report.summary.rating})`);
266
319
  console.log('');
267
320
  const dims = report.dimensions;
268
321
  const order = [
@@ -275,7 +328,11 @@ async function run(argv) {
275
328
  ];
276
329
  for (const [name, dim] of order) {
277
330
  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}`);
331
+ console.log(` ${name.padEnd(22)} ${bar} ${dim.score.toString().padStart(3)}/100 ${dim.rating}`);
332
+ const topAction = (0, scoring_1.formatTopActionLine)(dim);
333
+ if (topAction) {
334
+ logger.dim(` ${' '.repeat(22)} → ${topAction}`);
335
+ }
279
336
  }
280
337
  console.log('');
281
338
  logger.dim('Tools: ' + report.toolsUsed.join(', '));
@@ -283,27 +340,42 @@ async function run(argv) {
283
340
  logger.dim('Unavailable: ' + report.toolsUnavailable.join(', '));
284
341
  }
285
342
  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
- }
343
+ }
344
+ // Disk side: orthogonal to --json so consumers don't need separate
345
+ // `--detailed` and `--detailed --json` invocations (closes D018).
346
+ // `logger.success` routes to stderr in --json mode, so it's safe to
347
+ // call unconditionally.
348
+ if (!values['no-save']) {
349
+ const reportDir = path.join(targetPath, '.dxkit', 'reports');
350
+ const date = new Date().toISOString().slice(0, 10);
351
+ const reportPath = path.join(reportDir, `health-audit-${date}.md`);
352
+ fs.mkdirSync(reportDir, { recursive: true });
353
+ fs.writeFileSync(reportPath, formatMarkdownReport(report, elapsed));
354
+ if (!values.json)
355
+ console.log(''); // slop-ok
356
+ logger.success(`Report saved to ${path.relative(targetPath, reportPath)}`);
357
+ // D032 (2.4.7): always write BOTH `-detailed.json` AND
358
+ // `-detailed.md` so `vyuh-dxkit dashboard` finds fresh inputs
359
+ // on every run. The dashboard reads JSON for tile metrics and
360
+ // embeds the markdown for tab content (Language Breakdown +
361
+ // Plans live only in the detailed.md). Pre-fix gating these
362
+ // on `--detailed` meant a default `health → dashboard` workflow
363
+ // showed stale tile values + stale tab content from whichever
364
+ // run last passed `--detailed`. The `--detailed` flag now only
365
+ // controls the console success-log lines.
366
+ const { buildHealthDetailed, formatHealthDetailedMarkdown } = await Promise.resolve().then(() => __importStar(require('./analyzers/health/detailed')));
367
+ const detailed = buildHealthDetailed(report, healthMetrics);
368
+ const detailedJsonPath = path.join(reportDir, `health-audit-${date}-detailed.json`);
369
+ const detailedMdPath = path.join(reportDir, `health-audit-${date}-detailed.md`);
370
+ fs.writeFileSync(detailedJsonPath, JSON.stringify(detailed, null, 2));
371
+ fs.writeFileSync(detailedMdPath, formatHealthDetailedMarkdown(detailed, elapsed));
372
+ if (values.detailed) {
373
+ logger.success(`Detailed report saved to ${path.relative(targetPath, detailedMdPath)}`);
374
+ logger.success(`Detailed JSON saved to ${path.relative(targetPath, detailedJsonPath)}`);
305
375
  }
306
- // Hint about missing tools (exclude project-side config errors)
376
+ }
377
+ if (!values.json) {
378
+ // Hint about missing tools (exclude project-side config errors).
307
379
  const PROJECT_ISSUES = ['config error', 'legacy .eslintrc', 'no eslint config'];
308
380
  const trulyMissing = report.toolsUnavailable.filter((t) => !PROJECT_ISSUES.some((p) => t.includes(p)));
309
381
  if (trulyMissing.length > 0) {
@@ -363,7 +435,7 @@ async function run(argv) {
363
435
  const report = await analyzeSecurity(targetPath, { verbose: !!values.verbose });
364
436
  const elapsed = ((Date.now() - startTime) / 1000).toFixed(1);
365
437
  if (values.json) {
366
- console.log(JSON.stringify(report, null, 2));
438
+ await emitJson(report);
367
439
  }
368
440
  else {
369
441
  const s = report.summary.findings;
@@ -381,24 +453,27 @@ async function run(argv) {
381
453
  logger.dim('Unavailable: ' + report.toolsUnavailable.join(', '));
382
454
  }
383
455
  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
- }
456
+ }
457
+ // Disk side: orthogonal to --json (closes D018).
458
+ if (!values['no-save']) {
459
+ const reportDir = path.join(targetPath, '.dxkit', 'reports');
460
+ const date = new Date().toISOString().slice(0, 10);
461
+ const reportPath = path.join(reportDir, `vulnerability-scan-${date}.md`);
462
+ fs.mkdirSync(reportDir, { recursive: true });
463
+ fs.writeFileSync(reportPath, formatSecurityReport(report, elapsed));
464
+ if (!values.json)
465
+ console.log(''); // slop-ok
466
+ logger.success(`Report saved to ${path.relative(targetPath, reportPath)}`);
467
+ // D032 (2.4.7): detailed JSON + MD always written so dashboard finds fresh inputs.
468
+ const { buildSecurityDetailed, formatSecurityDetailedMarkdown } = await Promise.resolve().then(() => __importStar(require('./analyzers/security/detailed')));
469
+ const securityDetailed = buildSecurityDetailed(report);
470
+ const securityDetailedJsonPath = path.join(reportDir, `vulnerability-scan-${date}-detailed.json`);
471
+ const securityDetailedMdPath = path.join(reportDir, `vulnerability-scan-${date}-detailed.md`);
472
+ fs.writeFileSync(securityDetailedJsonPath, JSON.stringify(securityDetailed, null, 2));
473
+ fs.writeFileSync(securityDetailedMdPath, formatSecurityDetailedMarkdown(securityDetailed, elapsed));
474
+ if (values.detailed) {
475
+ logger.success(`Detailed report saved to ${path.relative(targetPath, securityDetailedMdPath)}`);
476
+ logger.success(`Detailed JSON saved to ${path.relative(targetPath, securityDetailedJsonPath)}`);
402
477
  }
403
478
  }
404
479
  break;
@@ -409,10 +484,32 @@ async function run(argv) {
409
484
  logger.header('vyuh-dxkit test-gaps');
410
485
  logger.info(`Analyzing ${targetPath}...`);
411
486
  const startTime = Date.now();
487
+ // D021 (2.4.7): --with-coverage materializes the coverage artifact
488
+ // before analysis so the test-gaps report reads line-coverage
489
+ // truth instead of falling back to filename-match. Same runner
490
+ // health --with-coverage uses.
491
+ if (values['with-coverage']) {
492
+ const { runCoverageAcrossPacks } = await Promise.resolve().then(() => __importStar(require('./analyzers/coverage-runner')));
493
+ const langFilter = values.lang;
494
+ logger.info('Running test-with-coverage across active packs...');
495
+ const { rows } = await runCoverageAcrossPacks(targetPath, {
496
+ langFilter,
497
+ failFast: !values['no-fail-fast'],
498
+ onPackStart: (id) => process.stderr.write(` → ${id}: running tests with coverage...\n`),
499
+ });
500
+ const successes = rows.filter((r) => r.status === 'success').length;
501
+ if (successes > 0) {
502
+ logger.success(`${successes}/${rows.length} packs produced coverage artifacts`);
503
+ }
504
+ else {
505
+ logger.warn(`0/${rows.length} packs produced coverage artifacts — falling back to heuristic`);
506
+ }
507
+ console.log(''); // slop-ok
508
+ }
412
509
  const report = await analyzeTestGaps(targetPath, { verbose: !!values.verbose });
413
510
  const elapsed = ((Date.now() - startTime) / 1000).toFixed(1);
414
511
  if (values.json) {
415
- console.log(JSON.stringify(report, null, 2));
512
+ await emitJson(report);
416
513
  }
417
514
  else {
418
515
  const s = report.summary;
@@ -426,24 +523,27 @@ async function run(argv) {
426
523
  console.log('');
427
524
  logger.dim('Tools: ' + report.toolsUsed.join(', '));
428
525
  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
- }
526
+ }
527
+ // Disk side: orthogonal to --json (closes D018).
528
+ if (!values['no-save']) {
529
+ const reportDir = path.join(targetPath, '.dxkit', 'reports');
530
+ const date = new Date().toISOString().slice(0, 10);
531
+ const reportPath = path.join(reportDir, `test-gaps-${date}.md`);
532
+ fs.mkdirSync(reportDir, { recursive: true });
533
+ fs.writeFileSync(reportPath, formatTestGapsReport(report, elapsed));
534
+ if (!values.json)
535
+ console.log(''); // slop-ok
536
+ logger.success(`Report saved to ${path.relative(targetPath, reportPath)}`);
537
+ // D032 (2.4.7): detailed JSON + MD always written so dashboard finds fresh inputs.
538
+ const { buildTestGapsDetailed, formatTestGapsDetailedMarkdown } = await Promise.resolve().then(() => __importStar(require('./analyzers/tests/detailed')));
539
+ const testGapsDetailed = buildTestGapsDetailed(report);
540
+ const testGapsDetailedJsonPath = path.join(reportDir, `test-gaps-${date}-detailed.json`);
541
+ const testGapsDetailedMdPath = path.join(reportDir, `test-gaps-${date}-detailed.md`);
542
+ fs.writeFileSync(testGapsDetailedJsonPath, JSON.stringify(testGapsDetailed, null, 2));
543
+ fs.writeFileSync(testGapsDetailedMdPath, formatTestGapsDetailedMarkdown(testGapsDetailed, elapsed));
544
+ if (values.detailed) {
545
+ logger.success(`Detailed report saved to ${path.relative(targetPath, testGapsDetailedMdPath)}`);
546
+ logger.success(`Detailed JSON saved to ${path.relative(targetPath, testGapsDetailedJsonPath)}`);
447
547
  }
448
548
  }
449
549
  break;
@@ -460,7 +560,7 @@ async function run(argv) {
460
560
  });
461
561
  const elapsed = ((Date.now() - startTime) / 1000).toFixed(1);
462
562
  if (values.json) {
463
- console.log(JSON.stringify(report, null, 2));
563
+ await emitJson(report);
464
564
  }
465
565
  else {
466
566
  const m = report.metrics;
@@ -495,24 +595,27 @@ async function run(argv) {
495
595
  logger.dim('Unavailable: ' + report.toolsUnavailable.join(', '));
496
596
  }
497
597
  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
- }
598
+ }
599
+ // Disk side: orthogonal to --json (closes D018).
600
+ if (!values['no-save']) {
601
+ const reportDir = path.join(targetPath, '.dxkit', 'reports');
602
+ const date = new Date().toISOString().slice(0, 10);
603
+ const reportPath = path.join(reportDir, `quality-review-${date}.md`);
604
+ fs.mkdirSync(reportDir, { recursive: true });
605
+ fs.writeFileSync(reportPath, formatQualityReport(report, elapsed));
606
+ if (!values.json)
607
+ console.log(''); // slop-ok
608
+ logger.success(`Report saved to ${path.relative(targetPath, reportPath)}`);
609
+ // D032 (2.4.7): detailed JSON + MD always written so dashboard finds fresh inputs.
610
+ const { buildQualityDetailed, formatQualityDetailedMarkdown } = await Promise.resolve().then(() => __importStar(require('./analyzers/quality/detailed')));
611
+ const qualityDetailed = buildQualityDetailed(report);
612
+ const qualityDetailedJsonPath = path.join(reportDir, `quality-review-${date}-detailed.json`);
613
+ const qualityDetailedMdPath = path.join(reportDir, `quality-review-${date}-detailed.md`);
614
+ fs.writeFileSync(qualityDetailedJsonPath, JSON.stringify(qualityDetailed, null, 2));
615
+ fs.writeFileSync(qualityDetailedMdPath, formatQualityDetailedMarkdown(qualityDetailed, elapsed));
616
+ if (values.detailed) {
617
+ logger.success(`Detailed report saved to ${path.relative(targetPath, qualityDetailedMdPath)}`);
618
+ logger.success(`Detailed JSON saved to ${path.relative(targetPath, qualityDetailedJsonPath)}`);
516
619
  }
517
620
  }
518
621
  break;
@@ -524,10 +627,12 @@ async function run(argv) {
524
627
  logger.header('vyuh-dxkit dev-report');
525
628
  logger.info(`Analyzing ${targetPath}...`);
526
629
  const startTime = Date.now();
527
- const report = analyzeDevActivity(targetPath, sinceFlag, { verbose: !!values.verbose });
630
+ const report = await analyzeDevActivity(targetPath, sinceFlag, {
631
+ verbose: !!values.verbose,
632
+ });
528
633
  const elapsed = ((Date.now() - startTime) / 1000).toFixed(1);
529
634
  if (values.json) {
530
- console.log(JSON.stringify(report, null, 2));
635
+ await emitJson(report);
531
636
  }
532
637
  else {
533
638
  const s = report.summary;
@@ -547,28 +652,30 @@ async function run(argv) {
547
652
  console.log('');
548
653
  logger.dim('Tools: ' + report.toolsUsed.join(', '));
549
654
  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
- }
655
+ }
656
+ // Disk side: orthogonal to --json (closes D018).
657
+ if (!values['no-save']) {
658
+ const reportDir = path.join(targetPath, '.dxkit', 'reports');
659
+ const date = new Date().toISOString().slice(0, 10);
660
+ const reportPath = path.join(reportDir, `developer-report-${date}.md`);
661
+ fs.mkdirSync(reportDir, { recursive: true });
662
+ fs.writeFileSync(reportPath, formatDevReport(report, elapsed));
663
+ if (!values.json)
664
+ console.log(''); // slop-ok
665
+ logger.success(`Report saved to ${path.relative(targetPath, reportPath)}`);
666
+ // D032 (2.4.7): detailed JSON + MD always written so dashboard finds fresh inputs.
667
+ const { buildDevDetailed, formatDevDetailedMarkdown } = await Promise.resolve().then(() => __importStar(require('./analyzers/developer/detailed')));
668
+ const { gatherVagueCommitExamples } = await Promise.resolve().then(() => __importStar(require('./analyzers/developer/gather')));
669
+ const sinceDate = sinceFlag || new Date(Date.now() - 90 * 24 * 60 * 60 * 1000).toISOString().slice(0, 10);
670
+ const vague = gatherVagueCommitExamples(targetPath, sinceDate);
671
+ const devDetailed = buildDevDetailed(report, vague);
672
+ const devDetailedJsonPath = path.join(reportDir, `developer-report-${date}-detailed.json`);
673
+ const devDetailedMdPath = path.join(reportDir, `developer-report-${date}-detailed.md`);
674
+ fs.writeFileSync(devDetailedJsonPath, JSON.stringify(devDetailed, null, 2));
675
+ fs.writeFileSync(devDetailedMdPath, formatDevDetailedMarkdown(devDetailed, elapsed));
676
+ if (values.detailed) {
677
+ logger.success(`Detailed report saved to ${path.relative(targetPath, devDetailedMdPath)}`);
678
+ logger.success(`Detailed JSON saved to ${path.relative(targetPath, devDetailedJsonPath)}`);
572
679
  }
573
680
  }
574
681
  break;
@@ -582,7 +689,7 @@ async function run(argv) {
582
689
  const report = await analyzeLicenses(targetPath, { verbose: !!values.verbose });
583
690
  const elapsed = ((Date.now() - startTime) / 1000).toFixed(1);
584
691
  if (values.json) {
585
- console.log(JSON.stringify(report, null, 2)); // slop-ok
692
+ await emitJson(report); // slop-ok
586
693
  }
587
694
  else {
588
695
  const s = report.summary;
@@ -606,35 +713,36 @@ async function run(argv) {
606
713
  console.log(''); // slop-ok
607
714
  logger.dim('Tools: ' + (report.toolsUsed.join(', ') || '(none)'));
608
715
  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));
716
+ }
717
+ // Disk side: orthogonal to --json (closes D018).
718
+ if (!values['no-save']) {
719
+ const reportDir = path.join(targetPath, '.dxkit', 'reports');
720
+ const date = new Date().toISOString().slice(0, 10);
721
+ const reportPath = path.join(reportDir, `licenses-${date}.md`);
722
+ fs.mkdirSync(reportDir, { recursive: true });
723
+ fs.writeFileSync(reportPath, formatLicensesReport(report, elapsed));
724
+ if (!values.json)
615
725
  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
- }
726
+ logger.success(`Report saved to ${path.relative(targetPath, reportPath)}`);
727
+ // D032 (2.4.7): detailed JSON + MD always written so dashboard finds fresh inputs.
728
+ const { buildLicensesDetailed, formatLicensesDetailedMarkdown } = await Promise.resolve().then(() => __importStar(require('./analyzers/licenses/detailed')));
729
+ const licensesDetailed = buildLicensesDetailed(report);
730
+ const licensesDetailedJsonPath = path.join(reportDir, `licenses-${date}-detailed.json`);
731
+ const licensesDetailedMdPath = path.join(reportDir, `licenses-${date}-detailed.md`);
732
+ fs.writeFileSync(licensesDetailedJsonPath, JSON.stringify(licensesDetailed, null, 2));
733
+ fs.writeFileSync(licensesDetailedMdPath, formatLicensesDetailedMarkdown(licensesDetailed, elapsed));
734
+ if (values.detailed) {
735
+ logger.success(`Detailed report saved to ${path.relative(targetPath, licensesDetailedMdPath)}`);
736
+ logger.success(`Detailed JSON saved to ${path.relative(targetPath, licensesDetailedJsonPath)}`);
737
+ }
738
+ if (values.xlsx) {
739
+ const { toLicensesXlsx } = await Promise.resolve().then(() => __importStar(require('./analyzers/xlsx')));
740
+ const xlsxPath = values.output
741
+ ? path.resolve(values.output)
742
+ : path.join(reportDir, `licenses-${date}.xlsx`);
743
+ const buf = await toLicensesXlsx(licensesDetailed);
744
+ fs.writeFileSync(xlsxPath, buf);
745
+ logger.success(`XLSX saved to ${path.relative(targetPath, xlsxPath)}`);
638
746
  }
639
747
  }
640
748
  break;
@@ -650,16 +758,14 @@ async function run(argv) {
650
758
  process.exit(1);
651
759
  }
652
760
  const filter = rawFilter;
653
- const nested = !values['no-nested'];
654
761
  const startTime = Date.now();
655
762
  const report = await analyzeBom(targetPath, {
656
763
  verbose: !!values.verbose,
657
764
  filter,
658
- nested,
659
765
  });
660
766
  const elapsed = ((Date.now() - startTime) / 1000).toFixed(1);
661
767
  if (values.json) {
662
- console.log(JSON.stringify(report, null, 2)); // slop-ok
768
+ await emitJson(report); // slop-ok
663
769
  }
664
770
  else {
665
771
  const s = report.summary;
@@ -696,36 +802,260 @@ async function run(argv) {
696
802
  console.log(''); // slop-ok
697
803
  logger.dim('Tools: ' + (report.toolsUsed.join(', ') || '(none)'));
698
804
  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));
805
+ }
806
+ // Disk side: orthogonal to --json (closes D018).
807
+ if (!values['no-save']) {
808
+ const reportDir = path.join(targetPath, '.dxkit', 'reports');
809
+ const date = new Date().toISOString().slice(0, 10);
810
+ const reportPath = path.join(reportDir, `bom-${date}.md`);
811
+ fs.mkdirSync(reportDir, { recursive: true });
812
+ fs.writeFileSync(reportPath, formatBomReport(report, elapsed));
813
+ if (!values.json)
705
814
  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
- }
815
+ logger.success(`Report saved to ${path.relative(targetPath, reportPath)}`);
816
+ // D032 (2.4.7): detailed JSON + MD always written so dashboard finds fresh inputs.
817
+ const { buildBomDetailed, formatBomDetailedMarkdown } = await Promise.resolve().then(() => __importStar(require('./analyzers/bom/detailed')));
818
+ const bomDetailed = buildBomDetailed(report);
819
+ const bomDetailedJsonPath = path.join(reportDir, `bom-${date}-detailed.json`);
820
+ const bomDetailedMdPath = path.join(reportDir, `bom-${date}-detailed.md`);
821
+ fs.writeFileSync(bomDetailedJsonPath, JSON.stringify(bomDetailed, null, 2));
822
+ fs.writeFileSync(bomDetailedMdPath, formatBomDetailedMarkdown(bomDetailed, elapsed));
823
+ if (values.detailed) {
824
+ logger.success(`Detailed report saved to ${path.relative(targetPath, bomDetailedMdPath)}`);
825
+ logger.success(`Detailed JSON saved to ${path.relative(targetPath, bomDetailedJsonPath)}`);
826
+ }
827
+ if (values.xlsx) {
828
+ const { toBomXlsx } = await Promise.resolve().then(() => __importStar(require('./analyzers/xlsx')));
829
+ const xlsxPath = values.output
830
+ ? path.resolve(values.output)
831
+ : path.join(reportDir, `bom-${date}.xlsx`);
832
+ const buf = await toBomXlsx(report);
833
+ fs.writeFileSync(xlsxPath, buf);
834
+ logger.success(`XLSX saved to ${path.relative(targetPath, xlsxPath)}`);
835
+ }
836
+ }
837
+ break;
838
+ }
839
+ case 'dashboard': {
840
+ const targetPath = resolveRepoPath(positionals[1]);
841
+ const { analyzeDashboard } = await Promise.resolve().then(() => __importStar(require('./analyzers/dashboard')));
842
+ logger.header('vyuh-dxkit dashboard');
843
+ const reportsDir = values['reports-dir']
844
+ ? path.resolve(values['reports-dir'])
845
+ : path.join(targetPath, '.dxkit', 'reports');
846
+ const jsonDir = values['json-dir'] ? path.resolve(values['json-dir']) : undefined;
847
+ const projectName = values['project-name'] ?? undefined;
848
+ const outputPath = values.output
849
+ ? path.resolve(values.output)
850
+ : path.join(reportsDir, 'dashboard.html');
851
+ let result;
852
+ try {
853
+ result = analyzeDashboard(targetPath, { reportsDir, jsonDir, projectName });
854
+ }
855
+ catch (err) {
856
+ const msg = err instanceof Error ? err.message : String(err);
857
+ logger.fail(msg);
858
+ process.exit(1);
859
+ }
860
+ if (result.reportCount === 0) {
861
+ logger.fail(`No report markdowns found in ${path.relative(targetPath, reportsDir) || reportsDir}.\n` +
862
+ `Run 'vyuh-dxkit health .' (or any other report command) first to populate the directory.`);
863
+ process.exit(1);
864
+ }
865
+ if (values['no-save']) {
866
+ // Drain-aware HTML emission to stdout. Mirrors emitJson() for
867
+ // payloads that can exceed the 64KB pipe buffer (a dashboard
868
+ // with all reports embedded routinely runs 300-500KB).
869
+ if (!process.stdout.write(result.html)) {
870
+ await new Promise((resolve) => process.stdout.once('drain', resolve));
871
+ }
872
+ }
873
+ else {
874
+ fs.mkdirSync(path.dirname(outputPath), { recursive: true });
875
+ fs.writeFileSync(outputPath, result.html);
876
+ logger.success(`Dashboard written to ${path.relative(targetPath, outputPath) || outputPath}`);
877
+ logger.dim(`${result.reportCount} reports · ${result.summary.healthScore !== null ? `health ${result.summary.healthScore}/100` : 'no health data'} · ` +
878
+ `${result.summary.vulnCount} vulns · ${result.summary.gapCount} test gaps · ` +
879
+ `${result.summary.advisoryCount} BoM advisories · ${result.criticalIssueCount} critical-issue tiles`);
880
+ }
881
+ break;
882
+ }
883
+ case 'coverage': {
884
+ const targetPath = resolveRepoPath(positionals[1]);
885
+ const { runCoverageAcrossPacks } = await Promise.resolve().then(() => __importStar(require('./analyzers/coverage-runner')));
886
+ const { detectActiveLanguages } = await Promise.resolve().then(() => __importStar(require('./languages')));
887
+ logger.header('vyuh-dxkit coverage');
888
+ const active = detectActiveLanguages(targetPath);
889
+ const langFilter = values.lang;
890
+ const failFast = !values['no-fail-fast'];
891
+ const candidates = active.filter((p) => !langFilter || p.id === langFilter);
892
+ if (candidates.length === 0) {
893
+ logger.fail(langFilter
894
+ ? `No active language pack matches --lang ${langFilter}. Active packs: ${active.map((p) => p.id).join(', ') || '(none)'}`
895
+ : `No active language packs detected in ${targetPath}. Nothing to run.`);
896
+ process.exit(2);
897
+ }
898
+ logger.info(`Stack: ${candidates.map((p) => p.id).join(', ')}`);
899
+ console.log(''); // slop-ok
900
+ const { rows } = await runCoverageAcrossPacks(targetPath, {
901
+ langFilter,
902
+ failFast,
903
+ onPackStart: (id) => process.stderr.write(` → ${id}: running tests with coverage...\n`),
904
+ });
905
+ // Render summary table via the same drain-aware stdout primitive
906
+ // emitJson uses — wide table rows would otherwise trip the
907
+ // no-bare-console-statements slop gate.
908
+ const writeRow = (s) => {
909
+ process.stdout.write(s + '\n');
910
+ };
911
+ writeRow('');
912
+ writeRow(` ${logger.bold('Pack'.padEnd(12))} ${logger.bold('Status'.padEnd(12))} ${logger.bold('Duration'.padEnd(10))} ${logger.bold('Artifact')}`);
913
+ writeRow(` ${'─'.repeat(12)} ${'─'.repeat(12)} ${'─'.repeat(10)} ${'─'.repeat(40)}`);
914
+ for (const r of rows) {
915
+ const icon = r.status === 'success'
916
+ ? '\x1b[32m✓\x1b[0m'
917
+ : r.status === 'unavailable' || r.status === 'skipped'
918
+ ? '\x1b[2m·\x1b[0m'
919
+ : '\x1b[31m✗\x1b[0m';
920
+ const duration = r.durationMs > 0 ? `${(r.durationMs / 1000).toFixed(1)}s`.padStart(10) : '—'.padStart(10);
921
+ const right = r.artifact ?? r.reason ?? '';
922
+ writeRow(` ${icon} ${r.pack.padEnd(10)} ${r.status.padEnd(12)} ${duration} ${right}`);
923
+ }
924
+ const successes = rows.filter((r) => r.status === 'success').length;
925
+ const failures = rows.filter((r) => r.status === 'failed').length;
926
+ const unavailable = rows.filter((r) => r.status === 'unavailable' || r.status === 'skipped').length;
927
+ console.log(''); // slop-ok
928
+ if (failures > 0) {
929
+ logger.fail(`${successes}/${rows.length} packs produced coverage. ${failures} failed.`);
930
+ process.exit(1);
931
+ }
932
+ else if (successes === 0) {
933
+ logger.fail(`0/${rows.length} packs produced coverage (${unavailable} unavailable / skipped).`);
934
+ process.exit(2);
935
+ }
936
+ else {
937
+ logger.success(`${successes}/${rows.length} packs produced coverage. ` +
938
+ `Run \`vyuh-dxkit health\` or \`vyuh-dxkit test-gaps\` to consume.`);
939
+ }
940
+ break;
941
+ }
942
+ case 'report': {
943
+ // D021 (2.4.7 sub-piece 3): single orchestrator that runs every
944
+ // analyzer in sequence and produces a fully-populated dashboard.
945
+ // Child-process model rather than direct function calls: each
946
+ // analyzer command already owns its file-write flow (D032 made
947
+ // the detailed JSON + MD unconditional), so spawning preserves
948
+ // every side effect without duplicating code. The ~7 extra Node
949
+ // startups add ~10-15s on top of 5-10 minutes of real analysis —
950
+ // acceptable for a "press one button, get a complete audit"
951
+ // command. Direct function refactoring is recipe-v4 candidate
952
+ // territory (would touch every analyzer's CLI wiring).
953
+ const targetPath = resolveRepoPath(positionals[1]);
954
+ const { spawnSync } = await Promise.resolve().then(() => __importStar(require('child_process')));
955
+ logger.header('vyuh-dxkit report');
956
+ logger.info(`Generating full audit for ${targetPath}...`);
957
+ console.log(''); // slop-ok
958
+ // Which analyzers run, in dependency order. `health` runs first
959
+ // so its detailed JSON exists when later commands or the
960
+ // dashboard look for it; `dashboard` runs last so every report
961
+ // it embeds is fresh.
962
+ const analyzerSteps = [
963
+ { label: 'Health', cmd: 'health', reportPrefix: 'health-audit' },
964
+ { label: 'Vulnerabilities', cmd: 'vulnerabilities', reportPrefix: 'vulnerability-scan' },
965
+ { label: 'Test gaps', cmd: 'test-gaps', reportPrefix: 'test-gaps' },
966
+ { label: 'Code quality', cmd: 'quality', reportPrefix: 'quality-review' },
967
+ { label: 'Developer report', cmd: 'dev-report', reportPrefix: 'developer-report' },
968
+ { label: 'BoM', cmd: 'bom', reportPrefix: 'bom' },
969
+ { label: 'Licenses', cmd: 'licenses', reportPrefix: 'licenses' },
970
+ ];
971
+ // Forward common analyzer flags to each child so the orchestrator
972
+ // honors the same options the user would pass to a single command.
973
+ const passthroughFlags = [];
974
+ if (values.detailed)
975
+ passthroughFlags.push('--detailed');
976
+ if (values.xlsx)
977
+ passthroughFlags.push('--xlsx');
978
+ if (values.verbose)
979
+ passthroughFlags.push('--verbose');
980
+ if (values.since)
981
+ passthroughFlags.push('--since', values.since);
982
+ if (values.filter)
983
+ passthroughFlags.push('--filter', values.filter);
984
+ if (values['no-nested'])
985
+ passthroughFlags.push('--no-nested');
986
+ // --with-coverage handled ONCE upfront via `vyuh-dxkit coverage`;
987
+ // health + test-gaps then read the materialized artifact via
988
+ // `loadCoverage()` without re-running the test suite per command.
989
+ // Pre-fix `report --with-coverage` (had it existed) would have
990
+ // double-run tests for health and again for test-gaps.
991
+ const runStartedAt = Date.now();
992
+ const stepDurations = [];
993
+ if (values['with-coverage']) {
994
+ logger.info('[setup] Materializing coverage artifacts (one run, shared)...');
995
+ const t0 = Date.now();
996
+ const rc = spawnSync(process.execPath, [
997
+ process.argv[1],
998
+ 'coverage',
999
+ targetPath,
1000
+ ...(values['no-fail-fast'] ? ['--no-fail-fast'] : []),
1001
+ ], { stdio: 'inherit' }).status;
1002
+ stepDurations.push({ label: 'Coverage', ms: Date.now() - t0, rc: rc ?? -1 });
1003
+ console.log(''); // slop-ok
1004
+ }
1005
+ const reportDir = path.join(targetPath, '.dxkit', 'reports');
1006
+ const dateStr = new Date().toISOString().slice(0, 10);
1007
+ for (const step of analyzerSteps) {
1008
+ logger.info(`[${stepDurations.length + 1}/${analyzerSteps.length + 1}] ${step.label}...`);
1009
+ const t0 = Date.now();
1010
+ const rc = spawnSync(process.execPath, [process.argv[1], step.cmd, targetPath, ...passthroughFlags, ...(step.extraFlags ?? [])], { stdio: 'inherit' }).status;
1011
+ let effectiveRc = rc ?? -1;
1012
+ // Post-step assertion: the child returned rc=0 BUT did the
1013
+ // expected markdown actually land on disk? On heavy polyglot
1014
+ // repos (web-client; 13K+ graphify nodes, jscpd timeout
1015
+ // exhaustion) the health child was observed to silently exit
1016
+ // 0 without writing its markdown — the dashboard then renders
1017
+ // "no <X> data" and the customer never learns their report
1018
+ // is missing. The orchestrator owns the "did the report
1019
+ // actually ship" assertion; analyzer subcommands keep their
1020
+ // own write logic unchanged.
1021
+ if (effectiveRc === 0) {
1022
+ const expectedReport = path.join(reportDir, `${step.reportPrefix}-${dateStr}.md`);
1023
+ if (!fs.existsSync(expectedReport)) {
1024
+ logger.warn(`${step.label} returned exit 0 but did NOT write ${path.relative(targetPath, expectedReport)}. ` +
1025
+ `Treating as failure so the final summary surfaces it.`);
1026
+ effectiveRc = -1;
727
1027
  }
728
1028
  }
1029
+ stepDurations.push({ label: step.label, ms: Date.now() - t0, rc: effectiveRc });
1030
+ console.log(''); // slop-ok
1031
+ }
1032
+ logger.info(`[${stepDurations.length + 1}/${analyzerSteps.length + 1}] Dashboard...`);
1033
+ const dashT0 = Date.now();
1034
+ const dashRc = spawnSync(process.execPath, [process.argv[1], 'dashboard', targetPath], {
1035
+ stdio: 'inherit',
1036
+ }).status;
1037
+ stepDurations.push({ label: 'Dashboard', ms: Date.now() - dashT0, rc: dashRc ?? -1 });
1038
+ // Final summary. Always emit it so the user sees the dashboard
1039
+ // location without scrolling through per-step output.
1040
+ const totalElapsed = ((Date.now() - runStartedAt) / 1000).toFixed(1);
1041
+ const failed = stepDurations.filter((s) => s.rc !== 0);
1042
+ console.log(''); // slop-ok
1043
+ logger.dim('─'.repeat(60));
1044
+ for (const s of stepDurations) {
1045
+ const status = s.rc === 0 ? '\x1b[32m✓\x1b[0m' : '\x1b[31m✗\x1b[0m';
1046
+ const duration = `${(s.ms / 1000).toFixed(1)}s`.padStart(8);
1047
+ process.stdout.write(` ${status} ${s.label.padEnd(20)} ${duration}\n`);
1048
+ }
1049
+ logger.dim('─'.repeat(60));
1050
+ console.log(''); // slop-ok
1051
+ if (failed.length === 0) {
1052
+ logger.success(`All ${stepDurations.length} steps completed in ${totalElapsed}s. ` +
1053
+ `Open .dxkit/reports/dashboard.html for the full picture.`);
1054
+ }
1055
+ else {
1056
+ logger.warn(`${stepDurations.length - failed.length}/${stepDurations.length} steps completed (${failed.length} failed: ${failed.map((s) => s.label).join(', ')}). ` +
1057
+ `Partial dashboard at .dxkit/reports/dashboard.html.`);
1058
+ process.exit(1);
729
1059
  }
730
1060
  break;
731
1061
  }
@@ -781,7 +1111,7 @@ function formatMarkdownReport(report, elapsed) {
781
1111
  lines.push('');
782
1112
  lines.push('---');
783
1113
  lines.push('');
784
- lines.push(`## Overall Health Score: ${report.summary.overallScore}/100 (Grade: ${report.summary.grade})`);
1114
+ lines.push(`## Overall Health Score: ${report.summary.overallScore}/100 (Rating: ${report.summary.rating})`);
785
1115
  lines.push('');
786
1116
  lines.push('| Dimension | Score | Status |');
787
1117
  lines.push('|---|---|---|');
@@ -795,7 +1125,7 @@ function formatMarkdownReport(report, elapsed) {
795
1125
  };
796
1126
  for (const [key, dim] of Object.entries(report.dimensions)) {
797
1127
  const name = dimNames[key] || key;
798
- lines.push(`| ${name} | ${dim.score}/100 | ${dim.status.charAt(0).toUpperCase() + dim.status.slice(1)} |`);
1128
+ lines.push(`| ${name} | ${dim.score}/100 | ${dim.rating} |`);
799
1129
  }
800
1130
  lines.push('');
801
1131
  lines.push('---');
@@ -803,10 +1133,13 @@ function formatMarkdownReport(report, elapsed) {
803
1133
  // Dimension details
804
1134
  for (const [key, dim] of Object.entries(report.dimensions)) {
805
1135
  const name = dimNames[key] || key;
806
- lines.push(`## ${name} (${dim.score}/100) -- ${dim.status.charAt(0).toUpperCase() + dim.status.slice(1)}`);
1136
+ lines.push(`## ${name} (${dim.score}/100) -- ${dim.rating}`);
807
1137
  lines.push('');
808
1138
  lines.push(dim.details);
809
1139
  lines.push('');
1140
+ const topActions = (0, scoring_1.formatTopActionsBlock)(dim);
1141
+ for (const line of topActions)
1142
+ lines.push(line);
810
1143
  lines.push('| Metric | Value |');
811
1144
  lines.push('|---|---|');
812
1145
  for (const [mk, mv] of Object.entries(dim.metrics)) {
@@ -818,6 +1151,37 @@ function formatMarkdownReport(report, elapsed) {
818
1151
  lines.push('---');
819
1152
  lines.push('');
820
1153
  }
1154
+ // 2.4.7: top-N largest files. Surfaces the file-size distribution
1155
+ // beyond the single "largest" callout in the Code Quality /
1156
+ // Maintainability dimensions. Skipped when the array is empty
1157
+ // (no source files counted or autogen excluded everything).
1158
+ if (report.largestFiles && report.largestFiles.length > 0) {
1159
+ lines.push('## Top Files by Size');
1160
+ lines.push('');
1161
+ lines.push('| Rank | File | Lines |');
1162
+ lines.push('|-----:|------|------:|');
1163
+ report.largestFiles.forEach((f, i) => {
1164
+ lines.push(`| ${i + 1} | \`${f.path}\` | ${f.lines.toLocaleString()} |`);
1165
+ });
1166
+ lines.push('');
1167
+ // Advisory: when largest-files contain paths matching a known
1168
+ // vendored-code convention not already in the customer's
1169
+ // exclusion chain, surface a single tip pointing at the
1170
+ // `.dxkit-ignore` escape hatch. Bundled defaults already cover
1171
+ // `vendor/`, `third_party/`, `playground/`, `lexical-playground/`,
1172
+ // etc.; the remaining cases (most commonly `/libs/`) live in
1173
+ // customer-specific paths that can't be defaulted-away without
1174
+ // false-positives on first-party monorepo layouts.
1175
+ const suspects = (0, vendored_advisor_1.suspectVendoredEntries)(report.largestFiles);
1176
+ if (suspects.length > 0) {
1177
+ lines.push(`> **Tip — possibly vendored:** ${suspects
1178
+ .map((s) => `\`${s.path}\``)
1179
+ .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.`);
1180
+ lines.push('');
1181
+ }
1182
+ lines.push('---');
1183
+ lines.push('');
1184
+ }
821
1185
  // Score calculation table
822
1186
  lines.push('## Score Calculation');
823
1187
  lines.push('');
@@ -841,14 +1205,18 @@ function formatMarkdownReport(report, elapsed) {
841
1205
  // Footer
842
1206
  lines.push('---');
843
1207
  lines.push('');
844
- if (report.languages.length > 0) {
845
- lines.push('**Languages:** ' + report.languages.map((l) => `${l.name} (${l.percentage}%)`).join(', '));
1208
+ // Drop languages that round to 0% — a single .py file alongside a
1209
+ // 300K-LOC C# codebase shouldn't surface as "Python (0%)" in the
1210
+ // header. Filter at the renderer rather than the detector so the
1211
+ // raw HealthReport.languages still carries everything for
1212
+ // programmatic consumers.
1213
+ const visibleLanguages = report.languages.filter((l) => l.percentage >= 1);
1214
+ if (visibleLanguages.length > 0) {
1215
+ lines.push('**Languages:** ' + visibleLanguages.map((l) => `${l.name} (${l.percentage}%)`).join(', '));
846
1216
  lines.push('');
847
1217
  }
848
1218
  lines.push(`**Tools used:** ${report.toolsUsed.join(', ')}`);
849
- if (report.toolsUnavailable.length > 0) {
850
- lines.push(`**Tools unavailable:** ${report.toolsUnavailable.join(', ')}`);
851
- }
1219
+ lines.push(...(0, tools_unavailable_prose_1.renderToolsUnavailableLines)(report.toolsUnavailable));
852
1220
  lines.push(`**Analysis time:** ${elapsed}s`);
853
1221
  lines.push('');
854
1222
  lines.push('*Generated by [VyuhLabs DXKit](https://www.npmjs.com/package/@vyuhlabs/dxkit)*');