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
@@ -7,140 +7,139 @@
7
7
  // Everything is keyed on file path, which is already the join key the rest of
8
8
  // the engine speaks — complexity comes over from scan.files, structural edges
9
9
  // from scan.edges, and the two views of "these change together" sit side by side.
10
-
11
10
  import { baseName } from './pathUtil.js';
12
-
13
11
  // Commits touching more files than this are skipped for pair counting, not for
14
12
  // churn — a 900-file reformat or a mass rename would otherwise contribute
15
13
  // C(900,2) ≈ 400k pairs of noise to co-change while saying nothing about
16
14
  // coupling. Churn still counts them; every file in them was really touched.
17
15
  const MAX_COMMIT_FILES_FOR_PAIRS = 50;
18
-
19
16
  // Once this many distinct pairs are tracked, new pairs stop being recorded and
20
17
  // only existing ones keep counting. A hard bound on memory for repos with long
21
18
  // histories of small commits; the top of the list is decided well before this.
22
19
  const MAX_TRACKED_PAIRS = 5000;
23
-
24
20
  export function analyzeHistory(scan, commits, opts = {}) {
25
- const totalCommits = opts.totalCommits ?? commits.length;
26
- const truncated = totalCommits > commits.length;
27
-
28
- // ---- per-file rollup -----------------------------------------------------
29
- // One pass over every commit's file list. Authors are identified by email —
30
- // names collide and change; the email is what git itself keys identity on.
31
- const churn = new Map(); // path -> commit count
32
- const authorsOf = new Map(); // path -> Map(email -> display name)
33
- const firstSeen = new Map(); // path -> earliest commit date (ISO strings sort)
34
- const lastTouched = new Map();
35
- const authorCommits = new Map(); // email -> { name, commits }
36
- let pathsGone = 0;
37
-
38
- for (const c of commits) {
39
- const date = c.date || '';
40
- const known = authorCommits.get(c.author?.email);
41
- if (known) known.commits++;
42
- else if (c.author?.email) authorCommits.set(c.author.email, { name: c.author.name || c.author.email, commits: 1 });
43
-
44
- for (const p of c.files || []) {
45
- churn.set(p, (churn.get(p) || 0) + 1);
46
- if (!firstSeen.has(p) || date < firstSeen.get(p)) firstSeen.set(p, date);
47
- if (!lastTouched.has(p) || date > lastTouched.get(p)) lastTouched.set(p, date);
48
- let byEmail = authorsOf.get(p);
49
- if (!byEmail) { byEmail = new Map(); authorsOf.set(p, byEmail); }
50
- if (!byEmail.has(c.author?.email)) byEmail.set(c.author?.email, c.author?.name || c.author?.email || '?');
21
+ const totalCommits = opts.totalCommits ?? commits.length;
22
+ const truncated = totalCommits > commits.length;
23
+ // ---- per-file rollup -----------------------------------------------------
24
+ // One pass over every commit's file list. Authors are identified by email —
25
+ // names collide and change; the email is what git itself keys identity on.
26
+ const churn = new Map(); // path -> commit count
27
+ const authorsOf = new Map(); // path -> Map(email -> display name)
28
+ const firstSeen = new Map(); // path -> earliest commit date (ISO strings sort)
29
+ const lastTouched = new Map();
30
+ const authorCommits = new Map(); // email -> { name, commits }
31
+ let pathsGone = 0;
32
+ for (const c of commits) {
33
+ const date = c.date || '';
34
+ const known = authorCommits.get(c.author?.email);
35
+ if (known)
36
+ known.commits++;
37
+ else if (c.author?.email)
38
+ authorCommits.set(c.author.email, { name: c.author.name || c.author.email, commits: 1 });
39
+ for (const p of c.files || []) {
40
+ churn.set(p, (churn.get(p) || 0) + 1);
41
+ if (!firstSeen.has(p) || date < firstSeen.get(p))
42
+ firstSeen.set(p, date);
43
+ if (!lastTouched.has(p) || date > lastTouched.get(p))
44
+ lastTouched.set(p, date);
45
+ let byEmail = authorsOf.get(p);
46
+ if (!byEmail) {
47
+ byEmail = new Map();
48
+ authorsOf.set(p, byEmail);
49
+ }
50
+ if (!byEmail.has(c.author?.email))
51
+ byEmail.set(c.author?.email, c.author?.name || c.author?.email || '?');
52
+ }
51
53
  }
52
- }
53
-
54
- // ---- join with the scan ---------------------------------------------------
55
- // Complexity lives on scan.files; history paths that are not in the scan are
56
- // files deleted or renamed inside the window. They had churn once — counted,
57
- // then set aside rather than silently mixed into rankings of files that no
58
- // longer exist.
59
- const cxOf = new Map(scan.files.map((f) => [f.path, f.complexity || 1]));
60
- for (const p of churn.keys()) if (!cxOf.has(p)) pathsGone++;
61
-
62
- const live = [...churn.entries()].filter(([p]) => cxOf.has(p));
63
- const maxChurn = Math.max(1, ...live.map(([, n]) => n));
64
- const maxCx = Math.max(1, ...live.map(([p]) => cxOf.get(p)));
65
-
66
- const perFile = live.map(([p, n]) => {
67
- const authors = authorsOf.get(p).size;
54
+ // ---- join with the scan ---------------------------------------------------
55
+ // Complexity lives on scan.files; history paths that are not in the scan are
56
+ // files deleted or renamed inside the window. They had churn once — counted,
57
+ // then set aside rather than silently mixed into rankings of files that no
58
+ // longer exist.
59
+ const cxOf = new Map(scan.files.map((f) => [f.path, f.complexity || 1]));
60
+ for (const p of churn.keys())
61
+ if (!cxOf.has(p))
62
+ pathsGone++;
63
+ const live = [...churn.entries()].filter(([p]) => cxOf.has(p));
64
+ const maxChurn = Math.max(1, ...live.map(([, n]) => n));
65
+ const maxCx = Math.max(1, ...live.map(([p]) => cxOf.get(p)));
66
+ const perFile = live.map(([p, n]) => {
67
+ const authors = authorsOf.get(p).size;
68
+ return {
69
+ path: p,
70
+ name: baseName(p),
71
+ churn: n,
72
+ authors,
73
+ solo: authors === 1,
74
+ firstSeen: firstSeen.get(p),
75
+ lastTouched: lastTouched.get(p),
76
+ complexity: cxOf.get(p),
77
+ // The cross-tab: both axes normalized against the repo's own worst, so
78
+ // the score reads "how far up both columns at once" and a file maxing
79
+ // one axis but not the other cannot outrank one high on both.
80
+ hotspot: Math.round(100 * (n / maxChurn) * (cxOf.get(p) / maxCx)),
81
+ };
82
+ });
83
+ perFile.sort((a, b) => b.hotspot - a.hotspot || b.churn - a.churn || a.path.localeCompare(b.path));
84
+ const byPath = {};
85
+ for (const row of perFile)
86
+ byPath[row.path] = row;
87
+ const soloFiles = perFile.filter((r) => r.solo && r.churn >= 3).length;
88
+ const dates = commits.map((c) => c.date).filter(Boolean);
68
89
  return {
69
- path: p,
70
- name: baseName(p),
71
- churn: n,
72
- authors,
73
- solo: authors === 1,
74
- firstSeen: firstSeen.get(p),
75
- lastTouched: lastTouched.get(p),
76
- complexity: cxOf.get(p),
77
- // The cross-tab: both axes normalized against the repo's own worst, so
78
- // the score reads "how far up both columns at once" and a file maxing
79
- // one axis but not the other cannot outrank one high on both.
80
- hotspot: Math.round(100 * (n / maxChurn) * (cxOf.get(p) / maxCx)),
90
+ available: true,
91
+ commitCount: commits.length,
92
+ totalCommits,
93
+ truncated,
94
+ // The raw commit list, not just the rollups. The insights view draws a
95
+ // 52-week heatmap and a punch card from per-commit dates; without the
96
+ // original array, that view rendered an empty calendar even when history
97
+ // was fully available. The cost is the same parsed commits we already hold
98
+ // — `parseGitLog` capped at 2,000 — so duplication here is honest.
99
+ commits,
100
+ authors: [...authorCommits.values()].sort((a, b) => b.commits - a.commits),
101
+ firstCommitAt: dates.length ? dates.reduce((a, b) => (a < b ? a : b)) : null,
102
+ lastCommitAt: dates.length ? dates.reduce((a, b) => (a > b ? a : b)) : null,
103
+ perFile,
104
+ byPath,
105
+ soloFiles,
106
+ coChanged: coChangePairs(commits, new Set(scan.edges.map((e) => e.from + '->' + e.to))),
107
+ pathsGone,
81
108
  };
82
- });
83
- perFile.sort((a, b) => b.hotspot - a.hotspot || b.churn - a.churn || a.path.localeCompare(b.path));
84
-
85
- const byPath = {};
86
- for (const row of perFile) byPath[row.path] = row;
87
- const soloFiles = perFile.filter((r) => r.solo && r.churn >= 3).length;
88
-
89
- const dates = commits.map((c) => c.date).filter(Boolean);
90
-
91
- return {
92
- available: true,
93
- commitCount: commits.length,
94
- totalCommits,
95
- truncated,
96
- // The raw commit list, not just the rollups. The insights view draws a
97
- // 52-week heatmap and a punch card from per-commit dates; without the
98
- // original array, that view rendered an empty calendar even when history
99
- // was fully available. The cost is the same parsed commits we already hold
100
- // — `parseGitLog` capped at 2,000 — so duplication here is honest.
101
- commits,
102
- authors: [...authorCommits.values()].sort((a, b) => b.commits - a.commits),
103
- firstCommitAt: dates.length ? dates.reduce((a, b) => (a < b ? a : b)) : null,
104
- lastCommitAt: dates.length ? dates.reduce((a, b) => (a > b ? a : b)) : null,
105
- perFile,
106
- byPath,
107
- soloFiles,
108
- coChanged: coChangePairs(commits, new Set(scan.edges.map((e) => e.from + '->' + e.to))),
109
- pathsGone,
110
- };
111
109
  }
112
-
113
110
  // Files that keep turning up in the same commit without importing each other.
114
111
  // The structural coupling matrix already shows what imports what; this is the
115
112
  // contrast — changes that travel together for reasons the import graph does
116
113
  // not know about (shared schema, feature pairs, copy-paste siblings).
117
114
  function coChangePairs(commits, structuralEdges) {
118
- const pairs = new Map(); // 'a|b' (sorted) -> count
119
-
120
- for (const c of commits) {
121
- const files = c.files || [];
122
- if (files.length < 2 || files.length > MAX_COMMIT_FILES_FOR_PAIRS) continue;
123
- for (let i = 0; i < files.length; i++) {
124
- for (let j = i + 1; j < files.length; j++) {
125
- const a = files[i] < files[j] ? files[i] : files[j];
126
- const b = files[i] < files[j] ? files[j] : files[i];
127
- if (structuralEdges.has(a + '->' + b) || structuralEdges.has(b + '->' + a)) continue;
128
- const key = a + '|' + b;
129
- const seen = pairs.get(key);
130
- if (seen) seen.count++;
131
- else if (pairs.size < MAX_TRACKED_PAIRS) pairs.set(key, { a, b, count: 1 });
132
- }
115
+ const pairs = new Map(); // 'a|b' (sorted) -> count
116
+ for (const c of commits) {
117
+ const files = c.files || [];
118
+ if (files.length < 2 || files.length > MAX_COMMIT_FILES_FOR_PAIRS)
119
+ continue;
120
+ for (let i = 0; i < files.length; i++) {
121
+ for (let j = i + 1; j < files.length; j++) {
122
+ const a = files[i] < files[j] ? files[i] : files[j];
123
+ const b = files[i] < files[j] ? files[j] : files[i];
124
+ if (structuralEdges.has(a + '->' + b) || structuralEdges.has(b + '->' + a))
125
+ continue;
126
+ const key = a + '|' + b;
127
+ const seen = pairs.get(key);
128
+ if (seen)
129
+ seen.count++;
130
+ else if (pairs.size < MAX_TRACKED_PAIRS)
131
+ pairs.set(key, { a, b, count: 1 });
132
+ }
133
+ }
133
134
  }
134
- }
135
-
136
- return [...pairs.values()]
137
- .filter((p) => p.count >= 2) // once is coincidence; the plan is about habits
138
- .sort((x, y) => y.count - x.count || x.a.localeCompare(y.a))
139
- .slice(0, 20);
135
+ return [...pairs.values()]
136
+ .filter((p) => p.count >= 2) // once is coincidence; the plan is about habits
137
+ .sort((x, y) => y.count - x.count || x.a.localeCompare(y.a))
138
+ .slice(0, 20);
140
139
  }
141
-
142
140
  // The shape callers should hold when there is nothing to analyze — same fields,
143
141
  // honest reason. Views render this instead of inventing zeros.
144
142
  export function unavailableHistory(reason) {
145
- return { available: false, reason };
143
+ return { available: false, reason };
146
144
  }
145
+ //# sourceMappingURL=history.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"history.js","sourceRoot":"","sources":["../../src/shared/analyzer/history.ts"],"names":[],"mappings":"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","sourcesContent":["// Git history analysis: churn, authorship, the hotspot cross-tab, and\n// temporal co-change coupling. Pure — the commits arrive as plain data from an\n// injected provider (the server's `gitLog`), exactly as `scanRepo` takes files\n// through `FileSource`. Nothing here knows git exists.\n//\n// The unit of history is a commit: { hash, author: { name, email }, date, files }.\n// Everything is keyed on file path, which is already the join key the rest of\n// the engine speaks — complexity comes over from scan.files, structural edges\n// from scan.edges, and the two views of \"these change together\" sit side by side.\n\nimport { baseName } from './pathUtil.js';\n\n// Commits touching more files than this are skipped for pair counting, not for\n// churn — a 900-file reformat or a mass rename would otherwise contribute\n// C(900,2) ≈ 400k pairs of noise to co-change while saying nothing about\n// coupling. Churn still counts them; every file in them was really touched.\nconst MAX_COMMIT_FILES_FOR_PAIRS = 50;\n\n// Once this many distinct pairs are tracked, new pairs stop being recorded and\n// only existing ones keep counting. A hard bound on memory for repos with long\n// histories of small commits; the top of the list is decided well before this.\nconst MAX_TRACKED_PAIRS = 5000;\n\nexport function analyzeHistory(scan, commits, opts: Record<string, any> = {}) {\n const totalCommits = opts.totalCommits ?? commits.length;\n const truncated = totalCommits > commits.length;\n\n // ---- per-file rollup -----------------------------------------------------\n // One pass over every commit's file list. Authors are identified by email —\n // names collide and change; the email is what git itself keys identity on.\n const churn = new Map(); // path -> commit count\n const authorsOf = new Map(); // path -> Map(email -> display name)\n const firstSeen = new Map(); // path -> earliest commit date (ISO strings sort)\n const lastTouched = new Map();\n const authorCommits = new Map(); // email -> { name, commits }\n let pathsGone = 0;\n\n for (const c of commits) {\n const date = c.date || '';\n const known = authorCommits.get(c.author?.email);\n if (known) known.commits++;\n else if (c.author?.email) authorCommits.set(c.author.email, { name: c.author.name || c.author.email, commits: 1 });\n\n for (const p of c.files || []) {\n churn.set(p, (churn.get(p) || 0) + 1);\n if (!firstSeen.has(p) || date < firstSeen.get(p)) firstSeen.set(p, date);\n if (!lastTouched.has(p) || date > lastTouched.get(p)) lastTouched.set(p, date);\n let byEmail = authorsOf.get(p);\n if (!byEmail) { byEmail = new Map(); authorsOf.set(p, byEmail); }\n if (!byEmail.has(c.author?.email)) byEmail.set(c.author?.email, c.author?.name || c.author?.email || '?');\n }\n }\n\n // ---- join with the scan ---------------------------------------------------\n // Complexity lives on scan.files; history paths that are not in the scan are\n // files deleted or renamed inside the window. They had churn once — counted,\n // then set aside rather than silently mixed into rankings of files that no\n // longer exist.\n const cxOf = new Map<string, number>(scan.files.map((f) => [f.path, f.complexity || 1]));\n for (const p of churn.keys()) if (!cxOf.has(p)) pathsGone++;\n\n const live = [...churn.entries()].filter(([p]) => cxOf.has(p));\n const maxChurn = Math.max(1, ...live.map(([, n]) => n));\n const maxCx = Math.max(1, ...live.map(([p]) => cxOf.get(p)));\n\n const perFile = live.map(([p, n]) => {\n const authors = authorsOf.get(p).size;\n return {\n path: p,\n name: baseName(p),\n churn: n,\n authors,\n solo: authors === 1,\n firstSeen: firstSeen.get(p),\n lastTouched: lastTouched.get(p),\n complexity: cxOf.get(p),\n // The cross-tab: both axes normalized against the repo's own worst, so\n // the score reads \"how far up both columns at once\" and a file maxing\n // one axis but not the other cannot outrank one high on both.\n hotspot: Math.round(100 * (n / maxChurn) * (cxOf.get(p) / maxCx)),\n };\n });\n perFile.sort((a, b) => b.hotspot - a.hotspot || b.churn - a.churn || a.path.localeCompare(b.path));\n\n const byPath: Record<string, any> = {};\n for (const row of perFile) byPath[row.path] = row;\n const soloFiles = perFile.filter((r) => r.solo && r.churn >= 3).length;\n\n const dates = commits.map((c) => c.date).filter(Boolean);\n\n return {\n available: true,\n commitCount: commits.length,\n totalCommits,\n truncated,\n // The raw commit list, not just the rollups. The insights view draws a\n // 52-week heatmap and a punch card from per-commit dates; without the\n // original array, that view rendered an empty calendar even when history\n // was fully available. The cost is the same parsed commits we already hold\n // — `parseGitLog` capped at 2,000 — so duplication here is honest.\n commits,\n authors: [...authorCommits.values()].sort((a, b) => b.commits - a.commits),\n firstCommitAt: dates.length ? dates.reduce((a, b) => (a < b ? a : b)) : null,\n lastCommitAt: dates.length ? dates.reduce((a, b) => (a > b ? a : b)) : null,\n perFile,\n byPath,\n soloFiles,\n coChanged: coChangePairs(commits, new Set(scan.edges.map((e) => e.from + '->' + e.to))),\n pathsGone,\n };\n}\n\n// Files that keep turning up in the same commit without importing each other.\n// The structural coupling matrix already shows what imports what; this is the\n// contrast — changes that travel together for reasons the import graph does\n// not know about (shared schema, feature pairs, copy-paste siblings).\nfunction coChangePairs(commits, structuralEdges) {\n const pairs = new Map(); // 'a|b' (sorted) -> count\n\n for (const c of commits) {\n const files = c.files || [];\n if (files.length < 2 || files.length > MAX_COMMIT_FILES_FOR_PAIRS) continue;\n for (let i = 0; i < files.length; i++) {\n for (let j = i + 1; j < files.length; j++) {\n const a = files[i] < files[j] ? files[i] : files[j];\n const b = files[i] < files[j] ? files[j] : files[i];\n if (structuralEdges.has(a + '->' + b) || structuralEdges.has(b + '->' + a)) continue;\n const key = a + '|' + b;\n const seen = pairs.get(key);\n if (seen) seen.count++;\n else if (pairs.size < MAX_TRACKED_PAIRS) pairs.set(key, { a, b, count: 1 });\n }\n }\n }\n\n return [...pairs.values()]\n .filter((p) => p.count >= 2) // once is coincidence; the plan is about habits\n .sort((x, y) => y.count - x.count || x.a.localeCompare(y.a))\n .slice(0, 20);\n}\n\n// The shape callers should hold when there is nothing to analyze — same fields,\n// honest reason. Views render this instead of inventing zeros.\nexport function unavailableHistory(reason) {\n return { available: false, reason };\n}\n"]}
@@ -0,0 +1,2 @@
1
+ /** Parse TypeScript's JSON-with-comments configs without altering quoted paths. */
2
+ export declare function parseJsonConfig(text: string): unknown;
@@ -0,0 +1,61 @@
1
+ /** Parse TypeScript's JSON-with-comments configs without altering quoted paths. */
2
+ export function parseJsonConfig(text) {
3
+ const characters = text.split('');
4
+ let quoted = false;
5
+ for (let i = 0; i < text.length; i++) {
6
+ const character = text[i];
7
+ if (quoted) {
8
+ if (character === '\\')
9
+ i++;
10
+ else if (character === '"')
11
+ quoted = false;
12
+ continue;
13
+ }
14
+ if (character === '"') {
15
+ quoted = true;
16
+ continue;
17
+ }
18
+ if (character === '/' && text[i + 1] === '/') {
19
+ while (i < text.length && text[i] !== '\n')
20
+ characters[i++] = ' ';
21
+ }
22
+ else if (character === '/' && text[i + 1] === '*') {
23
+ characters[i++] = ' ';
24
+ characters[i++] = ' ';
25
+ while (i < text.length && !(text[i] === '*' && text[i + 1] === '/')) {
26
+ if (text[i] !== '\n' && text[i] !== '\r')
27
+ characters[i] = ' ';
28
+ i++;
29
+ }
30
+ if (i >= text.length)
31
+ throw new SyntaxError('Unterminated config comment.');
32
+ characters[i++] = ' ';
33
+ characters[i] = ' ';
34
+ }
35
+ }
36
+ // JSONC also accepts trailing commas. Handle them only outside strings.
37
+ quoted = false;
38
+ for (let i = 0; i < characters.length; i++) {
39
+ const character = characters[i];
40
+ if (quoted) {
41
+ if (character === '\\')
42
+ i++;
43
+ else if (character === '"')
44
+ quoted = false;
45
+ continue;
46
+ }
47
+ if (character === '"') {
48
+ quoted = true;
49
+ continue;
50
+ }
51
+ if (character !== ',')
52
+ continue;
53
+ let next = i + 1;
54
+ while (next < characters.length && /\s/.test(characters[next]))
55
+ next++;
56
+ if (characters[next] === '}' || characters[next] === ']')
57
+ characters[i] = ' ';
58
+ }
59
+ return JSON.parse(characters.join(''));
60
+ }
61
+ //# sourceMappingURL=jsonConfig.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"jsonConfig.js","sourceRoot":"","sources":["../../src/shared/analyzer/jsonConfig.ts"],"names":[],"mappings":"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","sourcesContent":["/** Parse TypeScript's JSON-with-comments configs without altering quoted paths. */\nexport function parseJsonConfig(text: string): unknown {\n const characters = text.split('');\n let quoted = false;\n for (let i = 0; i < text.length; i++) {\n const character = text[i];\n if (quoted) {\n if (character === '\\\\') i++;\n else if (character === '\"') quoted = false;\n continue;\n }\n if (character === '\"') { quoted = true; continue; }\n if (character === '/' && text[i + 1] === '/') {\n while (i < text.length && text[i] !== '\\n') characters[i++] = ' ';\n } else if (character === '/' && text[i + 1] === '*') {\n characters[i++] = ' ';\n characters[i++] = ' ';\n while (i < text.length && !(text[i] === '*' && text[i + 1] === '/')) {\n if (text[i] !== '\\n' && text[i] !== '\\r') characters[i] = ' ';\n i++;\n }\n if (i >= text.length) throw new SyntaxError('Unterminated config comment.');\n characters[i++] = ' ';\n characters[i] = ' ';\n }\n }\n // JSONC also accepts trailing commas. Handle them only outside strings.\n quoted = false;\n for (let i = 0; i < characters.length; i++) {\n const character = characters[i];\n if (quoted) {\n if (character === '\\\\') i++;\n else if (character === '\"') quoted = false;\n continue;\n }\n if (character === '\"') { quoted = true; continue; }\n if (character !== ',') continue;\n let next = i + 1;\n while (next < characters.length && /\\s/.test(characters[next])) next++;\n if (characters[next] === '}' || characters[next] === ']') characters[i] = ' ';\n }\n return JSON.parse(characters.join('')) as unknown;\n}\n"]}
@@ -0,0 +1,26 @@
1
+ export declare const extensions: string[];
2
+ export declare function analyze(source: any, path: any): {
3
+ imports: any[];
4
+ exports: {
5
+ name: any;
6
+ kind: string;
7
+ }[];
8
+ functions: any[];
9
+ classes: any[];
10
+ hasMain: boolean;
11
+ namespace: any;
12
+ };
13
+ export declare function sourceRootFor(fromPath: any, namespace: any): string;
14
+ export declare function resolveImport(spec: any, fromPath: any, has: any, context?: Record<string, any>, meta?: Record<string, any>): {
15
+ path?: undefined;
16
+ unresolved: any;
17
+ packageDir?: undefined;
18
+ } | {
19
+ unresolved?: undefined;
20
+ path: string;
21
+ packageDir?: undefined;
22
+ } | {
23
+ path?: undefined;
24
+ unresolved?: undefined;
25
+ packageDir: any;
26
+ };
@@ -8,54 +8,45 @@
8
8
  // directory and the scanner links to every file in it — the same shape Go uses
9
9
  // for packages.
10
10
  import { blankComments, lineCounter } from '../util.js';
11
-
12
11
  export const extensions = ['.cs'];
13
-
14
12
  const NAMESPACE_RE = /^\s*namespace\s+([A-Za-z0-9_.]+)\s*[;{]/m;
15
-
16
13
  export function analyze(source, path) {
17
- const clean = blankComments(source);
18
- const lineAt = lineCounter(clean);
19
-
20
- // The file's own namespace. Captured first because every import is resolved
21
- // relative to it — this declaration is the only place the root is written down.
22
- // Block-scoped namespaces (`namespace A.B { }`) are the norm, and the regex
23
- // accepts both the `;` and `{` forms.
24
- const namespace = (clean.match(NAMESPACE_RE) || [])[1] || '';
25
-
26
- const imports = [];
27
- // `using static A.B.C;` names a type (and optionally a member of it); plain
28
- // `using A.B;` names a namespace. The distinction decides whether we look for
29
- // a file or a directory.
30
- for (const m of clean.matchAll(/\busing\s+(static\s+)?([A-Za-z0-9_.]+)\s*;/g)) {
31
- imports.push({ spec: m[2], static: !!m[1], namespace, line: lineAt(m.index) });
32
- }
33
- // `using X = Y;` aliases and global usings are out of scope; a spec that fails
34
- // to resolve is reported as unresolved rather than guessed at.
35
-
36
- const classes = [];
37
- for (const m of clean.matchAll(/(\[[^\]]+\]\s*)*\b(?:public\s+|private\s+|protected\s+|internal\s+)*(?:abstract\s+|sealed\s+|static\s+|partial\s+)*(?:class|interface|struct|record|enum)\s+([a-zA-Z0-9_]+)/g)) {
38
- const attributes = [];
39
- if (m[1]) {
40
- for (const am of m[1].matchAll(/\[([A-Za-z0-9_]+)/g)) {
41
- attributes.push(am[1]);
42
- }
14
+ const clean = blankComments(source);
15
+ const lineAt = lineCounter(clean);
16
+ // The file's own namespace. Captured first because every import is resolved
17
+ // relative to it — this declaration is the only place the root is written down.
18
+ // Block-scoped namespaces (`namespace A.B { }`) are the norm, and the regex
19
+ // accepts both the `;` and `{` forms.
20
+ const namespace = (clean.match(NAMESPACE_RE) || [])[1] || '';
21
+ const imports = [];
22
+ // `using static A.B.C;` names a type (and optionally a member of it); plain
23
+ // `using A.B;` names a namespace. The distinction decides whether we look for
24
+ // a file or a directory.
25
+ for (const m of clean.matchAll(/\busing\s+(static\s+)?([A-Za-z0-9_.]+)\s*;/g)) {
26
+ imports.push({ spec: m[2], static: !!m[1], namespace, line: lineAt(m.index) });
43
27
  }
44
- classes.push({ name: m[2], attributes, line: lineAt(m.index) });
45
- }
46
-
47
- const hasMain = /static\s+(?:async\s+Task|void|int|async\s+Task<int>)\s+Main\s*\(/.test(clean);
48
-
49
- return {
50
- imports,
51
- exports: classes.map(c => ({ name: c.name, kind: 'class' })),
52
- functions: [],
53
- classes,
54
- hasMain,
55
- namespace,
56
- };
28
+ // `using X = Y;` aliases and global usings are out of scope; a spec that fails
29
+ // to resolve is reported as unresolved rather than guessed at.
30
+ const classes = [];
31
+ for (const m of clean.matchAll(/(\[[^\]]+\]\s*)*\b(?:public\s+|private\s+|protected\s+|internal\s+)*(?:abstract\s+|sealed\s+|static\s+|partial\s+)*(?:class|interface|struct|record|enum)\s+([a-zA-Z0-9_]+)/g)) {
32
+ const attributes = [];
33
+ if (m[1]) {
34
+ for (const am of m[1].matchAll(/\[([A-Za-z0-9_]+)/g)) {
35
+ attributes.push(am[1]);
36
+ }
37
+ }
38
+ classes.push({ name: m[2], attributes, line: lineAt(m.index) });
39
+ }
40
+ const hasMain = /static\s+(?:async\s+Task|void|int|async\s+Task<int>)\s+Main\s*\(/.test(clean);
41
+ return {
42
+ imports,
43
+ exports: classes.map(c => ({ name: c.name, kind: 'class' })),
44
+ functions: [],
45
+ classes,
46
+ hasMain,
47
+ namespace,
48
+ };
57
49
  }
58
-
59
50
  // A C# `using` is resolved by *path suffix*, not by a root prefix. See
60
51
  // `resolveImport` below for why that distinction matters — it is the whole
61
52
  // difference between C# and Java, and getting it backwards drops every import.
@@ -64,51 +55,55 @@ export function analyze(source, path) {
64
55
  // where a project *does* mirror the full namespace (`Acme/Services/User.cs`).
65
56
  // It is a fallback, not the main path.
66
57
  export function sourceRootFor(fromPath, namespace) {
67
- if (!namespace) return null;
68
- const suffix = namespace.replace(/\./g, '/');
69
- const normalized = String(fromPath).replace(/\\/g, '/');
70
- const nested = normalized.lastIndexOf('/' + suffix + '/');
71
- if (nested >= 0) return normalized.slice(0, nested + 1);
72
- if (normalized === suffix || normalized.startsWith(suffix + '/')) return '';
73
- return null;
58
+ if (!namespace)
59
+ return null;
60
+ const suffix = namespace.replace(/\./g, '/');
61
+ const normalized = String(fromPath).replace(/\\/g, '/');
62
+ const nested = normalized.lastIndexOf('/' + suffix + '/');
63
+ if (nested >= 0)
64
+ return normalized.slice(0, nested + 1);
65
+ if (normalized === suffix || normalized.startsWith(suffix + '/'))
66
+ return '';
67
+ return null;
74
68
  }
75
-
76
69
  export function resolveImport(spec, fromPath, has, context = {}, meta = {}) {
77
- const parts = spec.split('.').filter(Boolean);
78
- const findDir = (segments) => (segments.length ? context.findDirEndingWith?.(segments.join('/')) : null);
79
- if (!parts.length) return { unresolved: spec };
80
-
81
- // A C# `using` is ambiguous from the string alone: in `using Acme.Models;`,
82
- // `Models` might be a namespace (link its directory) or a type (link the
83
- // file). Both readings are tried, most specific first.
84
- //
85
- // The other half of the puzzle is that C# resolves by *path suffix*, not by a
86
- // root prefix the way Java does. `namespace Acme.Services` conventionally
87
- // lives in `src/Services/`: the folders mirror the namespace, but the leading
88
- // `Acme` — the project or company root — is not a directory. So the namespace
89
- // segments are matched against the *tail* of a real directory path, which is
90
- // what `findDirEndingWith` answers. Stripping the whole namespace instead, the
91
- // Java approach, matches nothing here and silently drops every import.
92
-
93
- // Reading 1: the last segment is a type. Find the directory holding its
94
- // namespace, then the file named after it.
95
- const namespaceParts = parts.slice(0, -1);
96
- for (let take = namespaceParts.length; take > 0; take -= 1) {
97
- const dir = findDir(namespaceParts.slice(namespaceParts.length - take));
98
- if (!dir) continue;
99
- const exact = dir + '/' + parts[parts.length - 1] + '.cs';
100
- if (has(exact)) return { path: exact };
101
- if (meta.static) return { unresolved: spec }; // `using static A.B.C;` is always a type
102
- }
103
-
104
- // Reading 2: the whole spec is a namespace, so link its directory.
105
- for (let take = parts.length; take > 0; take -= 1) {
106
- const dir = findDir(parts.slice(parts.length - take));
107
- if (dir) return { packageDir: dir };
108
- }
109
-
110
- // The BCL (`System`, `System.Collections.Generic`) and every NuGet package land
111
- // here. Staying unresolved is the honest answer: a file we invent would be a
112
- // node in the graph that does not exist.
113
- return { unresolved: spec };
70
+ const parts = spec.split('.').filter(Boolean);
71
+ const findDir = (segments) => (segments.length ? context.findDirEndingWith?.(segments.join('/')) : null);
72
+ if (!parts.length)
73
+ return { unresolved: spec };
74
+ // A C# `using` is ambiguous from the string alone: in `using Acme.Models;`,
75
+ // `Models` might be a namespace (link its directory) or a type (link the
76
+ // file). Both readings are tried, most specific first.
77
+ //
78
+ // The other half of the puzzle is that C# resolves by *path suffix*, not by a
79
+ // root prefix the way Java does. `namespace Acme.Services` conventionally
80
+ // lives in `src/Services/`: the folders mirror the namespace, but the leading
81
+ // `Acme` — the project or company root — is not a directory. So the namespace
82
+ // segments are matched against the *tail* of a real directory path, which is
83
+ // what `findDirEndingWith` answers. Stripping the whole namespace instead, the
84
+ // Java approach, matches nothing here and silently drops every import.
85
+ // Reading 1: the last segment is a type. Find the directory holding its
86
+ // namespace, then the file named after it.
87
+ const namespaceParts = parts.slice(0, -1);
88
+ for (let take = namespaceParts.length; take > 0; take -= 1) {
89
+ const dir = findDir(namespaceParts.slice(namespaceParts.length - take));
90
+ if (!dir)
91
+ continue;
92
+ const exact = dir + '/' + parts[parts.length - 1] + '.cs';
93
+ if (has(exact))
94
+ return { path: exact };
95
+ if (meta.static)
96
+ return { unresolved: spec }; // `using static A.B.C;` is always a type
97
+ }
98
+ // Reading 2: the whole spec is a namespace, so link its directory.
99
+ for (let take = parts.length; take > 0; take -= 1) {
100
+ const dir = findDir(parts.slice(parts.length - take));
101
+ if (dir)
102
+ return { packageDir: dir };
103
+ }
104
+ // The BCL (`System`, `System.Collections.Generic`) and every NuGet package land
105
+ // here. Staying unresolved is the honest answer: a file we invent would be a
106
+ // node in the graph that does not exist.
107
+ return { unresolved: spec };
114
108
  }
109
+ //# sourceMappingURL=csharp.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"csharp.js","sourceRoot":"","sources":["../../../src/shared/analyzer/languages/csharp.ts"],"names":[],"mappings":"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","sourcesContent":["// C# analyzer\n//\n// C# is namespace-rooted exactly as Java is package-rooted: a file's path is its\n// namespace spelled with slashes, under a project root. The difference that\n// matters is the import. Java's `import a.b.C;` names a *class*, so one path\n// answers it; C#'s `using A.B;` names a *namespace*, and the classes inside it\n// are not knowable from the import string. So a namespace import resolves to a\n// directory and the scanner links to every file in it — the same shape Go uses\n// for packages.\nimport { blankComments, lineCounter } from '../util.js';\n\nexport const extensions = ['.cs'];\n\nconst NAMESPACE_RE = /^\\s*namespace\\s+([A-Za-z0-9_.]+)\\s*[;{]/m;\n\nexport function analyze(source, path) {\n const clean = blankComments(source);\n const lineAt = lineCounter(clean);\n\n // The file's own namespace. Captured first because every import is resolved\n // relative to it — this declaration is the only place the root is written down.\n // Block-scoped namespaces (`namespace A.B { }`) are the norm, and the regex\n // accepts both the `;` and `{` forms.\n const namespace = (clean.match(NAMESPACE_RE) || [])[1] || '';\n\n const imports = [];\n // `using static A.B.C;` names a type (and optionally a member of it); plain\n // `using A.B;` names a namespace. The distinction decides whether we look for\n // a file or a directory.\n for (const m of clean.matchAll(/\\busing\\s+(static\\s+)?([A-Za-z0-9_.]+)\\s*;/g)) {\n imports.push({ spec: m[2], static: !!m[1], namespace, line: lineAt(m.index) });\n }\n // `using X = Y;` aliases and global usings are out of scope; a spec that fails\n // to resolve is reported as unresolved rather than guessed at.\n\n const classes = [];\n for (const m of clean.matchAll(/(\\[[^\\]]+\\]\\s*)*\\b(?:public\\s+|private\\s+|protected\\s+|internal\\s+)*(?:abstract\\s+|sealed\\s+|static\\s+|partial\\s+)*(?:class|interface|struct|record|enum)\\s+([a-zA-Z0-9_]+)/g)) {\n const attributes = [];\n if (m[1]) {\n for (const am of m[1].matchAll(/\\[([A-Za-z0-9_]+)/g)) {\n attributes.push(am[1]);\n }\n }\n classes.push({ name: m[2], attributes, line: lineAt(m.index) });\n }\n\n const hasMain = /static\\s+(?:async\\s+Task|void|int|async\\s+Task<int>)\\s+Main\\s*\\(/.test(clean);\n\n return {\n imports,\n exports: classes.map(c => ({ name: c.name, kind: 'class' })),\n functions: [],\n classes,\n hasMain,\n namespace,\n };\n}\n\n// A C# `using` is resolved by *path suffix*, not by a root prefix. See\n// `resolveImport` below for why that distinction matters — it is the whole\n// difference between C# and Java, and getting it backwards drops every import.\n//\n// `sourceRootFor` is kept and exported because it is the answer for the layouts\n// where a project *does* mirror the full namespace (`Acme/Services/User.cs`).\n// It is a fallback, not the main path.\nexport function sourceRootFor(fromPath, namespace) {\n if (!namespace) return null;\n const suffix = namespace.replace(/\\./g, '/');\n const normalized = String(fromPath).replace(/\\\\/g, '/');\n const nested = normalized.lastIndexOf('/' + suffix + '/');\n if (nested >= 0) return normalized.slice(0, nested + 1);\n if (normalized === suffix || normalized.startsWith(suffix + '/')) return '';\n return null;\n}\n\nexport function resolveImport(spec, fromPath, has, context: Record<string, any> = {}, meta: Record<string, any> = {}) {\n const parts = spec.split('.').filter(Boolean);\n const findDir = (segments) => (segments.length ? context.findDirEndingWith?.(segments.join('/')) : null);\n if (!parts.length) return { unresolved: spec };\n\n // A C# `using` is ambiguous from the string alone: in `using Acme.Models;`,\n // `Models` might be a namespace (link its directory) or a type (link the\n // file). Both readings are tried, most specific first.\n //\n // The other half of the puzzle is that C# resolves by *path suffix*, not by a\n // root prefix the way Java does. `namespace Acme.Services` conventionally\n // lives in `src/Services/`: the folders mirror the namespace, but the leading\n // `Acme` — the project or company root — is not a directory. So the namespace\n // segments are matched against the *tail* of a real directory path, which is\n // what `findDirEndingWith` answers. Stripping the whole namespace instead, the\n // Java approach, matches nothing here and silently drops every import.\n\n // Reading 1: the last segment is a type. Find the directory holding its\n // namespace, then the file named after it.\n const namespaceParts = parts.slice(0, -1);\n for (let take = namespaceParts.length; take > 0; take -= 1) {\n const dir = findDir(namespaceParts.slice(namespaceParts.length - take));\n if (!dir) continue;\n const exact = dir + '/' + parts[parts.length - 1] + '.cs';\n if (has(exact)) return { path: exact };\n if (meta.static) return { unresolved: spec }; // `using static A.B.C;` is always a type\n }\n\n // Reading 2: the whole spec is a namespace, so link its directory.\n for (let take = parts.length; take > 0; take -= 1) {\n const dir = findDir(parts.slice(parts.length - take));\n if (dir) return { packageDir: dir };\n }\n\n // The BCL (`System`, `System.Collections.Generic`) and every NuGet package land\n // here. Staying unresolved is the honest answer: a file we invent would be a\n // node in the graph that does not exist.\n return { unresolved: spec };\n}\n"]}