codebase-onboarder 0.6.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.
- package/README.md +174 -46
- package/bin/build-if-source.js +23 -0
- package/cli/commands.d.ts +26 -0
- package/cli/commands.js +836 -811
- package/cli/commands.js.map +1 -0
- package/cli/explorer/advanced.d.ts +14 -0
- package/cli/explorer/advanced.js +321 -329
- package/cli/explorer/advanced.js.map +1 -0
- package/cli/explorer/app.d.ts +1 -0
- package/cli/explorer/app.js +384 -313
- package/cli/explorer/app.js.map +1 -0
- package/cli/explorer/assistant.d.ts +9 -0
- package/cli/explorer/assistant.js +58 -0
- package/cli/explorer/assistant.js.map +1 -0
- package/cli/explorer/commands.d.ts +17 -0
- package/cli/explorer/commands.js +368 -302
- package/cli/explorer/commands.js.map +1 -0
- package/cli/explorer/featureViews.d.ts +124 -0
- package/cli/explorer/featureViews.js +179 -0
- package/cli/explorer/featureViews.js.map +1 -0
- package/cli/explorer/github.d.ts +21 -0
- package/cli/explorer/github.js +42 -44
- package/cli/explorer/github.js.map +1 -0
- package/cli/explorer/graphs.d.ts +7 -0
- package/cli/explorer/graphs.js +90 -95
- package/cli/explorer/graphs.js.map +1 -0
- package/cli/explorer/picker.d.ts +22 -0
- package/cli/explorer/picker.js +150 -0
- package/cli/explorer/picker.js.map +1 -0
- package/cli/explorer/report.d.ts +85 -0
- package/cli/explorer/report.js +60 -0
- package/cli/explorer/report.js.map +1 -0
- package/cli/explorer/session.d.ts +449 -0
- package/cli/explorer/session.js +143 -144
- package/cli/explorer/session.js.map +1 -0
- package/cli/explorer/views.d.ts +19 -0
- package/cli/explorer/views.js +465 -482
- package/cli/explorer/views.js.map +1 -0
- package/cli/explorer/wrap.d.ts +1 -0
- package/cli/explorer/wrap.js +28 -44
- package/cli/explorer/wrap.js.map +1 -0
- package/cli/main.d.ts +1 -0
- package/cli/main.js +214 -161
- package/cli/main.js.map +1 -0
- package/cli/prompt.d.ts +5 -0
- package/cli/prompt.js +84 -80
- package/cli/prompt.js.map +1 -0
- package/cli/scanOptions.d.ts +2 -0
- package/cli/scanOptions.js +11 -0
- package/cli/scanOptions.js.map +1 -0
- package/cli/ui.d.ts +33 -0
- package/cli/ui.js +35 -44
- package/cli/ui.js.map +1 -0
- package/cli/wizard.d.ts +187 -0
- package/cli/wizard.js +304 -284
- package/cli/wizard.js.map +1 -0
- package/package.json +15 -4
- package/public/app.d.ts +1 -0
- package/public/app.js +1057 -1009
- package/public/app.js.map +1 -0
- package/public/browser.d.ts +68 -0
- package/public/browser.js +2 -0
- package/public/browser.js.map +1 -0
- package/public/index.html +30 -13
- package/public/js/about.d.ts +6 -0
- package/public/js/about.js +152 -172
- package/public/js/about.js.map +1 -0
- package/public/js/aiDraft.d.ts +3 -0
- package/public/js/aiDraft.js +116 -124
- package/public/js/aiDraft.js.map +1 -0
- package/public/js/analysisPanel.d.ts +7 -0
- package/public/js/analysisPanel.js +166 -166
- package/public/js/analysisPanel.js.map +1 -0
- package/public/js/analysisReport.d.ts +38 -0
- package/public/js/analysisReport.js +105 -110
- package/public/js/analysisReport.js.map +1 -0
- package/public/js/api.d.ts +26 -0
- package/public/js/api.js +176 -167
- package/public/js/api.js.map +1 -0
- package/public/js/atlas.d.ts +6 -0
- package/public/js/atlas.js +50 -58
- package/public/js/atlas.js.map +1 -0
- package/public/js/blameView.d.ts +2 -0
- package/public/js/blameView.js +36 -37
- package/public/js/blameView.js.map +1 -0
- package/public/js/codeTab.d.ts +3 -0
- package/public/js/codeTab.js +175 -180
- package/public/js/codeTab.js.map +1 -0
- package/public/js/codeViewer.d.ts +5 -0
- package/public/js/codeViewer.js +162 -150
- package/public/js/codeViewer.js.map +1 -0
- package/public/js/components/FileChip.d.ts +9 -0
- package/public/js/components/FileChip.js +45 -49
- package/public/js/components/FileChip.js.map +1 -0
- package/public/js/components/MetricSparkline.d.ts +7 -0
- package/public/js/components/MetricSparkline.js +35 -44
- package/public/js/components/MetricSparkline.js.map +1 -0
- package/public/js/components/RiskBadge.d.ts +9 -0
- package/public/js/components/RiskBadge.js +55 -63
- package/public/js/components/RiskBadge.js.map +1 -0
- package/public/js/deepAnalysisView.d.ts +99 -0
- package/public/js/deepAnalysisView.js +282 -302
- package/public/js/deepAnalysisView.js.map +1 -0
- package/public/js/diagramPane.d.ts +8 -0
- package/public/js/diagramPane.js +133 -135
- package/public/js/diagramPane.js.map +1 -0
- package/public/js/diffView.d.ts +6 -0
- package/public/js/diffView.js +222 -209
- package/public/js/diffView.js.map +1 -0
- package/public/js/docsView.d.ts +2 -0
- package/public/js/docsView.js +441 -442
- package/public/js/docsView.js.map +1 -0
- package/public/js/dom.d.ts +189 -0
- package/public/js/dom.js +5 -0
- package/public/js/dom.js.map +1 -0
- package/public/js/fileSourceBrowser.d.ts +5 -0
- package/public/js/fileSourceBrowser.js +33 -41
- package/public/js/fileSourceBrowser.js.map +1 -0
- package/public/js/flameGraph.d.ts +5 -0
- package/public/js/flameGraph.js +62 -72
- package/public/js/flameGraph.js.map +1 -0
- package/public/js/forceGraph.d.ts +10 -0
- package/public/js/forceGraph.js +581 -556
- package/public/js/forceGraph.js.map +1 -0
- package/public/js/heatmap.d.ts +4 -0
- package/public/js/heatmap.js +142 -164
- package/public/js/heatmap.js.map +1 -0
- package/public/js/highlight.d.ts +2 -0
- package/public/js/highlight.js +116 -107
- package/public/js/highlight.js.map +1 -0
- package/public/js/html.d.ts +2 -0
- package/public/js/html.js +14 -44
- package/public/js/html.js.map +1 -0
- package/public/js/insightsView.d.ts +5 -0
- package/public/js/insightsView.js +93 -111
- package/public/js/insightsView.js.map +1 -0
- package/public/js/inspector.d.ts +37 -0
- package/public/js/inspector.js +416 -445
- package/public/js/inspector.js.map +1 -0
- package/public/js/llm.d.ts +113 -0
- package/public/js/llm.js +233 -272
- package/public/js/llm.js.map +1 -0
- package/public/js/mapView.d.ts +3 -0
- package/public/js/mapView.js +121 -120
- package/public/js/mapView.js.map +1 -0
- package/public/js/markdown.d.ts +3 -0
- package/public/js/markdown.js +84 -91
- package/public/js/markdown.js.map +1 -0
- package/public/js/mindmap.d.ts +32 -0
- package/public/js/mindmap.js +244 -259
- package/public/js/mindmap.js.map +1 -0
- package/public/js/mobilePanels.d.ts +5 -0
- package/public/js/mobilePanels.js +50 -0
- package/public/js/mobilePanels.js.map +1 -0
- package/public/js/repoFiles.d.ts +12 -0
- package/public/js/repoFiles.js +28 -27
- package/public/js/repoFiles.js.map +1 -0
- package/public/js/reviewView.d.ts +28 -0
- package/public/js/reviewView.js +256 -0
- package/public/js/reviewView.js.map +1 -0
- package/public/js/sbomView.d.ts +3 -0
- package/public/js/sbomView.js +59 -67
- package/public/js/sbomView.js.map +1 -0
- package/public/js/scanCache.d.ts +16 -0
- package/public/js/scanCache.js +67 -65
- package/public/js/scanCache.js.map +1 -0
- package/public/js/search.d.ts +7 -0
- package/public/js/search.js +868 -800
- package/public/js/search.js.map +1 -0
- package/public/js/searchLocal.d.ts +42 -0
- package/public/js/searchLocal.js +87 -0
- package/public/js/searchLocal.js.map +1 -0
- package/public/js/serverSettings.d.ts +5 -0
- package/public/js/serverSettings.js +153 -164
- package/public/js/serverSettings.js.map +1 -0
- package/public/js/state.d.ts +798 -0
- package/public/js/state.js +114 -122
- package/public/js/state.js.map +1 -0
- package/public/js/tour.d.ts +6 -0
- package/public/js/tour.js +5 -7
- package/public/js/tour.js.map +1 -0
- package/public/js/transitions.d.ts +2 -0
- package/public/js/transitions.js +9 -10
- package/public/js/transitions.js.map +1 -0
- package/public/js/tree.d.ts +12 -0
- package/public/js/tree.js +157 -154
- package/public/js/tree.js.map +1 -0
- package/public/js/workflowsView.d.ts +3 -0
- package/public/js/workflowsView.js +15 -22
- package/public/js/workflowsView.js.map +1 -0
- package/public/search.css +495 -0
- package/public/styles.css +163 -346
- package/public/workbench.css +166 -0
- package/server/apiAuth.d.ts +3 -0
- package/server/apiAuth.js +18 -20
- package/server/apiAuth.js.map +1 -0
- package/server/apiDiff.d.ts +2 -0
- package/server/apiDiff.js +20 -18
- package/server/apiDiff.js.map +1 -0
- package/server/apiDocs.d.ts +2 -0
- package/server/apiDocs.js +38 -40
- package/server/apiDocs.js.map +1 -0
- package/server/apiFile.d.ts +1 -0
- package/server/apiFile.js +14 -17
- package/server/apiFile.js.map +1 -0
- package/server/apiGitBlame.d.ts +2 -0
- package/server/apiGitBlame.js +50 -51
- package/server/apiGitBlame.js.map +1 -0
- package/server/apiMcp.d.ts +4 -0
- package/server/apiMcp.js +64 -66
- package/server/apiMcp.js.map +1 -0
- package/server/apiReview.d.ts +2 -0
- package/server/apiReview.js +45 -0
- package/server/apiReview.js.map +1 -0
- package/server/apiScan.d.ts +5 -0
- package/server/apiScan.js +90 -71
- package/server/apiScan.js.map +1 -0
- package/server/apiSearch.d.ts +33 -0
- package/server/apiSearch.js +184 -200
- package/server/apiSearch.js.map +1 -0
- package/server/apiSettings.d.ts +4 -0
- package/server/apiSettings.js +84 -85
- package/server/apiSettings.js.map +1 -0
- package/server/apiTools.d.ts +3 -0
- package/server/apiTools.js +55 -56
- package/server/apiTools.js.map +1 -0
- package/server/auth.d.ts +5 -0
- package/server/auth.js +54 -51
- package/server/auth.js.map +1 -0
- package/server/config.d.ts +42 -0
- package/server/config.js +189 -194
- package/server/config.js.map +1 -0
- package/server/daemon.d.ts +12 -0
- package/server/daemon.js +124 -114
- package/server/daemon.js.map +1 -0
- package/server/fileSourceNode.d.ts +2 -0
- package/server/fileSourceNode.js +34 -36
- package/server/fileSourceNode.js.map +1 -0
- package/server/gitClone.d.ts +9 -0
- package/server/gitClone.js +59 -61
- package/server/gitClone.js.map +1 -0
- package/server/gitDiff.d.ts +36 -0
- package/server/gitDiff.js +271 -169
- package/server/gitDiff.js.map +1 -0
- package/server/gitHistory.d.ts +12 -0
- package/server/gitHistory.js +80 -80
- package/server/gitHistory.js.map +1 -0
- package/server/gitRemote.d.ts +12 -0
- package/server/gitRemote.js +37 -38
- package/server/gitRemote.js.map +1 -0
- package/server/htmlText.d.ts +4 -0
- package/server/htmlText.js +16 -23
- package/server/htmlText.js.map +1 -0
- package/server/http.d.ts +5 -0
- package/server/http.js +27 -30
- package/server/http.js.map +1 -0
- package/server/httpGuards.d.ts +2 -0
- package/server/httpGuards.js +47 -43
- package/server/httpGuards.js.map +1 -0
- package/server/https.d.ts +34 -0
- package/server/https.js +121 -111
- package/server/https.js.map +1 -0
- package/server/index.d.ts +40 -0
- package/server/index.js +139 -147
- package/server/index.js.map +1 -0
- package/server/layout.d.ts +26 -0
- package/server/layout.js +59 -64
- package/server/layout.js.map +1 -0
- package/server/llmProxy.d.ts +3 -0
- package/server/llmProxy.js +137 -135
- package/server/llmProxy.js.map +1 -0
- package/server/logger.d.ts +38 -0
- package/server/logger.js +198 -211
- package/server/logger.js.map +1 -0
- package/server/mcp/analysis.d.ts +29 -0
- package/server/mcp/analysis.js +99 -120
- package/server/mcp/analysis.js.map +1 -0
- package/server/mcp/http.d.ts +9 -0
- package/server/mcp/http.js +128 -137
- package/server/mcp/http.js.map +1 -0
- package/server/mcp/runner.d.ts +68 -0
- package/server/mcp/runner.js +208 -218
- package/server/mcp/runner.js.map +1 -0
- package/server/mcp/server.d.ts +29 -0
- package/server/mcp/server.js +166 -179
- package/server/mcp/server.js.map +1 -0
- package/server/mcp/standalone.d.ts +1 -0
- package/server/mcp/standalone.js +8 -10
- package/server/mcp/standalone.js.map +1 -0
- package/server/mcp/tools.d.ts +2020 -0
- package/server/mcp/tools.js +523 -574
- package/server/mcp/tools.js.map +1 -0
- package/server/paths.d.ts +3 -0
- package/server/paths.js +17 -15
- package/server/paths.js.map +1 -0
- package/server/pidfile.d.ts +9 -0
- package/server/pidfile.js +43 -45
- package/server/pidfile.js.map +1 -0
- package/server/review.d.ts +5 -0
- package/server/review.js +35 -0
- package/server/review.js.map +1 -0
- package/server/router.d.ts +33 -0
- package/server/router.js +191 -197
- package/server/router.js.map +1 -0
- package/server/scanRequest.d.ts +3 -0
- package/server/scanRequest.js +36 -0
- package/server/scanRequest.js.map +1 -0
- package/server/searchIndex.d.ts +17 -0
- package/server/searchIndex.js +83 -80
- package/server/searchIndex.js.map +1 -0
- package/server/sessions.d.ts +14 -0
- package/server/sessions.js +62 -70
- package/server/sessions.js.map +1 -0
- package/server/startup.d.ts +118 -0
- package/server/startup.js +143 -148
- package/server/startup.js.map +1 -0
- package/server/static.d.ts +6 -0
- package/server/static.js +28 -31
- package/server/static.js.map +1 -0
- package/server/tools/install.d.ts +4 -0
- package/server/tools/install.js +176 -185
- package/server/tools/install.js.map +1 -0
- package/server/tools/parse.d.ts +8 -0
- package/server/tools/parse.js +112 -114
- package/server/tools/parse.js.map +1 -0
- package/server/tools/platform.d.ts +9 -0
- package/server/tools/platform.js +49 -47
- package/server/tools/platform.js.map +1 -0
- package/server/tools/registry.d.ts +137 -0
- package/server/tools/registry.js +173 -172
- package/server/tools/registry.js.map +1 -0
- package/server/tools/scan.d.ts +34 -0
- package/server/tools/scan.js +89 -98
- package/server/tools/scan.js.map +1 -0
- package/server/tools.d.ts +34 -0
- package/server/tools.js +94 -107
- package/server/tools.js.map +1 -0
- package/server/tunnel.d.ts +29 -0
- package/server/tunnel.js +61 -66
- package/server/tunnel.js.map +1 -0
- package/shared/analyzer/docs.d.ts +16 -0
- package/shared/analyzer/docs.js +99 -93
- package/shared/analyzer/docs.js.map +1 -0
- package/shared/analyzer/explainLocal.d.ts +4 -0
- package/shared/analyzer/explainLocal.js +122 -142
- package/shared/analyzer/explainLocal.js.map +1 -0
- package/shared/analyzer/github.d.ts +37 -0
- package/shared/analyzer/github.js +41 -43
- package/shared/analyzer/github.js.map +1 -0
- package/shared/analyzer/graph.d.ts +41 -0
- package/shared/analyzer/graph.js +393 -400
- package/shared/analyzer/graph.js.map +1 -0
- package/shared/analyzer/health.d.ts +33 -0
- package/shared/analyzer/health.js +197 -193
- package/shared/analyzer/health.js.map +1 -0
- package/shared/analyzer/history.d.ts +29 -0
- package/shared/analyzer/history.js +110 -111
- package/shared/analyzer/history.js.map +1 -0
- package/shared/analyzer/jsonConfig.d.ts +2 -0
- package/shared/analyzer/jsonConfig.js +61 -0
- package/shared/analyzer/jsonConfig.js.map +1 -0
- package/shared/analyzer/languages/csharp.d.ts +26 -0
- package/shared/analyzer/languages/csharp.js +83 -88
- package/shared/analyzer/languages/csharp.js.map +1 -0
- package/shared/analyzer/languages/generic.d.ts +23 -0
- package/shared/analyzer/languages/generic.js +94 -105
- package/shared/analyzer/languages/generic.js.map +1 -0
- package/shared/analyzer/languages/go.d.ts +24 -0
- package/shared/analyzer/languages/go.js +45 -54
- package/shared/analyzer/languages/go.js.map +1 -0
- package/shared/analyzer/languages/index.d.ts +6 -0
- package/shared/analyzer/languages/index.js +19 -20
- package/shared/analyzer/languages/index.js.map +1 -0
- package/shared/analyzer/languages/java.d.ts +20 -0
- package/shared/analyzer/languages/java.js +73 -78
- package/shared/analyzer/languages/java.js.map +1 -0
- package/shared/analyzer/languages/javascript.d.ts +12 -0
- package/shared/analyzer/languages/javascript.js +258 -209
- package/shared/analyzer/languages/javascript.js.map +1 -0
- package/shared/analyzer/languages/python.d.ts +23 -0
- package/shared/analyzer/languages/python.js +92 -100
- package/shared/analyzer/languages/python.js.map +1 -0
- package/shared/analyzer/languages/rust.d.ts +17 -0
- package/shared/analyzer/languages/rust.js +96 -98
- package/shared/analyzer/languages/rust.js.map +1 -0
- package/shared/analyzer/languages/typescript.d.ts +11 -0
- package/shared/analyzer/languages/typescript.js +117 -116
- package/shared/analyzer/languages/typescript.js.map +1 -0
- package/shared/analyzer/licenses.d.ts +18 -0
- package/shared/analyzer/licenses.js +130 -133
- package/shared/analyzer/licenses.js.map +1 -0
- package/shared/analyzer/metrics.d.ts +20 -0
- package/shared/analyzer/metrics.js +112 -104
- package/shared/analyzer/metrics.js.map +1 -0
- package/shared/analyzer/pathUtil.d.ts +10 -0
- package/shared/analyzer/pathUtil.js +29 -35
- package/shared/analyzer/pathUtil.js.map +1 -0
- package/shared/analyzer/patterns.d.ts +11 -0
- package/shared/analyzer/patterns.js +231 -253
- package/shared/analyzer/patterns.js.map +1 -0
- package/shared/analyzer/scan.d.ts +62 -0
- package/shared/analyzer/scan.js +451 -442
- package/shared/analyzer/scan.js.map +1 -0
- package/shared/analyzer/scanControl.d.ts +11 -0
- package/shared/analyzer/scanControl.js +62 -0
- package/shared/analyzer/scanControl.js.map +1 -0
- package/shared/analyzer/security.d.ts +18 -0
- package/shared/analyzer/security.js +159 -159
- package/shared/analyzer/security.js.map +1 -0
- package/shared/analyzer/services.d.ts +5 -0
- package/shared/analyzer/services.js +141 -138
- package/shared/analyzer/services.js.map +1 -0
- package/shared/analyzer/stack.d.ts +6 -0
- package/shared/analyzer/stack.js +134 -141
- package/shared/analyzer/stack.js.map +1 -0
- package/shared/analyzer/tour.d.ts +2 -0
- package/shared/analyzer/tour.js +45 -53
- package/shared/analyzer/tour.js.map +1 -0
- package/shared/analyzer/util.d.ts +8 -0
- package/shared/analyzer/util.js +130 -76
- package/shared/analyzer/util.js.map +1 -0
- package/shared/analyzer/workflows.d.ts +8 -0
- package/shared/analyzer/workflows.js +139 -134
- package/shared/analyzer/workflows.js.map +1 -0
- package/shared/contracts.d.ts +250 -0
- package/shared/contracts.js +2 -0
- package/shared/contracts.js.map +1 -0
- package/shared/diagram/aiFacts.d.ts +35 -0
- package/shared/diagram/aiFacts.js +102 -105
- package/shared/diagram/aiFacts.js.map +1 -0
- package/shared/diagram/aiMermaid.d.ts +5 -0
- package/shared/diagram/aiMermaid.js +46 -45
- package/shared/diagram/aiMermaid.js.map +1 -0
- package/shared/diagram/atlas.d.ts +16 -0
- package/shared/diagram/atlas.js +51 -60
- package/shared/diagram/atlas.js.map +1 -0
- package/shared/diagram/mermaid.d.ts +43 -0
- package/shared/diagram/mermaid.js +348 -370
- package/shared/diagram/mermaid.js.map +1 -0
- package/shared/review/contracts.d.ts +95 -0
- package/shared/review/contracts.js +2 -0
- package/shared/review/contracts.js.map +1 -0
- package/shared/review/review.d.ts +10 -0
- package/shared/review/review.js +184 -0
- package/shared/review/review.js.map +1 -0
- package/shared/search/query.d.ts +32 -0
- package/shared/search/query.js +395 -388
- package/shared/search/query.js.map +1 -0
package/public/js/docsView.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { eventElement } from '/js/dom.js';
|
|
1
2
|
// The Docs tab: a written account of the repo.
|
|
2
3
|
//
|
|
3
4
|
// Three layers, in order of how much they cost. The overview page and the
|
|
@@ -9,7 +10,6 @@
|
|
|
9
10
|
//
|
|
10
11
|
// The static sentences live in `/shared/analyzer/docs.js` — pure, and tested.
|
|
11
12
|
// What is here is the DOM: tabs, streaming, and the wiring between them.
|
|
12
|
-
|
|
13
13
|
import { parseFolderDoc, fileFactsLine, fileStaticDoc, folderStaticDoc, docFileRow } from '/shared/analyzer/docs.js';
|
|
14
14
|
import { explainOverview } from '/shared/analyzer/explainLocal.js';
|
|
15
15
|
import { scanIndex } from '/shared/analyzer/graph.js';
|
|
@@ -20,100 +20,89 @@ import { streamExplain } from './api.js';
|
|
|
20
20
|
import { findNode, countFiles } from './tree.js';
|
|
21
21
|
import { readRepoFile, gatherFileContext } from './repoFiles.js';
|
|
22
22
|
import * as llm from './llm.js';
|
|
23
|
-
|
|
24
23
|
let host = null;
|
|
25
24
|
let hooks = {
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
25
|
+
onToast: (..._args) => { },
|
|
26
|
+
onOpenSettings: (..._args) => { },
|
|
27
|
+
onOpenFile: (..._args) => { },
|
|
28
|
+
repoOverview: () => '',
|
|
30
29
|
};
|
|
31
|
-
|
|
32
30
|
export function initDocs(options) {
|
|
33
|
-
|
|
34
|
-
|
|
31
|
+
host = options.host;
|
|
32
|
+
hooks = { ...hooks, ...options };
|
|
35
33
|
}
|
|
36
|
-
|
|
37
34
|
// Facts about one file or folder, bound to the loaded repo. Every prompt and
|
|
38
35
|
// every static sentence on this page goes through these two, so the doc tab and
|
|
39
36
|
// the folder pass can never describe the same file differently.
|
|
40
37
|
const factsLine = (path) => fileFactsLine(path, state.scan, state.facts);
|
|
41
38
|
const staticDoc = (path) => fileStaticDoc(path, state.scan, state.facts);
|
|
42
39
|
const fileRow = (path) => docFileRow(path, state.scan, state.facts);
|
|
43
|
-
|
|
44
40
|
// ---- the page ---------------------------------------------------------------
|
|
45
|
-
|
|
46
41
|
// One-time document build per scan; AI briefs update rows in place afterwards.
|
|
47
42
|
export function renderDocs() {
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
barEl.className = 'doc-askbar';
|
|
68
|
-
barEl.innerHTML = `
|
|
43
|
+
if (state.docs.rendered)
|
|
44
|
+
return;
|
|
45
|
+
state.docs.rendered = true;
|
|
46
|
+
const tabsEl = document.createElement('div');
|
|
47
|
+
tabsEl.className = 'doc-tabs';
|
|
48
|
+
const wrapEl = document.createElement('div');
|
|
49
|
+
const scrollEl = document.createElement('div');
|
|
50
|
+
scrollEl.className = 'doc-scroll';
|
|
51
|
+
scrollEl.append(tabsEl, wrapEl);
|
|
52
|
+
// Answers stream into this rail, above the bar, only once there's something
|
|
53
|
+
// to show.
|
|
54
|
+
const railEl = document.createElement('div');
|
|
55
|
+
railEl.className = 'doc-qa-rail';
|
|
56
|
+
railEl.hidden = true;
|
|
57
|
+
// The sticky bottom bar: questions about whatever doc is on screen, with
|
|
58
|
+
// clear/close once a conversation is underway.
|
|
59
|
+
const barEl = document.createElement('form');
|
|
60
|
+
barEl.className = 'doc-askbar';
|
|
61
|
+
barEl.innerHTML = `
|
|
69
62
|
<input type="text" id="docAskInput" class="text-input" placeholder="Ask me anything about this doc…" spellcheck="false" autocomplete="off">
|
|
70
63
|
<button type="submit" class="btn btn-ink">Ask</button>`;
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
clearBtn.hidden = true;
|
|
77
|
-
const closeBtn = document.createElement('button');
|
|
78
|
-
closeBtn.type = 'button';
|
|
79
|
-
closeBtn.className = 'btn btn-sm btn-ghost doc-close';
|
|
80
|
-
closeBtn.textContent = 'close';
|
|
81
|
-
closeBtn.title = 'Close the conversation';
|
|
82
|
-
closeBtn.hidden = true;
|
|
83
|
-
clearBtn.addEventListener('click', () => { railEl.innerHTML = ''; });
|
|
84
|
-
closeBtn.addEventListener('click', () => {
|
|
85
|
-
railEl.hidden = true;
|
|
64
|
+
const clearBtn = document.createElement('button');
|
|
65
|
+
clearBtn.type = 'button';
|
|
66
|
+
clearBtn.className = 'btn btn-sm btn-ghost doc-clear';
|
|
67
|
+
clearBtn.textContent = 'clear';
|
|
68
|
+
clearBtn.title = 'Clear the conversation';
|
|
86
69
|
clearBtn.hidden = true;
|
|
70
|
+
const closeBtn = document.createElement('button');
|
|
71
|
+
closeBtn.type = 'button';
|
|
72
|
+
closeBtn.className = 'btn btn-sm btn-ghost doc-close';
|
|
73
|
+
closeBtn.textContent = 'close';
|
|
74
|
+
closeBtn.title = 'Close the conversation';
|
|
87
75
|
closeBtn.hidden = true;
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
76
|
+
clearBtn.addEventListener('click', () => { railEl.innerHTML = ''; });
|
|
77
|
+
closeBtn.addEventListener('click', () => {
|
|
78
|
+
railEl.hidden = true;
|
|
79
|
+
clearBtn.hidden = true;
|
|
80
|
+
closeBtn.hidden = true;
|
|
81
|
+
});
|
|
82
|
+
barEl.append(clearBtn, closeBtn);
|
|
83
|
+
barEl.addEventListener('submit', (event) => {
|
|
84
|
+
event.preventDefault();
|
|
85
|
+
askAboutRepo();
|
|
86
|
+
});
|
|
87
|
+
host.innerHTML = '';
|
|
88
|
+
host.append(scrollEl, railEl, barEl);
|
|
89
|
+
state.docs.tabsEl = tabsEl;
|
|
90
|
+
state.docs.wrapEl = wrapEl;
|
|
91
|
+
state.docs.railEl = railEl;
|
|
92
|
+
state.docs.bar = barEl;
|
|
93
|
+
state.docs.pageEl = buildOverviewPage();
|
|
94
|
+
renderDocStrip();
|
|
95
|
+
showActiveDoc();
|
|
96
|
+
loadReadme();
|
|
106
97
|
}
|
|
107
|
-
|
|
108
98
|
// The landing tab: repo header, topic index, README, the documentation zone
|
|
109
99
|
// (filled on demand by the AI), and the question box at the bottom.
|
|
110
100
|
function buildOverviewPage() {
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
page.innerHTML = `
|
|
101
|
+
const { scan, facts, manifest } = state;
|
|
102
|
+
const root = state.treeData;
|
|
103
|
+
const page = document.createElement('div');
|
|
104
|
+
page.className = 'doc-page';
|
|
105
|
+
page.innerHTML = `
|
|
117
106
|
<div id="doc-top">
|
|
118
107
|
<h1 class="doc-title">${escapeHtml(scan.name)}</h1>
|
|
119
108
|
<p class="doc-sub">${escapeHtml(scan.root)}</p>
|
|
@@ -130,219 +119,217 @@ function buildOverviewPage() {
|
|
|
130
119
|
<button class="btn btn-ink" id="docGenBtn">Generate documentation</button>
|
|
131
120
|
</div>
|
|
132
121
|
`;
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
return page;
|
|
122
|
+
buildToc(page, root);
|
|
123
|
+
page.addEventListener('click', (event) => {
|
|
124
|
+
const b = eventElement(event)?.closest('[data-doc-open]');
|
|
125
|
+
if (!b)
|
|
126
|
+
return;
|
|
127
|
+
const raw = b.dataset.docOpen;
|
|
128
|
+
const i = raw.indexOf(':');
|
|
129
|
+
openDocTab(raw.slice(0, i), raw.slice(i + 1));
|
|
130
|
+
});
|
|
131
|
+
page.querySelector('#docGenBtn').addEventListener('click', writeDocsWithAI);
|
|
132
|
+
return page;
|
|
146
133
|
}
|
|
147
|
-
|
|
148
134
|
// The contents index: page anchors up top, then every folder and file as a
|
|
149
135
|
// topic — clicking any of them opens its detailed AI tab.
|
|
150
136
|
function buildToc(page, root) {
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
137
|
+
const toc = page.querySelector('#docToc');
|
|
138
|
+
const bits = [
|
|
139
|
+
`<span class="doc-toc-h">Get documentation about a topic</span>`,
|
|
140
|
+
`<a href="#doc-top">Overview</a>`,
|
|
141
|
+
`<a href="#doc-readme-h">In its own words</a>`,
|
|
142
|
+
`<span class="doc-toc-group">Folders and files — click for the AI document</span>`,
|
|
143
|
+
];
|
|
144
|
+
let folderCount = 0;
|
|
145
|
+
const parsedSet = new Set(state.scan.files.map((f) => f.path));
|
|
146
|
+
const walk = (node, depth) => {
|
|
147
|
+
if (folderCount >= 30)
|
|
148
|
+
return;
|
|
149
|
+
folderCount++;
|
|
150
|
+
const indent = Math.min(depth, 3);
|
|
151
|
+
bits.push(`<button class="doc-toc-link doc-toc-folder doc-indent-${indent}" data-doc-open="folder:${escapeHtml(node.path)}">${escapeHtml(node.path ? node.name + '/' : '(repo root)')}</button>`);
|
|
152
|
+
const files = node.files.filter((f) => parsedSet.has(f)).slice(0, 10);
|
|
153
|
+
if (files.length) {
|
|
154
|
+
const links = files
|
|
155
|
+
.map((f) => `<button class="doc-toc-link" data-doc-open="file:${escapeHtml(f)}">${escapeHtml(f.split('/').pop())}</button>`)
|
|
156
|
+
.join('<span class="doc-toc-sep"> · </span>');
|
|
157
|
+
bits.push(`<span class="doc-toc-files doc-indent-${indent}">${links}${node.files.length > files.length ? ' …' : ''}</span>`);
|
|
158
|
+
}
|
|
159
|
+
for (const child of [...node.dirs.values()].sort((a, b) => a.name.localeCompare(b.name))) {
|
|
160
|
+
walk(child, depth + 1);
|
|
161
|
+
}
|
|
162
|
+
};
|
|
163
|
+
if (root.files.length)
|
|
164
|
+
walk({ ...root, dirs: new Map() }, 0);
|
|
165
|
+
for (const child of [...root.dirs.values()].sort((a, b) => a.name.localeCompare(b.name))) {
|
|
166
|
+
walk(child, 0);
|
|
174
167
|
}
|
|
175
|
-
|
|
176
|
-
if (root.files.length) walk({ ...root, dirs: new Map() }, 0);
|
|
177
|
-
for (const child of [...root.dirs.values()].sort((a, b) => a.name.localeCompare(b.name))) {
|
|
178
|
-
walk(child, 0);
|
|
179
|
-
}
|
|
180
|
-
toc.innerHTML = bits.join('');
|
|
168
|
+
toc.innerHTML = bits.join('');
|
|
181
169
|
}
|
|
182
|
-
|
|
183
170
|
async function loadReadme() {
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
for (const name of ['README.md', 'readme.md', 'Readme.md', 'README', 'README.txt']) {
|
|
187
|
-
try {
|
|
188
|
-
const text = await readRepoFile(name);
|
|
189
|
-
if (text) {
|
|
190
|
-
box.innerHTML = mdRender(text);
|
|
171
|
+
const box = host.querySelector('#docReadme');
|
|
172
|
+
if (!box)
|
|
191
173
|
return;
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
174
|
+
for (const name of ['README.md', 'readme.md', 'Readme.md', 'README', 'README.txt']) {
|
|
175
|
+
try {
|
|
176
|
+
const text = await readRepoFile(name);
|
|
177
|
+
if (text) {
|
|
178
|
+
box.innerHTML = mdRender(text);
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
catch {
|
|
183
|
+
/* try the next spelling */
|
|
184
|
+
}
|
|
195
185
|
}
|
|
196
|
-
|
|
197
|
-
box.innerHTML = '<span class="doc-readme-none">This repo ships no README — the map and the patterns are all you get.</span>';
|
|
186
|
+
box.innerHTML = '<span class="doc-readme-none">This repo ships no README — the map and the patterns are all you get.</span>';
|
|
198
187
|
}
|
|
199
|
-
|
|
200
188
|
// ---- the sticky ask bar: questions scoped to the doc on screen -------------
|
|
201
|
-
|
|
202
189
|
async function askAboutRepo() {
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
190
|
+
const input = host.querySelector('#docAskInput');
|
|
191
|
+
const question = input?.value.trim();
|
|
192
|
+
if (!question)
|
|
193
|
+
return hooks.onToast('Type a question first.');
|
|
194
|
+
if (!llm.isConfigured()) {
|
|
195
|
+
hooks.onOpenSettings();
|
|
196
|
+
hooks.onToast('Add an endpoint and model first.');
|
|
197
|
+
return;
|
|
198
|
+
}
|
|
199
|
+
input.value = '';
|
|
200
|
+
const rail = state.docs.railEl;
|
|
201
|
+
rail.hidden = false;
|
|
202
|
+
// reveal the clear/close controls now that a conversation exists
|
|
203
|
+
const bar = state.docs.bar;
|
|
204
|
+
if (bar) {
|
|
205
|
+
const c = bar.querySelector('.doc-clear');
|
|
206
|
+
const x = bar.querySelector('.doc-close');
|
|
207
|
+
if (c)
|
|
208
|
+
c.hidden = false;
|
|
209
|
+
if (x)
|
|
210
|
+
x.hidden = false;
|
|
211
|
+
}
|
|
212
|
+
const settings = llm.getSettings();
|
|
213
|
+
const card = document.createElement('div');
|
|
214
|
+
card.className = 'doc-qa-card';
|
|
215
|
+
card.innerHTML = `<p class="doc-qa-q">“${escapeHtml(question)}”</p><div class="doc-qa-a"><span class="caret"></span></div>`;
|
|
216
|
+
rail.appendChild(card);
|
|
217
|
+
card.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
|
|
218
|
+
// Scope the answer to whatever is on screen: an open file/folder doc tab
|
|
219
|
+
// gets its facts (and source, for files) sent along; the overview answers
|
|
220
|
+
// from the repo-wide picture.
|
|
221
|
+
const active = state.docs.active || 'overview';
|
|
222
|
+
let file = null;
|
|
223
|
+
let scope = 'the repo overview';
|
|
224
|
+
let overview = hooks.repoOverview() + ' Top-level folders: ' + [...state.treeData.dirs.keys()].join(', ') + '.';
|
|
225
|
+
if (active.startsWith('file:')) {
|
|
226
|
+
const path = active.slice(5);
|
|
227
|
+
file = await gatherFileContext(path);
|
|
228
|
+
scope = path;
|
|
229
|
+
}
|
|
230
|
+
else if (active.startsWith('folder:')) {
|
|
231
|
+
scope = active.slice(7) + '/';
|
|
232
|
+
overview += ` The user is currently reading the documentation for the folder "${active.slice(7)}".`;
|
|
233
|
+
}
|
|
234
|
+
const messages = llm.questionMessages({ repoName: state.scan.name, overview, question, file });
|
|
235
|
+
let text = '';
|
|
236
|
+
try {
|
|
237
|
+
for await (const delta of streamExplain({ ...settings, messages, maxTokens: 1200 })) {
|
|
238
|
+
text += delta;
|
|
239
|
+
card.querySelector('.doc-qa-a').innerHTML = mdLite(text) + '<span class="caret"></span>';
|
|
240
|
+
}
|
|
241
|
+
card.querySelector('.doc-qa-a').innerHTML =
|
|
242
|
+
mdLite(text) + `<p class="explain-src">Answered by ${settings.model}, looking at ${escapeHtml(scope)}.</p>`;
|
|
243
|
+
}
|
|
244
|
+
catch (err) {
|
|
245
|
+
card.querySelector('.doc-qa-a').innerHTML =
|
|
246
|
+
`<p style="color:var(--warn)">${escapeHtml(err.message)} — check the API key drawer, top right.</p>`;
|
|
252
247
|
}
|
|
253
|
-
card.querySelector('.doc-qa-a').innerHTML =
|
|
254
|
-
mdLite(text) + `<p class="explain-src">Answered by ${settings.model}, looking at ${escapeHtml(scope)}.</p>`;
|
|
255
|
-
} catch (err) {
|
|
256
|
-
card.querySelector('.doc-qa-a').innerHTML =
|
|
257
|
-
`<p style="color:var(--warn)">${escapeHtml(err.message)} — check the API key drawer, top right.</p>`;
|
|
258
|
-
}
|
|
259
248
|
}
|
|
260
|
-
|
|
261
249
|
// ---- doc tabs: detailed AI documents per file/folder -----------------------
|
|
262
|
-
|
|
263
250
|
function renderDocStrip() {
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
251
|
+
const strip = state.docs.tabsEl;
|
|
252
|
+
if (!strip)
|
|
253
|
+
return;
|
|
254
|
+
strip.innerHTML = '';
|
|
255
|
+
const mk = (key, label, closable) => {
|
|
256
|
+
const b = document.createElement('button');
|
|
257
|
+
b.className = 'doc-tab' + (state.docs.active === key ? ' is-active' : '');
|
|
258
|
+
const entry = state.docs.tabs.get(key);
|
|
259
|
+
b.innerHTML = `<span>${escapeHtml(label)}</span>${entry?.status === 'writing' ? '<span class="doc-tab-spin">…</span>' : ''}`;
|
|
260
|
+
b.addEventListener('click', () => {
|
|
261
|
+
state.docs.active = key;
|
|
262
|
+
renderDocStrip();
|
|
263
|
+
showActiveDoc();
|
|
264
|
+
});
|
|
265
|
+
if (closable) {
|
|
266
|
+
const x = document.createElement('span');
|
|
267
|
+
x.className = 'doc-tab-x';
|
|
268
|
+
x.textContent = '×';
|
|
269
|
+
x.title = 'Close this tab';
|
|
270
|
+
x.addEventListener('click', (event) => {
|
|
271
|
+
event.stopPropagation();
|
|
272
|
+
closeDocTab(key);
|
|
273
|
+
});
|
|
274
|
+
b.appendChild(x);
|
|
275
|
+
}
|
|
276
|
+
return b;
|
|
277
|
+
};
|
|
278
|
+
strip.appendChild(mk('overview', 'Overview', false));
|
|
279
|
+
for (const [key, entry] of state.docs.tabs) {
|
|
280
|
+
strip.appendChild(mk(key, entry.title, true));
|
|
288
281
|
}
|
|
289
|
-
return b;
|
|
290
|
-
};
|
|
291
|
-
|
|
292
|
-
strip.appendChild(mk('overview', 'Overview', false));
|
|
293
|
-
for (const [key, entry] of state.docs.tabs) {
|
|
294
|
-
strip.appendChild(mk(key, entry.title, true));
|
|
295
|
-
}
|
|
296
282
|
}
|
|
297
|
-
|
|
298
283
|
function openDocTab(kind, path) {
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
284
|
+
const key = kind + ':' + path;
|
|
285
|
+
if (!state.docs.tabs.has(key)) {
|
|
286
|
+
const title = (path ? path.split('/').pop() : '(root)') + (kind === 'folder' ? '/' : '');
|
|
287
|
+
state.docs.tabs.set(key, { key, kind, path, title, status: 'idle', text: '', error: '' });
|
|
288
|
+
}
|
|
289
|
+
state.docs.active = key;
|
|
290
|
+
renderDocStrip();
|
|
291
|
+
showActiveDoc();
|
|
292
|
+
const entry = state.docs.tabs.get(key);
|
|
293
|
+
if (entry.status === 'idle')
|
|
294
|
+
generateDocTab(entry);
|
|
309
295
|
}
|
|
310
|
-
|
|
311
296
|
function closeDocTab(key) {
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
297
|
+
state.docs.tabs.delete(key);
|
|
298
|
+
if (state.docs.active === key)
|
|
299
|
+
state.docs.active = 'overview';
|
|
300
|
+
renderDocStrip();
|
|
301
|
+
showActiveDoc();
|
|
316
302
|
}
|
|
317
|
-
|
|
318
303
|
function showActiveDoc() {
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
304
|
+
const wrap = state.docs.wrapEl;
|
|
305
|
+
if (!wrap)
|
|
306
|
+
return;
|
|
307
|
+
if (state.docs.active === 'overview') {
|
|
308
|
+
wrap.replaceChildren(state.docs.pageEl);
|
|
309
|
+
return;
|
|
310
|
+
}
|
|
311
|
+
const entry = state.docs.tabs.get(state.docs.active);
|
|
312
|
+
if (entry)
|
|
313
|
+
wrap.replaceChildren(buildPaneEl(entry));
|
|
327
314
|
}
|
|
328
|
-
|
|
329
315
|
function buildPaneEl(entry) {
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
316
|
+
const pane = document.createElement('div');
|
|
317
|
+
pane.className = 'doc-page doc-detail';
|
|
318
|
+
const chips = [];
|
|
319
|
+
if (entry.kind === 'file') {
|
|
320
|
+
const f = scanIndex(state.scan).fileAt(entry.path);
|
|
321
|
+
const fin = state.facts.fanIn[entry.path] || 0;
|
|
322
|
+
const fout = state.facts.fanOut[entry.path] || 0;
|
|
323
|
+
chips.push(`<b>${fin}</b> dependents`, `<b>${fout}</b> imports`);
|
|
324
|
+
if (f)
|
|
325
|
+
chips.push(`<b>${f.functions.length}</b> functions`, `<b>${(f.size / 1024).toFixed(1)}</b> kb`);
|
|
326
|
+
}
|
|
327
|
+
else {
|
|
328
|
+
const node = findNode(state.treeData, entry.path);
|
|
329
|
+
if (node)
|
|
330
|
+
chips.push(`<b>${node.files.length}</b> files`, `<b>${node.dirs.size}</b> subfolders`);
|
|
331
|
+
}
|
|
332
|
+
pane.innerHTML = `
|
|
346
333
|
<h1 class="doc-title">${escapeHtml(entry.title)}</h1>
|
|
347
334
|
<p class="doc-sub">${escapeHtml(entry.path || '(repo root)')}</p>
|
|
348
335
|
<p class="doc-sub doc-detail-meta">${chips.join(' · ')}</p>
|
|
@@ -352,168 +339,172 @@ function buildPaneEl(entry) {
|
|
|
352
339
|
</div>
|
|
353
340
|
<div class="doc-detail-body"></div>
|
|
354
341
|
`;
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
body.innerHTML = `<p style="color:var(--warn)">${escapeHtml(entry.error)} — hit Regenerate to try again.</p>`;
|
|
359
|
-
} else if (entry.status === 'idle' || (entry.status === 'writing' && !entry.text)) {
|
|
360
|
-
// The static write-up is the floor: something true is on screen before the
|
|
361
|
-
// model answers, and stays there if it never does.
|
|
362
|
-
const fallback = entry.kind === 'file'
|
|
363
|
-
? staticDoc(entry.path)
|
|
364
|
-
: folderStaticDoc(entry.path, state.scan, state.facts, findNode(state.treeData, entry.path)?.dirs.size || 0);
|
|
365
|
-
body.innerHTML = `<p>${escapeHtml(fallback)}</p>`
|
|
366
|
-
+ '<p class="doc-readme-none">Gathering facts and writing<span class="caret"></span></p>';
|
|
367
|
-
} else {
|
|
368
|
-
body.innerHTML = mdRender(entry.text) + (entry.status === 'writing' ? '<span class="caret"></span>' : '');
|
|
369
|
-
}
|
|
370
|
-
|
|
371
|
-
pane.addEventListener('click', (event) => {
|
|
372
|
-
if (event.target.closest('[data-doc-dd]')) hooks.onOpenFile(entry.path);
|
|
373
|
-
if (event.target.closest('[data-doc-regen]')) {
|
|
374
|
-
entry.status = 'idle';
|
|
375
|
-
entry.text = '';
|
|
376
|
-
generateDocTab(entry);
|
|
377
|
-
showActiveDoc();
|
|
342
|
+
const body = pane.querySelector('.doc-detail-body');
|
|
343
|
+
if (entry.status === 'error') {
|
|
344
|
+
body.innerHTML = `<p style="color:var(--warn)">${escapeHtml(entry.error)} — hit Regenerate to try again.</p>`;
|
|
378
345
|
}
|
|
379
|
-
|
|
380
|
-
|
|
346
|
+
else if (entry.status === 'idle' || (entry.status === 'writing' && !entry.text)) {
|
|
347
|
+
// The static write-up is the floor: something true is on screen before the
|
|
348
|
+
// model answers, and stays there if it never does.
|
|
349
|
+
const fallback = entry.kind === 'file'
|
|
350
|
+
? staticDoc(entry.path)
|
|
351
|
+
: folderStaticDoc(entry.path, state.scan, state.facts, findNode(state.treeData, entry.path)?.dirs.size || 0);
|
|
352
|
+
body.innerHTML = `<p>${escapeHtml(fallback)}</p>`
|
|
353
|
+
+ '<p class="doc-readme-none">Gathering facts and writing<span class="caret"></span></p>';
|
|
354
|
+
}
|
|
355
|
+
else {
|
|
356
|
+
body.innerHTML = mdRender(entry.text) + (entry.status === 'writing' ? '<span class="caret"></span>' : '');
|
|
357
|
+
}
|
|
358
|
+
pane.addEventListener('click', (event) => {
|
|
359
|
+
if (eventElement(event)?.closest('[data-doc-dd]'))
|
|
360
|
+
hooks.onOpenFile(entry.path);
|
|
361
|
+
if (eventElement(event)?.closest('[data-doc-regen]')) {
|
|
362
|
+
entry.status = 'idle';
|
|
363
|
+
entry.text = '';
|
|
364
|
+
generateDocTab(entry);
|
|
365
|
+
showActiveDoc();
|
|
366
|
+
}
|
|
367
|
+
});
|
|
368
|
+
return pane;
|
|
381
369
|
}
|
|
382
|
-
|
|
383
370
|
let docRefreshTimer = null;
|
|
384
371
|
function scheduleDocPaneRefresh(entry) {
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
docRefreshTimer =
|
|
388
|
-
|
|
389
|
-
|
|
372
|
+
if (state.docs.active !== entry.key || docRefreshTimer)
|
|
373
|
+
return;
|
|
374
|
+
docRefreshTimer = setTimeout(() => {
|
|
375
|
+
docRefreshTimer = null;
|
|
376
|
+
if (state.docs.active === entry.key)
|
|
377
|
+
showActiveDoc();
|
|
378
|
+
}, 160);
|
|
390
379
|
}
|
|
391
|
-
|
|
392
380
|
async function generateDocTab(entry) {
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
381
|
+
if (!llm.isConfigured()) {
|
|
382
|
+
entry.status = 'error';
|
|
383
|
+
entry.error = 'No endpoint configured — open the API key drawer, top right.';
|
|
384
|
+
showActiveDoc();
|
|
385
|
+
renderDocStrip();
|
|
386
|
+
hooks.onOpenSettings();
|
|
387
|
+
return;
|
|
388
|
+
}
|
|
389
|
+
const settings = llm.getSettings();
|
|
390
|
+
entry.status = 'writing';
|
|
391
|
+
entry.text = '';
|
|
392
|
+
entry.error = '';
|
|
397
393
|
renderDocStrip();
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
});
|
|
394
|
+
try {
|
|
395
|
+
let messages;
|
|
396
|
+
if (entry.kind === 'file') {
|
|
397
|
+
const ctx = await gatherFileContext(entry.path);
|
|
398
|
+
messages = llm.fileDocMessages({ repoName: state.scan.name, overview: hooks.repoOverview(), file: ctx });
|
|
399
|
+
}
|
|
400
|
+
else {
|
|
401
|
+
const node = findNode(state.treeData, entry.path);
|
|
402
|
+
messages = llm.folderDocDetailedMessages({
|
|
403
|
+
repoName: state.scan.name,
|
|
404
|
+
folder: entry.path,
|
|
405
|
+
subfolders: node ? [...node.dirs.keys()].map((n) => n + '/') : [],
|
|
406
|
+
files: (node?.files || []).slice(0, 18).map(fileRow),
|
|
407
|
+
});
|
|
408
|
+
}
|
|
409
|
+
for await (const delta of streamExplain({ ...settings, messages, maxTokens: 1800 })) {
|
|
410
|
+
entry.text += delta;
|
|
411
|
+
scheduleDocPaneRefresh(entry);
|
|
412
|
+
}
|
|
413
|
+
entry.status = 'done';
|
|
419
414
|
}
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
415
|
+
catch (err) {
|
|
416
|
+
entry.status = 'error';
|
|
417
|
+
entry.error = err.message;
|
|
423
418
|
}
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
entry.error = err.message;
|
|
428
|
-
}
|
|
429
|
-
renderDocStrip();
|
|
430
|
-
if (state.docs.active === entry.key) showActiveDoc();
|
|
419
|
+
renderDocStrip();
|
|
420
|
+
if (state.docs.active === entry.key)
|
|
421
|
+
showActiveDoc();
|
|
431
422
|
}
|
|
432
|
-
|
|
433
423
|
// ---- the folder-by-folder pass --------------------------------------------
|
|
434
|
-
|
|
435
424
|
// Which folders get an AI brief, and with what file facts. Top-level folders
|
|
436
425
|
// by size, capped — one call briefs a folder and everything directly in it.
|
|
437
426
|
function docTargets() {
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
427
|
+
const root = state.treeData;
|
|
428
|
+
const targets = [];
|
|
429
|
+
if (root.files.length) {
|
|
430
|
+
targets.push({
|
|
431
|
+
path: '',
|
|
432
|
+
label: '(repo root)',
|
|
433
|
+
subfolders: [...root.dirs.keys()].map((n) => n + '/'),
|
|
434
|
+
files: root.files.slice(0, 18).map(fileRow),
|
|
435
|
+
});
|
|
436
|
+
}
|
|
437
|
+
const topDirs = [...root.dirs.values()]
|
|
438
|
+
.map((node) => ({ node, count: countFiles(node) }))
|
|
439
|
+
.sort((a, b) => b.count - a.count)
|
|
440
|
+
.slice(0, 8);
|
|
441
|
+
for (const { node } of topDirs) {
|
|
442
|
+
targets.push({
|
|
443
|
+
path: node.path,
|
|
444
|
+
label: node.path + '/',
|
|
445
|
+
subfolders: [...node.dirs.keys()].map((n) => n + '/'),
|
|
446
|
+
files: node.files.slice(0, 18).map(fileRow),
|
|
447
|
+
});
|
|
448
|
+
}
|
|
449
|
+
return targets;
|
|
461
450
|
}
|
|
462
|
-
|
|
463
451
|
async function writeDocsWithAI() {
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
const settings = llm.getSettings();
|
|
471
|
-
const page = state.docs.pageEl;
|
|
472
|
-
const zone = page?.querySelector('#docGenZone');
|
|
473
|
-
const btn = page?.querySelector('#docGenBtn');
|
|
474
|
-
const note = page?.querySelector('#docAiNote');
|
|
475
|
-
const overviewEl = page?.querySelector('#docOverview');
|
|
476
|
-
if (!zone) return;
|
|
477
|
-
|
|
478
|
-
state.docs.writing = true;
|
|
479
|
-
if (btn) {
|
|
480
|
-
btn.disabled = true;
|
|
481
|
-
btn.textContent = 'writing…';
|
|
482
|
-
}
|
|
483
|
-
zone.querySelectorAll('.doc-generated').forEach((el) => el.remove()); // regenerate = fresh page
|
|
484
|
-
const empty = zone.querySelector('.doc-gen-empty');
|
|
485
|
-
if (empty) empty.hidden = true;
|
|
486
|
-
|
|
487
|
-
try {
|
|
488
|
-
// 1. The repo-level overview, streamed in place.
|
|
489
|
-
if (note) note.textContent = 'Writing the overview…';
|
|
490
|
-
const readmeText = host.querySelector('#docReadme')?.innerText?.slice(0, 1500) || '';
|
|
491
|
-
let out = '';
|
|
492
|
-
if (overviewEl) overviewEl.innerHTML = '<span class="caret"></span>';
|
|
493
|
-
const overviewMsgs = llm.repoDocMessages({
|
|
494
|
-
repoName: state.scan.name,
|
|
495
|
-
overview: hooks.repoOverview(),
|
|
496
|
-
readmeExcerpt: readmeText,
|
|
497
|
-
entries: state.facts.entries,
|
|
498
|
-
hubs: state.facts.hubs,
|
|
499
|
-
});
|
|
500
|
-
for await (const delta of streamExplain({ ...settings, messages: overviewMsgs, maxTokens: 900 })) {
|
|
501
|
-
out += delta;
|
|
502
|
-
if (overviewEl) overviewEl.textContent = out;
|
|
452
|
+
if (state.docs.writing)
|
|
453
|
+
return;
|
|
454
|
+
if (!llm.isConfigured()) {
|
|
455
|
+
hooks.onOpenSettings();
|
|
456
|
+
hooks.onToast('Add an endpoint and model first.');
|
|
457
|
+
return;
|
|
503
458
|
}
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
const
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
459
|
+
const settings = llm.getSettings();
|
|
460
|
+
const page = state.docs.pageEl;
|
|
461
|
+
const zone = page?.querySelector('#docGenZone');
|
|
462
|
+
const btn = page?.querySelector('#docGenBtn');
|
|
463
|
+
const note = page?.querySelector('#docAiNote');
|
|
464
|
+
const overviewEl = page?.querySelector('#docOverview');
|
|
465
|
+
if (!zone)
|
|
466
|
+
return;
|
|
467
|
+
state.docs.writing = true;
|
|
468
|
+
if (btn) {
|
|
469
|
+
btn.disabled = true;
|
|
470
|
+
btn.textContent = 'writing…';
|
|
471
|
+
}
|
|
472
|
+
zone.querySelectorAll('.doc-generated').forEach((el) => el.remove()); // regenerate = fresh page
|
|
473
|
+
const empty = zone.querySelector('.doc-gen-empty');
|
|
474
|
+
if (empty)
|
|
475
|
+
empty.hidden = true;
|
|
476
|
+
try {
|
|
477
|
+
// 1. The repo-level overview, streamed in place.
|
|
478
|
+
if (note)
|
|
479
|
+
note.textContent = 'Writing the overview…';
|
|
480
|
+
const readmeText = host.querySelector('#docReadme')?.innerText?.slice(0, 1500) || '';
|
|
481
|
+
let out = '';
|
|
482
|
+
if (overviewEl)
|
|
483
|
+
overviewEl.innerHTML = '<span class="caret"></span>';
|
|
484
|
+
const overviewMsgs = llm.repoDocMessages({
|
|
485
|
+
repoName: state.scan.name,
|
|
486
|
+
overview: hooks.repoOverview(),
|
|
487
|
+
readmeExcerpt: readmeText,
|
|
488
|
+
entries: state.facts.entries,
|
|
489
|
+
hubs: state.facts.hubs,
|
|
490
|
+
});
|
|
491
|
+
for await (const delta of streamExplain({ ...settings, messages: overviewMsgs, maxTokens: 900 })) {
|
|
492
|
+
out += delta;
|
|
493
|
+
if (overviewEl)
|
|
494
|
+
overviewEl.textContent = out;
|
|
495
|
+
}
|
|
496
|
+
// 2. Folder by folder: each section appears with static sentences first,
|
|
497
|
+
// then the AI's documentation overwrites it as the answer lands.
|
|
498
|
+
const targets = docTargets();
|
|
499
|
+
let done = 0;
|
|
500
|
+
for (const t of targets) {
|
|
501
|
+
done++;
|
|
502
|
+
if (note)
|
|
503
|
+
note.textContent = `Writing documentation… ${done} of ${targets.length} — ${t.label}`;
|
|
504
|
+
const node = findNode(state.treeData, t.path);
|
|
505
|
+
const section = document.createElement('section');
|
|
506
|
+
section.className = 'doc-folder doc-generated';
|
|
507
|
+
section.innerHTML = `
|
|
517
508
|
<h3 class="doc-folder-h"><button data-doc-open="folder:${escapeHtml(t.path)}">${escapeHtml(t.label)}</button></h3>
|
|
518
509
|
<p class="doc-folder-brief"><span class="caret"></span></p>
|
|
519
510
|
${t.files.map((f) => `
|
|
@@ -525,42 +516,50 @@ async function writeDocsWithAI() {
|
|
|
525
516
|
<p class="doc-entry-text" data-doc-file="${escapeHtml(f.path)}">${escapeHtml(staticDoc(f.path))}</p>
|
|
526
517
|
</div>`).join('')}
|
|
527
518
|
`;
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
519
|
+
zone.appendChild(section);
|
|
520
|
+
section.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
|
|
521
|
+
let text = '';
|
|
522
|
+
const msgs = llm.folderDocMessages({
|
|
523
|
+
repoName: state.scan.name,
|
|
524
|
+
folder: t.path,
|
|
525
|
+
subfolders: t.subfolders,
|
|
526
|
+
files: t.files,
|
|
527
|
+
});
|
|
528
|
+
for await (const delta of streamExplain({ ...settings, messages: msgs, maxTokens: 1600 })) {
|
|
529
|
+
text += delta;
|
|
530
|
+
}
|
|
531
|
+
const parsed = parseFolderDoc(text);
|
|
532
|
+
const folderP = section.querySelector('.doc-folder-brief');
|
|
533
|
+
if (folderP) {
|
|
534
|
+
folderP.textContent = parsed.folderBrief
|
|
535
|
+
|| folderStaticDoc(t.path, state.scan, state.facts, node?.dirs.size || 0);
|
|
536
|
+
}
|
|
537
|
+
for (const [name, brief] of parsed.files) {
|
|
538
|
+
const row = t.files.find((f) => f.name === name);
|
|
539
|
+
if (!row)
|
|
540
|
+
continue;
|
|
541
|
+
const el = section.querySelector(`[data-doc-file="${CSS.escape(row.path)}"]`);
|
|
542
|
+
if (el)
|
|
543
|
+
el.textContent = brief;
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
if (note)
|
|
547
|
+
note.textContent = `Written by ${settings.model} from the real import graph. Press “Regenerate documentation” for a fresh take.`;
|
|
548
|
+
if (btn)
|
|
549
|
+
btn.textContent = 'Regenerate documentation';
|
|
553
550
|
}
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
551
|
+
catch (err) {
|
|
552
|
+
if (note)
|
|
553
|
+
note.textContent = 'AI writing stopped: ' + err.message;
|
|
554
|
+
hooks.onToast(err.message);
|
|
555
|
+
}
|
|
556
|
+
finally {
|
|
557
|
+
state.docs.writing = false;
|
|
558
|
+
if (btn) {
|
|
559
|
+
btn.disabled = false;
|
|
560
|
+
if (btn.textContent === 'writing…')
|
|
561
|
+
btn.textContent = 'Generate documentation';
|
|
562
|
+
}
|
|
564
563
|
}
|
|
565
|
-
}
|
|
566
564
|
}
|
|
565
|
+
//# sourceMappingURL=docsView.js.map
|