codebase-onboarder 0.5.0 → 0.7.0

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 (448) hide show
  1. package/README.md +196 -47
  2. package/bin/build-if-source.js +23 -0
  3. package/cli/commands.d.ts +26 -0
  4. package/cli/commands.js +836 -811
  5. package/cli/commands.js.map +1 -0
  6. package/cli/explorer/advanced.d.ts +14 -0
  7. package/cli/explorer/advanced.js +449 -0
  8. package/cli/explorer/advanced.js.map +1 -0
  9. package/cli/explorer/app.d.ts +1 -0
  10. package/cli/explorer/app.js +396 -229
  11. package/cli/explorer/app.js.map +1 -0
  12. package/cli/explorer/assistant.d.ts +9 -0
  13. package/cli/explorer/assistant.js +58 -0
  14. package/cli/explorer/assistant.js.map +1 -0
  15. package/cli/explorer/commands.d.ts +17 -0
  16. package/cli/explorer/commands.js +400 -162
  17. package/cli/explorer/commands.js.map +1 -0
  18. package/cli/explorer/featureViews.d.ts +124 -0
  19. package/cli/explorer/featureViews.js +179 -0
  20. package/cli/explorer/featureViews.js.map +1 -0
  21. package/cli/explorer/github.d.ts +21 -0
  22. package/cli/explorer/github.js +73 -0
  23. package/cli/explorer/github.js.map +1 -0
  24. package/cli/explorer/graphs.d.ts +7 -0
  25. package/cli/explorer/graphs.js +173 -0
  26. package/cli/explorer/graphs.js.map +1 -0
  27. package/cli/explorer/picker.d.ts +22 -0
  28. package/cli/explorer/picker.js +150 -0
  29. package/cli/explorer/picker.js.map +1 -0
  30. package/cli/explorer/report.d.ts +85 -0
  31. package/cli/explorer/report.js +60 -0
  32. package/cli/explorer/report.js.map +1 -0
  33. package/cli/explorer/session.d.ts +449 -0
  34. package/cli/explorer/session.js +184 -95
  35. package/cli/explorer/session.js.map +1 -0
  36. package/cli/explorer/views.d.ts +19 -0
  37. package/cli/explorer/views.js +496 -412
  38. package/cli/explorer/views.js.map +1 -0
  39. package/cli/explorer/wrap.d.ts +1 -0
  40. package/cli/explorer/wrap.js +33 -0
  41. package/cli/explorer/wrap.js.map +1 -0
  42. package/cli/main.d.ts +1 -0
  43. package/cli/main.js +235 -138
  44. package/cli/main.js.map +1 -0
  45. package/cli/prompt.d.ts +5 -0
  46. package/cli/prompt.js +84 -80
  47. package/cli/prompt.js.map +1 -0
  48. package/cli/scanOptions.d.ts +2 -0
  49. package/cli/scanOptions.js +11 -0
  50. package/cli/scanOptions.js.map +1 -0
  51. package/cli/ui.d.ts +33 -0
  52. package/cli/ui.js +55 -35
  53. package/cli/ui.js.map +1 -0
  54. package/cli/wizard.d.ts +187 -0
  55. package/cli/wizard.js +304 -284
  56. package/cli/wizard.js.map +1 -0
  57. package/package.json +15 -4
  58. package/public/app.d.ts +1 -0
  59. package/public/app.js +1057 -1009
  60. package/public/app.js.map +1 -0
  61. package/public/browser.d.ts +68 -0
  62. package/public/browser.js +2 -0
  63. package/public/browser.js.map +1 -0
  64. package/public/index.html +30 -13
  65. package/public/js/about.d.ts +6 -0
  66. package/public/js/about.js +165 -172
  67. package/public/js/about.js.map +1 -0
  68. package/public/js/aiDraft.d.ts +3 -0
  69. package/public/js/aiDraft.js +116 -124
  70. package/public/js/aiDraft.js.map +1 -0
  71. package/public/js/analysisPanel.d.ts +7 -0
  72. package/public/js/analysisPanel.js +166 -166
  73. package/public/js/analysisPanel.js.map +1 -0
  74. package/public/js/analysisReport.d.ts +38 -0
  75. package/public/js/analysisReport.js +105 -110
  76. package/public/js/analysisReport.js.map +1 -0
  77. package/public/js/api.d.ts +26 -0
  78. package/public/js/api.js +176 -167
  79. package/public/js/api.js.map +1 -0
  80. package/public/js/atlas.d.ts +6 -0
  81. package/public/js/atlas.js +50 -58
  82. package/public/js/atlas.js.map +1 -0
  83. package/public/js/blameView.d.ts +2 -0
  84. package/public/js/blameView.js +36 -37
  85. package/public/js/blameView.js.map +1 -0
  86. package/public/js/codeTab.d.ts +3 -0
  87. package/public/js/codeTab.js +175 -180
  88. package/public/js/codeTab.js.map +1 -0
  89. package/public/js/codeViewer.d.ts +5 -0
  90. package/public/js/codeViewer.js +162 -150
  91. package/public/js/codeViewer.js.map +1 -0
  92. package/public/js/components/FileChip.d.ts +9 -0
  93. package/public/js/components/FileChip.js +45 -49
  94. package/public/js/components/FileChip.js.map +1 -0
  95. package/public/js/components/MetricSparkline.d.ts +7 -0
  96. package/public/js/components/MetricSparkline.js +35 -44
  97. package/public/js/components/MetricSparkline.js.map +1 -0
  98. package/public/js/components/RiskBadge.d.ts +9 -0
  99. package/public/js/components/RiskBadge.js +55 -63
  100. package/public/js/components/RiskBadge.js.map +1 -0
  101. package/public/js/deepAnalysisView.d.ts +99 -0
  102. package/public/js/deepAnalysisView.js +282 -302
  103. package/public/js/deepAnalysisView.js.map +1 -0
  104. package/public/js/diagramPane.d.ts +8 -0
  105. package/public/js/diagramPane.js +133 -135
  106. package/public/js/diagramPane.js.map +1 -0
  107. package/public/js/diffView.d.ts +6 -0
  108. package/public/js/diffView.js +222 -209
  109. package/public/js/diffView.js.map +1 -0
  110. package/public/js/docsView.d.ts +2 -0
  111. package/public/js/docsView.js +441 -442
  112. package/public/js/docsView.js.map +1 -0
  113. package/public/js/dom.d.ts +189 -0
  114. package/public/js/dom.js +5 -0
  115. package/public/js/dom.js.map +1 -0
  116. package/public/js/fileSourceBrowser.d.ts +5 -0
  117. package/public/js/fileSourceBrowser.js +33 -41
  118. package/public/js/fileSourceBrowser.js.map +1 -0
  119. package/public/js/flameGraph.d.ts +5 -0
  120. package/public/js/flameGraph.js +62 -72
  121. package/public/js/flameGraph.js.map +1 -0
  122. package/public/js/forceGraph.d.ts +10 -0
  123. package/public/js/forceGraph.js +581 -556
  124. package/public/js/forceGraph.js.map +1 -0
  125. package/public/js/heatmap.d.ts +4 -0
  126. package/public/js/heatmap.js +142 -164
  127. package/public/js/heatmap.js.map +1 -0
  128. package/public/js/highlight.d.ts +2 -0
  129. package/public/js/highlight.js +116 -107
  130. package/public/js/highlight.js.map +1 -0
  131. package/public/js/html.d.ts +2 -0
  132. package/public/js/html.js +14 -44
  133. package/public/js/html.js.map +1 -0
  134. package/public/js/insightsView.d.ts +5 -0
  135. package/public/js/insightsView.js +93 -111
  136. package/public/js/insightsView.js.map +1 -0
  137. package/public/js/inspector.d.ts +37 -0
  138. package/public/js/inspector.js +416 -445
  139. package/public/js/inspector.js.map +1 -0
  140. package/public/js/llm.d.ts +113 -0
  141. package/public/js/llm.js +233 -272
  142. package/public/js/llm.js.map +1 -0
  143. package/public/js/mapView.d.ts +3 -0
  144. package/public/js/mapView.js +121 -120
  145. package/public/js/mapView.js.map +1 -0
  146. package/public/js/markdown.d.ts +3 -0
  147. package/public/js/markdown.js +84 -91
  148. package/public/js/markdown.js.map +1 -0
  149. package/public/js/mindmap.d.ts +32 -0
  150. package/public/js/mindmap.js +244 -259
  151. package/public/js/mindmap.js.map +1 -0
  152. package/public/js/mobilePanels.d.ts +5 -0
  153. package/public/js/mobilePanels.js +50 -0
  154. package/public/js/mobilePanels.js.map +1 -0
  155. package/public/js/repoFiles.d.ts +12 -0
  156. package/public/js/repoFiles.js +28 -27
  157. package/public/js/repoFiles.js.map +1 -0
  158. package/public/js/reviewView.d.ts +28 -0
  159. package/public/js/reviewView.js +256 -0
  160. package/public/js/reviewView.js.map +1 -0
  161. package/public/js/sbomView.d.ts +3 -0
  162. package/public/js/sbomView.js +59 -67
  163. package/public/js/sbomView.js.map +1 -0
  164. package/public/js/scanCache.d.ts +16 -0
  165. package/public/js/scanCache.js +67 -65
  166. package/public/js/scanCache.js.map +1 -0
  167. package/public/js/search.d.ts +7 -0
  168. package/public/js/search.js +868 -800
  169. package/public/js/search.js.map +1 -0
  170. package/public/js/searchLocal.d.ts +42 -0
  171. package/public/js/searchLocal.js +87 -0
  172. package/public/js/searchLocal.js.map +1 -0
  173. package/public/js/serverSettings.d.ts +5 -0
  174. package/public/js/serverSettings.js +153 -164
  175. package/public/js/serverSettings.js.map +1 -0
  176. package/public/js/state.d.ts +798 -0
  177. package/public/js/state.js +114 -122
  178. package/public/js/state.js.map +1 -0
  179. package/public/js/tour.d.ts +6 -0
  180. package/public/js/tour.js +5 -7
  181. package/public/js/tour.js.map +1 -0
  182. package/public/js/transitions.d.ts +2 -0
  183. package/public/js/transitions.js +9 -10
  184. package/public/js/transitions.js.map +1 -0
  185. package/public/js/tree.d.ts +12 -0
  186. package/public/js/tree.js +157 -154
  187. package/public/js/tree.js.map +1 -0
  188. package/public/js/workflowsView.d.ts +3 -0
  189. package/public/js/workflowsView.js +15 -22
  190. package/public/js/workflowsView.js.map +1 -0
  191. package/public/search.css +495 -0
  192. package/public/styles.css +163 -346
  193. package/public/workbench.css +166 -0
  194. package/server/apiAuth.d.ts +3 -0
  195. package/server/apiAuth.js +18 -20
  196. package/server/apiAuth.js.map +1 -0
  197. package/server/apiDiff.d.ts +2 -0
  198. package/server/apiDiff.js +20 -18
  199. package/server/apiDiff.js.map +1 -0
  200. package/server/apiDocs.d.ts +2 -0
  201. package/server/apiDocs.js +38 -40
  202. package/server/apiDocs.js.map +1 -0
  203. package/server/apiFile.d.ts +1 -0
  204. package/server/apiFile.js +14 -17
  205. package/server/apiFile.js.map +1 -0
  206. package/server/apiGitBlame.d.ts +2 -0
  207. package/server/apiGitBlame.js +50 -51
  208. package/server/apiGitBlame.js.map +1 -0
  209. package/server/apiMcp.d.ts +4 -0
  210. package/server/apiMcp.js +64 -66
  211. package/server/apiMcp.js.map +1 -0
  212. package/server/apiReview.d.ts +2 -0
  213. package/server/apiReview.js +45 -0
  214. package/server/apiReview.js.map +1 -0
  215. package/server/apiScan.d.ts +5 -0
  216. package/server/apiScan.js +90 -71
  217. package/server/apiScan.js.map +1 -0
  218. package/server/apiSearch.d.ts +33 -0
  219. package/server/apiSearch.js +184 -200
  220. package/server/apiSearch.js.map +1 -0
  221. package/server/apiSettings.d.ts +4 -0
  222. package/server/apiSettings.js +84 -85
  223. package/server/apiSettings.js.map +1 -0
  224. package/server/apiTools.d.ts +3 -0
  225. package/server/apiTools.js +55 -56
  226. package/server/apiTools.js.map +1 -0
  227. package/server/auth.d.ts +5 -0
  228. package/server/auth.js +54 -51
  229. package/server/auth.js.map +1 -0
  230. package/server/config.d.ts +42 -0
  231. package/server/config.js +189 -194
  232. package/server/config.js.map +1 -0
  233. package/server/daemon.d.ts +12 -0
  234. package/server/daemon.js +124 -114
  235. package/server/daemon.js.map +1 -0
  236. package/server/fileSourceNode.d.ts +2 -0
  237. package/server/fileSourceNode.js +34 -36
  238. package/server/fileSourceNode.js.map +1 -0
  239. package/server/gitClone.d.ts +9 -0
  240. package/server/gitClone.js +59 -61
  241. package/server/gitClone.js.map +1 -0
  242. package/server/gitDiff.d.ts +36 -0
  243. package/server/gitDiff.js +271 -169
  244. package/server/gitDiff.js.map +1 -0
  245. package/server/gitHistory.d.ts +12 -0
  246. package/server/gitHistory.js +80 -80
  247. package/server/gitHistory.js.map +1 -0
  248. package/server/gitRemote.d.ts +12 -0
  249. package/server/gitRemote.js +60 -0
  250. package/server/gitRemote.js.map +1 -0
  251. package/server/htmlText.d.ts +4 -0
  252. package/server/htmlText.js +16 -23
  253. package/server/htmlText.js.map +1 -0
  254. package/server/http.d.ts +5 -0
  255. package/server/http.js +27 -30
  256. package/server/http.js.map +1 -0
  257. package/server/httpGuards.d.ts +2 -0
  258. package/server/httpGuards.js +47 -43
  259. package/server/httpGuards.js.map +1 -0
  260. package/server/https.d.ts +34 -0
  261. package/server/https.js +121 -111
  262. package/server/https.js.map +1 -0
  263. package/server/index.d.ts +40 -0
  264. package/server/index.js +139 -147
  265. package/server/index.js.map +1 -0
  266. package/server/layout.d.ts +26 -0
  267. package/server/layout.js +59 -37
  268. package/server/layout.js.map +1 -0
  269. package/server/llmProxy.d.ts +3 -0
  270. package/server/llmProxy.js +137 -135
  271. package/server/llmProxy.js.map +1 -0
  272. package/server/logger.d.ts +38 -0
  273. package/server/logger.js +198 -211
  274. package/server/logger.js.map +1 -0
  275. package/server/mcp/analysis.d.ts +29 -0
  276. package/server/mcp/analysis.js +99 -120
  277. package/server/mcp/analysis.js.map +1 -0
  278. package/server/mcp/http.d.ts +9 -0
  279. package/server/mcp/http.js +128 -137
  280. package/server/mcp/http.js.map +1 -0
  281. package/server/mcp/runner.d.ts +68 -0
  282. package/server/mcp/runner.js +208 -218
  283. package/server/mcp/runner.js.map +1 -0
  284. package/server/mcp/server.d.ts +29 -0
  285. package/server/mcp/server.js +166 -179
  286. package/server/mcp/server.js.map +1 -0
  287. package/server/mcp/standalone.d.ts +1 -0
  288. package/server/mcp/standalone.js +8 -10
  289. package/server/mcp/standalone.js.map +1 -0
  290. package/server/mcp/tools.d.ts +2020 -0
  291. package/server/mcp/tools.js +523 -574
  292. package/server/mcp/tools.js.map +1 -0
  293. package/server/paths.d.ts +3 -0
  294. package/server/paths.js +17 -15
  295. package/server/paths.js.map +1 -0
  296. package/server/pidfile.d.ts +9 -0
  297. package/server/pidfile.js +43 -45
  298. package/server/pidfile.js.map +1 -0
  299. package/server/review.d.ts +5 -0
  300. package/server/review.js +35 -0
  301. package/server/review.js.map +1 -0
  302. package/server/router.d.ts +33 -0
  303. package/server/router.js +191 -197
  304. package/server/router.js.map +1 -0
  305. package/server/scanRequest.d.ts +3 -0
  306. package/server/scanRequest.js +36 -0
  307. package/server/scanRequest.js.map +1 -0
  308. package/server/searchIndex.d.ts +17 -0
  309. package/server/searchIndex.js +83 -80
  310. package/server/searchIndex.js.map +1 -0
  311. package/server/sessions.d.ts +14 -0
  312. package/server/sessions.js +62 -70
  313. package/server/sessions.js.map +1 -0
  314. package/server/startup.d.ts +118 -0
  315. package/server/startup.js +143 -148
  316. package/server/startup.js.map +1 -0
  317. package/server/static.d.ts +6 -0
  318. package/server/static.js +28 -31
  319. package/server/static.js.map +1 -0
  320. package/server/tools/install.d.ts +4 -0
  321. package/server/tools/install.js +176 -185
  322. package/server/tools/install.js.map +1 -0
  323. package/server/tools/parse.d.ts +8 -0
  324. package/server/tools/parse.js +112 -114
  325. package/server/tools/parse.js.map +1 -0
  326. package/server/tools/platform.d.ts +9 -0
  327. package/server/tools/platform.js +49 -47
  328. package/server/tools/platform.js.map +1 -0
  329. package/server/tools/registry.d.ts +137 -0
  330. package/server/tools/registry.js +173 -172
  331. package/server/tools/registry.js.map +1 -0
  332. package/server/tools/scan.d.ts +34 -0
  333. package/server/tools/scan.js +89 -98
  334. package/server/tools/scan.js.map +1 -0
  335. package/server/tools.d.ts +34 -0
  336. package/server/tools.js +94 -107
  337. package/server/tools.js.map +1 -0
  338. package/server/tunnel.d.ts +29 -0
  339. package/server/tunnel.js +61 -66
  340. package/server/tunnel.js.map +1 -0
  341. package/shared/analyzer/docs.d.ts +16 -0
  342. package/shared/analyzer/docs.js +99 -93
  343. package/shared/analyzer/docs.js.map +1 -0
  344. package/shared/analyzer/explainLocal.d.ts +4 -0
  345. package/shared/analyzer/explainLocal.js +122 -142
  346. package/shared/analyzer/explainLocal.js.map +1 -0
  347. package/shared/analyzer/github.d.ts +37 -0
  348. package/shared/analyzer/github.js +79 -0
  349. package/shared/analyzer/github.js.map +1 -0
  350. package/shared/analyzer/graph.d.ts +41 -0
  351. package/shared/analyzer/graph.js +393 -400
  352. package/shared/analyzer/graph.js.map +1 -0
  353. package/shared/analyzer/health.d.ts +33 -0
  354. package/shared/analyzer/health.js +197 -193
  355. package/shared/analyzer/health.js.map +1 -0
  356. package/shared/analyzer/history.d.ts +29 -0
  357. package/shared/analyzer/history.js +110 -111
  358. package/shared/analyzer/history.js.map +1 -0
  359. package/shared/analyzer/jsonConfig.d.ts +2 -0
  360. package/shared/analyzer/jsonConfig.js +61 -0
  361. package/shared/analyzer/jsonConfig.js.map +1 -0
  362. package/shared/analyzer/languages/csharp.d.ts +26 -0
  363. package/shared/analyzer/languages/csharp.js +83 -88
  364. package/shared/analyzer/languages/csharp.js.map +1 -0
  365. package/shared/analyzer/languages/generic.d.ts +23 -0
  366. package/shared/analyzer/languages/generic.js +94 -105
  367. package/shared/analyzer/languages/generic.js.map +1 -0
  368. package/shared/analyzer/languages/go.d.ts +24 -0
  369. package/shared/analyzer/languages/go.js +45 -54
  370. package/shared/analyzer/languages/go.js.map +1 -0
  371. package/shared/analyzer/languages/index.d.ts +6 -0
  372. package/shared/analyzer/languages/index.js +19 -20
  373. package/shared/analyzer/languages/index.js.map +1 -0
  374. package/shared/analyzer/languages/java.d.ts +20 -0
  375. package/shared/analyzer/languages/java.js +73 -78
  376. package/shared/analyzer/languages/java.js.map +1 -0
  377. package/shared/analyzer/languages/javascript.d.ts +12 -0
  378. package/shared/analyzer/languages/javascript.js +258 -209
  379. package/shared/analyzer/languages/javascript.js.map +1 -0
  380. package/shared/analyzer/languages/python.d.ts +23 -0
  381. package/shared/analyzer/languages/python.js +92 -100
  382. package/shared/analyzer/languages/python.js.map +1 -0
  383. package/shared/analyzer/languages/rust.d.ts +17 -0
  384. package/shared/analyzer/languages/rust.js +96 -98
  385. package/shared/analyzer/languages/rust.js.map +1 -0
  386. package/shared/analyzer/languages/typescript.d.ts +11 -0
  387. package/shared/analyzer/languages/typescript.js +117 -116
  388. package/shared/analyzer/languages/typescript.js.map +1 -0
  389. package/shared/analyzer/licenses.d.ts +18 -0
  390. package/shared/analyzer/licenses.js +130 -133
  391. package/shared/analyzer/licenses.js.map +1 -0
  392. package/shared/analyzer/metrics.d.ts +20 -0
  393. package/shared/analyzer/metrics.js +112 -104
  394. package/shared/analyzer/metrics.js.map +1 -0
  395. package/shared/analyzer/pathUtil.d.ts +10 -0
  396. package/shared/analyzer/pathUtil.js +29 -35
  397. package/shared/analyzer/pathUtil.js.map +1 -0
  398. package/shared/analyzer/patterns.d.ts +11 -0
  399. package/shared/analyzer/patterns.js +231 -253
  400. package/shared/analyzer/patterns.js.map +1 -0
  401. package/shared/analyzer/scan.d.ts +62 -0
  402. package/shared/analyzer/scan.js +451 -442
  403. package/shared/analyzer/scan.js.map +1 -0
  404. package/shared/analyzer/scanControl.d.ts +11 -0
  405. package/shared/analyzer/scanControl.js +62 -0
  406. package/shared/analyzer/scanControl.js.map +1 -0
  407. package/shared/analyzer/security.d.ts +18 -0
  408. package/shared/analyzer/security.js +159 -159
  409. package/shared/analyzer/security.js.map +1 -0
  410. package/shared/analyzer/services.d.ts +5 -0
  411. package/shared/analyzer/services.js +141 -138
  412. package/shared/analyzer/services.js.map +1 -0
  413. package/shared/analyzer/stack.d.ts +6 -0
  414. package/shared/analyzer/stack.js +134 -141
  415. package/shared/analyzer/stack.js.map +1 -0
  416. package/shared/analyzer/tour.d.ts +2 -0
  417. package/shared/analyzer/tour.js +45 -53
  418. package/shared/analyzer/tour.js.map +1 -0
  419. package/shared/analyzer/util.d.ts +8 -0
  420. package/shared/analyzer/util.js +130 -76
  421. package/shared/analyzer/util.js.map +1 -0
  422. package/shared/analyzer/workflows.d.ts +8 -0
  423. package/shared/analyzer/workflows.js +139 -134
  424. package/shared/analyzer/workflows.js.map +1 -0
  425. package/shared/contracts.d.ts +250 -0
  426. package/shared/contracts.js +2 -0
  427. package/shared/contracts.js.map +1 -0
  428. package/shared/diagram/aiFacts.d.ts +35 -0
  429. package/shared/diagram/aiFacts.js +102 -105
  430. package/shared/diagram/aiFacts.js.map +1 -0
  431. package/shared/diagram/aiMermaid.d.ts +5 -0
  432. package/shared/diagram/aiMermaid.js +46 -45
  433. package/shared/diagram/aiMermaid.js.map +1 -0
  434. package/shared/diagram/atlas.d.ts +16 -0
  435. package/shared/diagram/atlas.js +51 -60
  436. package/shared/diagram/atlas.js.map +1 -0
  437. package/shared/diagram/mermaid.d.ts +43 -0
  438. package/shared/diagram/mermaid.js +348 -370
  439. package/shared/diagram/mermaid.js.map +1 -0
  440. package/shared/review/contracts.d.ts +95 -0
  441. package/shared/review/contracts.js +2 -0
  442. package/shared/review/contracts.js.map +1 -0
  443. package/shared/review/review.d.ts +10 -0
  444. package/shared/review/review.js +184 -0
  445. package/shared/review/review.js.map +1 -0
  446. package/shared/search/query.d.ts +32 -0
  447. package/shared/search/query.js +395 -388
  448. package/shared/search/query.js.map +1 -0
