codebase-onboarder 0.6.0 → 1.0.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 (526) hide show
  1. package/README.md +284 -53
  2. package/bin/build-if-source.js +23 -0
  3. package/bin/onboarder.js +0 -0
  4. package/cli/agent/command.d.ts +71 -0
  5. package/cli/agent/command.js +177 -0
  6. package/cli/agent/command.js.map +1 -0
  7. package/cli/agent/config.d.ts +31 -0
  8. package/cli/agent/config.js +98 -0
  9. package/cli/agent/config.js.map +1 -0
  10. package/cli/agent/contracts.d.ts +77 -0
  11. package/cli/agent/contracts.js +2 -0
  12. package/cli/agent/contracts.js.map +1 -0
  13. package/cli/agent/files.d.ts +19 -0
  14. package/cli/agent/files.js +109 -0
  15. package/cli/agent/files.js.map +1 -0
  16. package/cli/agent/gitSource.d.ts +18 -0
  17. package/cli/agent/gitSource.js +69 -0
  18. package/cli/agent/gitSource.js.map +1 -0
  19. package/cli/agent/github.d.ts +21 -0
  20. package/cli/agent/github.js +94 -0
  21. package/cli/agent/github.js.map +1 -0
  22. package/cli/agent/mcp.d.ts +15 -0
  23. package/cli/agent/mcp.js +78 -0
  24. package/cli/agent/mcp.js.map +1 -0
  25. package/cli/agent/process.d.ts +18 -0
  26. package/cli/agent/process.js +118 -0
  27. package/cli/agent/process.js.map +1 -0
  28. package/cli/agent/protocol.d.ts +3 -0
  29. package/cli/agent/protocol.js +26 -0
  30. package/cli/agent/protocol.js.map +1 -0
  31. package/cli/agent/runner.d.ts +34 -0
  32. package/cli/agent/runner.js +152 -0
  33. package/cli/agent/runner.js.map +1 -0
  34. package/cli/agent/stdio.d.ts +1 -0
  35. package/cli/agent/stdio.js +62 -0
  36. package/cli/agent/stdio.js.map +1 -0
  37. package/cli/agent/tools.d.ts +7 -0
  38. package/cli/agent/tools.js +201 -0
  39. package/cli/agent/tools.js.map +1 -0
  40. package/cli/chat/app.d.ts +16 -0
  41. package/cli/chat/app.js +345 -0
  42. package/cli/chat/app.js.map +1 -0
  43. package/cli/chat/commands.d.ts +11 -0
  44. package/cli/chat/commands.js +120 -0
  45. package/cli/chat/commands.js.map +1 -0
  46. package/cli/chat/composer.d.ts +75 -0
  47. package/cli/chat/composer.js +467 -0
  48. package/cli/chat/composer.js.map +1 -0
  49. package/cli/chat/controller.d.ts +60 -0
  50. package/cli/chat/controller.js +393 -0
  51. package/cli/chat/controller.js.map +1 -0
  52. package/cli/chat/logo.d.ts +3 -0
  53. package/cli/chat/logo.js +14 -0
  54. package/cli/chat/logo.js.map +1 -0
  55. package/cli/chat/models.d.ts +18 -0
  56. package/cli/chat/models.js +88 -0
  57. package/cli/chat/models.js.map +1 -0
  58. package/cli/chat/profile.d.ts +5 -0
  59. package/cli/chat/profile.js +98 -0
  60. package/cli/chat/profile.js.map +1 -0
  61. package/cli/chat/render.d.ts +40 -0
  62. package/cli/chat/render.js +259 -0
  63. package/cli/chat/render.js.map +1 -0
  64. package/cli/chat/screen.d.ts +26 -0
  65. package/cli/chat/screen.js +117 -0
  66. package/cli/chat/screen.js.map +1 -0
  67. package/cli/chat/source.d.ts +29 -0
  68. package/cli/chat/source.js +116 -0
  69. package/cli/chat/source.js.map +1 -0
  70. package/cli/chat/store.d.ts +43 -0
  71. package/cli/chat/store.js +94 -0
  72. package/cli/chat/store.js.map +1 -0
  73. package/cli/chat/text.d.ts +4 -0
  74. package/cli/chat/text.js +27 -0
  75. package/cli/chat/text.js.map +1 -0
  76. package/cli/commands.d.ts +26 -0
  77. package/cli/commands.js +836 -811
  78. package/cli/commands.js.map +1 -0
  79. package/cli/explorer/advanced.d.ts +14 -0
  80. package/cli/explorer/advanced.js +321 -329
  81. package/cli/explorer/advanced.js.map +1 -0
  82. package/cli/explorer/app.d.ts +2 -0
  83. package/cli/explorer/app.js +404 -314
  84. package/cli/explorer/app.js.map +1 -0
  85. package/cli/explorer/assistant.d.ts +9 -0
  86. package/cli/explorer/assistant.js +58 -0
  87. package/cli/explorer/assistant.js.map +1 -0
  88. package/cli/explorer/commands.d.ts +17 -0
  89. package/cli/explorer/commands.js +373 -302
  90. package/cli/explorer/commands.js.map +1 -0
  91. package/cli/explorer/featureViews.d.ts +124 -0
  92. package/cli/explorer/featureViews.js +179 -0
  93. package/cli/explorer/featureViews.js.map +1 -0
  94. package/cli/explorer/github.d.ts +21 -0
  95. package/cli/explorer/github.js +42 -44
  96. package/cli/explorer/github.js.map +1 -0
  97. package/cli/explorer/graphs.d.ts +7 -0
  98. package/cli/explorer/graphs.js +90 -95
  99. package/cli/explorer/graphs.js.map +1 -0
  100. package/cli/explorer/picker.d.ts +22 -0
  101. package/cli/explorer/picker.js +150 -0
  102. package/cli/explorer/picker.js.map +1 -0
  103. package/cli/explorer/report.d.ts +85 -0
  104. package/cli/explorer/report.js +60 -0
  105. package/cli/explorer/report.js.map +1 -0
  106. package/cli/explorer/session.d.ts +449 -0
  107. package/cli/explorer/session.js +143 -144
  108. package/cli/explorer/session.js.map +1 -0
  109. package/cli/explorer/views.d.ts +19 -0
  110. package/cli/explorer/views.js +465 -482
  111. package/cli/explorer/views.js.map +1 -0
  112. package/cli/explorer/wrap.d.ts +1 -0
  113. package/cli/explorer/wrap.js +28 -44
  114. package/cli/explorer/wrap.js.map +1 -0
  115. package/cli/main.d.ts +1 -0
  116. package/cli/main.js +251 -164
  117. package/cli/main.js.map +1 -0
  118. package/cli/prompt.d.ts +5 -0
  119. package/cli/prompt.js +84 -80
  120. package/cli/prompt.js.map +1 -0
  121. package/cli/scanOptions.d.ts +2 -0
  122. package/cli/scanOptions.js +11 -0
  123. package/cli/scanOptions.js.map +1 -0
  124. package/cli/ui.d.ts +33 -0
  125. package/cli/ui.js +35 -44
  126. package/cli/ui.js.map +1 -0
  127. package/cli/wizard.d.ts +187 -0
  128. package/cli/wizard.js +304 -284
  129. package/cli/wizard.js.map +1 -0
  130. package/docs/HERMES_RESEARCH.md +51 -0
  131. package/package.json +16 -4
  132. package/public/app.d.ts +1 -0
  133. package/public/app.js +1133 -1011
  134. package/public/app.js.map +1 -0
  135. package/public/assets/onboarder-mark.svg +11 -0
  136. package/public/browser.d.ts +71 -0
  137. package/public/browser.js +2 -0
  138. package/public/browser.js.map +1 -0
  139. package/public/index.html +54 -20
  140. package/public/js/about.d.ts +6 -0
  141. package/public/js/about.js +152 -172
  142. package/public/js/about.js.map +1 -0
  143. package/public/js/aiDraft.d.ts +4 -0
  144. package/public/js/aiDraft.js +166 -125
  145. package/public/js/aiDraft.js.map +1 -0
  146. package/public/js/analysisPanel.d.ts +7 -0
  147. package/public/js/analysisPanel.js +166 -166
  148. package/public/js/analysisPanel.js.map +1 -0
  149. package/public/js/analysisReport.d.ts +38 -0
  150. package/public/js/analysisReport.js +105 -110
  151. package/public/js/analysisReport.js.map +1 -0
  152. package/public/js/api.d.ts +29 -0
  153. package/public/js/api.js +198 -166
  154. package/public/js/api.js.map +1 -0
  155. package/public/js/atlas.d.ts +6 -0
  156. package/public/js/atlas.js +50 -58
  157. package/public/js/atlas.js.map +1 -0
  158. package/public/js/blameView.d.ts +2 -0
  159. package/public/js/blameView.js +36 -37
  160. package/public/js/blameView.js.map +1 -0
  161. package/public/js/codeTab.d.ts +3 -0
  162. package/public/js/codeTab.js +175 -180
  163. package/public/js/codeTab.js.map +1 -0
  164. package/public/js/codeViewer.d.ts +5 -0
  165. package/public/js/codeViewer.js +162 -150
  166. package/public/js/codeViewer.js.map +1 -0
  167. package/public/js/components/FileChip.d.ts +9 -0
  168. package/public/js/components/FileChip.js +45 -49
  169. package/public/js/components/FileChip.js.map +1 -0
  170. package/public/js/components/MetricSparkline.d.ts +7 -0
  171. package/public/js/components/MetricSparkline.js +35 -44
  172. package/public/js/components/MetricSparkline.js.map +1 -0
  173. package/public/js/components/RiskBadge.d.ts +9 -0
  174. package/public/js/components/RiskBadge.js +55 -63
  175. package/public/js/components/RiskBadge.js.map +1 -0
  176. package/public/js/deepAnalysisView.d.ts +99 -0
  177. package/public/js/deepAnalysisView.js +282 -302
  178. package/public/js/deepAnalysisView.js.map +1 -0
  179. package/public/js/diagramPane.d.ts +9 -0
  180. package/public/js/diagramPane.js +138 -135
  181. package/public/js/diagramPane.js.map +1 -0
  182. package/public/js/diffView.d.ts +6 -0
  183. package/public/js/diffView.js +222 -209
  184. package/public/js/diffView.js.map +1 -0
  185. package/public/js/docsView.d.ts +2 -0
  186. package/public/js/docsView.js +441 -442
  187. package/public/js/docsView.js.map +1 -0
  188. package/public/js/dom.d.ts +197 -0
  189. package/public/js/dom.js +5 -0
  190. package/public/js/dom.js.map +1 -0
  191. package/public/js/fileSourceBrowser.d.ts +5 -0
  192. package/public/js/fileSourceBrowser.js +33 -41
  193. package/public/js/fileSourceBrowser.js.map +1 -0
  194. package/public/js/flameGraph.d.ts +5 -0
  195. package/public/js/flameGraph.js +62 -72
  196. package/public/js/flameGraph.js.map +1 -0
  197. package/public/js/forceGraph.d.ts +10 -0
  198. package/public/js/forceGraph.js +581 -556
  199. package/public/js/forceGraph.js.map +1 -0
  200. package/public/js/heatmap.d.ts +4 -0
  201. package/public/js/heatmap.js +142 -164
  202. package/public/js/heatmap.js.map +1 -0
  203. package/public/js/highlight.d.ts +2 -0
  204. package/public/js/highlight.js +116 -107
  205. package/public/js/highlight.js.map +1 -0
  206. package/public/js/html.d.ts +2 -0
  207. package/public/js/html.js +14 -44
  208. package/public/js/html.js.map +1 -0
  209. package/public/js/insightsView.d.ts +5 -0
  210. package/public/js/insightsView.js +93 -111
  211. package/public/js/insightsView.js.map +1 -0
  212. package/public/js/inspector.d.ts +37 -0
  213. package/public/js/inspector.js +416 -445
  214. package/public/js/inspector.js.map +1 -0
  215. package/public/js/llm.d.ts +115 -0
  216. package/public/js/llm.js +246 -278
  217. package/public/js/llm.js.map +1 -0
  218. package/public/js/mapView.d.ts +3 -0
  219. package/public/js/mapView.js +121 -120
  220. package/public/js/mapView.js.map +1 -0
  221. package/public/js/markdown.d.ts +3 -0
  222. package/public/js/markdown.js +84 -91
  223. package/public/js/markdown.js.map +1 -0
  224. package/public/js/mindmap.d.ts +32 -0
  225. package/public/js/mindmap.js +244 -259
  226. package/public/js/mindmap.js.map +1 -0
  227. package/public/js/mobilePanels.d.ts +5 -0
  228. package/public/js/mobilePanels.js +50 -0
  229. package/public/js/mobilePanels.js.map +1 -0
  230. package/public/js/repoFiles.d.ts +12 -0
  231. package/public/js/repoFiles.js +29 -28
  232. package/public/js/repoFiles.js.map +1 -0
  233. package/public/js/reviewView.d.ts +28 -0
  234. package/public/js/reviewView.js +256 -0
  235. package/public/js/reviewView.js.map +1 -0
  236. package/public/js/sbomView.d.ts +3 -0
  237. package/public/js/sbomView.js +59 -67
  238. package/public/js/sbomView.js.map +1 -0
  239. package/public/js/scanCache.d.ts +16 -0
  240. package/public/js/scanCache.js +67 -65
  241. package/public/js/scanCache.js.map +1 -0
  242. package/public/js/search.d.ts +7 -0
  243. package/public/js/search.js +868 -800
  244. package/public/js/search.js.map +1 -0
  245. package/public/js/searchLocal.d.ts +42 -0
  246. package/public/js/searchLocal.js +87 -0
  247. package/public/js/searchLocal.js.map +1 -0
  248. package/public/js/serverSettings.d.ts +5 -0
  249. package/public/js/serverSettings.js +153 -164
  250. package/public/js/serverSettings.js.map +1 -0
  251. package/public/js/state.d.ts +798 -0
  252. package/public/js/state.js +114 -122
  253. package/public/js/state.js.map +1 -0
  254. package/public/js/tour.d.ts +6 -0
  255. package/public/js/tour.js +5 -7
  256. package/public/js/tour.js.map +1 -0
  257. package/public/js/transitions.d.ts +2 -0
  258. package/public/js/transitions.js +9 -10
  259. package/public/js/transitions.js.map +1 -0
  260. package/public/js/tree.d.ts +12 -0
  261. package/public/js/tree.js +157 -154
  262. package/public/js/tree.js.map +1 -0
  263. package/public/js/workflowsView.d.ts +3 -0
  264. package/public/js/workflowsView.js +15 -22
  265. package/public/js/workflowsView.js.map +1 -0
  266. package/public/search.css +495 -0
  267. package/public/styles.css +188 -346
  268. package/public/workbench.css +166 -0
  269. package/server/apiAuth.d.ts +3 -0
  270. package/server/apiAuth.js +18 -20
  271. package/server/apiAuth.js.map +1 -0
  272. package/server/apiDiff.d.ts +2 -0
  273. package/server/apiDiff.js +20 -18
  274. package/server/apiDiff.js.map +1 -0
  275. package/server/apiDocs.d.ts +2 -0
  276. package/server/apiDocs.js +38 -40
  277. package/server/apiDocs.js.map +1 -0
  278. package/server/apiFile.d.ts +1 -0
  279. package/server/apiFile.js +14 -17
  280. package/server/apiFile.js.map +1 -0
  281. package/server/apiGitBlame.d.ts +2 -0
  282. package/server/apiGitBlame.js +50 -51
  283. package/server/apiGitBlame.js.map +1 -0
  284. package/server/apiMcp.d.ts +4 -0
  285. package/server/apiMcp.js +64 -66
  286. package/server/apiMcp.js.map +1 -0
  287. package/server/apiReview.d.ts +2 -0
  288. package/server/apiReview.js +45 -0
  289. package/server/apiReview.js.map +1 -0
  290. package/server/apiScan.d.ts +5 -0
  291. package/server/apiScan.js +90 -71
  292. package/server/apiScan.js.map +1 -0
  293. package/server/apiSearch.d.ts +33 -0
  294. package/server/apiSearch.js +184 -200
  295. package/server/apiSearch.js.map +1 -0
  296. package/server/apiSettings.d.ts +4 -0
  297. package/server/apiSettings.js +84 -85
  298. package/server/apiSettings.js.map +1 -0
  299. package/server/apiTools.d.ts +3 -0
  300. package/server/apiTools.js +55 -56
  301. package/server/apiTools.js.map +1 -0
  302. package/server/auth.d.ts +5 -0
  303. package/server/auth.js +54 -51
  304. package/server/auth.js.map +1 -0
  305. package/server/config.d.ts +42 -0
  306. package/server/config.js +189 -194
  307. package/server/config.js.map +1 -0
  308. package/server/daemon.d.ts +12 -0
  309. package/server/daemon.js +124 -114
  310. package/server/daemon.js.map +1 -0
  311. package/server/fileSourceNode.d.ts +2 -0
  312. package/server/fileSourceNode.js +34 -36
  313. package/server/fileSourceNode.js.map +1 -0
  314. package/server/gitClone.d.ts +9 -0
  315. package/server/gitClone.js +59 -61
  316. package/server/gitClone.js.map +1 -0
  317. package/server/gitDiff.d.ts +36 -0
  318. package/server/gitDiff.js +271 -169
  319. package/server/gitDiff.js.map +1 -0
  320. package/server/gitHistory.d.ts +12 -0
  321. package/server/gitHistory.js +80 -80
  322. package/server/gitHistory.js.map +1 -0
  323. package/server/gitRemote.d.ts +12 -0
  324. package/server/gitRemote.js +37 -38
  325. package/server/gitRemote.js.map +1 -0
  326. package/server/hermesChat.d.ts +13 -0
  327. package/server/hermesChat.js +145 -0
  328. package/server/hermesChat.js.map +1 -0
  329. package/server/htmlText.d.ts +4 -0
  330. package/server/htmlText.js +16 -23
  331. package/server/htmlText.js.map +1 -0
  332. package/server/http.d.ts +6 -0
  333. package/server/http.js +27 -34
  334. package/server/http.js.map +1 -0
  335. package/server/httpGuards.d.ts +2 -0
  336. package/server/httpGuards.js +47 -43
  337. package/server/httpGuards.js.map +1 -0
  338. package/server/https.d.ts +34 -0
  339. package/server/https.js +121 -111
  340. package/server/https.js.map +1 -0
  341. package/server/index.d.ts +40 -0
  342. package/server/index.js +139 -147
  343. package/server/index.js.map +1 -0
  344. package/server/layout.d.ts +26 -0
  345. package/server/layout.js +59 -64
  346. package/server/layout.js.map +1 -0
  347. package/server/llmProxy.d.ts +3 -0
  348. package/server/llmProxy.js +137 -135
  349. package/server/llmProxy.js.map +1 -0
  350. package/server/logger.d.ts +38 -0
  351. package/server/logger.js +198 -211
  352. package/server/logger.js.map +1 -0
  353. package/server/mcp/analysis.d.ts +29 -0
  354. package/server/mcp/analysis.js +99 -120
  355. package/server/mcp/analysis.js.map +1 -0
  356. package/server/mcp/http.d.ts +9 -0
  357. package/server/mcp/http.js +128 -137
  358. package/server/mcp/http.js.map +1 -0
  359. package/server/mcp/runner.d.ts +68 -0
  360. package/server/mcp/runner.js +208 -218
  361. package/server/mcp/runner.js.map +1 -0
  362. package/server/mcp/server.d.ts +29 -0
  363. package/server/mcp/server.js +167 -180
  364. package/server/mcp/server.js.map +1 -0
  365. package/server/mcp/standalone.d.ts +1 -0
  366. package/server/mcp/standalone.js +8 -10
  367. package/server/mcp/standalone.js.map +1 -0
  368. package/server/mcp/tools.d.ts +2020 -0
  369. package/server/mcp/tools.js +523 -574
  370. package/server/mcp/tools.js.map +1 -0
  371. package/server/paths.d.ts +3 -0
  372. package/server/paths.js +17 -15
  373. package/server/paths.js.map +1 -0
  374. package/server/pidfile.d.ts +9 -0
  375. package/server/pidfile.js +43 -45
  376. package/server/pidfile.js.map +1 -0
  377. package/server/review.d.ts +5 -0
  378. package/server/review.js +35 -0
  379. package/server/review.js.map +1 -0
  380. package/server/router.d.ts +33 -0
  381. package/server/router.js +195 -197
  382. package/server/router.js.map +1 -0
  383. package/server/scanRequest.d.ts +3 -0
  384. package/server/scanRequest.js +36 -0
  385. package/server/scanRequest.js.map +1 -0
  386. package/server/searchIndex.d.ts +17 -0
  387. package/server/searchIndex.js +83 -80
  388. package/server/searchIndex.js.map +1 -0
  389. package/server/sessions.d.ts +14 -0
  390. package/server/sessions.js +62 -70
  391. package/server/sessions.js.map +1 -0
  392. package/server/startup.d.ts +118 -0
  393. package/server/startup.js +143 -148
  394. package/server/startup.js.map +1 -0
  395. package/server/static.d.ts +6 -0
  396. package/server/static.js +28 -31
  397. package/server/static.js.map +1 -0
  398. package/server/tools/install.d.ts +4 -0
  399. package/server/tools/install.js +176 -185
  400. package/server/tools/install.js.map +1 -0
  401. package/server/tools/parse.d.ts +8 -0
  402. package/server/tools/parse.js +112 -114
  403. package/server/tools/parse.js.map +1 -0
  404. package/server/tools/platform.d.ts +9 -0
  405. package/server/tools/platform.js +49 -47
  406. package/server/tools/platform.js.map +1 -0
  407. package/server/tools/registry.d.ts +137 -0
  408. package/server/tools/registry.js +173 -172
  409. package/server/tools/registry.js.map +1 -0
  410. package/server/tools/scan.d.ts +34 -0
  411. package/server/tools/scan.js +89 -98
  412. package/server/tools/scan.js.map +1 -0
  413. package/server/tools.d.ts +34 -0
  414. package/server/tools.js +94 -107
  415. package/server/tools.js.map +1 -0
  416. package/server/tunnel.d.ts +29 -0
  417. package/server/tunnel.js +61 -66
  418. package/server/tunnel.js.map +1 -0
  419. package/shared/analyzer/docs.d.ts +16 -0
  420. package/shared/analyzer/docs.js +99 -93
  421. package/shared/analyzer/docs.js.map +1 -0
  422. package/shared/analyzer/explainLocal.d.ts +4 -0
  423. package/shared/analyzer/explainLocal.js +122 -142
  424. package/shared/analyzer/explainLocal.js.map +1 -0
  425. package/shared/analyzer/github.d.ts +37 -0
  426. package/shared/analyzer/github.js +41 -43
  427. package/shared/analyzer/github.js.map +1 -0
  428. package/shared/analyzer/graph.d.ts +41 -0
  429. package/shared/analyzer/graph.js +393 -400
  430. package/shared/analyzer/graph.js.map +1 -0
  431. package/shared/analyzer/health.d.ts +33 -0
  432. package/shared/analyzer/health.js +197 -193
  433. package/shared/analyzer/health.js.map +1 -0
  434. package/shared/analyzer/history.d.ts +29 -0
  435. package/shared/analyzer/history.js +110 -111
  436. package/shared/analyzer/history.js.map +1 -0
  437. package/shared/analyzer/jsonConfig.d.ts +2 -0
  438. package/shared/analyzer/jsonConfig.js +61 -0
  439. package/shared/analyzer/jsonConfig.js.map +1 -0
  440. package/shared/analyzer/languages/csharp.d.ts +26 -0
  441. package/shared/analyzer/languages/csharp.js +83 -88
  442. package/shared/analyzer/languages/csharp.js.map +1 -0
  443. package/shared/analyzer/languages/generic.d.ts +23 -0
  444. package/shared/analyzer/languages/generic.js +94 -105
  445. package/shared/analyzer/languages/generic.js.map +1 -0
  446. package/shared/analyzer/languages/go.d.ts +24 -0
  447. package/shared/analyzer/languages/go.js +45 -54
  448. package/shared/analyzer/languages/go.js.map +1 -0
  449. package/shared/analyzer/languages/index.d.ts +6 -0
  450. package/shared/analyzer/languages/index.js +19 -20
  451. package/shared/analyzer/languages/index.js.map +1 -0
  452. package/shared/analyzer/languages/java.d.ts +20 -0
  453. package/shared/analyzer/languages/java.js +73 -78
  454. package/shared/analyzer/languages/java.js.map +1 -0
  455. package/shared/analyzer/languages/javascript.d.ts +12 -0
  456. package/shared/analyzer/languages/javascript.js +258 -209
  457. package/shared/analyzer/languages/javascript.js.map +1 -0
  458. package/shared/analyzer/languages/python.d.ts +23 -0
  459. package/shared/analyzer/languages/python.js +92 -100
  460. package/shared/analyzer/languages/python.js.map +1 -0
  461. package/shared/analyzer/languages/rust.d.ts +17 -0
  462. package/shared/analyzer/languages/rust.js +96 -98
  463. package/shared/analyzer/languages/rust.js.map +1 -0
  464. package/shared/analyzer/languages/typescript.d.ts +11 -0
  465. package/shared/analyzer/languages/typescript.js +117 -116
  466. package/shared/analyzer/languages/typescript.js.map +1 -0
  467. package/shared/analyzer/licenses.d.ts +18 -0
  468. package/shared/analyzer/licenses.js +130 -133
  469. package/shared/analyzer/licenses.js.map +1 -0
  470. package/shared/analyzer/metrics.d.ts +20 -0
  471. package/shared/analyzer/metrics.js +112 -104
  472. package/shared/analyzer/metrics.js.map +1 -0
  473. package/shared/analyzer/pathUtil.d.ts +10 -0
  474. package/shared/analyzer/pathUtil.js +29 -35
  475. package/shared/analyzer/pathUtil.js.map +1 -0
  476. package/shared/analyzer/patterns.d.ts +11 -0
  477. package/shared/analyzer/patterns.js +231 -253
  478. package/shared/analyzer/patterns.js.map +1 -0
  479. package/shared/analyzer/scan.d.ts +62 -0
  480. package/shared/analyzer/scan.js +451 -442
  481. package/shared/analyzer/scan.js.map +1 -0
  482. package/shared/analyzer/scanControl.d.ts +11 -0
  483. package/shared/analyzer/scanControl.js +62 -0
  484. package/shared/analyzer/scanControl.js.map +1 -0
  485. package/shared/analyzer/security.d.ts +18 -0
  486. package/shared/analyzer/security.js +159 -159
  487. package/shared/analyzer/security.js.map +1 -0
  488. package/shared/analyzer/services.d.ts +5 -0
  489. package/shared/analyzer/services.js +141 -138
  490. package/shared/analyzer/services.js.map +1 -0
  491. package/shared/analyzer/stack.d.ts +6 -0
  492. package/shared/analyzer/stack.js +134 -141
  493. package/shared/analyzer/stack.js.map +1 -0
  494. package/shared/analyzer/tour.d.ts +2 -0
  495. package/shared/analyzer/tour.js +45 -53
  496. package/shared/analyzer/tour.js.map +1 -0
  497. package/shared/analyzer/util.d.ts +8 -0
  498. package/shared/analyzer/util.js +130 -76
  499. package/shared/analyzer/util.js.map +1 -0
  500. package/shared/analyzer/workflows.d.ts +8 -0
  501. package/shared/analyzer/workflows.js +139 -134
  502. package/shared/analyzer/workflows.js.map +1 -0
  503. package/shared/contracts.d.ts +250 -0
  504. package/shared/contracts.js +2 -0
  505. package/shared/contracts.js.map +1 -0
  506. package/shared/diagram/aiFacts.d.ts +35 -0
  507. package/shared/diagram/aiFacts.js +102 -105
  508. package/shared/diagram/aiFacts.js.map +1 -0
  509. package/shared/diagram/aiMermaid.d.ts +5 -0
  510. package/shared/diagram/aiMermaid.js +50 -46
  511. package/shared/diagram/aiMermaid.js.map +1 -0
  512. package/shared/diagram/atlas.d.ts +16 -0
  513. package/shared/diagram/atlas.js +51 -60
  514. package/shared/diagram/atlas.js.map +1 -0
  515. package/shared/diagram/mermaid.d.ts +43 -0
  516. package/shared/diagram/mermaid.js +348 -370
  517. package/shared/diagram/mermaid.js.map +1 -0
  518. package/shared/review/contracts.d.ts +95 -0
  519. package/shared/review/contracts.js +2 -0
  520. package/shared/review/contracts.js.map +1 -0
  521. package/shared/review/review.d.ts +10 -0
  522. package/shared/review/review.js +184 -0
  523. package/shared/review/review.js.map +1 -0
  524. package/shared/search/query.d.ts +32 -0
  525. package/shared/search/query.js +395 -388
  526. package/shared/search/query.js.map +1 -0
