@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/CHANGELOG.md CHANGED
@@ -7,6 +7,891 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [2.4.7] - 2026-05-17
11
+
12
+ ### Summary
13
+
14
+ 2.4.7 is the largest release since the language-pack architecture
15
+ landed. It bundles three distinct architectural deliverables
16
+ (actionable scoring foundation, per-stack architectural shape,
17
+ canonical security aggregator), customer-visible UX rework
18
+ (security top-5 actions, .env-in-git callout, lint-skip prose
19
+ honesty, tools-unavailable renderer split), one ship-blocker
20
+ root-cause fix (silent health failure under concurrent subprocess
21
+ load), and the project's OSS hygiene baseline. 17 defect IDs
22
+ closed in this version. Scoring methodology now anchored to
23
+ ISO/IEC 25010, ISO/IEC 5055, SQALE, CVSS v4, CWE, OWASP, and
24
+ OpenSSF Scorecard. Tests: 1241 / 0 (up from 1175 at the 2.4.6
25
+ baseline). No runtime regressions across the cross-ecosystem
26
+ matrix.
27
+
28
+ Customer-visible numeric impact: scores on some repos will shift
29
+ between 2.4.6 and 2.4.7 because the underlying methodology
30
+ changed (see the "Actionable scoring foundation" section below
31
+ and its "Customer-visible score changes" subsection), not because
32
+ of bugs. Migration notes at
33
+ [`docs/MIGRATING-TO-2.4.7-SCORING.md`](docs/MIGRATING-TO-2.4.7-SCORING.md).
34
+
35
+ ### Phase C11 — OSS hygiene baseline (2026-05-17)
36
+
37
+ Adds the standard set of OSS community files so the project
38
+ satisfies the OpenSSF Scorecard `Security-Policy`,
39
+ `Code-of-Conduct`, and `Contributors` checks and gives external
40
+ contributors a clear on-ramp.
41
+
42
+ - `SECURITY.md` — supported-versions table, response SLAs, explicit
43
+ scope, and a pointer to GitHub's [private vulnerability
44
+ reporting](https://github.com/vyuh-labs/dxkit/security/advisories/new)
45
+ (no public email; routes directly to maintainers).
46
+ - `CODE_OF_CONDUCT.md` — adopts the [Contributor Covenant
47
+ 2.1](https://www.contributor-covenant.org/version/2/1/code_of_conduct/)
48
+ by canonical URL reference. Reports route through the same
49
+ private channel as security disclosures.
50
+ - `.github/PULL_REQUEST_TEMPLATE.md` — summary + motivation +
51
+ verification checklist + an architectural-rules pointer section
52
+ nudging contributors at the relevant CLAUDE.md rules before
53
+ touching scoring / language packs / exclusions / tool invocation.
54
+ - `.github/ISSUE_TEMPLATE/bug.yml` — issue form: repro steps,
55
+ versions (dxkit + Node), OS, repo stack, logs. Confirmations
56
+ block routes security reports to private disclosure.
57
+ - `.github/ISSUE_TEMPLATE/feature.yml` — issue form: problem
58
+ framing, proposal, alternatives considered, scope dropdown.
59
+ - `.github/ISSUE_TEMPLATE/question.yml` — light triage form that
60
+ redirects bug / feature / security reports to the right channel
61
+ and surfaces existing docs (README, SCORING.md, CLAUDE.md).
62
+ - `docs/ARCHITECTURE.md` — short tour of the analyzer data flow,
63
+ the three core patterns (language packs, scoring specs,
64
+ centralized exclusions + tool registry), the `runDetached`
65
+ subprocess discipline, the `AnalysisResult` cache, and the
66
+ release flow. Entry-point doc; defers to CLAUDE.md as the
67
+ authoritative rule set.
68
+
69
+ No runtime code changes. Commits: `93a1790`.
70
+
71
+ ### Phase C10.25 — Audit-residue closures + silent-failure root-cause (2026-05-17)
72
+
73
+ The earlier phases of 2.4.7 brought enough new code into the
74
+ analyzer that a pre-ship convergence audit on the three external
75
+ customer repos surfaced one HIGH-severity defect — the report
76
+ orchestrator's health step intermittently exiting `rc=0` with no
77
+ `health-audit-*.md` written on the heaviest polyglot repo — plus a
78
+ batch of MEDIUM residue items. This phase closes all of them.
79
+ Pairs with the per-stack architectural-shape work below to leave
80
+ 2.4.7 with zero outstanding ship blockers.
81
+
82
+ #### Silent health-failure root-cause (D134)
83
+
84
+ The report orchestrator's health step on a heavy polyglot repo
85
+ (13k+ graphify function nodes, ~700 source files, large
86
+ `node_modules`) intermittently exited `rc=0` with no
87
+ `health-audit-*.md` written. The dashboard then read "no health
88
+ data" while the orchestrator itself printed `✓ Health`.
89
+ Investigation via a `spawnSync` reproducer plus targeted
90
+ diagnostic instrumentation captured the failure shape:
91
+
92
+ ```
93
+ [beforeExit] code=0 reachedWrite=false writeComplete=false
94
+ [exit] code=0 reachedWrite=false writeComplete=false
95
+ ```
96
+
97
+ No `uncaughtException`, no `unhandledRejection` — classic
98
+ abandoned-Promise. Under concurrent subprocess load (semgrep +
99
+ jscpd + graphify all spawning grandchildren), one `runDetached`
100
+ invocation's `exit` and `error` events both failed to fire. The
101
+ Promise stayed permanently pending, the capabilities `Promise.all`
102
+ hung, `analyzeHealthInternal`'s `await` never returned, Node's
103
+ event loop emptied and the process exited cleanly with the main
104
+ task still suspended.
105
+
106
+ Fix in `src/analyzers/tools/runner.ts` (commit `55ce0d6`):
107
+
108
+ - **Single-resolve `settle()` guard** — `exit` / `error` /
109
+ safety-deadline, first wins; subsequent events are no-ops.
110
+ - **Error listener registered BEFORE other setup** to close the
111
+ spawn-time-emission race window.
112
+ - **Safety deadline at `timeoutMs + 30_000`** — the Promise
113
+ mathematically must settle within that window even if every
114
+ event source fails.
115
+
116
+ Verification on the failure repo:
117
+
118
+ - Pre-fix: 795-800 s, `rc=0`, **no** health markdown on disk.
119
+ - Post-fix: 662.8 s, `rc=0`, full health markdown on disk.
120
+
121
+ Defense-in-depth (commit `5b6e360`): the `report` orchestrator
122
+ now asserts each step wrote its expected markdown post-step. A
123
+ future regression that re-introduces the hang surfaces a per-step
124
+ `✗` instead of a silent `✓`.
125
+
126
+ #### jscpd OOM class-fix — centralized exclusions plumbed into `--ignore` (D139)
127
+
128
+ jscpd was invoked with `--gitignore` + the autogen-pattern list but
129
+ NOT dxkit's bundled `default-exclusions.gitignore` / `.dxkit-ignore`
130
+ union — the same exclusion set every in-process walker (cloc, grep,
131
+ semgrep, graphify's Python filter) honors. Repos committing vendored
132
+ bundles outside `.gitignore` (e.g. minified library copies under a
133
+ `public/` tree) led jscpd to descend in, tokenize multi-thousand-line
134
+ minified bundles, exhaust heap, and OOM-kill before flushing its
135
+ JSON report. The quality report would then read
136
+ "Duplication unavailable" on the densest repos — exactly the repos
137
+ where the metric mattered most.
138
+
139
+ Fix (commit `2afc097`):
140
+
141
+ - New `getJscpdIgnorePatterns(cwd)` helper in
142
+ `src/analyzers/tools/exclusions.ts` returns the centralized
143
+ exclusion set as `**/<pattern>`-style globs.
144
+ - `gatherJscpdResult` unions it with the autogen patterns and
145
+ passes the union to jscpd's `--ignore`.
146
+ - `jscpdProvider` gains a `gatherOutcome` method so the
147
+ dispatcher captures jscpd's actual failure reason
148
+ ("not installed" / "timed out at 600s" / "exit code N
149
+ (stderr: ...)" / "no output" / "parse error") instead of
150
+ dropping it at the gather / `null` boundary.
151
+
152
+ CLAUDE.md Rule 4 ("Exclusions come from `exclusions.ts`") was
153
+ honored at the in-process walker layer but not at the
154
+ subprocess-tool argument-builder layer. This closes that drift
155
+ for jscpd and lays the pattern for any future subprocess tool
156
+ that walks the repo.
157
+
158
+ Verification on the worst-case repo:
159
+
160
+ - **Standalone smoke**: 569 s OOM → 17 s success, **7.26 %**
161
+ duplication, 444 clones, 7 423 duplicated lines.
162
+ - **End-to-end via `vyuh-dxkit quality`**: capabilities-gather
163
+ 770 s → 272 s (jscpd no longer the long pole; eslint also
164
+ surfaces its real findings as a side-effect, contributing
165
+ 10 496 errors + 2 787 warnings that were previously masked).
166
+
167
+ #### Tools-unavailable renderer prose-honesty (D138)
168
+
169
+ The dispatcher's `skipReasons` channel already carried the real
170
+ per-source failure reason for every attempted-but-failed tool, but
171
+ `availabilityFromOutcome` in `src/analyzers/health.ts` collapsed
172
+ every case to a generic "attempted but produced no output (likely
173
+ killed by resource limits — try running dxkit on this repo alone)"
174
+ prose. The renderer then printed `**Tools unavailable:** jscpd
175
+ (...)` — a reader reasonably concluded the binary needed
176
+ installing, when in fact the binary was fine and the run had
177
+ OOM'd at runtime. Same misleading-label class as D113 / D128
178
+ (lint-skip prose) and D135 (cache-level availability envelope) —
179
+ this fix extends the honesty pattern one layer up to the renderer
180
+ header label.
181
+
182
+ Fix (commit `425d0ef`):
183
+
184
+ - `semgrepProvider` + `graphifyProvider` gain `gatherOutcome`
185
+ (jscpdProvider's method came with the companion D139 commit).
186
+ The dispatcher now captures the real per-source reason into
187
+ `DispatchOutcome.skipReasons`.
188
+ - `availabilityFromOutcome` prefers `skipReasons[<source>]` when
189
+ present; falls back to the generic prose for legacy providers
190
+ without `gatherOutcome`.
191
+ - New `splitToolsUnavailable` / `renderToolsUnavailableLines`
192
+ helpers in `src/analyzers/tools/tools-unavailable-prose.ts`
193
+ route entries into two honest categories:
194
+ - `**Tools not installed:**` — action: install
195
+ - `**Tools that failed at runtime:**` — action: investigate
196
+ - 9 markdown renderer call-sites (`cli.ts`, tests / security /
197
+ quality / health / bom analyzer surfaces, each with their
198
+ `index.ts` and `detailed.ts` formatter pair) + the xlsx BoM
199
+ (two worksheet rows) all share the canonical helper.
200
+
201
+ #### Other audit-residue closures
202
+
203
+ | ID(s) | Description | Closing commit |
204
+ | -------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------- |
205
+ | D124 / D100 / D118 | Vendored-source exclusion class-fix — top-largest-file metric on all 3 customer repos now first-party (or correctly flagged by the per-file advisor). Generic `largest_files` walk routes through canonical exclusions. | `72ec70a` |
206
+ | D113 / D128 | Per-pack lint-skip reasons plumbed end-to-end. Tools row reads `ruff (not run: typescript — config error)` instead of dropping the skip silently. | `b878553` |
207
+ | D118-residue | Graphify enumeration honors file-glob + content-minified exclusions. Webpack-hash bundles no longer rank as the densest file on customer reports (web-client: 4 606 fn artifact → 228 fn real first-party densest). | `0da08bd` |
208
+ | D135 / D136 (interim) | Vendored-advisor token list extended for SAP B1 OData proxy classes, map-library, proto-gen conventions. Customers with heavy-autogen .NET ERP integrations now see actionable `.dxkit-ignore` guidance. | `d9f0c31` |
209
+
210
+ Tests at this phase close: 1241 / 0 (+15 new unit tests for
211
+ `getJscpdIgnorePatterns`, `splitToolsUnavailable`,
212
+ `renderToolsUnavailableLines`). Architecture gate clean.
213
+
214
+ ### Phase C8 — Per-stack architectural shape (2026-05-17)
215
+
216
+ Before this phase, the analyzers carried hardcoded Node-backend-
217
+ centric path patterns (`'/controllers/'`, `'/services/'`,
218
+ `'/models/'`) and a closed `SourceFile.type` union (`'controller'
219
+ | 'service' | 'model' | ...`). A pure React frontend or a .NET
220
+ WinForms desktop app matched none of the defaults and reported
221
+ `0/0/0` across test-gap CRITICAL / HIGH / MEDIUM buckets — the
222
+ kind of metric that reads as a bug to a frontend or desktop
223
+ developer scanning the report.
224
+
225
+ This phase replaces the hardcoded vocabulary with a per-pack
226
+ `architecturalShape` capability on `LanguageSupport`. Each pack
227
+ declares its own primary-component paths, route-handler paths,
228
+ data-model paths, prose vocabulary, and per-bucket test-gap
229
+ priority taxonomy. Cross-cutting analyzer code unions across
230
+ active packs at runtime — so a polyglot repo's metrics correctly
231
+ span TypeScript's `/controllers/` + `/components/` alongside
232
+ C#'s `Forms/`, and adding a new language pack auto-extends every
233
+ consumer.
234
+
235
+ #### What landed
236
+
237
+ - New `architecturalShape?: ArchitecturalShape` field on
238
+ `LanguageSupport` (commit `9a6c48d`).
239
+ - 7 packs contribute concrete shapes (commit `c313744`). E.g.
240
+ the csharp pack declares `Forms/`, `ViewModels/`, `Services/`;
241
+ the typescript pack declares `/controllers/`, `/services/`,
242
+ `/models/`, `/components/`, `/hooks/`; the python pack
243
+ declares `/views/`, `/viewsets/`, `/models/`, `/serializers/`.
244
+ Two packs (rust, go) intentionally omit `architecturalShape`
245
+ — they don't have a canonical convention strong enough to
246
+ declare without overfitting.
247
+ - Five consumer migrations onto the new helpers (commit
248
+ `6ab2712`): `analyzers/tools/generic.ts` (largest-file +
249
+ source-walk classification), `analyzers/maintainability/shallow.ts`
250
+ (vocabulary prose), `analyzers/security/actions.ts`
251
+ (route-handler attribution), `analyzers/tests/index.ts`
252
+ (test-gap priority taxonomy), `analyzers/health.ts`
253
+ (route-handler files count).
254
+ - Cross-cutting registry helpers in `src/languages/index.ts`:
255
+ `allPrimaryComponentPaths(flags)`, `allRoutePaths(flags)`,
256
+ `allModelPaths(flags)`, `allTestGapPriorityPaths(flags)`,
257
+ `dominantVocabulary(flags)` — every consumer reads from the
258
+ active-pack union.
259
+ - `dominantVocabulary` weighted by cloc line count (commit
260
+ `7147f3f`) so a polyglot repo's vocabulary prose matches the
261
+ dominant stack. A 106k-line-TS / 1.2k-line-Python monorepo
262
+ correctly renders as "controllers + components", not
263
+ "views + viewsets".
264
+ - Two new arch-gate rules (commit `c4f9c20`) in
265
+ `scripts/check-architecture.sh`:
266
+ - No quoted path-style framework literals (`'/controllers/'`,
267
+ `'/services/'`, etc.) inside `src/analyzers/` — they belong
268
+ in `LanguageSupport.architecturalShape`.
269
+ - No bare singular role-name string literals (`'controller'`,
270
+ `'service'`, `'handler'`, `'interceptor'`, `'repository'`,
271
+ `'viewmodel'`, `'viewset'`, `'router'`) — the pre-extension
272
+ closed enum is replaced by free string labels derived from
273
+ `patternToLabel(matched architectural-shape pattern)`.
274
+ - Synthetic 6th-pack injection assertion in
275
+ `test/recipe-playbook.test.ts` — confirms an
276
+ `architecturalShape` contribution from a brand-new pack flows
277
+ through test-gap taxonomy and Maintainability prose without
278
+ cross-cutting edits.
279
+ - CLAUDE.md gains Rule 8 documenting the new architecture.
280
+
281
+ #### Customer-visible effects (verified post-fix)
282
+
283
+ - **platform** (TS / Node backend): test-gap MEDIUM 207;
284
+ Maintainability prose reads "controllers / components"
285
+ (typescript wins cloc weight on a 106k-line monorepo).
286
+ - **web-client** (React frontend): test-gap MEDIUM 499 → 379
287
+ (-120) because the vendored-exclusion class-fix in the
288
+ audit-residue phase above also excludes lexical-playground
289
+ subtrees from primary-component matching. Honest count.
290
+ - **dpl-studio** (.NET WinForms): Maintainability vocabulary
291
+ reads "Forms / Services"; test-gap classification correctly
292
+ picks up the WinForms project structure.
293
+
294
+ #### Defects closed
295
+
296
+ | ID | Description | Closing commit(s) |
297
+ | ---- | ------------------------------------------------------------------------------------------ | -------------------- |
298
+ | D119 | Test-gap priority taxonomy backend-centric (HIGH; misleading on non-Node-backend stacks) | this phase, above |
299
+ | D101 | React / csharp Maintainability vocabulary | this phase, above |
300
+ | D065 | API-docs gate on `routeHandlerFiles` | this phase, above |
301
+
302
+ ### Phase C7 — Actionable scoring foundation (2026-05-17)
303
+
304
+ Reframes dxkit's six-dimension scoring from descriptive ("Code
305
+ Quality: 75/100, Good") to actionable ("Code Quality: 75/100, B —
306
+ top action: fix 11 lint errors for +10, would lift rating to A").
307
+ The numeric scores stay on the same 0-100 scale; every dimension
308
+ now also produces structured provenance that tells the customer
309
+ what to fix and how much the score would lift.
310
+
311
+ dxkit's scoring is now **deterministic** (same repo + same dxkit
312
+ version → identical score, every machine), **anchored** (cites
313
+ underlying open standards: ISO/IEC 25010, ISO/IEC 5055, SQALE
314
+ method, CVSS v4, CWE, OWASP, OpenSSF Scorecard), and **actionable**
315
+ (every score paired with structured `deductions`, `capsApplied`,
316
+ `topActions`).
317
+
318
+ See [`docs/SCORING.md`](docs/SCORING.md) for the full methodology
319
+ and [`docs/MIGRATING-TO-2.4.7-SCORING.md`](docs/MIGRATING-TO-2.4.7-SCORING.md)
320
+ for JSON-consumer migration.
321
+
322
+ #### Architecture
323
+
324
+ - **Single home for dimension scoring** at `src/scoring/`,
325
+ mirroring the per-language pattern from CLAUDE.md Rule 6. Each
326
+ of the six dimensions (Security, Code Quality, Tests,
327
+ Documentation, Maintainability, Developer Experience) declares
328
+ a `DimensionScoringSpec<T>` artifact under
329
+ `src/scoring/dimensions/<id>.ts` consumed by a shared
330
+ pure-function evaluator. Adding a new dimension is a recipe
331
+ documented in CONTRIBUTING.md.
332
+ - **Cap-tier taxonomy** named by severity: `trust-broken` (40,
333
+ catastrophic), `unmeasured` (35, no signal), `uncertainty` (65,
334
+ key tool missing), `partial-uncertainty` (75, partial gap),
335
+ `fixable-finding` (79, concrete bounded finding open). Caps
336
+ enforce the Label Contract: "A" means "no known blockers."
337
+ - **Zero scoring code remains in `src/analyzers/`**. Files deleted
338
+ in full: `src/analyzers/scoring.ts`, `src/analyzers/security/scoring.ts`,
339
+ `src/analyzers/quality/scoring.ts`. CLAUDE.md gains Rule 7
340
+ documenting the new architecture; three new arch-gate rules in
341
+ `scripts/check-architecture.sh` prevent regression.
342
+ - **Scoring playbook test** (`test/scoring-playbook.test.ts`)
343
+ injects a synthetic 7th-dimension spec to confirm the registry
344
+ + evaluator + format helpers stay spec-driven.
345
+
346
+ #### Customer-visible score changes
347
+
348
+ - **D131 closure — Security HIGH+ open caps at 79 (B)**. Pre-2.4.7
349
+ a single open HIGH-severity code finding (e.g. a TLS-validation
350
+ bypass) left the Security dimension at 95/100 ("Excellent") —
351
+ the headline contradicted the unfixed finding. Now repos with at
352
+ least one open HIGH or CRITICAL code finding cap at 79 (B grade)
353
+ with the cap explicit in the rendered report:
354
+ `Rating cap: 1 open HIGH+ code finding — bounded at 79/100`.
355
+ Repos with zero open HIGH+ are unaffected.
356
+ - **D129 closure — severe-debt disclosure**. Pre-2.4.7, "Code
357
+ Quality 0/100" rendered identically whether the penalty stack
358
+ totalled -5 (barely below the floor) or -85 (catastrophic). The
359
+ Top Actions block now surfaces the rawPenalty when the score
360
+ floors at 0: `Severe: raw penalty -85 (deductions exceed the
361
+ floor).`
362
+ - **Maintainability — SQALE baseline shift**. Methodology
363
+ migrated to ISO/IEC 25010 + SQALE-inspired step thresholds.
364
+ Baseline shifts from 70 to 100 (matches every other subtractive
365
+ dimension); the small-codebase bonus is removed as overfit.
366
+ Clean repos see Maintainability scores rise by ~30 points.
367
+ Documented behavior change.
368
+ - **Testing — cap-then-penalty ordering**. When `commentedCodeRatio
369
+ > 0.5` AND coverage data missing, the final score is now 35 (cap
370
+ binds as the ceiling). Pre-2.4.7 it was 20 (cap then sub-cap
371
+ subtraction). The new semantic is cleaner — caps are ceilings,
372
+ not floors-and-then-keep-subtracting. Affects a narrow edge case.
373
+ - **No-tests-found surfaces as a Top Action**. Repos with zero
374
+ test files now show a dedicated deduction +60 with severe-debt
375
+ disclosure, pointing at test-gaps for the ranked critical files.
376
+ Pre-2.4.7 these repos had a 0/E Tests score with no actionable
377
+ signal in the dimension's Top Actions block.
378
+ - **`DimensionScore.status` → `rating`**. The descriptive enum
379
+ (`'excellent' | 'good' | 'fair' | 'poor' | 'critical'`) is
380
+ replaced by a uniform letter rating (`'A' | 'B' | 'C' | 'D' |
381
+ 'E'`). The overall summary's `grade` field renames to `rating`
382
+ with the same `F` → `E` enum unification.
383
+ - **Documentation + Developer Experience specs inverted from
384
+ additive to subtractive**. Numeric scores preserved by
385
+ construction; the `deductions[]` list now reads as
386
+ actions-to-take ("README missing") rather than bonuses already
387
+ earned ("README present").
388
+
389
+ #### Renderer + JSON
390
+
391
+ - CLI grid prints a top-action continuation under each dimension
392
+ line: `→ 11 lint errors +10 (B → A)`.
393
+ - Health detailed markdown gains a `Top actions (sorted by score
394
+ uplift)` block per dimension with rating-transition annotations.
395
+ - Dashboard hero now reads `Rating D` instead of `Grade D`.
396
+ - Health-detailed JSON schema bumps `11` → `12` for the new
397
+ provenance fields on `DimensionScore` (`rawScore`,
398
+ `rawPenalty`, `methodology`, `deductions`, `capsApplied`,
399
+ `topActions`). All optional — pre-2.4.7 consumers continue to
400
+ work.
401
+
402
+ Two-phase release. **Phase A** (audit-driven hot-patches, originally
403
+ shipped 2026-05-13) closed a 17-defect cascade surfaced by a critical
404
+ post-shipment audit on dpl-studio (enterprise C# / 1500+ files / 68
405
+ sub-projects / 1.6M lines of cloc-counted JSON), plus the long-deferred
406
+ D021 (coverage workflow). **Phase B** (class-fix release, 2026-05-14)
407
+ pivoted from patch shipping to architectural class-fix shipping after
408
+ pre-ship audits on platform and web-client surfaced 12 NEW defects
409
+ (D074–D085), 9 of which were repeated instances of the same disease
410
+ class fixed at different sites.
411
+
412
+ ### Phase C2 — Security UX rework (2026-05-14)
413
+
414
+ Builds on Phase C1's typed canonical aggregator with the user-facing
415
+ UX changes the 2026-05-14 critical-perspective audit demanded. C1
416
+ closed the architectural drift class; C2 closes the labeling,
417
+ scoring-credibility, and prioritization gaps that remained.
418
+
419
+ - **C2.1 — Vuln-scan section split** (commit `e637911`). The pre-C2
420
+ executive summary had ONE "Code Findings" table that combined
421
+ codeBySeverity + secretsBySeverity under a label that meant
422
+ code-only to health-side prose. Readers (and AI agents) saw
423
+ apparent drift between health "10H code findings" and vuln-scan
424
+ "Code Findings: 16H." Both numbers were correct for their scopes,
425
+ but the labels obscured this. C2.1 splits the executive summary
426
+ into three labeled tables — **Code Findings** (code-pattern only,
427
+ matches health), **Secret & Config Findings** (gitleaks +
428
+ private-key + .env), **Dependency Vulnerabilities** (unchanged).
429
+ `SecurityReport.summary` grows `codeOnly` and `secretsOnly`
430
+ siblings of `findings`; renderer reads them by name.
431
+
432
+ - **C2.2 / D098 — `SECRETS_PRESENT_CAP = 40`** (commit `243fa86`).
433
+ Pre-C2 baseline: web-client scored Security 60/100 "Good" despite
434
+ 4 hardcoded API keys + 1 .env in git. Credentials in source-control
435
+ history are presumed compromised even after rotation, and a "Good"
436
+ score reads as deprioritizable. C2.2 caps the Security dimension at
437
+ ≤ 40 ("Fair" or worse) whenever `secretFindings > 0 ||
438
+ privateKeyFiles > 0 || envFilesInGit > 0`. Applied as a ceiling
439
+ AFTER all per-signal penalties and the dep-availability cap, so
440
+ it composes monotonically with everything else.
441
+ - Validated: web-client 60 → 40 "Fair" (✓ cap fires);
442
+ platform 45 → 40 "Fair" (✓); dpl-studio 90 → 90 (✓ cap
443
+ correctly does NOT fire — no committed credentials).
444
+
445
+ - **C2.3 / D099 — `.env`-in-git callout block** (commit `7f43dfc`).
446
+ Pre-C2 a `.env` finding appeared as a plain HIGH entry in the
447
+ Configuration Issues section with no actionable command. C2.3
448
+ adds a dedicated `## 🚨 .env files tracked in git` block between
449
+ the executive summary and the per-category sections. Contents:
450
+ rotation caveat ("presumed compromised even after deletion"),
451
+ working-tree bash block (`git rm --cached <file>` per file +
452
+ `.gitignore` + commit), and history-rewrite block
453
+ (`git filter-repo` preferred + BFG alternative + "every
454
+ collaborator must re-clone" coordination caveat).
455
+
456
+ - **C2.4 / D105 — Top 5 priority actions** (commit `6fe45fa`).
457
+ Pre-C2 reports listed every finding by severity within category;
458
+ no prioritization surface. A reader scanning the report had to
459
+ skim dozens of medium findings to spot the one KEV-listed dep
460
+ upgrade that actually mattered this week. C2.4 adds a
461
+ `## 🎯 Top 5 Priority Actions` markdown table at the top of the
462
+ findings sections. Priority order codifies the triage rubric:
463
+ KEV deps → hardcoded secrets → .env in git → private-key files →
464
+ non-KEV deps by risk-score tier → HIGH/CRITICAL code findings.
465
+ Capped at 5 — anything below the cap shows up in the per-category
466
+ sections below.
467
+
468
+ - **D108 — Top 5 sparse-tier fallback** (commit `c09ba87`). C2.5
469
+ audit surfaced D108: dpl-studio's Top 5 had only 1 entry despite
470
+ 2 unpatched dep vulns (MongoDB.Driver risk 19 + SharpCompress
471
+ risk 15). The original C2.4 dep filter required `riskScore >= 25`
472
+ which excluded the "watch" tier (10-25 per risk-score.ts), leaving
473
+ the table sparse. Fix: tier-iterate dep risk-score buckets
474
+ (`≥ 50 → 25-50 → 10-25 → ≥ 0`) and stop only when Top 5 is full.
475
+ Findings without scored risk surface in the lowest tier so
476
+ nothing is silently dropped.
477
+
478
+ ### Phase C2 — Verification audit (C2.5)
479
+
480
+ Cross-report parity audit on three customer repos (`platform`,
481
+ `dpl-studio`, `web-client`). All vuln-scan + health pairs verified:
482
+
483
+ - **D086 / D087 / D091 closures from C1 remain intact** across
484
+ all 3 repos. Cross-report parity holds.
485
+ - **D098 secrets-cap fires correctly**: web-client + platform both
486
+ drop to 40 "Fair"; dpl-studio (no secrets) stays at 90.
487
+ - **D099 .env callout renders correctly on web-client** (the only
488
+ repo with a tracked .env).
489
+ - **D105 Top 5 surfaces actionable rows on every repo**, post-D108
490
+ including the sparse-repo case.
491
+
492
+ ### D109 investigation — non-defect
493
+
494
+ C2.5 also surfaced a candidate drift: platform vuln-scan code-only
495
+ `10H 7M` vs health `10H 10M`. HIGH agreed; MEDIUM differed by 3.
496
+ Investigation via an in-process probe (`tmp/d109-probe.js` runs
497
+ both analyzers sequentially in ONE node process, sharing the
498
+ dispatcher cache) showed identical aggregates: `{ high: 10,
499
+ medium: 20 }` on both sides. **D109 is NOT a real defect** — the
500
+ architecture is sound. The observed drift across separate
501
+ processes was semgrep tool-runtime variance (MEDIUM count varied
502
+ 7 → 10 → 20 across runs while HIGH stayed stable at 10). Future
503
+ docs follow-up: note semgrep's non-determinism as a known
504
+ limitation.
505
+
506
+ ### Phase C1 — Canonical security aggregator (2026-05-14)
507
+
508
+ Three customer-facing aggregation-drift defects (D086, D087, D091)
509
+ shared one root: **multiple consumers re-counting severity from raw
510
+ envelope arrays with different inclusion rules**. Phase B closed
511
+ this class at the GATHER layer (canonical `walkSourceFiles`); Phase
512
+ C1 closes it at the AGGREGATION layer with the same class-fix
513
+ discipline plus two newly-surfaced defects (D107 BoM vs vuln-scan
514
+ disagreement, D091-boundary neighbor-bucket miss) caught during the
515
+ pre-release audit and fixed before ship.
516
+
517
+ - **Canonical `SecurityAggregate`** (commit `a3942f4`). New
518
+ `src/analyzers/security/aggregator.ts` exporting
519
+ `buildSecurityAggregate(envelopes) → SecurityAggregate`. The typed
520
+ contract carries three separately-named severity buckets
521
+ (`codeBySeverity`, `depBySeverity`, `secretsBySeverity`), two
522
+ distinct dep-count fields (`dependencyAdvisoryUniqueCount`
523
+ canonical user-facing + `dependencyFindingsRawCount` for audit),
524
+ fingerprint-stamped `CodeFinding[]` per category, dedup audit
525
+ trail, and per-source provenance. Renderers cannot accidentally
526
+ sum cross-axis or pick the wrong number — both defects become
527
+ impossible by the typed shape.
528
+
529
+ - **Six consumers migrated** onto the aggregate (4 user-facing + 2
530
+ internal):
531
+ - `security/index.ts` standalone vuln-scan (commit `f3bd69f`, D087
532
+ closure — Subtotal now matches "N advisories" by reading
533
+ `dependencyAdvisoryUniqueCount` by name)
534
+ - `security/shallow.ts` health-side scorer (commit `c73c7ca`, D086
535
+ closure — code-finding prose reads `codeBySeverity` from the same
536
+ field vuln-scan reads)
537
+ - `dashboard/index.ts` (commit `9fb0220` — reads severity buckets
538
+ from `vulns.summary.findings + dependencies` instead of re-summing
539
+ finding arrays)
540
+ - BoM (commit `4ae69ed` C1.8, D107 closure — see below)
541
+ - C# pack (commit `14b02a7` C1.9, G_v4_9 — see below)
542
+ - Action planner + legacy fallback (`actions.ts`, `shallow.ts`)
543
+ annotated `// aggregator-ok` for the two legitimate exceptions
544
+ (rebuilding `SecurityScoreInput` from a `SecurityReport`; legacy
545
+ ScoreInput fixtures predating the aggregator field).
546
+
547
+ - **D107 — BoM vs vuln-scan disagreement (NEW, surfaced in C1.7
548
+ audit)** (commit `4ae69ed`). dpl-studio: vuln-scan reported 2 dep
549
+ advisories (MongoDB.Driver HIGH + SharpCompress MEDIUM via
550
+ osv-scanner-nuget-direct) while BoM reported 0. Root cause: BoM
551
+ walks per-sub-root project directories and called `gatherDepVulns`
552
+ at each sub-root, hitting the csharp pack's cwd-sensitive routing
553
+ (at sub-root with stale `obj/project.assets.json`, dotnet returned
554
+ 0; at repo-root with no `.csproj`, the fallback fired). Fix: BoM
555
+ now gathers dep-vulns ONCE at the repo root and passes the result
556
+ to every per-sub-root entry builder via a new `depVulnsOverride`
557
+ option. License-side stays per-sub-root (legitimately
558
+ per-project). Post-fix: dpl-studio BoM 2 ≡ vuln-scan 2.
559
+
560
+ - **G_v4_9 — csharp pack cwd-invariant** (commit `14b02a7`). The
561
+ pack-contract defect underneath D107: `gatherCsharpDepVulnsResult`
562
+ produced different fingerprint sets depending on where `cwd`
563
+ pointed within the repo. Fix: always run BOTH `dotnet list package
564
+ --vulnerable` (when applicable) AND
565
+ `osv-scanner-nuget-direct` (the direct PackageReference parse)
566
+ in parallel, merge findings by `(package, installedVersion, id)`
567
+ fingerprint at the pack layer. Envelope counts recomputed from the
568
+ merged set; `tool` field joins what ran. Result: same fingerprint
569
+ set regardless of cwd. Any future multi-cwd caller now inherits
570
+ consistency.
571
+
572
+ - **D091 boundary case (NEW, surfaced in C1.7 audit)** (commit
573
+ `c7b72e2`). Web-client `DBConfigureForm.js:43` (semgrep MEDIUM
574
+ `bypass-tls-verification`) and `:45` (registry HIGH
575
+ `tls-validation-disabled`) — same root, 2 lines apart, same
576
+ canonical rule — failed to collapse because the
577
+ `Math.floor(line/3)*3` bucketing put them in different buckets
578
+ (42 and 45, straddling a multiple-of-3). Documented as a known
579
+ edge case in the C1.1 commit; biting in production was the trigger
580
+ to fix it. Fix: after the natural-bucket lookup misses, check
581
+ neighbor buckets at `(canonicalRule, file, line ± 3)`. Two
582
+ MEDIUMs absorbed into HIGHs on web-client, reducing apparent
583
+ code-finding count from 13 → 11 in the right direction.
584
+
585
+ - **G_v4_8 architectural gate** (commit `6e89131`) in
586
+ `scripts/check-architecture.sh`. Blocks the smoking-gun pattern
587
+ (`[<var>.severity]++` accumulator bump, or
588
+ `function countBySeverity(`) outside the canonical aggregator.
589
+ Static lookup maps (`SEV_RANK`, `SEV_LABEL`) and type-decl fields
590
+ inside interfaces don't match — only the actual aggregation shape.
591
+ BoM's per-package `[e.maxSeverity]++` naturally falls outside the
592
+ pattern (different attribute name) so BoM's legitimate per-package
593
+ aggregation is unaffected.
594
+
595
+ - **Recipe codification (G_v4_8 + G_v4_9 in
596
+ `tmp/recipe-v4-working-doc.md`)**. Two recipe-playbook
597
+ synthetic-pack assertions in `test/recipe-playbook.test.ts`
598
+ (synthetic depVuln finding flows into `depBySeverity` +
599
+ `dependencyAdvisoryUniqueCount`; cross-tool TLS-bypass collapses
600
+ regardless of pack identity). Future language packs feeding
601
+ security data through standard capability descriptors
602
+ automatically inherit drift prevention.
603
+
604
+ ### Phase C1 — Defect closures
605
+
606
+ | ID | Status | Closing commit(s) |
607
+ | --- | --- | --- |
608
+ | D086 | CLOSED (architectural) | `a3942f4` + `c73c7ca` — both surfaces read `aggregate.codeBySeverity` |
609
+ | D087 | CLOSED | `a3942f4` + `f3bd69f` — `dependencyAdvisoryUniqueCount` field forces the canonical count |
610
+ | D091 | CLOSED | `a3942f4` (canonical-rule registry + line-window) + `c7b72e2` (neighbor-bucket lookup for boundary case) |
611
+ | D107 | CLOSED (NEW, two layers) | `4ae69ed` (BoM single-source) + `14b02a7` (G_v4_9 csharp cwd-invariant) |
612
+
613
+ ### Phase C1 — Empirical validation
614
+
615
+ Cross-report parity audit on three customer repos (all numbers
616
+ post-Phase-C1):
617
+
618
+ - **platform** (1500+ TS/Node files, 2 project roots):
619
+ - vuln-scan Subtotal **81** ≡ "**81** advisories" ≡ "Showing 50 of
620
+ **81**" ≡ BoM `totalAdvisories` **81** ✓
621
+ - 5 cross-tool TLS-bypass collisions deduped (MEDIUM bucket
622
+ 14 → 9)
623
+ - **dpl-studio** (C#, 3 nested project roots):
624
+ - vuln-scan **2** ≡ BoM **2** ≡ health **2** (dep) ✓
625
+ - health code findings **1** ≡ vuln-scan code findings **1** ✓
626
+ - **web-client** (JS-heavy, large repo with degraded license info):
627
+ - dep advisories **31** ≡ **31** ≡ **31** ✓
628
+ - D091-boundary case on `DBConfigureForm.js:43+:45` collapses (the
629
+ C1.10 fix)
630
+ - 2 MEDIUMs absorbed into HIGHs via neighbor-bucket lookup
631
+
632
+ Tests: **1178 passed / 8 skipped** (1175 pre-Phase-C + 1 + 2 + 1 new
633
+ unit/synthetic-pack assertions). Architecture gate clean. No
634
+ regressions across the cross-ecosystem matrix.
635
+
636
+ Open and deferred to Phase C2-C8 (still inside 2.4.7 per the
637
+ 2026-05-14 reprioritization):
638
+
639
+ - C2: Security UX rework — split vuln-scan "Code Findings" section
640
+ into code-only + secret/config (closes the perception-level D086
641
+ drift even though architectural drift is gone), security rubric
642
+ weights secrets/.env heavily (D098), Top 5 actions in short
643
+ reports (D105), .env-in-git callout (D099)
644
+ - C3: D094 CWE truncation (`**CWE:** C` still on 2 platform
645
+ semgrep findings), D090 Remediation Commands split
646
+ - C4: D100 vendor-path exclusions
647
+ - C5: D093 word-boundary truncation, D096 densest-file
648
+ clarification
649
+ - C6: D106 agent rewrites
650
+ - C7: Final pre-release validation
651
+ - C8: PR → main → tag v2.4.7 → SLSA publish
652
+
653
+ ### Phase B — Class-fix release (2026-05-14)
654
+
655
+ Two architectural deliverables backed by 4 consumer-site migrations
656
+ and a permanent gate:
657
+
658
+ - **G_v4_7 — `walkSourceFiles` + `countLineMatches`** (new canonical
659
+ helpers in `src/analyzers/tools/walk-source-files.ts`). Pure JS,
660
+ no shell. The web-client D082/D083 silent-zero cascade was caused
661
+ by `grep -rEf <pat> --include=*.js .` producing 67MB of stdout on
662
+ minified files, overflowing `run()`'s 64MB ceiling, and returning
663
+ empty. The walker prunes excluded files at the directory boundary,
664
+ so grep is never asked to walk `public/build/*.min.js` in the
665
+ first place. Bumping `maxBuffer` is a moving target — the right
666
+ answer is "don't pass excluded files to the scanner at all."
667
+
668
+ - **Consumer migrations onto canonical helpers** (4 sites):
669
+ - `tools/generic.ts` (commits `3275e1e` + `226a56a`)
670
+ - `quality/gather.ts` (commit `82e0e75`)
671
+ - `tests/gather.ts` (commit `753a412`)
672
+ - `security/gather.ts` TLS-bypass walk (commit `099e844`)
673
+ Each migration is behavior-preserving by default (e.g. `includeTests: true`
674
+ preserves pre-migration semantics where the legacy grep matched in
675
+ test files too).
676
+
677
+ - **`gatherDebugStatements` shared helper** (commit `e7a8821`).
678
+ Replaces the two divergent implementations in `health.consoleLogCount`
679
+ (sum of JS console + Python print + Go fmt.Print across language-
680
+ scoped walks) and `quality.consoleLogCount` (single console.* pattern
681
+ across all extensions). After: both reports route through one
682
+ function — they cannot drift.
683
+
684
+ - **Architectural gate** (commit `32574e0`) in
685
+ `scripts/check-architecture.sh`. Blocks new
686
+ `grep -r{l,n,c,E,f}` calls in production code outside a 4-file
687
+ allowlist. After this release, the D082/D083 class of bug cannot
688
+ recur without explicitly bypassing the canonical helpers — which
689
+ the gate blocks.
690
+
691
+ ### Phase B — Defect closures (D074–D080, D082–D085)
692
+
693
+ | ID | Severity | Closing commit(s) |
694
+ | --- | --- | --- |
695
+ | D074 — commented-out matches inflate counts | HIGH | `3275e1e` + `82e0e75` + `099e844` + `e7a8821` (`skipComments: true` on print-family / anyType / eval / TLS-bypass) |
696
+ | D075 — sourceFiles cross-report drift | HIGH | `0e71683` + `3275e1e` + `753a412` + `e7a8821` (canonical walker + label alignment) |
697
+ | D076 — dep-vuln count drift health vs BoM | HIGH | `06b0cec` (BoM `totalAdvisories` uses unique fingerprint count) |
698
+ | D077 — dashboard tile drift | MED | closes with D075 |
699
+ | D078 — BoM Risk `**0.0**` for missing CVSS | MED | `46b0d6e` (`computeRiskScore` returns `null` for `cvssScore=0`) |
700
+ | D079 — duplicate grep-count implementations | MED | `82e0e75` + `e7a8821` (shared `gatherDebugStatements`) |
701
+ | D080 — lint dispatcher last-wins | MED | `72cd102` (`gatherWithProvenance` exposes attempted+skipped sources; label reads `"ruff (not run: typescript)"`) |
702
+ | D082 — web-client `consoleLogCount = 0` silent zero | CRITICAL | `0e71683` + `3275e1e` + `e7a8821` (walker prunes minified files at directory boundary) |
703
+ | D083 — `run()` maxBuffer overflow on minified-JS | CRITICAL | `0e71683` + `3275e1e` + `099e844` + `32574e0` |
704
+ | D084 — D082 cascade (anyType, eval) | HIGH | closes with D082/D083 |
705
+ | D085 — web-client dep-count drift | HIGH | `06b0cec` |
706
+
707
+ **Deferred to 2.4.8**:
708
+ - D081 (`Dead Imports: 0` suspicious) — investigated; root cause is
709
+ graphify Python script's `dead = imports - calls - module_ids`
710
+ zeroing out module-style imports. Fix requires a new metric
711
+ (`unreachableImportCount`) + synthetic tests + threshold tuning.
712
+ - **G_v4_8 full architectural enforcement** — typed gather-result
713
+ interfaces with explicit field-ownership claims. Narrow D076/D085
714
+ fix shipped (BoM uses fingerprint count); the typed-contract
715
+ prevention layer is preventive hardening, not on the convergence-
716
+ audit gate.
717
+
718
+ ### Phase B — Empirical validation
719
+
720
+ Convergence audit on three customer repos:
721
+
722
+ - **dpl-studio**: 1537 source files consistent across health,
723
+ test-gaps, maintainability dimension; `consoleLogCount=1`,
724
+ `tlsDisabledCount=1` stable.
725
+ - **platform** (the audit's most-troubled repo): `sourceFiles`
726
+ converged 447/438/444 → **444 / 444 / 444**;
727
+ `consoleLogCount` cross-report converged 1578/1555 → **698 / 698**;
728
+ `tlsDisabledCount` 18 reported / 11 active → **11**; lint label
729
+ `"ruff"` → `"ruff (not run: typescript)"`.
730
+ - **web-client**: `consoleLogCount` **0 → 1066** (D082/D083 closure;
731
+ catastrophic silent zero eliminated).
732
+
733
+ ### Phase A — Audit-driven hot-patches (2026-05-13)
734
+
735
+ The earlier portion of the 2.4.7 release. Same content as below —
736
+ preserved for traceability.
737
+
738
+ The cascade taught us that test-green ≠ report-correct: all 1091
739
+ tests passed before the audit. Reinforces
740
+ `feedback_critical_audit_before_shipping.md` — pre-delivery audit on
741
+ real customer reports is the gold standard, not the unit-test suite.
742
+
743
+ ### Added — D021 close (coverage workflow)
744
+
745
+ Four pieces shipped together:
746
+
747
+ - **`coverageFidelity` tier** classifies the `coverageSource` field
748
+ into three trust levels:
749
+ - `line-coverage` — real artifact (istanbul / coverage-py / jacoco /
750
+ simplecov / lcov / cobertura / go). The percent is line-coverage
751
+ truth.
752
+ - `import-graph` — derived from test-file import edges (up to N
753
+ hops). Informed heuristic.
754
+ - `filename-match` — share of source files with a name-matched
755
+ test. Pure heuristic.
756
+ Test-gap reports lead with a ⚠️ / ℹ️ banner when fidelity isn't
757
+ `line-coverage`, so a 0% from a heuristic can't be confused with a
758
+ 0% from a real coverage run.
759
+ - **`--with-coverage` flag** on `health` and `test-gaps`. Materializes
760
+ the coverage artifact via per-pack `runTests()` BEFORE analysis, so
761
+ `loadCoverage()` finds it and the report reads line-coverage truth.
762
+ Shares the same runner the `coverage` command uses.
763
+ - **`vyuh-dxkit report` orchestrator**. Single command that runs
764
+ every analyzer + dashboard in dependency order. `--with-coverage`
765
+ runs the coverage step ONCE upfront rather than per-command, so
766
+ `health` and `test-gaps` share the artifact without re-running the
767
+ test suite per analyzer.
768
+ - **Cross-ecosystem matrix coverage row × 8 packs** in
769
+ `test/integration/cross-ecosystem.test.ts` + per-pack contract
770
+ conformance assertions in `test/languages-contract.test.ts`. Locks
771
+ in the round-trip from "test runner" to "coverageFidelity:
772
+ line-coverage" across python / typescript / go / rust / csharp /
773
+ kotlin / java / ruby.
774
+
775
+ ### Added — language pack contracts
776
+
777
+ - **`LanguageSupport.upgradeCommand?(name, version)`** (G_v4_4) —
778
+ each pack ships its own per-ecosystem package upgrade template
779
+ (`dotnet add package`, `npm install`, `pip install`, `cargo update`,
780
+ `go get`, edit-pom for Maven, edit-Gemfile for Bundler). Replaces
781
+ the hardcoded switch on `tool` in `buildUpgradeCommand`
782
+ (security/index.ts). Dispatch now routes through
783
+ `getLanguage(packId).upgradeCommand()` — no language branching in
784
+ non-pack code (CLAUDE.md rule 6).
785
+ - **`DepVulnFinding.packId`** stamped at every producer site
786
+ (npm-audit / pip-audit / govulncheck / cargo-audit /
787
+ dotnet-vulnerable / osv-scanner-deps via new `packId` parameter on
788
+ `parseOsvScannerFindings` and `gatherOsvScannerDepVulnsResult`). The
789
+ vuln-scan "Remediation Commands" block now ships actual runnable
790
+ commands instead of bare `#` prose for every ecosystem.
791
+ - **`LanguageSupport.clocLanguageNames?`** (D073) — each pack
792
+ declares the names cloc emits in its `--json` output. cloc's
793
+ per-language summary + `totalLines` aggregation now filter to the
794
+ active-pack set, so markup/data formats (JSON / XML / CSV /
795
+ Markdown) stop deflating quality metrics. On dpl-studio: Comment
796
+ Ratio 4.3% → 27.9% (a 1.6M JSON denominator vs C#'s 568K).
797
+
798
+ ### Fixed — Tier 1 (credibility critical)
799
+
800
+ The post-shipment audit's master bug + its direct cascade:
801
+
802
+ - **D055** — `.dxkit-ignore` multi-segment paths flatten to basenames
803
+ in cloc / graphify / grep. `Dev/Addons/DPLAddon/SAPB1/` silently
804
+ became `{Dev, Addons, DPLAddon, SAPB1}` — cloc then excluded every
805
+ directory named `Dev` in the tree, killing 90% of source visibility.
806
+ Fix: `getClocExcludeFlags` emits `--exclude-dir` (basenames) PLUS
807
+ `--fullpath --not-match-d` (Perl regex on full path).
808
+ `getPythonExcludeFilter` emits both a basename set AND a multi-
809
+ segment path list for graphify's walker. Grep callers post-filter
810
+ via `isExcludedPath()`.
811
+ - **D056** — Registry-driven greps (docCommentFiles, tlsBypassFindings)
812
+ now post-filter through `isExcludedPath()`. Pre-fix the shell pipe
813
+ only filtered hardcoded `node_modules` + `dist` — every other
814
+ exclusion was silently ignored.
815
+ - **D057** — Cloc no longer writes `sourceFiles`. Generic.ts owns
816
+ the source-file count; cloc owns line counts + language breakdown.
817
+ Pre-fix `mergeLayer2` blindly overwrote generic's find-based 1537
818
+ with cloc's broken 141. Class-fix (merger field-ownership claims,
819
+ G_v4_8) deferred to 2.4.8.
820
+ - **D072** — Registry-greps now apply the SAME autogen filters
821
+ (`autogeneratedSourcePatterns` basename glob + `isAutogeneratedByHeader`
822
+ content marker) that `gatherGenericMetrics` uses for `sourceFiles`.
823
+ Pre-D072 docCommentFiles counted designer.cs / .g.cs files in the
824
+ numerator but not in `sourceFiles`'s denominator, producing 104%
825
+ docRatio on dpl-studio even after D055.
826
+ - **D062** closure via **G_v4_4** above.
827
+
828
+ ### Fixed — Tier 2 (visible UX bugs)
829
+
830
+ - **D060** — Weekly velocity fills empty weeks with 0-row entries
831
+ between first and last week with commits. Pre-fix `W08 2, W09 1,
832
+ W10 7, W14 1, W16 6, ...` had silent gaps that implied "data
833
+ missing" when reality was zero commits.
834
+ - **D061** — Hot Files filters auto-generated files via the existing
835
+ `autogeneratedSourcePatterns` registry. Pre-fix dpl-studio's hot
836
+ list included `*.Designer.cs` files (WinForms designer regeneration
837
+ noise).
838
+ - **D063** — BoM Risk column rendered to one decimal (`18.5`,
839
+ `14.8`) in both Triage and Vulnerable Packages tables. Pre-fix
840
+ `toFixed(0)` rounded 14.8 → 15, making it look like SharpCompress
841
+ should appear in the ≥15 triage when it was actually 14.8 (below
842
+ threshold).
843
+ - **D064** — BoM Reach column three-state: `✓` / `✗` / blank. Pre-
844
+ fix blank silently merged "checked and not reachable" with "no
845
+ data."
846
+ - **D032** — Two-part dashboard-input fix. `analyzeHealthWithMetrics`
847
+ runs unconditionally (was gated on `--detailed`); every report
848
+ command writes BOTH `-detailed.json` AND `-detailed.md`
849
+ unconditionally. `--detailed` flag now only controls the
850
+ success-log console output. Pre-fix a default `dxkit health . &&
851
+ dxkit dashboard .` workflow showed stale tile numbers + stale tab
852
+ content from whatever the last `--detailed` run had left behind.
853
+
854
+ ### Fixed — Tier 3 (cosmetic)
855
+
856
+ - **D065** — Health "Add API documentation" recommendation no longer
857
+ fires when `controllers === 0`. Pre-fix it triggered for any 100+
858
+ source file repo, including desktop apps with no HTTP surface.
859
+ - **D068** — Dashboard "Critical Issues at a Glance" discloses
860
+ "(showing N of M)" when the per-surface caps (3 vulns + 3 gaps +
861
+ 2 bom-triage) drop items. Pre-fix a customer with 20 CRITICAL
862
+ untested files saw 3 and could reasonably infer "only 3 critical
863
+ things in the repo."
864
+ - **D070** — BoM main report collapses the project-roots paragraph
865
+ to a 5-root preview + count; the full list moves to the detailed
866
+ report under a dedicated `## Project Roots (N)` section, one root
867
+ per line for grep / sort.
868
+
869
+ ### Recipe v4 status
870
+
871
+ - **G_v4_4** (per-pack `upgradeCommand`) — **delivered** (promoted
872
+ from 2.4.8 because D062 fix was otherwise a switch-statement patch).
873
+ - Still queued for 2.4.8: G_v4_5 (per-pack
874
+ `autogeneratedHeaderPatterns`), G_v4_6 (unified TLS bypass count +
875
+ findings), G_v4_7 (`walkSourceFiles` unified helper, class-fix for
876
+ D072), G_v4_8 (merger field-ownership claims, class-fix for D057),
877
+ G_v4_inherited_G2opt2 / _G3 / _G7.
878
+
879
+ ### Architecture — class lessons from the cascade
880
+
881
+ Two layering insights from D057 and D072, both with concrete class-
882
+ fix candidates queued for 2.4.8:
883
+
884
+ - **Layer ownership** — when two gather functions write the same
885
+ field (e.g. generic.ts and cloc.ts both writing `sourceFiles`),
886
+ the merger should reject overlap rather than last-write-wins.
887
+ Tracked as G_v4_8.
888
+ - **Source-file definition uniformity** — every metric claiming
889
+ "files matching X among source files" must share the predicate
890
+ `sourceFiles` uses (exclusions + autogen-basename + autogen-header).
891
+ Tracked as G_v4_7 (`walkSourceFiles` shared helper). Until it
892
+ lands, every grep caller funnels through
893
+ `isCountedSourceFile(cwd, relPath)` in `tools/generic.ts`.
894
+
10
895
  ## [2.4.6] - 2026-05-07
11
896
 
12
897
  ### Added — Ruby language pack (Phase 10k.2)