@vyuhlabs/dxkit 2.4.6 → 2.4.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (357) hide show
  1. package/CHANGELOG.md +1076 -0
  2. package/README.md +132 -27
  3. package/dist/analysis-result.d.ts +112 -0
  4. package/dist/analysis-result.d.ts.map +1 -0
  5. package/dist/analysis-result.js +52 -0
  6. package/dist/analysis-result.js.map +1 -0
  7. package/dist/analyzers/bom/detailed.d.ts.map +1 -1
  8. package/dist/analyzers/bom/detailed.js +19 -0
  9. package/dist/analyzers/bom/detailed.js.map +1 -1
  10. package/dist/analyzers/bom/gather.d.ts +27 -26
  11. package/dist/analyzers/bom/gather.d.ts.map +1 -1
  12. package/dist/analyzers/bom/gather.js +26 -87
  13. package/dist/analyzers/bom/gather.js.map +1 -1
  14. package/dist/analyzers/bom/index.d.ts +0 -7
  15. package/dist/analyzers/bom/index.d.ts.map +1 -1
  16. package/dist/analyzers/bom/index.js +98 -48
  17. package/dist/analyzers/bom/index.js.map +1 -1
  18. package/dist/analyzers/bom/types.d.ts +11 -13
  19. package/dist/analyzers/bom/types.d.ts.map +1 -1
  20. package/dist/analyzers/cache.d.ts +95 -0
  21. package/dist/analyzers/cache.d.ts.map +1 -0
  22. package/dist/analyzers/cache.js +309 -0
  23. package/dist/analyzers/cache.js.map +1 -0
  24. package/dist/analyzers/coverage-runner.d.ts +56 -0
  25. package/dist/analyzers/coverage-runner.d.ts.map +1 -0
  26. package/dist/analyzers/coverage-runner.js +72 -0
  27. package/dist/analyzers/coverage-runner.js.map +1 -0
  28. package/dist/analyzers/dashboard/index.d.ts +24 -0
  29. package/dist/analyzers/dashboard/index.d.ts.map +1 -0
  30. package/dist/analyzers/dashboard/index.js +667 -0
  31. package/dist/analyzers/dashboard/index.js.map +1 -0
  32. package/dist/analyzers/developer/gather.d.ts.map +1 -1
  33. package/dist/analyzers/developer/gather.js +205 -37
  34. package/dist/analyzers/developer/gather.js.map +1 -1
  35. package/dist/analyzers/developer/index.d.ts +1 -1
  36. package/dist/analyzers/developer/index.d.ts.map +1 -1
  37. package/dist/analyzers/developer/index.js +21 -9
  38. package/dist/analyzers/developer/index.js.map +1 -1
  39. package/dist/analyzers/dispatcher.d.ts +52 -0
  40. package/dist/analyzers/dispatcher.d.ts.map +1 -1
  41. package/dist/analyzers/dispatcher.js +92 -9
  42. package/dist/analyzers/dispatcher.js.map +1 -1
  43. package/dist/analyzers/docs/shallow.d.ts +17 -5
  44. package/dist/analyzers/docs/shallow.d.ts.map +1 -1
  45. package/dist/analyzers/docs/shallow.js +65 -2
  46. package/dist/analyzers/docs/shallow.js.map +1 -1
  47. package/dist/analyzers/dx/shallow.d.ts +17 -5
  48. package/dist/analyzers/dx/shallow.d.ts.map +1 -1
  49. package/dist/analyzers/dx/shallow.js +66 -2
  50. package/dist/analyzers/dx/shallow.js.map +1 -1
  51. package/dist/analyzers/health/actions.d.ts +1 -1
  52. package/dist/analyzers/health/actions.d.ts.map +1 -1
  53. package/dist/analyzers/health/actions.js +27 -9
  54. package/dist/analyzers/health/actions.js.map +1 -1
  55. package/dist/analyzers/health/detailed.d.ts +2 -1
  56. package/dist/analyzers/health/detailed.d.ts.map +1 -1
  57. package/dist/analyzers/health/detailed.js +11 -7
  58. package/dist/analyzers/health/detailed.js.map +1 -1
  59. package/dist/analyzers/health.d.ts +27 -0
  60. package/dist/analyzers/health.d.ts.map +1 -1
  61. package/dist/analyzers/health.js +282 -34
  62. package/dist/analyzers/health.js.map +1 -1
  63. package/dist/analyzers/licenses/gather.d.ts +35 -8
  64. package/dist/analyzers/licenses/gather.d.ts.map +1 -1
  65. package/dist/analyzers/licenses/gather.js +86 -13
  66. package/dist/analyzers/licenses/gather.js.map +1 -1
  67. package/dist/analyzers/licenses/index.d.ts +1 -1
  68. package/dist/analyzers/licenses/index.d.ts.map +1 -1
  69. package/dist/analyzers/licenses/index.js +52 -11
  70. package/dist/analyzers/licenses/index.js.map +1 -1
  71. package/dist/analyzers/licenses/types.d.ts +15 -0
  72. package/dist/analyzers/licenses/types.d.ts.map +1 -1
  73. package/dist/analyzers/maintainability/shallow.d.ts +17 -5
  74. package/dist/analyzers/maintainability/shallow.d.ts.map +1 -1
  75. package/dist/analyzers/maintainability/shallow.js +80 -2
  76. package/dist/analyzers/maintainability/shallow.js.map +1 -1
  77. package/dist/analyzers/quality/detailed.d.ts.map +1 -1
  78. package/dist/analyzers/quality/detailed.js +4 -6
  79. package/dist/analyzers/quality/detailed.js.map +1 -1
  80. package/dist/analyzers/quality/gather.d.ts +1 -14
  81. package/dist/analyzers/quality/gather.d.ts.map +1 -1
  82. package/dist/analyzers/quality/gather.js +48 -137
  83. package/dist/analyzers/quality/gather.js.map +1 -1
  84. package/dist/analyzers/quality/index.d.ts +9 -2
  85. package/dist/analyzers/quality/index.d.ts.map +1 -1
  86. package/dist/analyzers/quality/index.js +197 -117
  87. package/dist/analyzers/quality/index.js.map +1 -1
  88. package/dist/analyzers/quality/shallow.d.ts +50 -5
  89. package/dist/analyzers/quality/shallow.d.ts.map +1 -1
  90. package/dist/analyzers/quality/shallow.js +155 -2
  91. package/dist/analyzers/quality/shallow.js.map +1 -1
  92. package/dist/analyzers/quality/types.d.ts +14 -0
  93. package/dist/analyzers/quality/types.d.ts.map +1 -1
  94. package/dist/analyzers/security/actions.d.ts +11 -4
  95. package/dist/analyzers/security/actions.d.ts.map +1 -1
  96. package/dist/analyzers/security/actions.js +87 -37
  97. package/dist/analyzers/security/actions.js.map +1 -1
  98. package/dist/analyzers/security/aggregator.d.ts +236 -0
  99. package/dist/analyzers/security/aggregator.d.ts.map +1 -0
  100. package/dist/analyzers/security/aggregator.js +349 -0
  101. package/dist/analyzers/security/aggregator.js.map +1 -0
  102. package/dist/analyzers/security/detailed.d.ts +2 -2
  103. package/dist/analyzers/security/detailed.d.ts.map +1 -1
  104. package/dist/analyzers/security/detailed.js +10 -9
  105. package/dist/analyzers/security/detailed.js.map +1 -1
  106. package/dist/analyzers/security/gather.d.ts +104 -1
  107. package/dist/analyzers/security/gather.d.ts.map +1 -1
  108. package/dist/analyzers/security/gather.js +299 -9
  109. package/dist/analyzers/security/gather.js.map +1 -1
  110. package/dist/analyzers/security/index.d.ts +15 -0
  111. package/dist/analyzers/security/index.d.ts.map +1 -1
  112. package/dist/analyzers/security/index.js +463 -50
  113. package/dist/analyzers/security/index.js.map +1 -1
  114. package/dist/analyzers/security/shallow.d.ts +50 -6
  115. package/dist/analyzers/security/shallow.d.ts.map +1 -1
  116. package/dist/analyzers/security/shallow.js +154 -2
  117. package/dist/analyzers/security/shallow.js.map +1 -1
  118. package/dist/analyzers/security/types.d.ts +51 -0
  119. package/dist/analyzers/security/types.d.ts.map +1 -1
  120. package/dist/analyzers/tests/detailed.d.ts.map +1 -1
  121. package/dist/analyzers/tests/detailed.js +2 -3
  122. package/dist/analyzers/tests/detailed.js.map +1 -1
  123. package/dist/analyzers/tests/gather.d.ts +2 -1
  124. package/dist/analyzers/tests/gather.d.ts.map +1 -1
  125. package/dist/analyzers/tests/gather.js +98 -69
  126. package/dist/analyzers/tests/gather.js.map +1 -1
  127. package/dist/analyzers/tests/index.d.ts +11 -2
  128. package/dist/analyzers/tests/index.d.ts.map +1 -1
  129. package/dist/analyzers/tests/index.js +83 -18
  130. package/dist/analyzers/tests/index.js.map +1 -1
  131. package/dist/analyzers/tests/shallow.d.ts +19 -5
  132. package/dist/analyzers/tests/shallow.d.ts.map +1 -1
  133. package/dist/analyzers/tests/shallow.js +89 -2
  134. package/dist/analyzers/tests/shallow.js.map +1 -1
  135. package/dist/analyzers/tests/types.d.ts +41 -1
  136. package/dist/analyzers/tests/types.d.ts.map +1 -1
  137. package/dist/analyzers/tools/autogen-header.d.ts +8 -0
  138. package/dist/analyzers/tools/autogen-header.d.ts.map +1 -0
  139. package/dist/analyzers/tools/autogen-header.js +107 -0
  140. package/dist/analyzers/tools/autogen-header.js.map +1 -0
  141. package/dist/analyzers/tools/cloc.d.ts.map +1 -1
  142. package/dist/analyzers/tools/cloc.js +36 -5
  143. package/dist/analyzers/tools/cloc.js.map +1 -1
  144. package/dist/analyzers/tools/deadline.d.ts +67 -0
  145. package/dist/analyzers/tools/deadline.d.ts.map +1 -0
  146. package/dist/analyzers/tools/deadline.js +81 -0
  147. package/dist/analyzers/tools/deadline.js.map +1 -0
  148. package/dist/analyzers/tools/debug-statements.d.ts +17 -0
  149. package/dist/analyzers/tools/debug-statements.d.ts.map +1 -0
  150. package/dist/analyzers/tools/debug-statements.js +58 -0
  151. package/dist/analyzers/tools/debug-statements.js.map +1 -0
  152. package/dist/analyzers/tools/default-exclusions.gitignore +28 -0
  153. package/dist/analyzers/tools/exclusions.d.ts +33 -6
  154. package/dist/analyzers/tools/exclusions.d.ts.map +1 -1
  155. package/dist/analyzers/tools/exclusions.js +95 -26
  156. package/dist/analyzers/tools/exclusions.js.map +1 -1
  157. package/dist/analyzers/tools/generic.d.ts +17 -2
  158. package/dist/analyzers/tools/generic.d.ts.map +1 -1
  159. package/dist/analyzers/tools/generic.js +206 -109
  160. package/dist/analyzers/tools/generic.js.map +1 -1
  161. package/dist/analyzers/tools/gitleaks.d.ts.map +1 -1
  162. package/dist/analyzers/tools/gitleaks.js +48 -1
  163. package/dist/analyzers/tools/gitleaks.js.map +1 -1
  164. package/dist/analyzers/tools/graphify.d.ts +30 -2
  165. package/dist/analyzers/tools/graphify.d.ts.map +1 -1
  166. package/dist/analyzers/tools/graphify.js +131 -15
  167. package/dist/analyzers/tools/graphify.js.map +1 -1
  168. package/dist/analyzers/tools/jscpd.d.ts +12 -2
  169. package/dist/analyzers/tools/jscpd.d.ts.map +1 -1
  170. package/dist/analyzers/tools/jscpd.js +129 -6
  171. package/dist/analyzers/tools/jscpd.js.map +1 -1
  172. package/dist/analyzers/tools/lint-label.d.ts +29 -0
  173. package/dist/analyzers/tools/lint-label.d.ts.map +1 -0
  174. package/dist/analyzers/tools/lint-label.js +23 -0
  175. package/dist/analyzers/tools/lint-label.js.map +1 -0
  176. package/dist/analyzers/tools/minified-detection.d.ts +9 -0
  177. package/dist/analyzers/tools/minified-detection.d.ts.map +1 -0
  178. package/dist/analyzers/tools/minified-detection.js +147 -0
  179. package/dist/analyzers/tools/minified-detection.js.map +1 -0
  180. package/dist/analyzers/tools/nuget-package-reference.d.ts +133 -0
  181. package/dist/analyzers/tools/nuget-package-reference.d.ts.map +1 -0
  182. package/dist/analyzers/tools/nuget-package-reference.js +177 -0
  183. package/dist/analyzers/tools/nuget-package-reference.js.map +1 -0
  184. package/dist/analyzers/tools/osv-scanner-deps.d.ts +3 -2
  185. package/dist/analyzers/tools/osv-scanner-deps.d.ts.map +1 -1
  186. package/dist/analyzers/tools/osv-scanner-deps.js +32 -14
  187. package/dist/analyzers/tools/osv-scanner-deps.js.map +1 -1
  188. package/dist/analyzers/tools/osv.d.ts +36 -0
  189. package/dist/analyzers/tools/osv.d.ts.map +1 -1
  190. package/dist/analyzers/tools/osv.js +26 -0
  191. package/dist/analyzers/tools/osv.js.map +1 -1
  192. package/dist/analyzers/tools/parallel.d.ts +1 -1
  193. package/dist/analyzers/tools/parallel.d.ts.map +1 -1
  194. package/dist/analyzers/tools/parallel.js +2 -2
  195. package/dist/analyzers/tools/parallel.js.map +1 -1
  196. package/dist/analyzers/tools/report-date.d.ts +17 -0
  197. package/dist/analyzers/tools/report-date.d.ts.map +1 -0
  198. package/dist/analyzers/tools/report-date.js +26 -0
  199. package/dist/analyzers/tools/report-date.js.map +1 -0
  200. package/dist/analyzers/tools/risk-score.d.ts +7 -0
  201. package/dist/analyzers/tools/risk-score.d.ts.map +1 -1
  202. package/dist/analyzers/tools/risk-score.js +9 -2
  203. package/dist/analyzers/tools/risk-score.js.map +1 -1
  204. package/dist/analyzers/tools/run-tests-helper.d.ts +43 -0
  205. package/dist/analyzers/tools/run-tests-helper.d.ts.map +1 -0
  206. package/dist/analyzers/tools/run-tests-helper.js +156 -0
  207. package/dist/analyzers/tools/run-tests-helper.js.map +1 -0
  208. package/dist/analyzers/tools/runner.d.ts.map +1 -1
  209. package/dist/analyzers/tools/runner.js +75 -12
  210. package/dist/analyzers/tools/runner.js.map +1 -1
  211. package/dist/analyzers/tools/semgrep.d.ts +39 -2
  212. package/dist/analyzers/tools/semgrep.d.ts.map +1 -1
  213. package/dist/analyzers/tools/semgrep.js +131 -9
  214. package/dist/analyzers/tools/semgrep.js.map +1 -1
  215. package/dist/analyzers/tools/timing.d.ts +17 -3
  216. package/dist/analyzers/tools/timing.d.ts.map +1 -1
  217. package/dist/analyzers/tools/timing.js +36 -14
  218. package/dist/analyzers/tools/timing.js.map +1 -1
  219. package/dist/analyzers/tools/tool-registry.d.ts.map +1 -1
  220. package/dist/analyzers/tools/tool-registry.js +11 -1
  221. package/dist/analyzers/tools/tool-registry.js.map +1 -1
  222. package/dist/analyzers/tools/tools-unavailable-prose.d.ts +18 -0
  223. package/dist/analyzers/tools/tools-unavailable-prose.d.ts.map +1 -0
  224. package/dist/analyzers/tools/tools-unavailable-prose.js +69 -0
  225. package/dist/analyzers/tools/tools-unavailable-prose.js.map +1 -0
  226. package/dist/analyzers/tools/upgrade-plan-resolver.d.ts.map +1 -1
  227. package/dist/analyzers/tools/upgrade-plan-resolver.js +7 -0
  228. package/dist/analyzers/tools/upgrade-plan-resolver.js.map +1 -1
  229. package/dist/analyzers/tools/vendored-advisor.d.ts +43 -0
  230. package/dist/analyzers/tools/vendored-advisor.d.ts.map +1 -0
  231. package/dist/analyzers/tools/vendored-advisor.js +107 -0
  232. package/dist/analyzers/tools/vendored-advisor.js.map +1 -0
  233. package/dist/analyzers/tools/walk-paths.d.ts +78 -0
  234. package/dist/analyzers/tools/walk-paths.d.ts.map +1 -0
  235. package/dist/analyzers/tools/walk-paths.js +150 -0
  236. package/dist/analyzers/tools/walk-paths.js.map +1 -0
  237. package/dist/analyzers/tools/walk-source-files.d.ts +70 -0
  238. package/dist/analyzers/tools/walk-source-files.d.ts.map +1 -0
  239. package/dist/analyzers/tools/walk-source-files.js +369 -0
  240. package/dist/analyzers/tools/walk-source-files.js.map +1 -0
  241. package/dist/analyzers/types.d.ts +204 -4
  242. package/dist/analyzers/types.d.ts.map +1 -1
  243. package/dist/analyzers/xlsx/bom.d.ts.map +1 -1
  244. package/dist/analyzers/xlsx/bom.js +8 -1
  245. package/dist/analyzers/xlsx/bom.js.map +1 -1
  246. package/dist/cli.d.ts.map +1 -1
  247. package/dist/cli.js +581 -189
  248. package/dist/cli.js.map +1 -1
  249. package/dist/detect.d.ts.map +1 -1
  250. package/dist/detect.js +24 -7
  251. package/dist/detect.js.map +1 -1
  252. package/dist/doctor.d.ts.map +1 -1
  253. package/dist/doctor.js +103 -53
  254. package/dist/doctor.js.map +1 -1
  255. package/dist/languages/capabilities/provider.d.ts +130 -1
  256. package/dist/languages/capabilities/provider.d.ts.map +1 -1
  257. package/dist/languages/capabilities/types.d.ts +68 -7
  258. package/dist/languages/capabilities/types.d.ts.map +1 -1
  259. package/dist/languages/csharp.d.ts +15 -1
  260. package/dist/languages/csharp.d.ts.map +1 -1
  261. package/dist/languages/csharp.js +624 -146
  262. package/dist/languages/csharp.js.map +1 -1
  263. package/dist/languages/go.d.ts.map +1 -1
  264. package/dist/languages/go.js +89 -11
  265. package/dist/languages/go.js.map +1 -1
  266. package/dist/languages/index.d.ts +132 -2
  267. package/dist/languages/index.d.ts.map +1 -1
  268. package/dist/languages/index.js +207 -0
  269. package/dist/languages/index.js.map +1 -1
  270. package/dist/languages/java.d.ts.map +1 -1
  271. package/dist/languages/java.js +113 -26
  272. package/dist/languages/java.js.map +1 -1
  273. package/dist/languages/kotlin.d.ts.map +1 -1
  274. package/dist/languages/kotlin.js +132 -26
  275. package/dist/languages/kotlin.js.map +1 -1
  276. package/dist/languages/python.d.ts.map +1 -1
  277. package/dist/languages/python.js +149 -44
  278. package/dist/languages/python.js.map +1 -1
  279. package/dist/languages/ruby.d.ts +39 -1
  280. package/dist/languages/ruby.d.ts.map +1 -1
  281. package/dist/languages/ruby.js +178 -44
  282. package/dist/languages/ruby.js.map +1 -1
  283. package/dist/languages/rust.d.ts.map +1 -1
  284. package/dist/languages/rust.js +103 -16
  285. package/dist/languages/rust.js.map +1 -1
  286. package/dist/languages/types.d.ts +228 -5
  287. package/dist/languages/types.d.ts.map +1 -1
  288. package/dist/languages/typescript.d.ts.map +1 -1
  289. package/dist/languages/typescript.js +201 -14
  290. package/dist/languages/typescript.js.map +1 -1
  291. package/dist/scoring/dimensions/documentation.d.ts +53 -0
  292. package/dist/scoring/dimensions/documentation.d.ts.map +1 -0
  293. package/dist/scoring/dimensions/documentation.js +106 -0
  294. package/dist/scoring/dimensions/documentation.js.map +1 -0
  295. package/dist/scoring/dimensions/dx.d.ts +53 -0
  296. package/dist/scoring/dimensions/dx.d.ts.map +1 -0
  297. package/dist/scoring/dimensions/dx.js +105 -0
  298. package/dist/scoring/dimensions/dx.js.map +1 -0
  299. package/dist/scoring/dimensions/maintainability.d.ts +53 -0
  300. package/dist/scoring/dimensions/maintainability.d.ts.map +1 -0
  301. package/dist/scoring/dimensions/maintainability.js +101 -0
  302. package/dist/scoring/dimensions/maintainability.js.map +1 -0
  303. package/dist/scoring/dimensions/quality.d.ts +108 -0
  304. package/dist/scoring/dimensions/quality.d.ts.map +1 -0
  305. package/dist/scoring/dimensions/quality.js +174 -0
  306. package/dist/scoring/dimensions/quality.js.map +1 -0
  307. package/dist/scoring/dimensions/security.d.ts +84 -0
  308. package/dist/scoring/dimensions/security.d.ts.map +1 -0
  309. package/dist/scoring/dimensions/security.js +135 -0
  310. package/dist/scoring/dimensions/security.js.map +1 -0
  311. package/dist/scoring/dimensions/testing.d.ts +56 -0
  312. package/dist/scoring/dimensions/testing.d.ts.map +1 -0
  313. package/dist/scoring/dimensions/testing.js +98 -0
  314. package/dist/scoring/dimensions/testing.js.map +1 -0
  315. package/dist/scoring/evaluator.d.ts +27 -0
  316. package/dist/scoring/evaluator.d.ts.map +1 -0
  317. package/dist/scoring/evaluator.js +124 -0
  318. package/dist/scoring/evaluator.js.map +1 -0
  319. package/dist/scoring/format.d.ts +34 -0
  320. package/dist/scoring/format.d.ts.map +1 -0
  321. package/dist/scoring/format.js +63 -0
  322. package/dist/scoring/format.js.map +1 -0
  323. package/dist/scoring/index.d.ts +37 -0
  324. package/dist/scoring/index.d.ts.map +1 -0
  325. package/dist/scoring/index.js +57 -0
  326. package/dist/scoring/index.js.map +1 -0
  327. package/dist/scoring/overall.d.ts +54 -0
  328. package/dist/scoring/overall.d.ts.map +1 -0
  329. package/dist/scoring/overall.js +76 -0
  330. package/dist/scoring/overall.js.map +1 -0
  331. package/dist/scoring/result.d.ts +111 -0
  332. package/dist/scoring/result.d.ts.map +1 -0
  333. package/dist/scoring/result.js +14 -0
  334. package/dist/scoring/result.js.map +1 -0
  335. package/dist/scoring/spec.d.ts +76 -0
  336. package/dist/scoring/spec.d.ts.map +1 -0
  337. package/dist/scoring/spec.js +22 -0
  338. package/dist/scoring/spec.js.map +1 -0
  339. package/dist/scoring/thresholds.d.ts +56 -0
  340. package/dist/scoring/thresholds.d.ts.map +1 -0
  341. package/dist/scoring/thresholds.js +75 -0
  342. package/dist/scoring/thresholds.js.map +1 -0
  343. package/dist/tools-cli.d.ts.map +1 -1
  344. package/dist/tools-cli.js +21 -2
  345. package/dist/tools-cli.js.map +1 -1
  346. package/dist/types.d.ts +16 -0
  347. package/dist/types.d.ts.map +1 -1
  348. package/package.json +1 -1
  349. package/templates/.claude/commands/dashboard.md +17 -9
  350. package/dist/analyzers/scoring.d.ts +0 -49
  351. package/dist/analyzers/scoring.d.ts.map +0 -1
  352. package/dist/analyzers/scoring.js +0 -422
  353. package/dist/analyzers/scoring.js.map +0 -1
  354. package/dist/analyzers/security/scoring.d.ts +0 -29
  355. package/dist/analyzers/security/scoring.d.ts.map +0 -1
  356. package/dist/analyzers/security/scoring.js +0 -40
  357. package/dist/analyzers/security/scoring.js.map +0 -1