@@ -1,7 +1,7 @@
1
+ import { eventElement } from '/js/dom.js';
1
2
  // The inspector: everything we know about the thing you clicked, written up
2
3
  // like margin notes. Explanations come from the static graph; the AI button
3
4
  // hands off to the LLM when one is configured.
4
-
5
5
  import { roleOf, scanIndex } from '/shared/analyzer/graph.js';
6
6
  import { explainFile, explainFolder, explainOverview } from '/shared/analyzer/explainLocal.js';
7
7
  import { baseName } from '/shared/analyzer/pathUtil.js';
@@ -9,527 +9,498 @@ import { languageLabel } from '/shared/analyzer/languages/index.js';
9
9
  import { escapeHtml } from './html.js';
10
10
  import { mdLite, mdInline } from './markdown.js';
11
11
  import { renderAnalysisPanel } from './analysisPanel.js';
12
-
13
12
  const el = {};
14
13
  ['inspectorEmpty', 'inspectorBody', 'inspTitle', 'inspRole', 'inspPath', 'inspStats', 'inspExplain', 'inspLists', 'aiExplainBtn', 'askInput', 'inspBack']
15
- .forEach((id) => (el[id] = document.getElementById(id)));
16
-
14
+ .forEach((id) => (el[id] = document.getElementById(id)));
17
15
  let askAIHandler = null;