@@ -1,270 +1,264 @@
1
1
  // Graph facts: entry points, hubs, orphans, cycles, fan-in/fan-out.
2
2
  // Pure functions over the scan result — no I/O here.
3
-
4
- import { topFolderOf } from './pathUtil.js';
5
-
3
+ import { isTestPath, topFolderOf } from './pathUtil.js';
6
4
  const ENTRY_NAMES = new Set([
7
- 'index', 'main', 'app', 'server', 'cli', 'manage', 'mod', '__main__',
8
- 'run', 'start', 'cmd',
5
+ 'index', 'main', 'app', 'server', 'cli', 'manage', 'mod', '__main__',
6
+ 'run', 'start', 'cmd',
9
7
  ]);
10
-
11
- const TEST_RE = /(^|[._-])(test|spec|tests|__tests__)([._-]|\/|$)/i;
12
-
13
8
  export function computeFacts(scan, manifest = {}) {
14
- const { files, edges } = scan;
15
- const fanIn = new Map();
16
- const fanOut = new Map();
17
- const importers = new Map();
18
- const importsOf = new Map();
19
-
20
- for (const f of files) {
21
- fanIn.set(f.path, 0);
22
- fanOut.set(f.path, 0);
23
- importers.set(f.path, []);
24
- importsOf.set(f.path, []);
25
- }
26
- for (const e of edges) {
27
- if (!fanIn.has(e.to) || !fanOut.has(e.from)) continue;
28
- fanIn.set(e.to, fanIn.get(e.to) + 1);
29
- fanOut.set(e.from, fanOut.get(e.from) + 1);
30
- importers.get(e.to).push(e.from);
31
- importsOf.get(e.from).push(e.to);
32
- }
33
-
34
- const entrySet = new Set();
35
- for (const f of files) {
36
- const stem = (f.name || f.path || '').replace(/\.[^.]+$/, '');
37
- if (ENTRY_NAMES.has(stem) || f.hasMain) entrySet.add(f.path);
38
- }
39
- for (const p of manifest.entryPoints || []) entrySet.add(p);
40
-
41
- // Tarjan is the most expensive thing in this function, and one run answers
42
- // both questions asked of it: which files sit in a cycle, and what the
43
- // largest cycles are. It used to run twice on identical input.
44
- const sccs = stronglyConnected(files.map((f) => f.path), edges);
45
- const multi = sccs.filter((scc) => scc.length > 1);
46
-
47
- const inCycle = new Set();
48
- for (const scc of multi) for (const p of scc) inCycle.add(p);
49
-
50
- const entries = files
51
- .filter((f) => entrySet.has(f.path))
52
- .map((f) => f.path)
53
- .sort();
54
-
55
- // A hub is a file other files depend on. The threshold is 2, not 3: in a
56
- // repo with 20 files, "imported by two others" *is* the most-depended-upon
57
- // file, and a threshold of 3 reported no hubs at all for a whole small project
58
- // — the map of a small repo is exactly where a hub is most useful. Two is the
59
- // lowest value that still means "more than one other file reaches for this".
60
- const hubs = files
61
- .map((f) => ({ path: f.path, fanIn: fanIn.get(f.path) || 0, fanOut: fanOut.get(f.path) || 0 }))
62
- .filter((h) => h.fanIn >= 2)
63
- .sort((a, b) => b.fanIn - a.fanIn);
64
-
65
- const orphans = files
66
- .filter((f) => (fanIn.get(f.path) || 0) === 0 && !entrySet.has(f.path) && !TEST_RE.test(f.path))
67
- .map((f) => f.path)
68
- .sort();
69
-
70
- const cycles = multi
71
- .slice()
72
- .sort((a, b) => b.length - a.length)
73
- .slice(0, 10);
74
-
75
- // Collect all imported symbols per target file
76
- const importedSymbolsByTarget = new Map();
77
- const wildcards = new Set();
78
- for (const e of edges) {
79
- if (!e.to) continue;
80
- if (!importedSymbolsByTarget.has(e.to)) importedSymbolsByTarget.set(e.to, new Set());
81
- const set = importedSymbolsByTarget.get(e.to);
82
- if (!e.symbols || !e.symbols.length || e.symbols.includes('*')) {
83
- wildcards.add(e.to);
84
- } else {
85
- for (const s of e.symbols) set.add(s);
9
+ const { files, edges } = scan;
10
+ const fanIn = new Map();
11
+ const fanOut = new Map();
12
+ const importers = new Map();
13
+ const importsOf = new Map();
14
+ for (const f of files) {
15
+ fanIn.set(f.path, 0);
16
+ fanOut.set(f.path, 0);
17
+ importers.set(f.path, []);
18
+ importsOf.set(f.path, []);
86
19
  }
87
- }
88
-
89
- const deadExports = [];
90
- for (const f of files) {
91
- if (!f.exports || !f.exports.length) continue;
92
- if (entrySet.has(f.path) || TEST_RE.test(f.path) || wildcards.has(f.path)) continue;
93
- const importedSet = importedSymbolsByTarget.get(f.path);
94
- if (!importedSet || importedSet.size === 0) {
95
- for (const exp of f.exports) {
96
- deadExports.push({ file: f.path, name: exp.name, kind: exp.kind });
97
- }
98
- } else {
99
- for (const exp of f.exports) {
100
- if (!importedSet.has(exp.name) && exp.name !== 'default') {
101
- deadExports.push({ file: f.path, name: exp.name, kind: exp.kind });
20
+ for (const e of edges) {
21
+ if (!fanIn.has(e.to) || !fanOut.has(e.from))
22
+ continue;
23
+ fanIn.set(e.to, fanIn.get(e.to) + 1);
24
+ fanOut.set(e.from, fanOut.get(e.from) + 1);
25
+ importers.get(e.to).push(e.from);
26
+ importsOf.get(e.from).push(e.to);
27
+ }
28
+ const entrySet = new Set();
29
+ for (const f of files) {
30
+ const stem = (f.name || f.path || '').replace(/\.[^.]+$/, '');
31
+ if (ENTRY_NAMES.has(stem) || f.hasMain)
32
+ entrySet.add(f.path);
33
+ }
34
+ for (const p of manifest.entryPoints || [])
35
+ entrySet.add(p);
36
+ // Tarjan is the most expensive thing in this function, and one run answers
37
+ // both questions asked of it: which files sit in a cycle, and what the
38
+ // largest cycles are. It used to run twice on identical input.
39
+ const sccs = stronglyConnected(files.map((f) => f.path), edges);
40
+ const multi = sccs.filter((scc) => scc.length > 1);
41
+ const inCycle = new Set();
42
+ for (const scc of multi)
43
+ for (const p of scc)
44
+ inCycle.add(p);
45
+ const entries = files
46
+ .filter((f) => entrySet.has(f.path))
47
+ .map((f) => f.path)
48
+ .sort();
49
+ // A hub is a file other files depend on. The threshold is 2, not 3: in a
50
+ // repo with 20 files, "imported by two others" *is* the most-depended-upon
51
+ // file, and a threshold of 3 reported no hubs at all for a whole small project
52
+ // — the map of a small repo is exactly where a hub is most useful. Two is the
53
+ // lowest value that still means "more than one other file reaches for this".
54
+ const hubs = files
55
+ .map((f) => ({ path: f.path, fanIn: fanIn.get(f.path) || 0, fanOut: fanOut.get(f.path) || 0 }))
56
+ .filter((h) => h.fanIn >= 2)
57
+ .sort((a, b) => b.fanIn - a.fanIn);
58
+ const orphans = files
59
+ .filter((f) => (fanIn.get(f.path) || 0) === 0 && !entrySet.has(f.path) && !isTestPath(f.path))
60
+ .map((f) => f.path)
61
+ .sort();
62
+ const cycles = multi
63
+ .slice()
64
+ .sort((a, b) => b.length - a.length)
65
+ .slice(0, 10);
66
+ // Collect all imported symbols per target file
67
+ const importedSymbolsByTarget = new Map();
68
+ const wildcards = new Set();
69
+ for (const e of edges) {
70
+ if (!e.to)
71
+ continue;
72
+ if (!importedSymbolsByTarget.has(e.to))
73
+ importedSymbolsByTarget.set(e.to, new Set());
74
+ const set = importedSymbolsByTarget.get(e.to);
75
+ if (!e.symbols || !e.symbols.length || e.symbols.includes('*')) {
76
+ wildcards.add(e.to);
77
+ }
78
+ else {
79
+ for (const s of e.symbols)
80
+ set.add(s);
81
+ }
82
+ }
83
+ const deadExports = [];
84
+ for (const f of files) {
85
+ if (!f.exports || !f.exports.length)
86
+ continue;
87
+ if (entrySet.has(f.path) || isTestPath(f.path) || wildcards.has(f.path))
88
+ continue;
89
+ const importedSet = importedSymbolsByTarget.get(f.path);
90
+ if (!importedSet || importedSet.size === 0) {
91
+ for (const exp of f.exports) {
92
+ deadExports.push({ file: f.path, name: exp.name, kind: exp.kind });
93
+ }
94
+ }
95
+ else {
96
+ for (const exp of f.exports) {
97
+ if (!importedSet.has(exp.name)) {
98
+ deadExports.push({ file: f.path, name: exp.name, kind: exp.kind });
99
+ }
100
+ }
101
+ }
102
+ }
103
+ // Test coverage & untested hubs
104
+ const testFiles = files.filter((f) => isTestPath(f.path));
105
+ const testedSet = new Set();
106
+ const testQueue = [];
107
+ for (const tf of testFiles) {
108
+ for (const target of importsOf.get(tf.path) || []) {
109
+ if (!isTestPath(target) && !testedSet.has(target)) {
110
+ testedSet.add(target);
111
+ testQueue.push(target);
112
+ }
102
113
  }
103
- }
104
114
  }
105
- }
106
-
107
- // Test coverage & untested hubs
108
- const testFiles = files.filter((f) => TEST_RE.test(f.path));
109
- const testedSet = new Set();
110
- const testQueue = [];
111
- for (const tf of testFiles) {
112
- for (const target of importsOf.get(tf.path) || []) {
113
- if (!TEST_RE.test(target) && !testedSet.has(target)) {
114
- testedSet.add(target);
115
- testQueue.push(target);
116
- }
115
+ // Each file is enqueued once, so an index gives a complete O(V + E) walk.
116
+ // The old 500-step ceiling silently undercounted coverage in large repos.
117
+ for (let cursor = 0; cursor < testQueue.length; cursor++) {
118
+ const curr = testQueue[cursor];
119
+ for (const next of importsOf.get(curr) || []) {
120
+ if (!isTestPath(next) && !testedSet.has(next)) {
121
+ testedSet.add(next);
122
+ testQueue.push(next);
123
+ }
124
+ }
125
+ }
126
+ const nonTestFiles = files.filter((f) => !isTestPath(f.path));
127
+ const untestedHubs = hubs.filter((h) => !isTestPath(h.path) && !testedSet.has(h.path));
128
+ const testCoverage = {
129
+ testedCount: testedSet.size,
130
+ totalNonTest: nonTestFiles.length,
131
+ ratio: nonTestFiles.length ? Math.round((testedSet.size / nonTestFiles.length) * 100) : 100,
132
+ };
133
+ // Dependency drift
134
+ const declaredDeps = new Set([
135
+ ...Object.keys(manifest.deps?.npm || {}),
136
+ ...Object.keys(manifest.deps?.dev || {}),
137
+ ...(manifest.deps?.pip || []),
138
+ ...(manifest.deps?.go || []),
139
+ ...(manifest.deps?.cargo || []),
140
+ ]);
141
+ const importedExternals = new Set((scan.externals || []).map((x) => x.name));
142
+ const unusedDeclared = [...declaredDeps].filter((d) => !importedExternals.has(d)).sort();
143
+ const BUILTIN_MODULES = new Set([
144
+ 'fs', 'path', 'http', 'https', 'url', 'crypto', 'os', 'stream', 'util',
145
+ 'events', 'child_process', 'buffer', 'assert', 'net', 'zlib', 'tls', 'dns',
146
+ 'perf_hooks', 'worker_threads',
147
+ ]);
148
+ const undeclaredImported = [...importedExternals]
149
+ .filter((i) => !declaredDeps.has(i) && !BUILTIN_MODULES.has(i))
150
+ .sort();
151
+ const depsDrift = { unusedDeclared, undeclaredImported };
152
+ const folderEdges = new Map();
153
+ for (const e of edges) {
154
+ const a = topFolderOf(e.from);
155
+ const b = topFolderOf(e.to);
156
+ if (a === b)
157
+ continue;
158
+ const key = a + '->' + b;
159
+ folderEdges.set(key, (folderEdges.get(key) || 0) + 1);
117
160
  }
118
- }
119
- let hop = 0;
120
- while (testQueue.length && hop < 500) {
121
- hop++;
122
- const curr = testQueue.shift();
123
- for (const next of importsOf.get(curr) || []) {
124
- if (!TEST_RE.test(next) && !testedSet.has(next)) {
125
- testedSet.add(next);
126
- testQueue.push(next);
127
- }
161
+ const paths = files.map(f => f.path);
162
+ const communitiesMap = louvainCommunities(paths, edges);
163
+ const commGroups = new Map();
164
+ for (const [n, c] of communitiesMap.entries()) {
165
+ if (!commGroups.has(c))
166
+ commGroups.set(c, []);
167
+ commGroups.get(c).push(n);
128
168
  }
129
- }
130
- const nonTestFiles = files.filter((f) => !TEST_RE.test(f.path));
131
- const untestedHubs = hubs.filter((h) => !TEST_RE.test(h.path) && !testedSet.has(h.path));
132
- const testCoverage = {
133
- testedCount: testedSet.size,
134
- totalNonTest: nonTestFiles.length,
135
- ratio: nonTestFiles.length ? Math.round((testedSet.size / nonTestFiles.length) * 100) : 100,
136
- };
137
-
138
- // Dependency drift
139
- const declaredDeps = new Set([
140
- ...Object.keys(manifest.deps?.npm || {}),
141
- ...Object.keys(manifest.deps?.dev || {}),
142
- ...(manifest.deps?.pip || []),
143
- ...(manifest.deps?.go || []),
144
- ...(manifest.deps?.cargo || []),
145
- ]);
146
- const importedExternals = new Set((scan.externals || []).map((x) => x.name));
147
- const unusedDeclared = [...declaredDeps].filter((d) => !importedExternals.has(d)).sort();
148
- const BUILTIN_MODULES = new Set([
149
- 'fs', 'path', 'http', 'https', 'url', 'crypto', 'os', 'stream', 'util',
150
- 'events', 'child_process', 'buffer', 'assert', 'net', 'zlib', 'tls', 'dns',
151
- 'perf_hooks', 'worker_threads',
152
- ]);
153
- const undeclaredImported = [...importedExternals]
154
- .filter((i) => !declaredDeps.has(i) && !BUILTIN_MODULES.has(i))
155
- .sort();
156
- const depsDrift = { unusedDeclared, undeclaredImported };
157
-
158
- const folderEdges = new Map();
159
- for (const e of edges) {
160
- const a = topFolderOf(e.from);
161
- const b = topFolderOf(e.to);
162
- if (a === b) continue;
163
- const key = a + '->' + b;
164
- folderEdges.set(key, (folderEdges.get(key) || 0) + 1);
165
- }
166
-
167
- const paths = files.map(f => f.path);
168
- const communitiesMap = louvainCommunities(paths, edges);
169
- const commGroups = new Map();
170
- for (const [n, c] of communitiesMap.entries()) {
171
- if (!commGroups.has(c)) commGroups.set(c, []);
172
- commGroups.get(c).push(n);
173
- }
174
- const communities = [...commGroups.entries()]
175
- .map(([id, members]) => ({ id, members, size: members.length }))
176
- .sort((a, b) => b.size - a.size);
177
-
178
- const betweennessResult = brandesBetweenness(paths, edges);
179
- const betweenness = Object.fromEntries(betweennessResult.map);
180
-
181
- return {
182
- communities,
183
- betweenness,
184
- // Whether `betweenness` above is a real measurement or all zeros
185
- // because the graph was over the Brandes cap. The UI uses this the
186
- // same way it uses `health.blastExact`: it shows an honest
187
- // "not computed for repos of this size" instead of pretending the
188
- // zeros are a finding.
189
- betweennessExact: betweennessResult.exact,
190
- fanIn: Object.fromEntries(fanIn),
191
- fanOut: Object.fromEntries(fanOut),
192
- importers: Object.fromEntries(importers),
193
- importsOf: Object.fromEntries(importsOf),
194
- entries,
195
- hubs,
196
- orphans,
197
- cycles,
198
- inCycle: [...inCycle],
199
- deadExports,
200
- testCoverage,
201
- untestedHubs,
202
- depsDrift,
203
- folderEdges: [...folderEdges.entries()].map(([key, count]) => {
204
- const [from, to] = key.split('->');
205
- return { from, to, count };
206
- }),
207
- entrySet: entries,
208
- };
169
+ const communities = [...commGroups.entries()]
170
+ .map(([id, members]) => ({ id, members, size: members.length }))
171
+ .sort((a, b) => b.size - a.size);
172
+ const betweennessResult = brandesBetweenness(paths, edges);
173
+ const betweenness = Object.fromEntries(betweennessResult.map);
174
+ return {
175
+ communities,
176
+ betweenness,
177
+ // Whether `betweenness` above is a real measurement or all zeros
178
+ // because the graph was over the Brandes cap. The UI uses this the
179
+ // same way it uses `health.blastExact`: it shows an honest
180
+ // "not computed for repos of this size" instead of pretending the
181
+ // zeros are a finding.
182
+ betweennessExact: betweennessResult.exact,
183
+ fanIn: Object.fromEntries(fanIn),
184
+ fanOut: Object.fromEntries(fanOut),
185
+ importers: Object.fromEntries(importers),
186
+ importsOf: Object.fromEntries(importsOf),
187
+ entries,
188
+ hubs,
189
+ orphans,
190
+ cycles,
191
+ inCycle: [...inCycle],
192
+ deadExports,
193
+ testCoverage,
194
+ untestedHubs,
195
+ depsDrift,
196
+ folderEdges: [...folderEdges.entries()].map(([key, count]) => {
197
+ const [from, to] = key.split('->');
198
+ return { from, to, count };
199
+ }),
200
+ entrySet: entries,
201
+ };
209
202
  }
210
-
211
203
  // Tarjan's strongly connected components, iterative so deep graphs don't
212
204
  // blow the stack.
213
205
  function stronglyConnected(nodes, edges) {
214
- const adj = new Map(nodes.map((n) => [n, []]));
215
- for (const e of edges) {
216
- if (adj.has(e.from)) adj.get(e.from).push(e.to);
217
- }
218
- const index = new Map();
219
- const low = new Map();
220
- const onStack = new Set();
221
- const stack = [];
222
- const result = [];
223
- let counter = 0;
224
-
225
- for (const start of nodes) {
226
- if (index.has(start)) continue;
227
- const work = [[start, 0]];
228
- while (work.length) {
229
- const [v, pi] = work[work.length - 1];
230
- if (pi === 0) {
231
- index.set(v, counter);
232
- low.set(v, counter);
233
- counter++;
234
- stack.push(v);
235
- onStack.add(v);
236
- }
237
- const children = adj.get(v) || [];
238
- if (pi < children.length) {
239
- work[work.length - 1][1] = pi + 1;
240
- const w = children[pi];
241
- if (!index.has(w)) {
242
- work.push([w, 0]);
243
- } else if (onStack.has(w)) {
244
- low.set(v, Math.min(low.get(v), index.get(w)));
245
- }
246
- } else {
247
- work.pop();
248
- if (work.length) {
249
- const parent = work[work.length - 1][0];
250
- low.set(parent, Math.min(low.get(parent), low.get(v)));
251
- }
252
- if (low.get(v) === index.get(v)) {
253
- const scc = [];
254
- let w;
255
- do {
256
- w = stack.pop();
257
- onStack.delete(w);
258
- scc.push(w);
259
- } while (w !== v);
260
- result.push(scc);
206
+ const adj = new Map(nodes.map((n) => [n, []]));
207
+ for (const e of edges) {
208
+ if (adj.has(e.from))
209
+ adj.get(e.from).push(e.to);
210
+ }
211
+ const index = new Map();
212
+ const low = new Map();
213
+ const onStack = new Set();
214
+ const stack = [];
215
+ const result = [];
216
+ let counter = 0;
217
+ for (const start of nodes) {
218
+ if (index.has(start))
219
+ continue;
220
+ const work = [[start, 0]];
221
+ while (work.length) {
222
+ const [v, pi] = work[work.length - 1];
223
+ if (pi === 0) {
224
+ index.set(v, counter);
225
+ low.set(v, counter);
226
+ counter++;
227
+ stack.push(v);
228
+ onStack.add(v);
229
+ }
230
+ const children = adj.get(v) || [];
231
+ if (pi < children.length) {
232
+ work[work.length - 1][1] = pi + 1;
233
+ const w = children[pi];
234
+ if (!index.has(w)) {
235
+ work.push([w, 0]);
236
+ }
237
+ else if (onStack.has(w)) {
238
+ low.set(v, Math.min(low.get(v), index.get(w)));
239
+ }
240
+ }
241
+ else {
242
+ work.pop();
243
+ if (work.length) {
244
+ const parent = work[work.length - 1][0];
245
+ low.set(parent, Math.min(low.get(parent), low.get(v)));
246
+ }
247
+ if (low.get(v) === index.get(v)) {
248
+ const scc = [];
249
+ let w;
250
+ do {
251
+ w = stack.pop();
252
+ onStack.delete(w);
253
+ scc.push(w);
254
+ } while (w !== v);
255
+ result.push(scc);
256
+ }
257
+ }
261
258
  }
262
- }
263
259
  }
264
- }
265
- return result;
260
+ return result;
266
261
  }
267
-
268
262
  // Lookup shapes for the render loops.
269
263
  //
270
264
  // `facts` is plain data on purpose — it is serialized to JSON and handed to the
@@ -278,38 +272,40 @@ function stronglyConnected(nodes, edges) {
278
272
  // It assumes nobody mutates `facts` after computing it. Nothing does — the
279
273
  // engine returns it and every consumer reads.
280
274
  const INDEXES = new WeakMap();
281
-
282
275
  export function factIndex(facts) {
283
- let index = INDEXES.get(facts);
284
- if (index) return index;
285
- index = {
286
- entries: new Set(facts.entries || []),
287
- inCycle: new Set(facts.inCycle || []),
288
- hubs: new Map((facts.hubs || []).map((h) => [h.path, h])),
289
- };
290
- INDEXES.set(facts, index);
291
- return index;
276
+ let index = INDEXES.get(facts);
277
+ if (index)
278
+ return index;
279
+ index = {
280
+ entries: new Set(facts.entries || []),
281
+ inCycle: new Set(facts.inCycle || []),
282
+ hubs: new Map((facts.hubs || []).map((h) => [h.path, h])),
283
+ };
284
+ INDEXES.set(facts, index);
285
+ return index;
292
286
  }
293
-
294
287
  export function roleOf(path, facts) {
295
- if (TEST_RE.test(path)) return 'test';
296
- const index = factIndex(facts);
297
- if (index.entries.has(path)) return 'entry';
298
- const hub = index.hubs.get(path);
299
- if (hub && hub.fanIn >= 5) return 'hub';
300
- const fin = facts.fanIn[path] || 0;
301
- const fout = facts.fanOut[path] || 0;
302
- if (fout === 0 && fin > 0) return 'leaf';
303
- if (/config|settings|\.json$|\.ya?ml$|\.toml$/.test(path)) return 'config';
304
- return 'module';
288
+ if (isTestPath(path))
289
+ return 'test';
290
+ const index = factIndex(facts);
291
+ if (index.entries.has(path))
292
+ return 'entry';
293
+ const hub = index.hubs.get(path);
294
+ if (hub && hub.fanIn >= 5)
295
+ return 'hub';
296
+ const fin = facts.fanIn[path] || 0;
297
+ const fout = facts.fanOut[path] || 0;
298
+ if (fout === 0 && fin > 0)
299
+ return 'leaf';
300
+ if (/config|settings|\.json$|\.ya?ml$|\.toml$/.test(path))
301
+ return 'config';
302
+ return 'module';
305
303
  }
306
-
307
304
  // Is this file part of an import cycle? Same answer as
308
305
  // `facts.inCycle.includes(path)`, without the scan.
309
306
  export function inCycle(path, facts) {
310
- return factIndex(facts).inCycle.has(path);
307
+ return factIndex(facts).inCycle.has(path);
311
308
  }
312
-
313
309
  // The same trick for the scan itself. `scan.files.find((f) => f.path === p)` and
314
310
  // `scan.files.filter((f) => f.dir === d)` were written a dozen times across the
315
311
  // engine and the UI, both inside per-file loops — the docs generator alone ran
@@ -319,91 +315,88 @@ export function inCycle(path, facts) {
319
315
  // Treat it as read-only: copy before sorting.
320
316
  const SCAN_INDEXES = new WeakMap();
321
317
  const NO_FILES = Object.freeze([]);
322
-
323
318
  export function scanIndex(scan) {
324
- let index = SCAN_INDEXES.get(scan);
325
- if (index) return index;
326
- const byPath = new Map();
327
- const byDir = new Map();
328
- for (const f of scan.files || []) {
329
- byPath.set(f.path, f);
330
- const bucket = byDir.get(f.dir);
331
- if (bucket) bucket.push(f);
332
- else byDir.set(f.dir, [f]);
333
- }
334
- index = {
335
- byPath,
336
- byDir,
337
- fileAt: (path) => byPath.get(path) || null,
338
- filesIn: (dir) => byDir.get(dir) || NO_FILES,
339
- };
340
- SCAN_INDEXES.set(scan, index);
341
- return index;
319
+ let index = SCAN_INDEXES.get(scan);
320
+ if (index)
321
+ return index;
322
+ const byPath = new Map();
323
+ const byDir = new Map();
324
+ for (const f of scan.files || []) {
325
+ byPath.set(f.path, f);
326
+ const bucket = byDir.get(f.dir);
327
+ if (bucket)
328
+ bucket.push(f);
329
+ else
330
+ byDir.set(f.dir, [f]);
331
+ }
332
+ index = {
333
+ byPath,
334
+ byDir,
335
+ fileAt: (path) => byPath.get(path) || null,
336
+ filesIn: (dir) => byDir.get(dir) || NO_FILES,
337
+ };
338
+ SCAN_INDEXES.set(scan, index);
339
+ return index;
342
340
  }
343
-
344
341
  function louvainCommunities(nodes, edges) {
345
- let communities = new Map(nodes.map(n => [n, n]));
346
- let tot = new Map(nodes.map(n => [n, 0]));
347
- let inC = new Map(nodes.map(n => [n, 0]));
348
- let k = new Map(nodes.map(n => [n, 0]));
349
- let m2 = 0;
350
-
351
- const adj = new Map(nodes.map(n => [n, []]));
352
- for (const e of edges) {
353
- if (!adj.has(e.from)) adj.set(e.from, []);
354
- adj.get(e.from).push(e.to);
355
- k.set(e.from, (k.get(e.from) || 0) + 1);
356
- k.set(e.to, (k.get(e.to) || 0) + 1);
357
- m2 += 2;
358
- }
359
- if (m2 === 0) return communities;
360
-
361
- for (const n of nodes) tot.set(n, k.get(n));
362
-
363
- // Eight passes is enough for any practical graph to settle. Three was
364
- // the early limit and worked for small repos, but on denser graphs the
365
- // modularity gain was still climbing at iteration three. The early-exit
366
- // (`improved === false`) keeps the cost bounded on graphs that converge
367
- // quickly, so this is not a free pass on every scan — it is a ceiling.
368
- let iters = 0;
369
- let improved = true;
370
- while (iters < 8 && improved) {
371
- improved = false;
372
- for (const u of nodes) {
373
- const c_u = communities.get(u);
374
- const k_u = k.get(u);
375
-
376
- const counts = new Map();
377
- for (const v of (adj.get(u) || [])) {
378
- const c_v = communities.get(v);
379
- counts.set(c_v, (counts.get(c_v) || 0) + 1);
380
- }
381
-
382
- let best_c = c_u;
383
- let max_gain = 0;
384
-
385
- for (const [c_v, weight] of counts.entries()) {
386
- if (c_v === c_u) continue;
387
- const tot_c = tot.get(c_v);
388
- const gain = weight - (k_u * tot_c) / m2;
389
- if (gain > max_gain) {
390
- max_gain = gain;
391
- best_c = c_v;
342
+ let communities = new Map(nodes.map(n => [n, n]));
343
+ let tot = new Map(nodes.map(n => [n, 0]));
344
+ let inC = new Map(nodes.map(n => [n, 0]));
345
+ let k = new Map(nodes.map(n => [n, 0]));
346
+ let m2 = 0;
347
+ const adj = new Map(nodes.map(n => [n, []]));
348
+ for (const e of edges) {
349
+ if (!adj.has(e.from))
350
+ adj.set(e.from, []);
351
+ adj.get(e.from).push(e.to);
352
+ k.set(e.from, (k.get(e.from) || 0) + 1);
353
+ k.set(e.to, (k.get(e.to) || 0) + 1);
354
+ m2 += 2;
355
+ }
356
+ if (m2 === 0)
357
+ return communities;
358
+ for (const n of nodes)
359
+ tot.set(n, k.get(n));
360
+ // Eight passes is enough for any practical graph to settle. Three was
361
+ // the early limit and worked for small repos, but on denser graphs the
362
+ // modularity gain was still climbing at iteration three. The early-exit
363
+ // (`improved === false`) keeps the cost bounded on graphs that converge
364
+ // quickly, so this is not a free pass on every scan — it is a ceiling.
365
+ let iters = 0;
366
+ let improved = true;
367
+ while (iters < 8 && improved) {
368
+ improved = false;
369
+ for (const u of nodes) {
370
+ const c_u = communities.get(u);
371
+ const k_u = k.get(u);
372
+ const counts = new Map();
373
+ for (const v of (adj.get(u) || [])) {
374
+ const c_v = communities.get(v);
375
+ counts.set(c_v, (counts.get(c_v) || 0) + 1);
376
+ }
377
+ let best_c = c_u;
378
+ let max_gain = 0;
379
+ for (const [c_v, weight] of counts.entries()) {
380
+ if (c_v === c_u)
381
+ continue;
382
+ const tot_c = tot.get(c_v);
383
+ const gain = weight - (k_u * tot_c) / m2;
384
+ if (gain > max_gain) {
385
+ max_gain = gain;
386
+ best_c = c_v;
387
+ }
388
+ }
389
+ if (best_c !== c_u) {
390
+ tot.set(c_u, tot.get(c_u) - k_u);
391
+ tot.set(best_c, tot.get(best_c) + k_u);
392
+ communities.set(u, best_c);
393
+ improved = true;
394
+ }
392
395
  }
393
- }
394
-
395
- if (best_c !== c_u) {
396
- tot.set(c_u, tot.get(c_u) - k_u);
397
- tot.set(best_c, tot.get(best_c) + k_u);
398
- communities.set(u, best_c);
399
- improved = true;
400
- }
396
+ iters++;
401
397
  }
402
- iters++;
403
- }
404
- return communities;
398
+ return communities;
405
399
  }
406
-
407
400
  // Brandes's algorithm. The old return was just a Map of zeros when the
408
401
  // graph had more than 1,000 nodes, which is honest about the cost (O(V·E)
409
402
  // in the unweighted case) but dishonest about what the consumer is looking
@@ -412,55 +405,55 @@ function louvainCommunities(nodes, edges) {
412
405
  // for repos over 1,000 files" the way the health panel shows
413
406
  // `blastExact: false` next to a fan-in fallback.
414
407
  function brandesBetweenness(nodes, edges) {
415
- const cb = new Map(nodes.map(n => [n, 0]));
416
- if (nodes.length > 1000) return { map: cb, exact: false };
417
-
418
- const adj = new Map(nodes.map(n => [n, []]));
419
- for (const e of edges) {
420
- if (adj.has(e.from)) adj.get(e.from).push(e.to);
421
- }
422
-
423
- for (const s of nodes) {
424
- const S = [];
425
- const P = new Map(nodes.map(n => [n, []]));
426
- const sigma = new Map(nodes.map(n => [n, 0]));
427
- sigma.set(s, 1);
428
- const d = new Map(nodes.map(n => [n, -1]));
429
- d.set(s, 0);
430
- const Q = [s];
431
-
432
- while (Q.length > 0) {
433
- const v = Q.shift();
434
- S.push(v);
435
- for (const w of (adj.get(v) || [])) {
436
- if (d.get(w) < 0) {
437
- d.set(w, d.get(v) + 1);
438
- Q.push(w);
408
+ const cb = new Map(nodes.map(n => [n, 0]));
409
+ if (nodes.length > 1000)
410
+ return { map: cb, exact: false };
411
+ const adj = new Map(nodes.map(n => [n, []]));
412
+ for (const e of edges) {
413
+ if (adj.has(e.from))
414
+ adj.get(e.from).push(e.to);
415
+ }
416
+ for (const s of nodes) {
417
+ const S = [];
418
+ const P = new Map(nodes.map(n => [n, []]));
419
+ const sigma = new Map(nodes.map(n => [n, 0]));
420
+ sigma.set(s, 1);
421
+ const d = new Map(nodes.map(n => [n, -1]));
422
+ d.set(s, 0);
423
+ const Q = [s];
424
+ while (Q.length > 0) {
425
+ const v = Q.shift();
426
+ S.push(v);
427
+ for (const w of (adj.get(v) || [])) {
428
+ if (d.get(w) < 0) {
429
+ d.set(w, d.get(v) + 1);
430
+ Q.push(w);
431
+ }
432
+ if (d.get(w) === d.get(v) + 1) {
433
+ sigma.set(w, sigma.get(w) + sigma.get(v));
434
+ P.get(w).push(v);
435
+ }
436
+ }
439
437
  }
440
- if (d.get(w) === d.get(v) + 1) {
441
- sigma.set(w, sigma.get(w) + sigma.get(v));
442
- P.get(w).push(v);
438
+ const delta = new Map(nodes.map(n => [n, 0]));
439
+ while (S.length > 0) {
440
+ const w = S.pop();
441
+ for (const v of P.get(w)) {
442
+ delta.set(v, delta.get(v) + (sigma.get(v) / sigma.get(w)) * (1 + delta.get(w)));
443
+ }
444
+ if (w !== s) {
445
+ cb.set(w, cb.get(w) + delta.get(w));
446
+ }
443
447
  }
444
- }
445
448
  }
446
-
447
- const delta = new Map(nodes.map(n => [n, 0]));
448
- while (S.length > 0) {
449
- const w = S.pop();
450
- for (const v of P.get(w)) {
451
- delta.set(v, delta.get(v) + (sigma.get(v) / sigma.get(w)) * (1 + delta.get(w)));
452
- }
453
- if (w !== s) {
454
- cb.set(w, cb.get(w) + delta.get(w));
455
- }
449
+ let max = 0;
450
+ for (const val of cb.values())
451
+ if (val > max)
452
+ max = val;
453
+ if (max > 0) {
454
+ for (const [k, v] of cb.entries())
455
+ cb.set(k, v / max);
456
456
  }
457
- }
458
-
459
- let max = 0;
460
- for (const val of cb.values()) if (val > max) max = val;
461
- if (max > 0) {
462
- for (const [k, v] of cb.entries()) cb.set(k, v / max);
463
- }
464
-
465
- return { map: cb, exact: true };
457
+ return { map: cb, exact: true };
466
458
  }
459
+ //# sourceMappingURL=graph.js.map