@@ -39,14 +39,20 @@ exports.parseDotnetVulnerableOutput = parseDotnetVulnerableOutput;
39
39
  exports.parseProjectAssetsJson = parseProjectAssetsJson;
40
40
  exports.buildCsharpTopLevelDepIndex = buildCsharpTopLevelDepIndex;
41
41
  exports.findAllProjectAssetsJson = findAllProjectAssetsJson;
42
+ exports.findAllCsprojFiles = findAllCsprojFiles;
42
43
  exports.mergeAssetParses = mergeAssetParses;
43
44
  exports.extractCsharpImportsRaw = extractCsharpImportsRaw;
44
45
  const fs = __importStar(require("fs"));
46
+ const os = __importStar(require("os"));
45
47
  const path = __importStar(require("path"));
46
48
  const exclusions_1 = require("../analyzers/tools/exclusions");
49
+ const nuget_package_reference_1 = require("../analyzers/tools/nuget-package-reference");
47
50
  const osv_1 = require("../analyzers/tools/osv");
51
+ const osv_scanner_deps_1 = require("../analyzers/tools/osv-scanner-deps");
48
52
  const runner_1 = require("../analyzers/tools/runner");
53
+ const run_tests_helper_1 = require("../analyzers/tools/run-tests-helper");
49
54
  const tool_registry_1 = require("../analyzers/tools/tool-registry");