18
16
  let clearFocusHandler = null;
19
17
  let inspHooks = {
20
- onOpenCode: () => {},
21
- onDeepDive: () => {},
22
- onToast: () => {},
18
+ onOpenCode: (..._args) => { },
19
+ onDeepDive: (..._args) => { },
20
+ onToast: (..._args) => { },
23
21
  };
24
-
25
22
  export function initInspector(options = {}) {
26
- const { onAskAI, onClearFocus, ...rest } = options;
27
- askAIHandler = onAskAI;
28
- clearFocusHandler = onClearFocus;
29
- inspHooks = { ...inspHooks, ...rest };
30
- el.aiExplainBtn.addEventListener('click', () => askAIHandler?.());
31
- el.inspBack.addEventListener('click', () => clearFocusHandler?.());
32
-
33
- el.inspectorBody.addEventListener('click', (e) => {
34
- const btn = e.target.closest('button[data-insp-action]');
35
- if (!btn) return;
36
- const action = btn.dataset.inspAction;
37
- const targetPath = btn.dataset.path || el.inspPath.textContent;
38
- if (action === 'code') {
39
- inspHooks.onOpenCode?.(targetPath);
40
- } else if (action === 'dive') {
41
- inspHooks.onDeepDive?.(targetPath);
42
- } else if (action === 'copy') {
43
- if (navigator.clipboard) {
44
- navigator.clipboard.writeText(targetPath)
45
- .then(() => inspHooks.onToast?.('Path copied to clipboard'))
46
- .catch(() => inspHooks.onToast?.('Failed to copy path'));
47
- }
48
- }
49
- });
23
+ const { onAskAI, onClearFocus, ...rest } = options;
24
+ askAIHandler = onAskAI;
25
+ clearFocusHandler = onClearFocus;
26
+ inspHooks = { ...inspHooks, ...rest };
27
+ el.aiExplainBtn.addEventListener('click', () => askAIHandler?.());
28
+ el.inspBack.addEventListener('click', () => clearFocusHandler?.());
29
+ el.inspectorBody.addEventListener('click', (e) => {
30
+ const btn = eventElement(e)?.closest('button[data-insp-action]');
31
+ if (!btn)
32
+ return;
33
+ const action = btn.dataset.inspAction;
34
+ const targetPath = btn.dataset.path || el.inspPath.textContent;
35
+ if (action === 'code') {
36
+ inspHooks.onOpenCode?.(targetPath);
37
+ }
38
+ else if (action === 'dive') {
39
+ inspHooks.onDeepDive?.(targetPath);
40
+ }
41
+ else if (action === 'copy') {
42
+ if (navigator.clipboard) {
43
+ navigator.clipboard.writeText(targetPath)
44
+ .then(() => inspHooks.onToast?.('Path copied to clipboard'))
45
+ .catch(() => inspHooks.onToast?.('Failed to copy path'));
46
+ }
47
+ }
48
+ });
50
49
  }
51
-
52
50
  // Show/hide the small "‹ back" control that returns to the view's own panel.
53
51
  function setBack(label) {
54
- if (label) {
55
- el.inspBack.hidden = false;
56
- el.inspBack.textContent = '‹ ' + label;
57
- } else {
58
- el.inspBack.hidden = true;
59
- }
52
+ if (label) {
53
+ el.inspBack.hidden = false;
54
+ el.inspBack.textContent = '‹ ' + label;
55
+ }
56
+ else {
57
+ el.inspBack.hidden = true;
58
+ }
60
59
  }
61
-
62
60
  export function clearInspector() {
63
- el.inspectorEmpty.hidden = false;
64
- el.inspectorBody.hidden = true;
61
+ el.inspectorEmpty.hidden = false;
62
+ el.inspectorBody.hidden = true;
65
63
  }
66
-
67
64
  export function showFile(path, state) {
68
- const { scan, facts, history, backLabel } = state;
69
- const file = scanIndex(scan).fileAt(path);
70
- const role = roleOf(path, facts);
71
- setBack(backLabel);
72
-
73
- el.inspTitle.textContent = baseName(path);
74
- el.inspRole.textContent = role;
75
- el.inspRole.className = 'role-chip is-' + role;
76
- el.inspPath.textContent = path;
77
- el.askInput.placeholder = `Ask about ${baseName(path)}…`;
78
-
79
- const fin = facts.fanIn[path] || 0;
80
- const fout = facts.fanOut[path] || 0;
81
- const hist = history?.byPath?.[path];
82
-
83
- const actionsHtml = `
65
+ const { scan, facts, history, backLabel } = state;
66
+ const file = scanIndex(scan).fileAt(path);
67
+ const role = roleOf(path, facts);
68
+ setBack(backLabel);
69
+ el.inspTitle.textContent = baseName(path);
70
+ el.inspRole.textContent = role;
71
+ el.inspRole.className = 'role-chip is-' + role;
72
+ el.inspPath.textContent = path;
73
+ el.askInput.placeholder = `Ask about ${baseName(path)}…`;
74
+ const fin = facts.fanIn[path] || 0;
75
+ const fout = facts.fanOut[path] || 0;
76
+ const hist = history?.byPath?.[path];
77
+ const actionsHtml = `
84
78
  <div class="insp-actions" style="display:flex; gap:6px; margin: 6px 0 10px;">
85
79
  <button class="btn btn-ghost btn-sm" data-insp-action="code" data-path="${escapeHtml(path)}" title="Open file in Code view">Code</button>
86
80
  <button class="btn btn-ghost btn-sm" data-insp-action="dive" data-path="${escapeHtml(path)}" title="Open Deep Dive flowchart">Deep Dive →</button>
87
81
  <button class="btn btn-ghost btn-sm" data-insp-action="copy" data-path="${escapeHtml(path)}" title="Copy relative path">Copy Path</button>
88
82
  </div>
89
83
  `;
90
-
91
- el.inspStats.innerHTML = [
92
- chip(`<b>${fin}</b> dependents`),
93
- chip(`<b>${fout}</b> imports`),
94
- hist ? chip(`<b>${hist.churn}</b> commit${hist.churn === 1 ? '' : 's'}`) : '',
95
- hist?.hotspot ? chip(`hotspot <b>${hist.hotspot}</b>`) : '',
96
- hist?.solo && hist.churn >= 2 ? chip(`<b>solo</b> author`) : '',
97
- file ? chip(`<b>${file.functions.length}</b> functions`) : '',
98
- file ? chip(`<b>${(file.size / 1024).toFixed(1)}</b> kb`) : '',
99
- file ? chip(langName(file.lang)) : '',
100
- file && file.cognitive !== undefined ? chip(`<b>${file.cognitive}</b> cognitive cx`) : '',
101
- file && file.maintainability !== undefined ? chip(`<b>${file.maintainability}</b> MI`) : '',
102
- facts.communities && facts.communities[path] ? chip(`community <b>${facts.communities[path]}</b>`) : '',
103
- ].join('');
104
-
105
- if (file && state && state.health) {
106
- const riskData = state.health.perFile?.find(x => x.path === path);
107
- if (riskData) {
108
- el.inspStats.innerHTML += ` <risk-badge score="${riskData.risk}" style="vertical-align: middle; margin-left: 8px; --risk-size: 32px;"></risk-badge>`;
84
+ el.inspStats.innerHTML = [
85
+ chip(`<b>${fin}</b> dependents`),
86
+ chip(`<b>${fout}</b> imports`),
87
+ hist ? chip(`<b>${hist.churn}</b> commit${hist.churn === 1 ? '' : 's'}`) : '',
88
+ hist?.hotspot ? chip(`hotspot <b>${hist.hotspot}</b>`) : '',
89
+ hist?.solo && hist.churn >= 2 ? chip(`<b>solo</b> author`) : '',
90
+ file ? chip(`<b>${file.functions.length}</b> functions`) : '',
91
+ file ? chip(`<b>${(file.size / 1024).toFixed(1)}</b> kb`) : '',
92
+ file ? chip(langName(file.lang)) : '',
93
+ file && file.cognitive !== undefined ? chip(`<b>${file.cognitive}</b> cognitive cx`) : '',
94
+ file && file.maintainability !== undefined ? chip(`<b>${file.maintainability}</b> MI`) : '',
95
+ facts.communities && facts.communities[path] ? chip(`community <b>${facts.communities[path]}</b>`) : '',
96
+ ].join('');
97
+ if (file && state && state.health) {
98
+ const riskData = state.health.perFile?.find(x => x.path === path);
99
+ if (riskData) {
100
+ el.inspStats.innerHTML += ` <risk-badge score="${riskData.risk}" style="vertical-align: middle; margin-left: 8px; --risk-size: 32px;"></risk-badge>`;
101
+ }
102
+ }
103
+ setExplain(actionsHtml + mdLite(explainFile(path, file, facts)), 'From the static analysis — no AI involved.');
104
+ el.inspLists.innerHTML = '';
105
+ addPathList('Imports', facts.importsOf[path] || [], scan);
106
+ addPathList('Imported by', facts.importers[path] || [], scan);
107
+ if (file?.exports.length) {
108
+ addList('Exports', file.exports.map((e) => ({ text: e.name, kind: e.kind })));
109
+ }
110
+ if (file?.functions.length) {
111
+ addList('Functions', file.functions.map((f) => ({ text: f.name + '()', kind: f.kind + ' · line ' + f.line })));
109
112
  }
110
- }
111
-
112
- setExplain(actionsHtml + mdLite(explainFile(path, file, facts)), 'From the static analysis — no AI involved.');
113
-
114
- el.inspLists.innerHTML = '';
115
- addPathList('Imports', facts.importsOf[path] || [], scan);
116
- addPathList('Imported by', facts.importers[path] || [], scan);
117
- if (file?.exports.length) {
118
- addList('Exports', file.exports.map((e) => ({ text: e.name, kind: e.kind })));
119
- }
120
- if (file?.functions.length) {
121
- addList('Functions', file.functions.map((f) => ({ text: f.name + '()', kind: f.kind + ' · line ' + f.line })));
122
- }
123
-
124
- el.inspectorEmpty.hidden = true;
125
- el.inspectorBody.hidden = false;
113
+ el.inspectorEmpty.hidden = true;
114
+ el.inspectorBody.hidden = false;
126
115
  }
127
-
128
116
  export function showFolder(folder, { scan, facts, backLabel }) {
129
- setBack(backLabel);
130
- el.inspTitle.textContent = (folder || '(repo root)') + '/';
131
- el.inspRole.textContent = 'folder';
132
- el.inspRole.className = 'role-chip is-test';
133
- el.inspPath.textContent = folder || '(the top of the repository)';
134
- el.askInput.placeholder = folder ? `Ask about ${folder}/…` : 'Ask anything about this repo…';
135
- const here = scanIndex(scan).filesIn(folder);
136
- const count = here.length;
137
- el.inspStats.innerHTML = chip(`<b>${count}</b> parsed files`);
138
- setExplain(mdLite(explainFolder(folder, scan, facts)), 'From the static analysis — no AI involved.');
139
- el.inspLists.innerHTML = '';
140
- addPathList('Files here', here.map((f) => f.path), scan);
141
- el.inspectorEmpty.hidden = true;
142
- el.inspectorBody.hidden = false;
117
+ setBack(backLabel);
118
+ el.inspTitle.textContent = (folder || '(repo root)') + '/';
119
+ el.inspRole.textContent = 'folder';
120
+ el.inspRole.className = 'role-chip is-test';
121
+ el.inspPath.textContent = folder || '(the top of the repository)';
122
+ el.askInput.placeholder = folder ? `Ask about ${folder}/…` : 'Ask anything about this repo…';
123
+ const here = scanIndex(scan).filesIn(folder);
124
+ const count = here.length;
125
+ el.inspStats.innerHTML = chip(`<b>${count}</b> parsed files`);
126
+ setExplain(mdLite(explainFolder(folder, scan, facts)), 'From the static analysis — no AI involved.');
127
+ el.inspLists.innerHTML = '';
128
+ addPathList('Files here', here.map((f) => f.path), scan);
129
+ el.inspectorEmpty.hidden = true;
130
+ el.inspectorBody.hidden = false;
143
131
  }
144
-
145
132
  export function showOverview({ scan, facts, manifest, history }) {
146
- setBack();
147
- el.inspTitle.textContent = scan.name;
148
- el.inspRole.textContent = 'repo';
149
- el.inspRole.className = 'role-chip is-entry';
150
- el.inspPath.textContent = scan.root;
151
- el.askInput.placeholder = 'Ask anything about this repo…';
152
- const conf = scan.stats.imports?.confidence;
153
- el.inspStats.innerHTML = [
154
- chip(`<b>${scan.stats.filesParsed}</b> files${scan.stats.truncated ? ' (partial)' : ''}`),
155
- chip(`<b>${scan.stats.edgeCount}</b> connections`),
156
- chip(`<b>${facts.entries.length}</b> entries`),
157
- chip(`<b>${facts.hubs.length}</b> hubs`),
158
- history?.available ? chip(`<b>${history.commitCount}</b> commits`) : '',
159
- // How much of the import graph we actually placed. Worth a permanent slot:
160
- // every other number on this page is downstream of it.
161
- typeof conf === 'number' ? chip(`<b>${conf}%</b> of imports placed`) : '',
162
- ].join('');
163
- setExplain(mdLite(explainOverview(scan, facts, manifest)), 'From the static analysis — no AI involved.');
164
- el.inspLists.innerHTML = '';
165
- addPathList('Entry points', facts.entries, scan);
166
- addList('Load-bearing files', facts.hubs.slice(0, 8).map((h) => ({ text: h.path, kind: h.fanIn + ' dependents', path: h.path })), scan);
167
- if (history?.available) {
168
- if (history.perFile?.length) {
169
- const topHotspots = history.perFile.filter((f) => f.hotspot > 10).slice(0, 6);
170
- if (topHotspots.length) {
171
- addList('Hotspots (churn × complexity)', topHotspots.map((f) => ({ text: f.path, kind: `hotspot ${f.hotspot} · ${f.churn} commits`, path: f.path })));
172
- }
133
+ setBack();
134
+ el.inspTitle.textContent = scan.name;
135
+ el.inspRole.textContent = 'repo';
136
+ el.inspRole.className = 'role-chip is-entry';
137
+ el.inspPath.textContent = scan.root;
138
+ el.askInput.placeholder = 'Ask anything about this repo…';
139
+ const conf = scan.stats.imports?.confidence;
140
+ el.inspStats.innerHTML = [
141
+ chip(`<b>${scan.stats.filesParsed}</b> files${scan.stats.truncated ? ' (partial)' : ''}`),
142
+ chip(`<b>${scan.stats.edgeCount}</b> connections`),
143
+ chip(`<b>${facts.entries.length}</b> entries`),
144
+ chip(`<b>${facts.hubs.length}</b> hubs`),
145
+ history?.available ? chip(`<b>${history.commitCount}</b> commits`) : '',
146
+ // How much of the import graph we actually placed. Worth a permanent slot:
147
+ // every other number on this page is downstream of it.
148
+ typeof conf === 'number' ? chip(`<b>${conf}%</b> of imports placed`) : '',
149
+ ].join('');
150
+ setExplain(mdLite(explainOverview(scan, facts, manifest)), 'From the static analysis — no AI involved.');
151
+ el.inspLists.innerHTML = '';
152
+ addPathList('Entry points', facts.entries, scan);
153
+ addList('Load-bearing files', facts.hubs.slice(0, 8).map((h) => ({ text: h.path, kind: h.fanIn + ' dependents', path: h.path })), scan);
154
+ if (history?.available) {
155
+ if (history.perFile?.length) {
156
+ const topHotspots = history.perFile.filter((f) => f.hotspot > 10).slice(0, 6);
157
+ if (topHotspots.length) {
158
+ addList('Hotspots (churn × complexity)', topHotspots.map((f) => ({ text: f.path, kind: `hotspot ${f.hotspot} · ${f.churn} commits`, path: f.path })));
159
+ }
160
+ }
161
+ if (history.coChanged?.length) {
162
+ addList('Co-changed pairs', history.coChanged.slice(0, 5).map((p) => ({ text: `${p.a} + ${p.b}`, kind: `${p.count} joint commits` })));
163
+ }
173
164
  }
174
- if (history.coChanged?.length) {
175
- addList('Co-changed pairs', history.coChanged.slice(0, 5).map((p) => ({ text: `${p.a} + ${p.b}`, kind: `${p.count} joint commits` })));
165
+ if (scan.externals.length) {
166
+ addList('Outside packages', scan.externals.slice(0, 10).map((x) => ({ text: x.name, kind: 'used by ' + x.usedBy.length })));
176
167
  }
177
- }
178
- if (scan.externals.length) {
179
- addList('Outside packages', scan.externals.slice(0, 10).map((x) => ({ text: x.name, kind: 'used by ' + x.usedBy.length })));
180
- }
181
- el.inspectorEmpty.hidden = true;
182
- el.inspectorBody.hidden = false;
168
+ el.inspectorEmpty.hidden = true;
169
+ el.inspectorBody.hidden = false;
183
170
  }
184
-
185
171
  // The Patterns view panel: written observations plus the folder-traffic grid.
186
172
  export function showPatterns({ scan, patterns }) {
187
- setBack();
188
- const { layersInfo, findings, coupling } = patterns;
189
- el.inspTitle.textContent = 'The shape of it';
190
- el.inspRole.textContent = 'patterns';
191
- el.inspRole.className = 'role-chip is-entry';
192
- el.inspPath.textContent = `${scan.name} — ${layersInfo.layers.length} layers deep from the door`;
193
- el.askInput.placeholder = 'Ask about the architecture…';
194
- el.inspStats.innerHTML = [
195
- chip(`<b>${layersInfo.layers.length}</b> layers`),
196
- chip(`<b>${layersInfo.unreachable.length}</b> off the path`),
197
- chip(`<b>${findings.length}</b> observations`),
198
- ].join('');
199
-
200
- el.inspExplain.innerHTML =
201
- findings.map((f) => `
173
+ setBack();
174
+ const { layersInfo, findings, coupling } = patterns;
175
+ el.inspTitle.textContent = 'The shape of it';
176
+ el.inspRole.textContent = 'patterns';
177
+ el.inspRole.className = 'role-chip is-entry';
178
+ el.inspPath.textContent = `${scan.name} — ${layersInfo.layers.length} layers deep from the door`;
179
+ el.askInput.placeholder = 'Ask about the architecture…';
180
+ el.inspStats.innerHTML = [
181
+ chip(`<b>${layersInfo.layers.length}</b> layers`),
182
+ chip(`<b>${layersInfo.unreachable.length}</b> off the path`),
183
+ chip(`<b>${findings.length}</b> observations`),
184
+ ].join('');
185
+ el.inspExplain.innerHTML =
186
+ findings.map((f) => `
202
187
  <div class="finding is-${f.tone}">
203
188
  <h4>${escapeHtml(f.title)}</h4>
204
189
  <p>${mdInline(f.detail)}</p>
205
190
  ${f.paths && f.paths.length ? `<div style="margin-top:6px;display:flex;flex-wrap:wrap;gap:4px;">${f.paths.slice(0, 4).map((p) => `<button class="role-chip" data-goto="${escapeHtml(p)}" style="cursor:pointer;font-size:11px;">${escapeHtml(p)}</button>`).join('')}${f.paths.length > 4 ? `<span style="font-size:11px;color:var(--faint);align-self:center;">+${f.paths.length - 4} more</span>` : ''}</div>` : ''}
206
191
  </div>`).join('') +
207
- '<p class="explain-src">Read straight from the import graph — no AI involved.</p>';
208
-
209
- el.inspLists.innerHTML = '';
210
- if (coupling.folders.length) {
211
- const h = document.createElement('h3');
212
- h.className = 'inspector-h';
213
- h.textContent = 'Folder traffic';
214
- el.inspLists.appendChild(h);
215
- el.inspLists.appendChild(heatGrid(coupling));
216
- const note = document.createElement('p');
217
- note.className = 'explain-src';
218
- note.textContent = 'Rows pull in columns; darker means more.';
219
- el.inspLists.appendChild(note);
220
- }
221
- el.inspectorEmpty.hidden = true;
222
- el.inspectorBody.hidden = false;
192
+ '<p class="explain-src">Read straight from the import graph — no AI involved.</p>';
193
+ el.inspLists.innerHTML = '';
194
+ if (coupling.folders.length) {
195
+ const h = document.createElement('h3');
196
+ h.className = 'inspector-h';
197
+ h.textContent = 'Folder traffic';
198
+ el.inspLists.appendChild(h);
199
+ el.inspLists.appendChild(heatGrid(coupling));
200
+ const note = document.createElement('p');
201
+ note.className = 'explain-src';
202
+ note.textContent = 'Rows pull in columns; darker means more.';
203
+ el.inspLists.appendChild(note);
204
+ }
205
+ el.inspectorEmpty.hidden = true;
206
+ el.inspectorBody.hidden = false;
223
207
  }