55
+ const walk_paths_1 = require("../analyzers/tools/walk-paths");
50
56
  function dirHasMatching(dir, regex) {
51
57
  try {
52
58
  return fs.readdirSync(dir).some((name) => regex.test(name));
@@ -55,49 +61,32 @@ function dirHasMatching(dir, regex) {
55
61
  return false;
56
62
  }
57
63
  }
58
- function findMatchingRecursive(cwd, regex, maxDepth = 3) {
59
- function search(dir, depth) {
60
- if (depth > maxDepth)
61
- return null;
62
- let entries;
63
- try {
64
- entries = fs.readdirSync(dir, { withFileTypes: true });
65
- }
66
- catch {
67
- return null;
68
- }
69
- for (const e of entries) {
70
- if (e.name.startsWith('.') ||
71
- ['node_modules', 'bin', 'obj', 'TestResults', 'packages'].includes(e.name)) {
72
- continue;
73
- }
74
- const full = path.join(dir, e.name);
75
- if (e.isFile() && regex.test(e.name))
76
- return full;
77
- if (e.isDirectory()) {
78
- const nested = search(full, depth + 1);
79
- if (nested)
80
- return nested;
81
- }
82
- }
83
- return null;
84
- }
85
- return search(cwd, 0);
86
- }
87
64
  function findCoberturaArtifact(cwd) {
88
65
  // Common layouts:
89
66
  // coverage/coverage.cobertura.xml (explicit run)
90
67
  // TestResults/<guid>/coverage.cobertura.xml (default `dotnet test --collect`)
68
+ //
69
+ // The canonical walker rightly excludes `TestResults/` (a build-
70
+ // output subtree) from manifest/source discovery, but here we need
71
+ // to look INSIDE it. We run the walker scoped to the TestResults
72
+ // directory itself with `respectIgnore: false` so the bundled
73
+ // excludes don't fire — we're starting from inside the very subtree
74
+ // they were meant to prune. Depth-unlimited so future tooling that
75
+ // nests artifacts deeper than today's `TestResults/<guid>/` shape
76
+ // still finds them.
91
77
  const top = path.join(cwd, 'coverage', 'coverage.cobertura.xml');
92
78
  if (fs.existsSync(top))
93
79
  return top;
94
80
  const testResults = path.join(cwd, 'TestResults');
95
- if (fs.existsSync(testResults)) {
96
- const nested = findMatchingRecursive(testResults, /coverage\.cobertura\.xml$/, 4);
97
- if (nested)
98
- return nested;
99
- }
100
- return null;
81
+ if (!fs.existsSync(testResults))
82
+ return null;
83
+ const matches = (0, walk_paths_1.walkPaths)(testResults, {
84
+ extensions: ['.xml'],
85
+ respectIgnore: false,
86
+ }).filter((rel) => rel.endsWith('coverage.cobertura.xml'));
87
+ if (matches.length === 0)
88
+ return null;
89
+ return path.join(testResults, matches[0]);
101
90
  }
102
91
  function round1(n) {
103
92
  return Math.round(n * 10) / 10;
@@ -218,6 +207,7 @@ function parseDotnetVulnerableOutput(raw) {
218
207
  package: pkgId,
219
208
  installedVersion: resolvedVersion,
220
209
  tool: 'dotnet-vulnerable',
210
+ packId: 'csharp',
221
211
  severity,
222
212
  };
223
213
  if (topLevelDep && topLevelDep.length > 0)
@@ -343,43 +333,39 @@ function buildCsharpTopLevelDepIndex(parsed) {
343
333
  *
344
334
  * Exported for test coverage.
345
335
  */
346
- function findAllProjectAssetsJson(cwd, maxDepth = 4) {
336
+ function findAllProjectAssetsJson(cwd) {
337
+ // Project-anchored discovery: every `obj/project.assets.json` sits
338
+ // next to a `.csproj`. Find every `.csproj` via the canonical walker
339
+ // (depth-unlimited, exclusion-aware), then probe each project's
340
+ // `obj/project.assets.json` directly. This replaces a parallel
341
+ // depth-capped walker that missed deep monorepos (the .NET WinForms
342
+ // benchmark's .csproj files sit 6–9 levels under repo root) and naturally
343
+ // inherits the same `.gitignore` / `.dxkit-ignore` / standard
344
+ // exclusion rules every other dxkit walker honors.
347
345
  const out = [];
348
- function search(dir, depth) {
349
- if (depth > maxDepth)
350
- return;
351
- let entries;
352
- try {
353
- entries = fs.readdirSync(dir, { withFileTypes: true });
354
- }
355
- catch {
356
- return;
357
- }
358
- // Direct-child shortcut: if this directory is `obj`, collect the
359
- // file right here before recursing siblings. Skipping recursion
360
- // into `obj/` keeps the walk bounded (obj/ contains no deeper
361
- // projects of its own).
362
- if (path.basename(dir) === 'obj') {
363
- for (const e of entries) {
364
- if (e.isFile() && e.name === 'project.assets.json') {
365
- out.push(path.join(dir, e.name));
366
- }
367
- }
368
- return;
369
- }
370
- for (const e of entries) {
371
- if (e.name.startsWith('.') ||
372
- ['node_modules', 'bin', 'TestResults', 'packages'].includes(e.name)) {
373
- continue;
374
- }
375
- if (e.isDirectory()) {
376
- search(path.join(dir, e.name), depth + 1);
377
- }
378
- }
346
+ for (const csproj of findAllCsprojFiles(cwd)) {
347
+ const assets = path.join(path.dirname(csproj), 'obj', 'project.assets.json');
348
+ if (fs.existsSync(assets))
349
+ out.push(assets);
379
350
  }
380
- search(cwd, 0);
381
351
  return out;
382
352
  }
353
+ /**
354
+ * Locate every `.csproj` reachable from cwd within `maxDepth` levels.
355
+ * D025f (2.4.7): the direct-parsing fallback iterates this list when
356
+ * `dotnet list package` can't produce output (D036: cwd is a parent
357
+ * of the actual project files, dotnet has no way to pick one).
358
+ *
359
+ * Skips standard non-source dirs (node_modules / bin / obj /
360
+ * TestResults / packages). Unlike `findAllProjectAssetsJson` (which
361
+ * needs `obj/`), this walk's targets sit in project root directories
362
+ * so the obj-skip is fine.
363
+ *
364
+ * Exported for test coverage.
365
+ */
366
+ function findAllCsprojFiles(cwd) {
367
+ return (0, walk_paths_1.walkPaths)(cwd, { extensions: ['.csproj'] }).map((rel) => path.join(cwd, rel));
368
+ }
383
369
  /**
384
370
  * Merge multiple `project.assets.json` parse results into a single
385
371
  * graph. `topLevels` unions across projects; `edges` unions the
@@ -434,35 +420,175 @@ function loadCsharpTopLevelDepIndex(cwd) {
434
420
  return buildCsharpTopLevelDepIndex(mergeAssetParses(parses));
435
421
  }
436
422
  /**
437
- * Single source of truth for the csharp pack's dep-vuln gathering.
438
- * Consumed by `csharpDepVulnsProvider` (capability dispatcher). Runs
439
- * independently of `dotnet-format` availability — historical bug where
440
- * projects with `dotnet` but no `dotnet-format` saw zero vuln data.
423
+ * D025f (2.4.7) direct PackageReference scan via osv-scanner.
441
424
  *
442
- * Project-file gating: dotnet list requires a .csproj or .sln in cwd
443
- * to identify what to scan. Without one it'd fail with a non-JSON
444
- * error message. Mirrors the python pack manifest gating from 10h.3.3
445
- * return null cleanly rather than scan an unrelated scope.
425
+ * Fallback gather path that bypasses `dotnet list package` entirely.
426
+ * Used when (a) dotnet isn't installed or (b) dotnet ran but produced
427
+ * no output (D036: cwd is a parent directory and dotnet can't pick
428
+ * a project file).
429
+ *
430
+ * Architecture:
431
+ * 1. Walk every `.csproj` under cwd (depth 5).
432
+ * 2. Parse each via `parseCsprojPackageReferences`.
433
+ * 3. Union direct PackageReferences across all .csprojs (dedup by
434
+ * name@version; cross-csproj version collisions documented in
435
+ * the parser-helper layer as last-write-wins at lockfile time).
436
+ * 4. Generate ad-hoc `packages.lock.json` schema → temp file.
437
+ * 5. Invoke osv-scanner via `--lockfile=NuGet:<tmp>`.
438
+ * 6. Parse output via the shared `parseOsvScannerFindings` (same
439
+ * helper kotlin/java/ruby packs already use for their osv path).
440
+ * 7. Clean up temp file.
441
+ *
442
+ * Transitive coverage: NOT included. The direct-parsing path only
443
+ * surfaces vulnerabilities in packages explicitly listed in a .csproj's
444
+ * `<PackageReference>` blocks. Industry studies put ~80% of typical
445
+ * .NET CVE surface on direct refs; the remaining ~20% needs
446
+ * `project.assets.json` or `dotnet list --include-transitive` which
447
+ * the D025c path covers when available.
448
+ *
449
+ * Return shape: same `DepVulnGatherOutcome` as the primary gather.
450
+ * `unavailableReason` carries the original dotnet failure reason
451
+ * concatenated with the fallback's own failure (osv-scanner missing,
452
+ * no parseable references, etc.) so the customer can trace BOTH
453
+ * paths' state from a single message.
446
454
  */
447
- async function gatherCsharpDepVulnsResult(cwd) {
448
- if (!hasCsharpProject(cwd))
449
- return { kind: 'tool-missing' };
450
- // `--include-transitive` (10h.4.4.c) extends the scan to indirect
451
- // deps; without it NuGet would only report vulns where the
452
- // vulnerable package is itself declared in .csproj. Transitive
453
- // attribution lands via `project.assets.json` below.
454
- const vulnRaw = (0, runner_1.run)('dotnet list package --vulnerable --include-transitive --format json 2>/dev/null', cwd, 120000);
455
- if (!vulnRaw)
456
- return { kind: 'no-output' };
455
+ async function gatherDirectPackageReferenceFallback(cwd, dotnetFailureReason) {
456
+ const csprojs = findAllCsprojFiles(cwd);
457
+ if (csprojs.length === 0) {
458
+ // Shouldn't happen (hasCsharpProject already passed) but defensive
459
+ // in case the walk + the gate diverge somehow.
460
+ return {
461
+ kind: 'unavailable',
462
+ reason: `${dotnetFailureReason}; no .csproj files for direct-parsing fallback`,
463
+ };
464
+ }
465
+ const scanner = (0, tool_registry_1.findTool)(tool_registry_1.TOOL_DEFS['osv-scanner'], cwd);
466
+ if (!scanner.available || !scanner.path) {
467
+ return {
468
+ kind: 'unavailable',
469
+ reason: `${dotnetFailureReason}; osv-scanner also unavailable for D025f fallback`,
470
+ };
471
+ }
472
+ // Aggregate PackageReferences across every .csproj. Cross-csproj
473
+ // dedup at the name+version level here; package-only collisions get
474
+ // resolved by `buildNugetAdhocLockfile`'s last-write-wins.
475
+ const entries = [];
476
+ const seen = new Set();
477
+ for (const csprojPath of csprojs) {
478
+ let xml;
479
+ try {
480
+ xml = fs.readFileSync(csprojPath, 'utf-8');
481
+ }
482
+ catch {
483
+ continue;
484
+ }
485
+ for (const entry of (0, nuget_package_reference_1.parseCsprojPackageReferences)(xml)) {
486
+ const key = `${entry.name}@${entry.version}`;
487
+ if (seen.has(key))
488
+ continue;
489
+ seen.add(key);
490
+ entries.push(entry);
491
+ }
492
+ }
493
+ if (entries.length === 0) {
494
+ return {
495
+ kind: 'no-manifest',
496
+ reason: `${csprojs.length} .csproj files contain no parseable PackageReferences (possibly Central Package Management — Version comes from Directory.Packages.props which D025f doesn't yet resolve)`,
497
+ };
498
+ }
499
+ // osv-scanner v2.x detects lockfile ecosystem by FILENAME — there's
500
+ // no `--lockfile=NuGet:<path>` syntax (verified 2026-05-12 during
501
+ // D025f integration; that syntax was speculation). The NuGet
502
+ // extractor only fires when the file is literally named
503
+ // `packages.lock.json`. We create a process-unique temp DIRECTORY
504
+ // and write the adhoc file inside it as `packages.lock.json`; the
505
+ // dir wraps the file so concurrent dxkit runs don't collide on a
506
+ // single `/tmp/packages.lock.json` path.
507
+ const adhocDir = fs.mkdtempSync(path.join(os.tmpdir(), `dxkit-nuget-adhoc-${process.pid}-`));
508
+ const adhocPath = path.join(adhocDir, 'packages.lock.json');
509
+ try {
510
+ fs.writeFileSync(adhocPath, (0, nuget_package_reference_1.buildNugetAdhocLockfile)(entries));
511
+ const raw = (0, runner_1.run)(`${scanner.path} scan source --lockfile=${adhocPath} --format json 2>/dev/null`, cwd, 180000);
512
+ if (!raw) {
513
+ return {
514
+ kind: 'unavailable',
515
+ reason: `${dotnetFailureReason}; D025f fallback ran but osv-scanner produced no output on ${entries.length} parsed PackageReferences`,
516
+ };
517
+ }
518
+ const { counts, findings, vulnsForCvss } = (0, osv_scanner_deps_1.parseOsvScannerFindings)(raw, 'NuGet', 'csharp');
519
+ // Per-finding CVSS enrichment — mirrors the primary csharp gather's
520
+ // OSV alias-fallback path. Direct PackageReferences carry the
521
+ // package name + version; osv-scanner emits the advisory IDs;
522
+ // resolveCvssScores attaches scores via GHSA → CVE alias chain.
523
+ if (findings.length > 0) {
524
+ const resolved = await (0, osv_1.resolveCvssScores)(vulnsForCvss);
525
+ for (const f of findings) {
526
+ const score = resolved.get(f.id);
527
+ if (score !== null && score !== undefined)
528
+ f.cvssScore = score;
529
+ }
530
+ }
531
+ const envelope = {
532
+ schemaVersion: 1,
533
+ tool: 'osv-scanner-nuget-direct',
534
+ enrichment: 'osv.dev',
535
+ counts,
536
+ findings,
537
+ };
538
+ return { kind: 'success', envelope };
539
+ }
540
+ finally {
541
+ // Clean up the whole adhoc directory (file + dir). Best-effort —
542
+ // the OS will reap `/tmp/` on next boot if we miss it.
543
+ try {
544
+ fs.unlinkSync(adhocPath);
545
+ }
546
+ catch {
547
+ /* best-effort */
548
+ }
549
+ try {
550
+ fs.rmdirSync(adhocDir);
551
+ }
552
+ catch {
553
+ /* best-effort */
554
+ }
555
+ }
556
+ }
557
+ /**
558
+ * Run `dotnet list package --vulnerable --include-transitive` on cwd
559
+ * and return a DepVulnGatherOutcome. The "primary" half of the
560
+ * always-merge G_v4_9 strategy. Pulled out of
561
+ * `gatherCsharpDepVulnsResult` so both halves can run in parallel.
562
+ *
563
+ * Returns no-manifest for the dotnet-not-installed case (the
564
+ * dotnet-vulnerable path simply can't run); the fallback-half is
565
+ * the source of truth in that case.
566
+ */
567
+ async function runDotnetVulnerablePath(cwd) {
568
+ const dotnet = (0, tool_registry_1.findTool)(tool_registry_1.TOOL_DEFS['dotnet-format'], cwd);
569
+ if (!dotnet.available || !dotnet.path) {
570
+ return {
571
+ kind: 'no-manifest',
572
+ reason: 'dotnet SDK not installed (osv-scanner path covers this case)',
573
+ };
574
+ }
575
+ const vulnRaw = (0, runner_1.run)(`${dotnet.path} list package --vulnerable --include-transitive --format json 2>/dev/null`, cwd, 120000);
576
+ if (!vulnRaw) {
577
+ // D036 case: dotnet couldn't pick a project from this cwd (e.g.
578
+ // cwd is the repo root, the .csproj lives in a sub-dir). Treat
579
+ // as no-manifest at this layer; the always-merge wrapper relies
580
+ // on the osv-scanner-nuget-direct half to surface vulns instead.
581
+ return { kind: 'no-manifest', reason: 'dotnet list package produced no output (D036)' };
582
+ }
457
583
  const parsed = parseDotnetVulnerableOutput(vulnRaw);
458
- if (!parsed)
459
- return { kind: 'parse-error' };
584
+ if (!parsed) {
585
+ return { kind: 'unavailable', reason: 'dotnet list package output failed JSON parse' };
586
+ }
460
587
  const { counts, findings } = parsed;
461
588
  // Attach top-level attribution to transitive findings (top-level
462
589
  // findings already carry self-attribution from the parser). Skipped
463
590
  // when project.assets.json is absent — user hasn't run
464
- // `dotnet restore`, or the obj/ dir was cleaned. Findings ship with
465
- // `topLevelDep` unset in that case, matching Python's no-venv path.
591
+ // `dotnet restore`, or the obj/ dir was cleaned.
466
592
  const transitiveNeedsAttribution = findings.some((f) => !f.topLevelDep || f.topLevelDep.length === 0);
467
593
  if (transitiveNeedsAttribution) {
468
594
  const topLevelIndex = loadCsharpTopLevelDepIndex(cwd);
@@ -476,56 +602,149 @@ async function gatherCsharpDepVulnsResult(cwd) {
476
602
  }
477
603
  }
478
604
  }
605
+ const envelope = {
606
+ schemaVersion: 1,
607
+ tool: 'dotnet-vulnerable',
608
+ enrichment: null,
609
+ counts,
610
+ findings,
611
+ };
612
+ return { kind: 'success', envelope };
613
+ }
614
+ /**
615
+ * Single source of truth for the csharp pack's dep-vuln gathering.
616
+ * Consumed by `csharpDepVulnsProvider` (capability dispatcher).
617
+ *
618
+ * G_v4_9 (2.4.7 Phase C1.9) always-merge strategy:
619
+ *
620
+ * **Both tools run, results merge by fingerprint** when each is
621
+ * available. The csharp pack's gather is now cwd-invariant: the
622
+ * same fingerprint set is produced regardless of where cwd points
623
+ * within the repo. Pre-G_v4_9 the gather was cwd-sensitive (D107):
624
+ * at sub-roots with stale `obj/project.assets.json`, dotnet
625
+ * returned 0 findings and the safety-net fallback didn't fire
626
+ * (its condition required no project.assets.json); at repo-root,
627
+ * dotnet returned no output (D036) and the fallback fired correctly,
628
+ * surfacing 2 NuGet CVEs on the .NET WinForms benchmark. Different cwd, different
629
+ * totals — same disease class as D086/D087/D091 at the pack-contract
630
+ * layer instead of the consumer layer.
631
+ *
632
+ * **dotnet path** (`dotnet list package --vulnerable --include-transitive`):
633
+ * surfaces transitive vulns via NuGet's own resolution when
634
+ * `dotnet restore` has been run.
635
+ *
636
+ * **osv-scanner-nuget-direct path**: parses every `.csproj` for
637
+ * direct PackageReferences, writes an adhoc `packages.lock.json`,
638
+ * runs osv-scanner. Covers ~80% of typical .NET CVE surface
639
+ * (direct refs only — no transitive resolution).
640
+ *
641
+ * Findings union, fingerprint-deduped at (package, installedVersion,
642
+ * id). Envelope counts recomputed from the merged set. Both tool
643
+ * names join in the envelope's `tool` field so users see what
644
+ * actually ran.
645
+ *
646
+ * Manifest gate: a `.csproj` or `.sln` must be findable within depth 5
647
+ * (D035 / `hasCsharpProject`). Below that, the gather doesn't even
648
+ * try — that's a `no-manifest` outcome.
649
+ */
650
+ async function gatherCsharpDepVulnsResult(cwd) {
651
+ if (!hasCsharpProject(cwd)) {
652
+ return { kind: 'no-manifest', reason: 'no .csproj or .sln found within depth 5' };
653
+ }
654
+ // Run both tools in parallel. Each returns a DepVulnGatherOutcome;
655
+ // we merge whatever succeeds.
656
+ const [primaryOutcome, fallbackOutcome] = await Promise.all([
657
+ runDotnetVulnerablePath(cwd),
658
+ gatherDirectPackageReferenceFallback(cwd, 'G_v4_9 always-merge'),
659
+ ]);
660
+ // Pick the better outcome when both fail to succeed.
661
+ if (primaryOutcome.kind !== 'success' && fallbackOutcome.kind !== 'success') {
662
+ // Surface the more-informative reason. `unavailable` (tool ran
663
+ // and failed) beats `no-manifest` (tool didn't apply).
664
+ if (primaryOutcome.kind === 'unavailable')
665
+ return primaryOutcome;
666
+ if (fallbackOutcome.kind === 'unavailable')
667
+ return fallbackOutcome;
668
+ // Both no-manifest → genuinely nothing to scan.
669
+ return {
670
+ kind: 'no-manifest',
671
+ reason: 'csharp dep-vuln scan unavailable on this cwd: both dotnet and osv-scanner-nuget-direct returned no-manifest',
672
+ };
673
+ }
674
+ // Merge findings from whichever succeeded. Dedup at the pack layer
675
+ // by (package, installedVersion, id) so envelope counts are honest;
676
+ // downstream aggregator also fingerprints to be safe.
677
+ const primaryFindings = primaryOutcome.kind === 'success' ? (primaryOutcome.envelope.findings ?? []) : [];
678
+ const fallbackFindings = fallbackOutcome.kind === 'success' ? (fallbackOutcome.envelope.findings ?? []) : [];
679
+ const merged = [];
680
+ const seen = new Set();
681
+ for (const f of [...primaryFindings, ...fallbackFindings]) {
682
+ const key = `${f.package}\0${f.installedVersion ?? ''}\0${f.id}`;
683
+ if (seen.has(key))
684
+ continue;
685
+ seen.add(key);
686
+ merged.push(f);
687
+ }
479
688
  // Alias-fallback CVSS pass: dotnet --vulnerable ships zero CVSS data
480
689
  // per advisory; the GHSA id (extracted from advisoryUrl) is the only
481
690
  // anchor. resolveCvssScores looks up via GHSA → CVE alias chain.
482
- if (findings.length > 0) {
483
- const cvssInputs = findings.map((f) => ({
691
+ // Run on the merged set so fallback findings also get enriched if
692
+ // dotnet's path didn't carry them.
693
+ if (merged.length > 0) {
694
+ const cvssInputs = merged.map((f) => ({
484
695
  primaryId: f.id,
485
696
  embeddedCvss: f.cvssScore ?? null,
486
697
  aliases: f.aliases ?? [],
487
698
  }));
488
699
  const resolved = await (0, osv_1.resolveCvssScores)(cvssInputs);
489
- for (const f of findings) {
700
+ for (const f of merged) {
490
701
  const score = resolved.get(f.id);
491
702
  if (score !== null && score !== undefined)
492
703
  f.cvssScore = score;
493
704
  }
494
705
  }
706
+ // Recompute counts from merged set so the envelope is internally
707
+ // consistent (pre-G_v4_9 each path computed its own counts; sum
708
+ // would double-count overlapping advisories). aggregator-ok: this
709
+ // builds the pack's DepVulnResult envelope from its OWN findings,
710
+ // which is the dispatcher input — distinct from consumer-side
711
+ // re-aggregation across envelopes that G_v4_8 prohibits.
712
+ const counts = { critical: 0, high: 0, medium: 0, low: 0 };
713
+ for (const f of merged)
714
+ counts[f.severity]++; // aggregator-ok: pack envelope counts from own merged findings
715
+ // Compose envelope tool names from what actually succeeded.
716
+ const tools = [];
717
+ if (primaryOutcome.kind === 'success')
718
+ tools.push(primaryOutcome.envelope.tool);
719
+ if (fallbackOutcome.kind === 'success')
720
+ tools.push(fallbackOutcome.envelope.tool);
721
+ const enrichment = fallbackOutcome.kind === 'success' ? fallbackOutcome.envelope.enrichment : null;
495
722
  const envelope = {
496
723
  schemaVersion: 1,
497
- tool: 'dotnet-vulnerable',
498
- enrichment: null,
724
+ tool: tools.join(', '),
725
+ enrichment,
499
726
  counts,
500
- findings,
727
+ findings: merged,
501
728
  };
502
729
  return { kind: 'success', envelope };
503
730
  }
504
- /** True if `cwd` has a .csproj or .sln file at depth 0 or 1 (workspace
505
- * layouts often nest projects one level under the repo root). */
731
+ /**
732
+ * True if `cwd` (or any nested directory up to depth 5) contains a
733
+ * `.csproj` or `.sln` file. Used as the depVulns gather preflight —
734
+ * `dotnet list package --vulnerable` needs a project file in scope.
735
+ *
736
+ * Depth 5 mirrors `csharp.detect()`'s recursive walk (D024 / 2.4.7):
737
+ * enterprise layouts like the .NET WinForms benchmark nest .csproj under
738
+ * `Code/Source/Dev/Core/<Module>/`. Pre-D035, this was a depth-1
739
+ * custom walk; symmetrical with detect()'s old depth-3 limit until
740
+ * D024 broke the symmetry. The Sprint A validation
741
+ * (2026-05-12) surfaced the inconsistency — D025c's tool-registry
742
+ * probe was unreachable from `Code/Source/` because this preflight
743
+ * rejected before reaching it. See D035 in tmp/known-defects.md.
744
+ */
506
745
  function hasCsharpProject(cwd) {
507
- let entries;
508
- try {
509
- entries = fs.readdirSync(cwd, { withFileTypes: true });
510
- }
511
- catch {
512
- return false;
513
- }
514
- for (const e of entries) {
515
- if (e.isFile() && (e.name.endsWith('.csproj') || e.name.endsWith('.sln')))
516
- return true;
517
- if (e.isDirectory() && !e.name.startsWith('.') && e.name !== 'node_modules') {
518
- try {
519
- const sub = fs.readdirSync(path.join(cwd, e.name), { withFileTypes: true });
520
- if (sub.some((s) => s.isFile() && (s.name.endsWith('.csproj') || s.name.endsWith('.sln'))))
521
- return true;
522
- }
523
- catch {
524
- /* unreadable subdir — ignore */
525
- }
526
- }
527
- }
528
- return false;
746
+ return (dirHasMatching(cwd, /\.(sln|csproj)$/) ||
747
+ (0, walk_paths_1.walkPaths)(cwd, { extensions: ['.csproj', '.sln'] }).length > 0);
529
748
  }
530
749
  const csharpDepVulnsProvider = {
531
750
  source: 'csharp',
@@ -533,6 +752,9 @@ const csharpDepVulnsProvider = {
533
752
  const outcome = await gatherCsharpDepVulnsResult(cwd);
534
753
  return outcome.kind === 'success' ? outcome.envelope : null;
535
754
  },
755
+ async gatherOutcome(cwd) {
756
+ return gatherCsharpDepVulnsResult(cwd);
757
+ },
536
758
  };
537
759
  /**
538
760
  * Single source of truth for the csharp pack's lint gathering.
@@ -599,11 +821,56 @@ function gatherCsharpCoverageResult(cwd) {
599
821
  return null;
600
822
  return { schemaVersion: 1, tool: `coverage:${coverage.source}`, coverage };
601
823
  }
824
+ /**
825
+ * Run `dotnet test --collect:"XPlat Code Coverage"` from cwd (D021).
826
+ *
827
+ * `dotnet test --collect` is the canonical way to materialize coverage
828
+ * across the whole solution. It auto-discovers test projects and writes
829
+ * a cobertura XML into `TestResults/<guid>/coverage.cobertura.xml`,
830
+ * where `<guid>` is generated per test run. The artifact param uses
831
+ * the function form so the helper locates the actual file post-run via
832
+ * the existing `findCoberturaArtifact` (which already knows the
833
+ * TestResults layout and the explicit `coverage/coverage.cobertura.xml`
834
+ * fallback).
835
+ *
836
+ * `--results-directory TestResults` pins the parent directory so
837
+ * `findCoberturaArtifact` doesn't have to walk arbitrary roots — the
838
+ * GUID-named subdirectory underneath is still non-deterministic but
839
+ * bounded.
840
+ *
841
+ * Preflight: require a `.csproj` or `.sln` in cwd. Without one, this
842
+ * isn't a .NET project root. We don't preflight `dotnet` itself — the
843
+ * spawn ENOENT path classifies it as `unavailable` cleanly.
844
+ */
845
+ function runCsharpTestsWithCoverage(cwd) {
846
+ return Promise.resolve((0, run_tests_helper_1.runTestsWithCoverage)({
847
+ pack: 'csharp',
848
+ cmd: 'dotnet test --collect:"XPlat Code Coverage" --results-directory TestResults',
849
+ cwd,
850
+ artifact: (cwd) => {
851
+ const abs = findCoberturaArtifact(cwd);
852
+ if (!abs)
853
+ return null;
854
+ return path.relative(cwd, abs).split(path.sep).join('/');
855
+ },
856
+ preflight: (cwd) => {
857
+ const hasManifest = dirHasMatching(cwd, /\.(sln|csproj)$/i) ||
858
+ (0, walk_paths_1.walkPaths)(cwd, { extensions: ['.csproj', '.sln'] }).length > 0;
859
+ if (!hasManifest) {
860
+ return 'no .csproj or .sln in this directory tree — not a .NET project';
861
+ }
862
+ return null;
863
+ },
864
+ }));
865
+ }
602
866
  const csharpCoverageProvider = {
603
867
  source: 'csharp',
604
868
  async gather(cwd) {
605
869
  return gatherCsharpCoverageResult(cwd);
606
870
  },
871
+ async runTests(cwd) {
872
+ return runCsharpTestsWithCoverage(cwd);
873
+ },
607
874
  };
608
875
  /**
609
876
  * Capture C# `using` directives from source text, including
@@ -669,7 +936,7 @@ const csharpImportsProvider = {
669
936
  * `.csproj` referencing these returns null.
670
937
  */
671
938
  function gatherCsharpTestFrameworkResult(cwd) {
672
- const hasCsproj = (0, runner_1.fileExists)(cwd, '*.csproj') || !!findMatchingRecursive(cwd, /\.csproj$/, 3);
939
+ const hasCsproj = (0, runner_1.fileExists)(cwd, '*.csproj') || (0, walk_paths_1.walkPaths)(cwd, { extensions: ['.csproj'] }).length > 0;
673
940
  if (!hasCsproj)
674
941
  return null;
675
942
  const csproj = (0, runner_1.run)("find . -name '*.csproj' -exec grep -l 'xunit\\|nunit\\|MSTest' {} \\; 2>/dev/null | head -1", cwd);
@@ -690,19 +957,23 @@ const csharpTestFrameworkProvider = {
690
957
  * or null — callers skip cleanly on null.
691
958
  */
692
959
  function findCsharpLicenseInput(cwd) {
693
- // .sln in root first one pass over the whole solution.
694
- let entries;
695
- try {
696
- entries = fs.readdirSync(cwd, { withFileTypes: true });
697
- }
698
- catch {
960
+ // Prefer a `.sln` (covers every csproj in the solution in one
961
+ // nuget-license pass) over a single `.csproj`. Within each kind,
962
+ // pick the shallowest match — closest to repo root is the most
963
+ // likely "primary" manifest. Depth-unlimited via the canonical
964
+ // walker, so deep monorepos (manifests 6–9 levels under repo root)
965
+ // discover them where prior hard depth caps silently missed.
966
+ const manifests = (0, walk_paths_1.walkPaths)(cwd, { extensions: ['.csproj', '.sln'] });
967
+ if (manifests.length === 0)
699
968
  return null;
700
- }
701
- const sln = entries.find((e) => e.isFile() && e.name.endsWith('.sln'));
702
- if (sln)
703
- return path.join(cwd, sln.name);
704
- // Fall back to first .csproj reachable within the standard depth.
705
- return findMatchingRecursive(cwd, /\.csproj$/, 3);
969
+ manifests.sort((a, b) => {
970
+ const aIsSln = a.endsWith('.sln') ? 0 : 1;
971
+ const bIsSln = b.endsWith('.sln') ? 0 : 1;
972
+ if (aIsSln !== bIsSln)
973
+ return aIsSln - bIsSln;
974
+ return a.split('/').length - b.split('/').length;
975
+ });
976
+ return path.join(cwd, manifests[0]);
706
977
  }
707
978
  /**
708
979
  * Single source of truth for the csharp pack's license gathering.
@@ -714,25 +985,116 @@ function findCsharpLicenseInput(cwd) {
714
985
  * tool, wrapped. Returns null cleanly when no .sln/.csproj is present
715
986
  * or when the tool isn't installed.
716
987
  */
988
+ /**
989
+ * D031-2 (2.4.7) — degraded license inventory fallback.
990
+ *
991
+ * When `nuget-license` is unavailable but we have `.csproj` files,
992
+ * parse direct PackageReferences from each .csproj and emit a
993
+ * LicensesResult with `licenseType: 'UNKNOWN'` per package — name +
994
+ * version only. Reuses the D025f-1 `parseCsprojPackageReferences`
995
+ * helper (and `findAllCsprojFiles` walker) so this fallback shares
996
+ * one truth source with the depVulns direct-scan path.
997
+ *
998
+ * Customer outcome on the .NET WinForms benchmark: pre-D031 → "0 packages" (with the
999
+ * pack reporting `unavailable` because `nuget-license` is missing).
1000
+ * Post-D031 → "53 packages identified; license info unavailable" via
1001
+ * the markdown framing banner from `analyzers/licenses/index.ts`.
1002
+ * The customer can decide remediation BEFORE installing nuget-license
1003
+ * (e.g., "we have 53 NuGet deps; install nuget-license to see their
1004
+ * licenses" is more actionable than "0 packages").
1005
+ *
1006
+ * Returns `null` when the degraded path itself can't produce data
1007
+ * (no .csproj files at all, or every .csproj has zero parseable
1008
+ * PackageReferences). Caller propagates the original `unavailable`
1009
+ * outcome in those cases.
1010
+ */
1011
+ function gatherCsharpLicensesDegradedInventory(cwd) {
1012
+ const csprojs = findAllCsprojFiles(cwd);
1013
+ if (csprojs.length === 0)
1014
+ return null;
1015
+ const entries = [];
1016
+ const seen = new Set();
1017
+ for (const csprojPath of csprojs) {
1018
+ let xml;
1019
+ try {
1020
+ xml = fs.readFileSync(csprojPath, 'utf-8');
1021
+ }
1022
+ catch {
1023
+ continue;
1024
+ }
1025
+ for (const entry of (0, nuget_package_reference_1.parseCsprojPackageReferences)(xml)) {
1026
+ const key = `${entry.name}@${entry.version}`;
1027
+ if (seen.has(key))
1028
+ continue;
1029
+ seen.add(key);
1030
+ entries.push(entry);
1031
+ }
1032
+ }
1033
+ if (entries.length === 0)
1034
+ return null;
1035
+ const findings = entries.map((e) => ({
1036
+ package: e.name,
1037
+ version: e.version,
1038
+ licenseType: 'UNKNOWN',
1039
+ // No license URL / description / supplier — the degraded path only
1040
+ // has the manifest reference. Customer can identify what's
1041
+ // installed; install nuget-license for the full inventory.
1042
+ }));
1043
+ return {
1044
+ schemaVersion: 1,
1045
+ tool: 'csharp-package-reference-degraded',
1046
+ findings,
1047
+ };
1048
+ }
717
1049
  function gatherCsharpLicensesResult(cwd) {
718
1050
  const input = findCsharpLicenseInput(cwd);
719
- if (!input)
720
- return null;
1051
+ if (!input) {
1052
+ return { kind: 'no-manifest', reason: 'no .csproj or .sln found within depth 3' };
1053
+ }
721
1054
  const status = (0, tool_registry_1.findTool)(tool_registry_1.TOOL_DEFS['nuget-license'], cwd);
722
- if (!status.available || !status.path)
723
- return null;
1055
+ if (!status.available || !status.path) {
1056
+ // D031-2: degraded-inventory fallback. nuget-license is the
1057
+ // canonical tool but parsing direct PackageReferences gives the
1058
+ // customer at minimum a name+version inventory while the tool
1059
+ // is being installed. We surface this through the SUCCESS path
1060
+ // (envelope's tool name carries the `-degraded` suffix so
1061
+ // downstream renderers can disambiguate) so the LicensesReport's
1062
+ // summary count reflects real data — the framing banner from
1063
+ // `analyzers/licenses/index.ts` explains the asterisk.
1064
+ //
1065
+ // Path B (no .csproj or empty PackageReferences across all
1066
+ // csprojs): degraded inventory returns null; we propagate the
1067
+ // original `unavailable` so the report frames the gap honestly
1068
+ // ("0 packages" with the explanatory ⚠ banner).
1069
+ const degraded = gatherCsharpLicensesDegradedInventory(cwd);
1070
+ if (degraded)
1071
+ return { kind: 'success', envelope: degraded };
1072
+ return { kind: 'unavailable', reason: 'nuget-license not installed' };
1073
+ }
724
1074
  const raw = (0, runner_1.run)(`${status.path} -i "${input}" -o JsonPretty 2>/dev/null`, cwd, 180000);
725
- if (!raw)
726
- return null;
1075
+ if (!raw) {
1076
+ // D031-2 extended (2.4.7): nuget-license commonly produces no
1077
+ // output when `dotnet restore` hasn't been run (no
1078
+ // `obj/project.assets.json` for it to read). Rather than tell
1079
+ // the customer "0 packages," fall back to direct PackageReference
1080
+ // parsing so they get a real inventory. Validated on the .NET WinForms benchmark
1081
+ // 2026-05-13: 53 packages surface via the fallback when
1082
+ // nuget-license itself returns empty.
1083
+ const degraded = gatherCsharpLicensesDegradedInventory(cwd);
1084
+ if (degraded)
1085
+ return { kind: 'success', envelope: degraded };
1086
+ return { kind: 'unavailable', reason: 'nuget-license produced no output' };
1087
+ }
727
1088
  let data;
728
1089
  try {
729
1090
  data = JSON.parse(raw);
730
1091
  }
731
- catch {
732
- return null;
1092
+ catch (err) {
1093
+ return { kind: 'unavailable', reason: `nuget-license parse error: ${err.message}` };
1094
+ }
1095
+ if (!Array.isArray(data)) {
1096
+ return { kind: 'unavailable', reason: 'nuget-license output was not a JSON array' };
733
1097
  }
734
- if (!Array.isArray(data))
735
- return null;
736
1098
  // Top-level attribution reuses project.assets.json via
737
1099
  // loadCsharpTopLevelDepIndex. Same self-parent invariant as the
738
1100
  // other packs. Missing assets-json (user hasn't run dotnet restore)
@@ -757,15 +1119,34 @@ function gatherCsharpLicensesResult(cwd) {
757
1119
  isTopLevel: hasIndex ? (parents?.includes(entry.PackageId) ?? false) : undefined,
758
1120
  });
759
1121
  }
760
- return {
1122
+ // D031-2 extended (2.4.7): if nuget-license ran AND parsed cleanly
1123
+ // but produced zero valid findings, the canonical scan failed
1124
+ // silently (typically: assets.json absent → tool emits an empty
1125
+ // array). Same customer-visibility concern as the no-output path
1126
+ // — fall back to degraded inventory so the customer sees real
1127
+ // packages instead of "0 packages." If the degraded path also
1128
+ // returns null (no .csproj parseable), drop through to the
1129
+ // empty-success path (legitimate "no licenseable deps in this
1130
+ // pack's scope").
1131
+ if (findings.length === 0) {
1132
+ const degraded = gatherCsharpLicensesDegradedInventory(cwd);
1133
+ if (degraded)
1134
+ return { kind: 'success', envelope: degraded };
1135
+ }
1136
+ const envelope = {
761
1137
  schemaVersion: 1,
762
1138
  tool: 'nuget-license',
763
1139
  findings,
764
1140
  };
1141
+ return { kind: 'success', envelope };
765
1142
  }
766
1143
  const csharpLicensesProvider = {
767
1144
  source: 'csharp',
768
1145
  async gather(cwd) {
1146
+ const outcome = gatherCsharpLicensesResult(cwd);
1147
+ return outcome.kind === 'success' ? outcome.envelope : null;
1148
+ },
1149
+ async gatherOutcome(cwd) {
769
1150
  return gatherCsharpLicensesResult(cwd);
770
1151
  },
771
1152
  };
@@ -777,10 +1158,107 @@ exports.csharp = {
777
1158
  // because gather.ts only matched *.test.*, *.spec.*, *_test.*, test_*.
778
1159
  testFilePatterns: ['*Tests.cs', '*.Tests.cs'],
779
1160
  extraExcludes: ['bin', 'obj', 'TestResults', 'packages'],
1161
+ // D028 (2.4.7): auto-generated .NET source patterns. Visual Studio's
1162
+ // WinForms designer creates `*.designer.cs` (UI scaffolding,
1163
+ // typically hundreds of lines, repetitive). T4 templates emit
1164
+ // `*.g.cs`. Source generators (Roslyn) emit `*.g.i.cs`. The legacy
1165
+ // AssemblyInfo split files (`*.AssemblyInfo.cs`,
1166
+ // `*.AssemblyAttributes.cs`) are MSBuild-generated. None of these
1167
+ // are human-authored; counting them as "source" inflates Code
1168
+ // Quality + Maintainability dimensions on any .NET UI codebase
1169
+ // (the .NET WinForms benchmark is the motivating case — large
1170
+ // WinForms enterprise app with extensive designer.cs files).
1171
+ autogeneratedSourcePatterns: [
1172
+ '*.designer.cs',
1173
+ '*.Designer.cs',
1174
+ '*.g.cs',
1175
+ '*.g.i.cs',
1176
+ '*.generated.cs',
1177
+ '*.AssemblyInfo.cs',
1178
+ '*.AssemblyAttributes.cs',
1179
+ // WCF "Connected Services" auto-generated proxy classes
1180
+ // (svcutil.exe / Add Service Reference). The .NET WinForms
1181
+ // benchmark's `Reference.cs` was 42,370 lines of WCF proxy. Other tools'
1182
+ // service-reference output (gRPC tools, OpenAPI generators)
1183
+ // commonly also emit `Reference.cs`.
1184
+ 'Reference.cs',
1185
+ ],
1186
+ // D027 (2.4.7): C# uses XML-doc triple-slash comments above
1187
+ // public APIs. Pre-D027 the generic doc-comment heuristic
1188
+ // matched JSDoc `/**` only, so the .NET WinForms benchmark's 3,234 .cs files
1189
+ // contributed zero to docCommentFiles even though many carry
1190
+ // `/// <summary>` blocks. POSIX class `[[:space:]]` for cross-
1191
+ // platform grep (BSD vs GNU).
1192
+ docCommentPatterns: ['^[[:space:]]*///'],
1193
+ // D034 (2.4.7): canonical .NET TLS-bypass idioms. Pre-D034 only
1194
+ // Node-shaped (`NODE_TLS_REJECT_UNAUTHORIZED`) and Python-shaped
1195
+ // (`VERIFY_SSL`) tokens were grep'd — every C# project reported
1196
+ // zero TLS-bypass findings even when using
1197
+ // `ServerCertificateValidationCallback = (s,c,ch,e) => true` (the
1198
+ // canonical "accept any certificate" pattern). The .NET WinForms
1199
+ // benchmark is the motivating case: WCF + enterprise integration code commonly
1200
+ // ships permissive callbacks.
1201
+ //
1202
+ // Listed tokens:
1203
+ // - ServicePointManager.ServerCertificateValidationCallback (legacy)
1204
+ // - HttpClientHandler.ServerCertificateCustomValidationCallback
1205
+ // (HttpClient — .NET Core / 5+)
1206
+ // - SslStream.AuthenticateAsClient* with RemoteCertificateValidationCallback
1207
+ // - HttpClientHandler.DangerousAcceptAnyServerCertificateValidator
1208
+ // (.NET 5+ shorthand — literally named `Dangerous*`)
1209
+ tlsBypassPatterns: [
1210
+ 'ServerCertificateValidationCallback',
1211
+ 'ServerCertificateCustomValidationCallback',
1212
+ 'RemoteCertificateValidationCallback',
1213
+ 'DangerousAcceptAnyServerCertificateValidator',
1214
+ ],
1215
+ upgradeCommand(name, version) {
1216
+ return `dotnet add package ${name} --version ${version}`;
1217
+ },
1218
+ // .NET spans two very different application shapes: ASP.NET MVC /
1219
+ // Web API (controllers, endpoints) and WinForms / WPF desktop
1220
+ // (Forms, ViewModels, UserControls). Both are first-class
1221
+ // contributors here — a WinForms enterprise app's primary
1222
+ // architecture IS its Forms + Services layer, not a notional
1223
+ // controllers/ directory that doesn't exist on disk. The narrower
1224
+ // routePaths subset stays silent on desktop-only repos so the
1225
+ // "Add API documentation" recommendation doesn't fire there.
1226
+ architecturalShape: {
1227
+ primaryComponentPaths: [
1228
+ '/Controllers/',
1229
+ '/Services/',
1230
+ '/Forms/',
1231
+ '/ViewModels/',
1232
+ '/Pages/',
1233
+ '/Views/',
1234
+ '/UserControls/',
1235
+ '/Handlers/',
1236
+ ],
1237
+ routePaths: ['/Controllers/', '/Endpoints/'],
1238
+ modelPaths: ['/Models/', '/Entities/', '/DTOs/', '/DataTransferObjects/', '/Domain/'],
1239
+ vocabulary: {
1240
+ components: 'Forms/Services',
1241
+ models: 'models',
1242
+ routes: 'endpoints',
1243
+ },
1244
+ testGapPriority: {
1245
+ high: ['/Controllers/', '/Services/', '/Handlers/'],
1246
+ medium: ['/Forms/', '/ViewModels/', '/Pages/', '/Views/', '/UserControls/'],
1247
+ },
1248
+ },
1249
+ clocLanguageNames: ['C#'],
780
1250
  detect(cwd) {
781
- return (dirHasMatching(cwd, /\.(sln|csproj)$/) || findMatchingRecursive(cwd, /\.csproj$/, 3) !== null);
1251
+ // Depth 5 covers enterprise .NET layouts like
1252
+ // `Code/Source/Dev/Core/<Module>/<Module>.csproj` (D024).
1253
+ // Lower limits silently miss these from the repo root.
1254
+ return (dirHasMatching(cwd, /\.(sln|csproj)$/) ||
1255
+ (0, walk_paths_1.walkPaths)(cwd, { extensions: ['.csproj', '.sln'] }).length > 0);
782
1256
  },
783
- tools: ['dotnet-format', 'nuget-license'],
1257
+ // D025f (2.4.7): osv-scanner added — used by the direct-PackageReference
1258
+ // fallback in gatherDirectPackageReferenceFallback when `dotnet list
1259
+ // package` can't produce output (D036). Cross-pack tool; kotlin/java/ruby
1260
+ // already use it via the shared osv-scanner-deps helper.
1261
+ tools: ['dotnet-format', 'nuget-license', 'osv-scanner'],
784
1262
  // p/csharp semgrep ruleset is sparse — skip until it matures.
785
1263
  semgrepRulesets: [],
786
1264
  capabilities: {