224
-
225
208
  // The Health view panel: the repo's grade and score with an itemized
226
209
  // breakdown, a legend for the metrics, and the riskiest files — clickable.
227
210
  export function showHealth({ scan, health }) {
228
- setBack();
229
- const t = health.totals;
230
- el.inspTitle.textContent = 'Health';
231
- el.inspRole.textContent = 'grade ' + health.grade;
232
- el.inspRole.className = 'role-chip is-' + (health.score >= 70 ? 'entry' : health.score >= 60 ? 'hub' : 'cycle');
233
- el.inspPath.textContent = `${scan.name} — ${health.score}/100`;
234
- el.askInput.placeholder = 'Ask about the riskiest files…';
235
- el.inspStats.innerHTML = [
236
- chip(`<b>${health.score}</b> / 100`),
237
- chip(`<b>${t.crit}</b> critical`),
238
- chip(`<b>${t.high}</b> high risk`),
239
- chip(`avg cx <b>${t.avgCx}</b>`),
240
- ].join('');
241
-
242
- const top = health.perFile[0];
243
- let note = `<b>${escapeHtml(scan.name)}</b> grades <b>${health.grade}</b> (${health.score}/100).`;
244
- if (health.score >= 90) note += ' Clean dependency shape — keep it that way.';
245
- if (t.crit) note += ` ${t.crit} file${t.crit === 1 ? ' sits' : 's sit'} in the critical band — change those with care.`;
246
- else if (t.high) note += ` ${t.high} file${t.high === 1 ? '' : 's'} in the high band.`;
247
- if (top) note += ` The single riskiest file is <b>${escapeHtml(top.name)}</b> (risk ${top.risk}${top.inCycle ? ', in a cycle' : ''}, ${top.fanIn} dependents, complexity ${top.complexity}).`;
248
-
249
- const breakdownHtml = health.breakdown.length
250
- ? `<div class="score-bd"><h4>Why ${health.score}/100</h4><ul>`
251
- + health.breakdown.map((b) => `<li><span>${escapeHtml(b.label)}</span><b>${b.points}</b></li>`).join('')
252
- + '</ul></div>'
253
- : '<p class="explain-src">A perfect run — nothing was deducted.</p>';
254
-
255
- const legendHtml = `<div class="score-bd"><h4>How the risk score works</h4><ul class="legend">`
256
- + `<li><b>centrality</b> — how much of the repo flows through a file (PageRank)</li>`
257
- + (health.blastExact === false
258
- ? `<li><b>blast radius</b> — direct dependents only: this repo is over 2,500 files, where the transitive walk is too slow to run</li>`
259
- : `<li><b>blast radius</b> — how many files break if it does</li>`)
260
- + `<li><b>complexity</b> — decision points; how hard it is to change safely</li>`
261
- + `<li><b>cycles</b> — circular dependencies, the sharpest edge</li>`
262
- + '</ul></div>';
263
-
264
- el.inspExplain.innerHTML = `<p>${note}</p>` + breakdownHtml + legendHtml
265
- + '<p class="explain-src">Scored from centrality, blast radius, complexity and cycles — no AI involved.</p>';
266
- el.inspLists.innerHTML = '';
267
- addList('Riskiest files', health.perFile.map((f) => ({ text: f.name, kind: `risk ${f.risk} · blast ${f.blast} · cx ${f.complexity}`, path: f.path })), scan);
268
- el.inspectorEmpty.hidden = true;
269
- el.inspectorBody.hidden = false;
211
+ setBack();
212
+ const t = health.totals;
213
+ el.inspTitle.textContent = 'Health';
214
+ el.inspRole.textContent = 'grade ' + health.grade;
215
+ el.inspRole.className = 'role-chip is-' + (health.score >= 70 ? 'entry' : health.score >= 60 ? 'hub' : 'cycle');
216
+ el.inspPath.textContent = `${scan.name} — ${health.score}/100`;
217
+ el.askInput.placeholder = 'Ask about the riskiest files…';
218
+ el.inspStats.innerHTML = [
219
+ chip(`<b>${health.score}</b> / 100`),
220
+ chip(`<b>${t.crit}</b> critical`),
221
+ chip(`<b>${t.high}</b> high risk`),
222
+ chip(`avg cx <b>${t.avgCx}</b>`),
223
+ ].join('');
224
+ const top = health.perFile[0];
225
+ let note = `<b>${escapeHtml(scan.name)}</b> grades <b>${health.grade}</b> (${health.score}/100).`;
226
+ if (health.score >= 90)
227
+ note += ' Clean dependency shape — keep it that way.';
228
+ if (t.crit)
229
+ note += ` ${t.crit} file${t.crit === 1 ? ' sits' : 's sit'} in the critical band — change those with care.`;
230
+ else if (t.high)
231
+ note += ` ${t.high} file${t.high === 1 ? '' : 's'} in the high band.`;
232
+ if (top)
233
+ note += ` The single riskiest file is <b>${escapeHtml(top.name)}</b> (risk ${top.risk}${top.inCycle ? ', in a cycle' : ''}, ${top.fanIn} dependents, complexity ${top.complexity}).`;
234
+ const breakdownHtml = health.breakdown.length
235
+ ? `<div class="score-bd"><h4>Why ${health.score}/100</h4><ul>`
236
+ + health.breakdown.map((b) => `<li><span>${escapeHtml(b.label)}</span><b>${b.points}</b></li>`).join('')
237
+ + '</ul></div>'
238
+ : '<p class="explain-src">A perfect run — nothing was deducted.</p>';
239
+ const legendHtml = `<div class="score-bd"><h4>How the risk score works</h4><ul class="legend">`
240
+ + `<li><b>centrality</b> — how much of the repo flows through a file (PageRank)</li>`
241
+ + (health.blastExact === false
242
+ ? `<li><b>blast radius</b> — direct dependents only: this repo is over 2,500 files, where the transitive walk is too slow to run</li>`
243
+ : `<li><b>blast radius</b> — how many files break if it does</li>`)
244
+ + `<li><b>complexity</b> — decision points; how hard it is to change safely</li>`
245
+ + `<li><b>cycles</b> — circular dependencies, the sharpest edge</li>`
246
+ + '</ul></div>';
247
+ el.inspExplain.innerHTML = `<p>${note}</p>` + breakdownHtml + legendHtml
248
+ + '<p class="explain-src">Scored from centrality, blast radius, complexity and cycles — no AI involved.</p>';
249
+ el.inspLists.innerHTML = '';
250
+ addList('Riskiest files', health.perFile.map((f) => ({ text: f.name, kind: `risk ${f.risk} · blast ${f.blast} · cx ${f.complexity}`, path: f.path })), scan);
251
+ el.inspectorEmpty.hidden = true;
252
+ el.inspectorBody.hidden = false;
270
253
  }
271
-
272
254
  // The Security view panel: severity counts, a written summary, and the
273
255
  // findings list — each one clickable into its file's deep-dive.
274
256
  export function showSecurity({ scan, security }) {
275
- setBack();
276
- const c = security.counts;
277
- el.inspTitle.textContent = 'Security';
278
- el.inspRole.textContent = 'grade ' + security.grade;
279
- el.inspRole.className = 'role-chip is-' + (security.score >= 75 ? 'entry' : security.score >= 60 ? 'hub' : 'cycle');
280
- el.inspPath.textContent = `${scan.name} — ${security.score}/100 · ${security.total} findings`;
281
- el.askInput.placeholder = 'Ask about the security findings…';
282
- el.inspStats.innerHTML = [
283
- chip(`<b>${c.critical}</b> critical`),
284
- chip(`<b>${c.high}</b> high`),
285
- chip(`<b>${c.medium}</b> medium`),
286
- chip(`<b>${c.low + c.info}</b> low/info`),
287
- ].join('');
288
-
289
- let note = `<b>${escapeHtml(scan.name)}</b> grades <b>${security.grade}</b> for security (${security.score}/100) — ${security.total} finding${security.total === 1 ? '' : 's'} across ${security.files.length} file${security.files.length === 1 ? '' : 's'}.`;
290
- if (!security.total) note += ' The rule engine found nothing to flag — a clean bill.';
291
- else {
292
- const worst = security.files[0];
293
- note += ` The loudest file is <b>${escapeHtml(worst.name)}</b> (${worst.count} finding${worst.count === 1 ? '' : 's'}, worst: ${worst.worst}).`;
294
- if (c.critical) note += ` ${c.critical} critical — fix those first.`;
295
- else if (c.high) note += ` ${c.high} high — worth a pass soon.`;
296
- }
297
-
298
- const sevLegend = `<div class="score-bd"><h4>What the severities mean</h4><ul class="legend">`
299
- + `<li><b class="sev-critical">critical</b> — secrets &amp; keys committed; fix now</li>`
300
- + `<li><b class="sev-high">high</b> — injection &amp; unsafe deserialization; sanitize, parameterize</li>`
301
- + `<li><b class="sev-medium">medium</b> — XSS sinks, weak crypto, open redirects, swallowed errors</li>`
302
- + `<li><b class="sev-low">low / info</b> — config, debug leftovers, style — tidy-up</li>`
303
- + '</ul></div>';
304
-
305
- const catEntries = Object.entries(security.byCat).sort((a, b) => b[1] - a[1]);
306
- const catHtml = catEntries.length
307
- ? `<div class="score-bd"><h4>By category</h4><ul>`
308
- + catEntries.map(([cat, n]) => `<li><span>${escapeHtml(cat)}</span><b>${n}</b></li>`).join('')
309
- + '</ul></div>'
310
- : '';
311
-
312
- // Attribution: the security story should say which engine told it. Built-in
313
- // findings carry no `source`; external ones carry the tool's name.
314
- const externalCount = security.files.reduce((n, f) => n + f.findings.filter((x) => x.source === 'external').length, 0);
315
- const sourceLine = externalCount
316
- ? `From the built-in rule engine plus ${externalCount} finding${externalCount === 1 ? '' : 's'} from external engines (see the list below).`
317
- : 'From the built-in rule engine — no external scanner, no AI.';
318
-
319
- el.inspExplain.innerHTML = `<p>${note}</p>` + catHtml + sevLegend
320
- + `<p class="explain-src">${sourceLine}</p>`;
321
-
322
- // The deep-analysis panel: which engines are installed, and the button that
323
- // runs them. Rendered on every pass so a finished run re-paints in place.
324
- renderAnalysisPanel();
325
-
326
- el.inspLists.innerHTML = '';
327
- const rows = security.files.flatMap((f) => f.findings.slice(0, 6).map((x) => ({ text: `${f.name}:${x.line} — ${x.message}`, kind: x.severity, path: f.path })));
328
- addList('Findings', rows, scan);
329
- el.inspectorEmpty.hidden = true;
330
- el.inspectorBody.hidden = false;
257
+ setBack();
258
+ const c = security.counts;
259
+ el.inspTitle.textContent = 'Security';
260
+ el.inspRole.textContent = 'grade ' + security.grade;
261
+ el.inspRole.className = 'role-chip is-' + (security.score >= 75 ? 'entry' : security.score >= 60 ? 'hub' : 'cycle');
262
+ el.inspPath.textContent = `${scan.name} — ${security.score}/100 · ${security.total} findings`;
263
+ el.askInput.placeholder = 'Ask about the security findings…';
264
+ el.inspStats.innerHTML = [
265
+ chip(`<b>${c.critical}</b> critical`),
266
+ chip(`<b>${c.high}</b> high`),
267
+ chip(`<b>${c.medium}</b> medium`),
268
+ chip(`<b>${c.low + c.info}</b> low/info`),
269
+ ].join('');
270
+ let note = `<b>${escapeHtml(scan.name)}</b> grades <b>${security.grade}</b> for security (${security.score}/100) — ${security.total} finding${security.total === 1 ? '' : 's'} across ${security.files.length} file${security.files.length === 1 ? '' : 's'}.`;
271
+ if (!security.total)
272
+ note += ' The rule engine found nothing to flag — a clean bill.';
273
+ else {
274
+ const worst = security.files[0];
275
+ note += ` The loudest file is <b>${escapeHtml(worst.name)}</b> (${worst.count} finding${worst.count === 1 ? '' : 's'}, worst: ${worst.worst}).`;
276
+ if (c.critical)
277
+ note += ` ${c.critical} critical — fix those first.`;
278
+ else if (c.high)
279
+ note += ` ${c.high} high — worth a pass soon.`;
280
+ }
281
+ const sevLegend = `<div class="score-bd"><h4>What the severities mean</h4><ul class="legend">`
282
+ + `<li><b class="sev-critical">critical</b> — secrets &amp; keys committed; fix now</li>`
283
+ + `<li><b class="sev-high">high</b> — injection &amp; unsafe deserialization; sanitize, parameterize</li>`
284
+ + `<li><b class="sev-medium">medium</b> — XSS sinks, weak crypto, open redirects, swallowed errors</li>`
285
+ + `<li><b class="sev-low">low / info</b> — config, debug leftovers, style — tidy-up</li>`
286
+ + '</ul></div>';
287
+ const catEntries = Object.entries(security.byCat).sort((a, b) => Number(b[1]) - Number(a[1]));
288
+ const catHtml = catEntries.length
289
+ ? `<div class="score-bd"><h4>By category</h4><ul>`
290
+ + catEntries.map(([cat, n]) => `<li><span>${escapeHtml(cat)}</span><b>${n}</b></li>`).join('')
291
+ + '</ul></div>'
292
+ : '';
293
+ // Attribution: the security story should say which engine told it. Built-in
294
+ // findings carry no `source`; external ones carry the tool's name.
295
+ const externalCount = security.files.reduce((n, f) => n + f.findings.filter((x) => x.source === 'external').length, 0);
296
+ const sourceLine = externalCount
297
+ ? `From the built-in rule engine plus ${externalCount} finding${externalCount === 1 ? '' : 's'} from external engines (see the list below).`
298
+ : 'From the built-in rule engine — no external scanner, no AI.';
299
+ el.inspExplain.innerHTML = `<p>${note}</p>` + catHtml + sevLegend
300
+ + `<p class="explain-src">${sourceLine}</p>`;
301
+ // The deep-analysis panel: which engines are installed, and the button that
302
+ // runs them. Rendered on every pass so a finished run re-paints in place.
303
+ renderAnalysisPanel();
304
+ el.inspLists.innerHTML = '';
305
+ const rows = security.files.flatMap((f) => f.findings.slice(0, 6).map((x) => ({ text: `${f.name}:${x.line} — ${x.message}`, kind: x.severity, path: f.path })));
306
+ addList('Findings', rows, scan);
307
+ el.inspectorEmpty.hidden = true;
308
+ el.inspectorBody.hidden = false;
331
309
  }
332
-
333
310
  // The History view panel: what the git log says — churn, authorship, the
334
311
  // hotspot cross-tab — with the top of each list clickable into the file's
335
312
  // deep-dive. When there is no history it says why rather than showing zeros.
336
313
  export function showHistory({ scan, history }) {
337
- setBack();
338
- el.inspTitle.textContent = 'History';
339
- if (!history?.available) {
340
- el.inspRole.textContent = 'unavailable';
341
- el.inspRole.className = 'role-chip is-test';
342
- el.inspPath.textContent = scan.name;
343
- el.askInput.placeholder = 'Ask anything about this repo…';
344
- el.inspStats.innerHTML = '';
345
- el.inspExplain.innerHTML =
346
- `<p>${escapeHtml(history?.reason || 'No history available.')}</p>`
347
- + '<p class="explain-src">History needs a checkout with commits — a git URL clone has one; a folder picked in the browser or a plain directory may not.</p>';
314
+ setBack();
315
+ el.inspTitle.textContent = 'History';
316
+ if (!history?.available) {
317
+ el.inspRole.textContent = 'unavailable';
318
+ el.inspRole.className = 'role-chip is-test';
319
+ el.inspPath.textContent = scan.name;
320
+ el.askInput.placeholder = 'Ask anything about this repo…';
321
+ el.inspStats.innerHTML = '';
322
+ el.inspExplain.innerHTML =
323
+ `<p>${escapeHtml(history?.reason || 'No history available.')}</p>`
324
+ + '<p class="explain-src">History needs a checkout with commits — a git URL clone has one; a folder picked in the browser or a plain directory may not.</p>';
325
+ el.inspLists.innerHTML = '';
326
+ el.inspectorEmpty.hidden = true;
327
+ el.inspectorBody.hidden = false;
328
+ return;
329
+ }
330
+ const hotspots = history.perFile.filter((f) => f.hotspot > 0);
331
+ const top = hotspots[0];
332
+ el.inspRole.textContent = history.truncated
333
+ ? `${history.commitCount} of ${history.totalCommits} commits`
334
+ : `${history.commitCount} commits`;
335
+ el.inspRole.className = 'role-chip is-entry';
336
+ el.inspPath.textContent = `${scan.name} — ${span(history.firstCommitAt, history.lastCommitAt)}`;
337
+ el.askInput.placeholder = 'Ask about the hotspots…';
338
+ el.inspStats.innerHTML = [
339
+ chip(`<b>${history.authors.length}</b> ${history.authors.length === 1 ? 'author' : 'authors'}`),
340
+ chip(`<b>${hotspots.length}</b> ${hotspots.length === 1 ? 'hotspot' : 'hotspots'}`),
341
+ history.soloFiles ? chip(`<b>${history.soloFiles}</b> solo-owned`) : '',
342
+ history.pathsGone ? chip(`<b>${history.pathsGone}</b> files gone`) : '',
343
+ ].join('');
344
+ let note = `<b>${escapeHtml(scan.name)}</b> shows ${history.commitCount} commits by ${history.authors.length} ${history.authors.length === 1 ? 'person' : 'people'}, ${span(history.firstCommitAt, history.lastCommitAt)}.`;
345
+ if (top) {
346
+ note += ` The sharpest hotspot is <b>${escapeHtml(top.name)}</b> — touched ${top.churn} ${top.churn === 1 ? 'time' : 'times'} at complexity ${top.complexity}`;
347
+ note += top.solo ? ', always by the same person.' : ` by ${top.authors} ${top.authors === 1 ? 'person' : 'people'}.`;
348
+ }
349
+ else {
350
+ note += ' Nothing stands out — no file is both complex and frequently changed.';
351
+ }
352
+ if (history.truncated)
353
+ note += ` The window was capped at ${history.commitCount} of ${history.totalCommits} total commits.`;
354
+ const legendHtml = `<div class="score-bd"><h4>How the hotspot score works</h4><ul class="legend">`
355
+ + `<li><b>churn</b> — how many commits have touched the file</li>`
356
+ + `<li><b>complexity</b> — decision points, the same number Health uses</li>`
357
+ + `<li><b>hotspot</b> — both at once, each against the repo's own worst: where change and difficulty meet</li>`
358
+ + `<li><b>solo</b> — only one person has ever touched it; knowledge leaves with them</li>`
359
+ + '</ul></div>';
360
+ el.inspExplain.innerHTML = `<p>${note}</p>` + legendHtml
361
+ + '<p class="explain-src">From the git log — authors keyed by email, no AI involved.</p>';
348
362
  el.inspLists.innerHTML = '';
363
+ addList('Hotspots', hotspots.map((f) => ({
364
+ text: f.name,
365
+ kind: `hotspot ${f.hotspot} · ${f.churn} commits · cx ${f.complexity}${f.solo ? ' · solo' : ''}`,
366
+ path: f.path,
367
+ })), scan);
368
+ if (history.coChanged?.length) {
369
+ addList('Change together, unconnected', history.coChanged.map((p) => ({
370
+ text: p.a.split('/').pop() + ' + ' + p.b.split('/').pop(),
371
+ kind: p.count + '× together',
372
+ path: p.a,
373
+ title: p.a + ' ↔ ' + p.b,
374
+ })), scan);
375
+ }
349
376
  el.inspectorEmpty.hidden = true;
350
377
  el.inspectorBody.hidden = false;
351
- return;
352
- }
353
-
354
- const hotspots = history.perFile.filter((f) => f.hotspot > 0);
355
- const top = hotspots[0];
356
- el.inspRole.textContent = history.truncated
357
- ? `${history.commitCount} of ${history.totalCommits} commits`
358
- : `${history.commitCount} commits`;
359
- el.inspRole.className = 'role-chip is-entry';
360
- el.inspPath.textContent = `${scan.name} — ${span(history.firstCommitAt, history.lastCommitAt)}`;
361
- el.askInput.placeholder = 'Ask about the hotspots…';
362
- el.inspStats.innerHTML = [
363
- chip(`<b>${history.authors.length}</b> ${history.authors.length === 1 ? 'author' : 'authors'}`),
364
- chip(`<b>${hotspots.length}</b> ${hotspots.length === 1 ? 'hotspot' : 'hotspots'}`),
365
- history.soloFiles ? chip(`<b>${history.soloFiles}</b> solo-owned`) : '',
366
- history.pathsGone ? chip(`<b>${history.pathsGone}</b> files gone`) : '',
367
- ].join('');
368
-
369
- let note = `<b>${escapeHtml(scan.name)}</b> shows ${history.commitCount} commits by ${history.authors.length} ${history.authors.length === 1 ? 'person' : 'people'}, ${span(history.firstCommitAt, history.lastCommitAt)}.`;
370
- if (top) {
371
- note += ` The sharpest hotspot is <b>${escapeHtml(top.name)}</b> — touched ${top.churn} ${top.churn === 1 ? 'time' : 'times'} at complexity ${top.complexity}`;
372
- note += top.solo ? ', always by the same person.' : ` by ${top.authors} ${top.authors === 1 ? 'person' : 'people'}.`;
373
- } else {
374
- note += ' Nothing stands out — no file is both complex and frequently changed.';
375
- }
376
- if (history.truncated) note += ` The window was capped at ${history.commitCount} of ${history.totalCommits} total commits.`;
377
-
378
- const legendHtml = `<div class="score-bd"><h4>How the hotspot score works</h4><ul class="legend">`
379
- + `<li><b>churn</b> — how many commits have touched the file</li>`
380
- + `<li><b>complexity</b> — decision points, the same number Health uses</li>`
381
- + `<li><b>hotspot</b> — both at once, each against the repo's own worst: where change and difficulty meet</li>`
382
- + `<li><b>solo</b> — only one person has ever touched it; knowledge leaves with them</li>`
383
- + '</ul></div>';
384
-
385
- el.inspExplain.innerHTML = `<p>${note}</p>` + legendHtml
386
- + '<p class="explain-src">From the git log — authors keyed by email, no AI involved.</p>';
387
- el.inspLists.innerHTML = '';
388
- addList('Hotspots', hotspots.map((f) => ({
389
- text: f.name,
390
- kind: `hotspot ${f.hotspot} · ${f.churn} commits · cx ${f.complexity}${f.solo ? ' · solo' : ''}`,
391
- path: f.path,
392
- })), scan);
393
- if (history.coChanged?.length) {
394
- addList('Change together, unconnected', history.coChanged.map((p) => ({
395
- text: p.a.split('/').pop() + ' + ' + p.b.split('/').pop(),
396
- kind: p.count + '× together',
397
- path: p.a,
398
- title: p.a + ' ↔ ' + p.b,
399
- })), scan);
400
- }
401
- el.inspectorEmpty.hidden = true;
402
- el.inspectorBody.hidden = false;
403
378
  }
404
-
405
379
  function span(firstIso, lastIso) {
406
- const fmt = (iso) => {
407
- if (!iso) return '?';
408
- const d = new Date(iso);
409
- if (Number.isNaN(d.getTime())) return iso.slice(0, 10);
410
- return d.toLocaleDateString(undefined, { year: 'numeric', month: 'short', day: 'numeric' });
411
- };
412
- return fmt(firstIso) + ' → ' + fmt(lastIso);
380
+ const fmt = (iso) => {
381
+ if (!iso)
382
+ return '?';
383
+ const d = new Date(iso);
384
+ if (Number.isNaN(d.getTime()))
385
+ return iso.slice(0, 10);
386
+ return d.toLocaleDateString(undefined, { year: 'numeric', month: 'short', day: 'numeric' });
387
+ };
388
+ return fmt(firstIso) + ' → ' + fmt(lastIso);
413
389
  }
414
-
415
390
  function heatGrid(coupling) {
416
- const { folders, counts, max } = coupling;
417
- const grid = document.createElement('div');
418
- grid.className = 'heat';
419
- grid.style.gridTemplateColumns = `64px repeat(${folders.length}, 1fr)`;
420
-
421
- grid.appendChild(document.createElement('span'));
422
- for (const c of folders) {
423
- const label = document.createElement('span');
424
- label.className = 'heat-col-label';
425
- label.textContent = shortFolder(c);
426
- label.title = c;
427
- grid.appendChild(label);
428
- }
429
- for (const r of folders) {
430
- const label = document.createElement('span');
431
- label.className = 'heat-label';
432
- label.textContent = shortFolder(r);
433
- label.title = r;
434
- grid.appendChild(label);
391
+ const { folders, counts, max } = coupling;
392
+ const grid = document.createElement('div');
393
+ grid.className = 'heat';
394
+ grid.style.gridTemplateColumns = `64px repeat(${folders.length}, 1fr)`;
395
+ grid.appendChild(document.createElement('span'));
435
396
  for (const c of folders) {
436
- const cell = document.createElement('span');
437
- cell.className = 'heat-cell';
438
- const n = counts.get(r + '->' + c) || 0;
439
- if (n > 0) {
440
- const pct = Math.max(14, Math.round((n / max) * 86));
441
- cell.style.background = `color-mix(in srgb, var(--accent) ${pct}%, transparent)`;
442
- cell.textContent = n;
443
- cell.title = `${r} → ${c}: ${n} ${n === 1 ? 'import' : 'imports'}`;
444
- }
445
- grid.appendChild(cell);
397
+ const label = document.createElement('span');
398
+ label.className = 'heat-col-label';
399
+ label.textContent = shortFolder(c);
400
+ label.title = c;
401
+ grid.appendChild(label);
402
+ }
403
+ for (const r of folders) {
404
+ const label = document.createElement('span');
405
+ label.className = 'heat-label';
406
+ label.textContent = shortFolder(r);
407
+ label.title = r;
408
+ grid.appendChild(label);
409
+ for (const c of folders) {
410
+ const cell = document.createElement('span');
411
+ cell.className = 'heat-cell';
412
+ const n = counts.get(r + '->' + c) || 0;
413
+ if (n > 0) {
414
+ const pct = Math.max(14, Math.round((n / max) * 86));
415
+ cell.style.background = `color-mix(in srgb, var(--accent) ${pct}%, transparent)`;
416
+ cell.textContent = n;
417
+ cell.title = `${r} → ${c}: ${n} ${n === 1 ? 'import' : 'imports'}`;
418
+ }
419
+ grid.appendChild(cell);
420
+ }
446
421
  }
447
- }
448
- return grid;
422
+ return grid;
449
423
  }
450
-
451
424
  function shortFolder(name) {
452
- const clean = name === '(root)' ? 'root' : name;
453
- return clean.length > 9 ? clean.slice(0, 8) + '…' : clean;
425
+ const clean = name === '(root)' ? 'root' : name;
426
+ return clean.length > 9 ? clean.slice(0, 8) + '…' : clean;
454
427
  }
455
-
456
428
  // The AI flow reuses the same box: streaming text lands here. `headerHtml`
457
429
  // pins a line above the answer — the question being answered, for instance.
458
430
  export function setExplain(html, sourceNote) {
459
- el.inspExplain.innerHTML = html + (sourceNote ? `<p class="explain-src">${escapeHtml(sourceNote)}</p>` : '');
431
+ el.inspExplain.innerHTML = html + (sourceNote ? `<p class="explain-src">${escapeHtml(sourceNote)}</p>` : '');
460
432
  }
461
-
462
433
  export function explainStreamWriter(headerHtml = '') {
463
- let text = '';
464
- el.inspExplain.innerHTML = headerHtml + '<p><span class="caret"></span></p>';
465
- return {
466
- push(delta) {
467
- text += delta;
468
- el.inspExplain.innerHTML = headerHtml + mdLite(text) + '<span class="caret"></span>';
469
- },
470
- finish(note) {
471
- el.inspExplain.innerHTML = headerHtml + mdLite(text) + (note ? `<p class="explain-src">${escapeHtml(note)}</p>` : '');
472
- },
473
- fail(message) {
474
- el.inspExplain.innerHTML = headerHtml + `<p style="color:var(--warn)">${escapeHtml(message)}</p>`;
475
- },
476
- };
434
+ let text = '';
435
+ el.inspExplain.innerHTML = headerHtml + '<p><span class="caret"></span></p>';
436
+ return {
437
+ push(delta) {
438
+ text += delta;
439
+ el.inspExplain.innerHTML = headerHtml + mdLite(text) + '<span class="caret"></span>';
440
+ },
441
+ finish(note) {
442
+ el.inspExplain.innerHTML = headerHtml + mdLite(text) + (note ? `<p class="explain-src">${escapeHtml(note)}</p>` : '');
443
+ },
444
+ fail(message) {
445
+ el.inspExplain.innerHTML = headerHtml + `<p style="color:var(--warn)">${escapeHtml(message)}</p>`;
446
+ },
447
+ };
477
448
  }
478
-
479
449
  // ---- helpers ---------------------------------------------------------------
480
-
481
450
  function addPathList(title, paths, scan) {
482
- if (!paths.length) return;
483
- addList(title, paths.map((p) => ({ text: p, path: p })), scan);
451
+ if (!paths.length)
452
+ return;
453
+ addList(title, paths.map((p) => ({ text: p, path: p })), scan);
484
454
  }
485
-
486
455
  function addList(title, items, scan) {
487
- if (!items || !items.length) return;
488
- const h = document.createElement('h3');
489
- h.className = 'inspector-h';
490
- h.textContent = title;
491
- const ul = document.createElement('ul');
492
- ul.className = 'link-list';
493
- for (const item of items.slice(0, 14)) {
494
- const li = document.createElement('li');
495
- if (item.path) {
496
- const b = document.createElement('button');
497
- b.textContent = item.text;
498
- b.dataset.goto = item.path;
499
- if (item.title) b.title = item.title;
500
- li.appendChild(b);
501
- } else {
502
- li.textContent = item.text;
456
+ if (!items || !items.length)
457
+ return;
458
+ const h = document.createElement('h3');
459
+ h.className = 'inspector-h';
460
+ h.textContent = title;
461
+ const ul = document.createElement('ul');
462
+ ul.className = 'link-list';
463
+ for (const item of items.slice(0, 14)) {
464
+ const li = document.createElement('li');
465
+ if (item.path) {
466
+ const b = document.createElement('button');
467
+ b.textContent = item.text;
468
+ b.dataset.goto = item.path;
469
+ if (item.title)
470
+ b.title = item.title;
471
+ li.appendChild(b);
472
+ }
473
+ else {
474
+ li.textContent = item.text;
475
+ }
476
+ if (item.kind) {
477
+ const k = document.createElement('span');
478
+ k.className = 'fn-kind';
479
+ k.textContent = item.kind;
480
+ li.appendChild(k);
481
+ }
482
+ ul.appendChild(li);
503
483
  }
504
- if (item.kind) {
505
- const k = document.createElement('span');
506
- k.className = 'fn-kind';
507
- k.textContent = item.kind;
508
- li.appendChild(k);
484
+ if (items.length > 14) {
485
+ const more = document.createElement('li');
486
+ more.textContent = `…and ${items.length - 14} more`;
487
+ more.style.color = 'var(--faint)';
488
+ ul.appendChild(more);
509
489
  }
510
- ul.appendChild(li);
511
- }
512
- if (items.length > 14) {
513
- const more = document.createElement('li');
514
- more.textContent = `…and ${items.length - 14} more`;
515
- more.style.color = 'var(--faint)';
516
- ul.appendChild(more);
517
- }
518
- el.inspLists.append(h, ul);
490
+ el.inspLists.append(h, ul);
519
491
  }
520
-
521
492
  // Clicking a path inside the inspector navigates to it (wired in app.js).
522
493
  export function onInspectorNavigate(handler) {
523
- el.inspLists.addEventListener('click', (event) => {
524
- const b = event.target.closest('button[data-goto]');
525
- if (b) handler(b.dataset.goto);
526
- });
494
+ el.inspLists.addEventListener('click', (event) => {
495
+ const b = eventElement(event)?.closest('button[data-goto]');
496
+ if (b)
497
+ handler(b.dataset.goto);
498
+ });
527
499
  }
528
-
529
500
  function chip(html) {
530
- return `<span class="stat-chip">${html}</span>`;
501
+ return `<span class="stat-chip">${html}</span>`;
531
502
  }
532
-
533
503
  function langName(id) {
534
- return languageLabel(id, { short: true });
504
+ return languageLabel(id, { short: true });
535
505
  }
506
+ //# sourceMappingURL=inspector.js.map