codebase-onboarder 0.5.0 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +196 -47
- 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 +449 -0
- package/cli/explorer/advanced.js.map +1 -0
- package/cli/explorer/app.d.ts +1 -0
- package/cli/explorer/app.js +396 -229
- 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 +400 -162
- 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 +73 -0
- package/cli/explorer/github.js.map +1 -0
- package/cli/explorer/graphs.d.ts +7 -0
- package/cli/explorer/graphs.js +173 -0
- 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 +184 -95
- package/cli/explorer/session.js.map +1 -0
- package/cli/explorer/views.d.ts +19 -0
- package/cli/explorer/views.js +496 -412
- package/cli/explorer/views.js.map +1 -0
- package/cli/explorer/wrap.d.ts +1 -0
- package/cli/explorer/wrap.js +33 -0
- package/cli/explorer/wrap.js.map +1 -0
- package/cli/main.d.ts +1 -0
- package/cli/main.js +235 -138
- 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 +55 -35
- 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 +165 -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 +60 -0
- 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 -37
- 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 +79 -0
- 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/server/mcp/tools.js
CHANGED
|
@@ -15,223 +15,205 @@
|
|
|
15
15
|
// Every tool declares `path` in its schema, and every handler resolves it through
|
|
16
16
|
// `analyzeRepository`, so a client that forgets the path gets a sentence that says
|
|
17
17
|
// so rather than a validation error with no context.
|
|
18
|
-
|
|
19
|
-
import {
|
|
20
|
-
analyzeRepository, cachedRoots, fileIn, readFileText, filesUnder, parsedFilesUnder,
|
|
21
|
-
ToolError, scanIndex, baseName, explainFile, explainFolder,
|
|
22
|
-
docFileRow, fileFactsLine, fileStaticDoc, folderStaticDoc, searchDocuments,
|
|
23
|
-
} from './analysis.js';
|
|
18
|
+
import { analyzeRepository, cachedRoots, fileIn, readFileText, filesUnder, parsedFilesUnder, ToolError, scanIndex, baseName, explainFile, explainFolder, docFileRow, fileFactsLine, fileStaticDoc, folderStaticDoc, searchDocuments, } from './analysis.js';
|
|
24
19
|
import { buildTourStops } from '../../shared/analyzer/tour.js';
|
|
25
20
|
import { runExternalAnalysis, toolsStatus } from '../tools/scan.js';
|
|
26
21
|
import { TOOL_DEFS } from '../tools/registry.js';
|
|
27
|
-
|
|
28
22
|
const PATH_PROPERTY = {
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
+ 'The path returned by onboarder_scan is the one to reuse. Omit it to use the last repository scanned.',
|
|
23
|
+
type: 'string',
|
|
24
|
+
description: 'Absolute path to the repository on this machine (for example /Users/you/code/my-app). '
|
|
25
|
+
+ 'The path returned by onboarder_scan is the one to reuse. Omit it to use the last repository scanned.',
|
|
33
26
|
};
|
|
34
|
-
|
|
35
27
|
const LIMIT_PROPERTY = {
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
28
|
+
type: 'integer',
|
|
29
|
+
description: 'Maximum number of results. Defaults to 20, capped at 200.',
|
|
30
|
+
minimum: 1,
|
|
31
|
+
maximum: 200,
|
|
40
32
|
};
|
|
41
|
-
|
|
42
33
|
// A client that never scans anything still gets a useful answer from the path it
|
|
43
34
|
// happens to send, and a client that scanned something and then omits the path
|
|
44
35
|
// gets the one it scanned. `lastRoot` is that memory.
|
|
45
36
|
let lastRoot = null;
|
|
46
|
-
|
|
47
37
|
async function resolve({ path: p }) {
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
38
|
+
const root = String(p || '').trim() || lastRoot;
|
|
39
|
+
if (!root) {
|
|
40
|
+
throw new ToolError('No repository yet — call onboarder_scan with a path first.');
|
|
41
|
+
}
|
|
42
|
+
const analysis = await analyzeRepository(root);
|
|
43
|
+
lastRoot = analysis.root;
|
|
44
|
+
return analysis;
|
|
55
45
|
}
|
|
56
|
-
|
|
57
46
|
// Scans are expensive and the cache holds a few; a tool that takes a limit should
|
|
58
47
|
// not be able to be talked into returning the whole repository.
|
|
59
48
|
function limitOf(value, fallback = 20, max = 200) {
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
49
|
+
const n = Number(value);
|
|
50
|
+
if (!Number.isFinite(n))
|
|
51
|
+
return fallback;
|
|
52
|
+
return Math.max(1, Math.min(max, Math.floor(n)));
|
|
63
53
|
}
|
|
64
|
-
|
|
65
54
|
// ---- the payloads -----------------------------------------------------------
|
|
66
55
|
//
|
|
67
56
|
// Each of these is a view's data, flattened into plain JSON. The point is that an
|
|
68
57
|
// agent can act on the answer without knowing this codebase: a hub is a path and a
|
|
69
58
|
// count, not an index into an array the tool kept to itself.
|
|
70
|
-
|
|
71
59
|
function overviewPayload(a) {
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
60
|
+
const { scan, facts, manifest, health, security } = a;
|
|
61
|
+
return {
|
|
62
|
+
root: a.root,
|
|
63
|
+
name: scan.name,
|
|
64
|
+
summary: a.overview,
|
|
65
|
+
stats: {
|
|
66
|
+
files: scan.stats.filesParsed,
|
|
67
|
+
filesSeen: scan.stats.filesTotal,
|
|
68
|
+
languages: scan.stats.languages,
|
|
69
|
+
connections: scan.stats.edgeCount,
|
|
70
|
+
importsResolved: scan.stats.imports?.confidence ?? null,
|
|
71
|
+
},
|
|
72
|
+
entryPoints: facts.entries.slice(0, 10),
|
|
73
|
+
hubs: facts.hubs.slice(0, 10).map((h) => ({ path: h.path, dependents: h.fanIn, dependencies: h.fanOut })),
|
|
74
|
+
cycles: facts.cycles.slice(0, 5),
|
|
75
|
+
orphanCount: facts.orphans.length,
|
|
76
|
+
deadExportCount: (facts.deadExports || []).length,
|
|
77
|
+
services: (manifest.services || []).map((s) => ({ name: s.name, kind: s.kind, port: s.port ?? null })),
|
|
78
|
+
// A repo's dependencies are a different fact from its services — `express` is
|
|
79
|
+
// a library, not a thing this app runs — so they are named apart. They are
|
|
80
|
+
// here, rather than only in `onboarder_dependencies`, because "what does this
|
|
81
|
+
// project use" is a first-scan question and an extra round trip for it would
|
|
82
|
+
// be a tool the agent has to learn exists.
|
|
83
|
+
packageName: manifest.packageName || null,
|
|
84
|
+
dependencies: Object.entries(manifest.deps?.npm || {})
|
|
85
|
+
.map(([name, version]) => ({ name, version })),
|
|
86
|
+
devDependencies: Object.keys(manifest.deps?.dev || {}),
|
|
87
|
+
health: { score: health.score, grade: health.grade },
|
|
88
|
+
security: { score: security.score, grade: security.grade, findings: security.total },
|
|
89
|
+
caveats: a.caveats,
|
|
90
|
+
};
|
|
103
91
|
}
|
|
104
|
-
|
|
105
92
|
function architecturePayload(a) {
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
93
|
+
const { layersInfo, findings, coupling } = a.patterns;
|
|
94
|
+
// `couplingMatrix` returns a folder list and a `Map` keyed `'from->to'`, not a
|
|
95
|
+
// list of edges. Flattening it here means the agent gets pairs; leaving it raw
|
|
96
|
+
// means it has to know this module's encoding — and a Map does not survive
|
|
97
|
+
// `JSON.stringify`, so it would arrive as `{}` rather than as an error.
|
|
98
|
+
const pairs = coupling.counts instanceof Map
|
|
99
|
+
? [...coupling.counts.entries()]
|
|
100
|
+
: Object.entries(coupling.counts || {});
|
|
101
|
+
const edges = pairs
|
|
102
|
+
.map(([key, count]) => {
|
|
103
|
+
const [from, to] = key.split('->');
|
|
104
|
+
return { from, to, count };
|
|
118
105
|
})
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
};
|
|
106
|
+
.sort((x, y) => y.count - x.count);
|
|
107
|
+
return {
|
|
108
|
+
// A layer here is a list of paths, not an object: depth is the array's index.
|
|
109
|
+
layers: layersInfo.layers.map((files, depth) => ({
|
|
110
|
+
depth,
|
|
111
|
+
fileCount: files.length,
|
|
112
|
+
files: files.slice(0, 20),
|
|
113
|
+
})),
|
|
114
|
+
seeds: layersInfo.seeds,
|
|
115
|
+
unreachable: (layersInfo.unreachable || []).slice(0, 20),
|
|
116
|
+
patterns: findings.map((p) => ({
|
|
117
|
+
id: p.id,
|
|
118
|
+
tone: p.tone,
|
|
119
|
+
severity: p.severity,
|
|
120
|
+
title: p.title,
|
|
121
|
+
detail: p.detail,
|
|
122
|
+
paths: (p.paths || []).slice(0, 10),
|
|
123
|
+
})),
|
|
124
|
+
coupling: { folders: coupling.folders, edges: edges.slice(0, 30), max: coupling.max },
|
|
125
|
+
services: (a.manifest.services || []).map((s) => ({ name: s.name, kind: s.kind, port: s.port ?? null })),
|
|
126
|
+
};
|
|
141
127
|
}
|
|
142
|
-
|
|
143
128
|
function healthPayload(a, limit) {
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
129
|
+
return {
|
|
130
|
+
root: a.root,
|
|
131
|
+
score: a.health.score,
|
|
132
|
+
grade: a.health.grade,
|
|
133
|
+
totals: a.health.totals,
|
|
134
|
+
breakdown: a.health.breakdown,
|
|
135
|
+
// `blast` is a transitive-dependency count, exact only on smaller repos;
|
|
136
|
+
// `blastExact` says which, because a number that quietly changes meaning is
|
|
137
|
+
// worse than no number.
|
|
138
|
+
blastExact: a.health.blastExact,
|
|
139
|
+
riskiestFiles: a.health.perFile.slice(0, limit).map((f) => ({
|
|
140
|
+
path: f.path,
|
|
141
|
+
risk: f.risk,
|
|
142
|
+
blast: f.blast,
|
|
143
|
+
complexity: f.complexity,
|
|
144
|
+
loc: f.loc,
|
|
145
|
+
fanIn: f.fanIn,
|
|
146
|
+
fanOut: f.fanOut,
|
|
147
|
+
inCycle: f.inCycle,
|
|
148
|
+
})),
|
|
149
|
+
};
|
|
165
150
|
}
|
|
166
|
-
|
|
167
151
|
function securityPayload(a, limit) {
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
152
|
+
const s = a.security;
|
|
153
|
+
// `summarizeSecurity` rolls up per file, keeping each file's own findings. The
|
|
154
|
+
// flat list an agent wants is the concatenation, ordered worst-first by file.
|
|
155
|
+
const findings = [];
|
|
156
|
+
for (const file of s.files || []) {
|
|
157
|
+
for (const f of file.findings || []) {
|
|
158
|
+
findings.push({
|
|
159
|
+
severity: f.severity,
|
|
160
|
+
rule: f.rule,
|
|
161
|
+
category: f.category,
|
|
162
|
+
path: file.path,
|
|
163
|
+
line: f.line ?? null,
|
|
164
|
+
message: f.message,
|
|
165
|
+
excerpt: f.excerpt,
|
|
166
|
+
});
|
|
167
|
+
}
|
|
183
168
|
}
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
};
|
|
169
|
+
return {
|
|
170
|
+
root: a.root,
|
|
171
|
+
score: s.score,
|
|
172
|
+
grade: s.grade,
|
|
173
|
+
total: s.total,
|
|
174
|
+
bySeverity: s.counts,
|
|
175
|
+
byCategory: s.byCat,
|
|
176
|
+
filesWithFindings: (s.files || []).length,
|
|
177
|
+
findings: findings.slice(0, limit),
|
|
178
|
+
};
|
|
195
179
|
}
|
|
196
|
-
|
|
197
180
|
function historyPayload(a, limit) {
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
181
|
+
const h = a.history;
|
|
182
|
+
if (!h.available)
|
|
183
|
+
return { available: false, reason: h.reason };
|
|
184
|
+
return {
|
|
185
|
+
available: true,
|
|
186
|
+
commitCount: h.commitCount,
|
|
187
|
+
totalCommits: h.totalCommits,
|
|
188
|
+
truncated: h.truncated,
|
|
189
|
+
firstCommitAt: h.firstCommitAt,
|
|
190
|
+
lastCommitAt: h.lastCommitAt,
|
|
191
|
+
contributors: (h.authors || []).slice(0, limit).map((c) => ({ name: c.name, commits: c.commits })),
|
|
192
|
+
hotspots: (h.perFile || []).slice(0, limit).map((r) => ({
|
|
193
|
+
path: r.path, churn: r.churn, authors: r.authors, complexity: r.complexity,
|
|
194
|
+
hotspot: r.hotspot, firstSeen: r.firstSeen, lastTouched: r.lastTouched,
|
|
195
|
+
})),
|
|
196
|
+
soloFiles: h.soloFiles,
|
|
197
|
+
// Files that keep changing together without importing each other — the
|
|
198
|
+
// coupling the import graph cannot see.
|
|
199
|
+
coChanged: (h.coChanged || []).slice(0, 15),
|
|
200
|
+
pathsGone: h.pathsGone,
|
|
201
|
+
};
|
|
218
202
|
}
|
|
219
|
-
|
|
220
203
|
function dependenciesPayload(a) {
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
204
|
+
return {
|
|
205
|
+
languages: a.stack.languages,
|
|
206
|
+
packageManagers: a.stack.pm,
|
|
207
|
+
dependencies: a.stack.items.map((d) => ({
|
|
208
|
+
name: d.name, version: d.version, category: d.category, dev: d.dev, docs: d.docs,
|
|
209
|
+
})),
|
|
210
|
+
projectLicense: a.scan.licenseReport?.projectLicense || a.scan.license,
|
|
211
|
+
licenseCounts: a.scan.licenseReport?.counts || null,
|
|
212
|
+
workflows: (a.scan.workflows || []).map((w) => ({
|
|
213
|
+
name: w.name, file: w.file || w.path, triggers: w.triggers, jobs: (w.jobs || []).map((j) => j.name),
|
|
214
|
+
})),
|
|
215
|
+
};
|
|
233
216
|
}
|
|
234
|
-
|
|
235
217
|
// The folder boundary crossing for one folder, derived from the real edge list.
|
|
236
218
|
//
|
|
237
219
|
// `facts.folderEdges` is a rollup over top-level folders only — the coupling
|
|
@@ -245,439 +227,406 @@ function dependenciesPayload(a) {
|
|
|
245
227
|
// side is named at the same depth as the folder, so a report about `server/mcp`
|
|
246
228
|
// talks about its siblings rather than collapsing to `server`.
|
|
247
229
|
function folderCrossings(a, folder) {
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
230
|
+
const prefix = folder ? folder + '/' : '';
|
|
231
|
+
const depth = folder ? folder.split('/').length : 1;
|
|
232
|
+
const topFolder = (p) => p.split('/')[0];
|
|
233
|
+
const other = (p) => (prefix && p.startsWith(prefix)
|
|
234
|
+
? p.slice(prefix.length).split('/').slice(0, depth).join('/')
|
|
235
|
+
: p.split('/').slice(0, depth).join('/'));
|
|
236
|
+
const out = new Map();
|
|
237
|
+
const into = new Map();
|
|
238
|
+
for (const e of a.scan.edges || []) {
|
|
239
|
+
const fromIn = !prefix || e.from.startsWith(prefix);
|
|
240
|
+
const toIn = !prefix || e.to.startsWith(prefix);
|
|
241
|
+
// Every path is "in" the root, so with no prefix both flags are always true
|
|
242
|
+
// and nothing counts as a crossing. The root's own coupling is the top-level
|
|
243
|
+
// rollup, which is exactly the same question asked one level up.
|
|
244
|
+
if (!prefix) {
|
|
245
|
+
const from = topFolder(e.from);
|
|
246
|
+
const to = topFolder(e.to);
|
|
247
|
+
if (from !== to)
|
|
248
|
+
out.set(to, (out.get(to) || 0) + 1);
|
|
249
|
+
continue;
|
|
250
|
+
}
|
|
251
|
+
if (fromIn === toIn)
|
|
252
|
+
continue; // an edge entirely inside the folder is not a crossing
|
|
253
|
+
if (fromIn) {
|
|
254
|
+
const key = other(e.to);
|
|
255
|
+
out.set(key, (out.get(key) || 0) + 1);
|
|
256
|
+
}
|
|
257
|
+
else {
|
|
258
|
+
const key = other(e.from);
|
|
259
|
+
into.set(key, (into.get(key) || 0) + 1);
|
|
260
|
+
}
|
|
276
261
|
}
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
return { imports: toList(out), importedBy: toList(into) };
|
|
262
|
+
const toList = (m) => [...m.entries()]
|
|
263
|
+
.map(([name, count]) => ({ folder: name, count }))
|
|
264
|
+
.sort((x, y) => y.count - x.count);
|
|
265
|
+
return { imports: toList(out), importedBy: toList(into) };
|
|
282
266
|
}
|
|
283
|
-
|
|
284
267
|
// The tour is the app's "read this first" list. The ordering lives in
|
|
285
268
|
// `shared/analyzer/tour.js` so the browser and this server cannot disagree about
|
|
286
269
|
// which files a newcomer should read.
|
|
287
270
|
function tourPayload(a) {
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
271
|
+
return {
|
|
272
|
+
root: a.root,
|
|
273
|
+
summary: a.overview,
|
|
274
|
+
stops: buildTourStops(a.scan, a.facts).map((s, i) => ({
|
|
275
|
+
order: i + 1,
|
|
276
|
+
path: s.path,
|
|
277
|
+
name: baseName(s.path),
|
|
278
|
+
why: s.why,
|
|
279
|
+
dependents: a.facts.fanIn[s.path] || 0,
|
|
280
|
+
dependencies: a.facts.fanOut[s.path] || 0,
|
|
281
|
+
})),
|
|
282
|
+
};
|
|
300
283
|
}
|
|
301
|
-
|
|
302
284
|
// ---- the tool table ---------------------------------------------------------
|
|
303
|
-
|
|
304
285
|
export const TOOLS = [
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
},
|
|
324
|
-
},
|
|
325
|
-
|
|
326
|
-
{
|
|
327
|
-
name: 'onboarder_rescan',
|
|
328
|
-
description:
|
|
329
|
-
'Discard the cached analysis for a repository and scan it again. Use after changing files on disk, '
|
|
330
|
-
+ 'since every other tool answers from the cached scan.',
|
|
331
|
-
inputSchema: { type: 'object', properties: { path: PATH_PROPERTY }, required: [] },
|
|
332
|
-
async run(args) {
|
|
333
|
-
const a = await analyzeRepository(args.path, { force: true });
|
|
334
|
-
lastRoot = a.root;
|
|
335
|
-
return overviewPayload(a);
|
|
286
|
+
{
|
|
287
|
+
name: 'onboarder_scan',
|
|
288
|
+
description: 'Scan a repository and return everything Onboarder knows about it: structure, entry points, '
|
|
289
|
+
+ 'hubs, cycles, dependencies, services, health, security, history and a written overview. '
|
|
290
|
+
+ 'Start here. The returned `root` is what every other tool takes as its `path`.',
|
|
291
|
+
inputSchema: {
|
|
292
|
+
type: 'object',
|
|
293
|
+
properties: {
|
|
294
|
+
path: PATH_PROPERTY,
|
|
295
|
+
force: { type: 'boolean', description: 'Rescan even if this repository is already cached. Defaults to false.' },
|
|
296
|
+
},
|
|
297
|
+
required: [],
|
|
298
|
+
},
|
|
299
|
+
async run(args) {
|
|
300
|
+
const a = await analyzeRepository(args.path, { force: !!args.force });
|
|
301
|
+
lastRoot = a.root;
|
|
302
|
+
return overviewPayload(a);
|
|
303
|
+
},
|
|
336
304
|
},
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
},
|
|
348
|
-
|
|
349
|
-
{
|
|
350
|
-
name: 'onboarder_architecture',
|
|
351
|
-
description:
|
|
352
|
-
'How the code is arranged: the dependency layers and which files sit in each, files nothing '
|
|
353
|
-
+ 'reaches, the architectural patterns detected (hub-and-spoke, cycles, god objects, layer '
|
|
354
|
-
+ 'violations), which folders import each other, and the services detected.',
|
|
355
|
-
inputSchema: { type: 'object', properties: { path: PATH_PROPERTY }, required: [] },
|
|
356
|
-
async run(args) { return architecturePayload(await resolve(args)); },
|
|
357
|
-
},
|
|
358
|
-
|
|
359
|
-
{
|
|
360
|
-
name: 'onboarder_tour',
|
|
361
|
-
description:
|
|
362
|
-
'A guided reading order for a new contributor: the handful of files worth reading first, each with '
|
|
363
|
-
+ 'the reason it matters and how many files depend on it. The fastest way to understand an '
|
|
364
|
-
+ 'unfamiliar repository.',
|
|
365
|
-
inputSchema: { type: 'object', properties: { path: PATH_PROPERTY }, required: [] },
|
|
366
|
-
async run(args) { return tourPayload(await resolve(args)); },
|
|
367
|
-
},
|
|
368
|
-
|
|
369
|
-
{
|
|
370
|
-
name: 'onboarder_health',
|
|
371
|
-
description:
|
|
372
|
-
'Maintainability report: an overall score and letter grade, what the score is made of, and the '
|
|
373
|
-
+ 'riskiest files with their size, complexity, dependency counts and blast radius.',
|
|
374
|
-
inputSchema: {
|
|
375
|
-
type: 'object',
|
|
376
|
-
properties: { path: PATH_PROPERTY, limit: LIMIT_PROPERTY },
|
|
377
|
-
required: [],
|
|
305
|
+
{
|
|
306
|
+
name: 'onboarder_rescan',
|
|
307
|
+
description: 'Discard the cached analysis for a repository and scan it again. Use after changing files on disk, '
|
|
308
|
+
+ 'since every other tool answers from the cached scan.',
|
|
309
|
+
inputSchema: { type: 'object', properties: { path: PATH_PROPERTY }, required: [] },
|
|
310
|
+
async run(args) {
|
|
311
|
+
const a = await analyzeRepository(args.path, { force: true });
|
|
312
|
+
lastRoot = a.root;
|
|
313
|
+
return overviewPayload(a);
|
|
314
|
+
},
|
|
378
315
|
},
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
+ 'patterns and the rest, ordered by severity. These are the built-in rules only — use '
|
|
387
|
-
+ 'onboarder_deep_analysis for the external scanners.',
|
|
388
|
-
inputSchema: {
|
|
389
|
-
type: 'object',
|
|
390
|
-
properties: { path: PATH_PROPERTY, limit: LIMIT_PROPERTY },
|
|
391
|
-
required: [],
|
|
316
|
+
{
|
|
317
|
+
name: 'onboarder_overview',
|
|
318
|
+
description: 'The repository in one call: written summary, languages, entry points, the files everything depends '
|
|
319
|
+
+ 'on, dependency cycles, services, and what the scan is unsure about. Cheap, because it reads the '
|
|
320
|
+
+ 'cached analysis rather than scanning again.',
|
|
321
|
+
inputSchema: { type: 'object', properties: { path: PATH_PROPERTY }, required: [] },
|
|
322
|
+
async run(args) { return overviewPayload(await resolve(args)); },
|
|
392
323
|
},
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
+ 'complexity — the change-risk hotspots, plus files only one person has ever touched and files '
|
|
401
|
-
+ 'that keep changing together without importing each other.',
|
|
402
|
-
inputSchema: {
|
|
403
|
-
type: 'object',
|
|
404
|
-
properties: { path: PATH_PROPERTY, limit: LIMIT_PROPERTY },
|
|
405
|
-
required: [],
|
|
324
|
+
{
|
|
325
|
+
name: 'onboarder_architecture',
|
|
326
|
+
description: 'How the code is arranged: the dependency layers and which files sit in each, files nothing '
|
|
327
|
+
+ 'reaches, the architectural patterns detected (hub-and-spoke, cycles, god objects, layer '
|
|
328
|
+
+ 'violations), which folders import each other, and the services detected.',
|
|
329
|
+
inputSchema: { type: 'object', properties: { path: PATH_PROPERTY }, required: [] },
|
|
330
|
+
async run(args) { return architecturePayload(await resolve(args)); },
|
|
406
331
|
},
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
+ 'category and documentation link, the project license with its compliance counts, and the CI '
|
|
415
|
-
+ 'workflows with their triggers and jobs.',
|
|
416
|
-
inputSchema: { type: 'object', properties: { path: PATH_PROPERTY }, required: [] },
|
|
417
|
-
async run(args) { return dependenciesPayload(await resolve(args)); },
|
|
418
|
-
},
|
|
419
|
-
{
|
|
420
|
-
name: 'onboarder_explain_file',
|
|
421
|
-
description:
|
|
422
|
-
'Explain one file: its role in the codebase, what it imports, what imports it, its exports, its '
|
|
423
|
-
+ 'metrics, and a written summary. Set includeSource to get the file text as well.',
|
|
424
|
-
inputSchema: {
|
|
425
|
-
type: 'object',
|
|
426
|
-
properties: {
|
|
427
|
-
path: PATH_PROPERTY,
|
|
428
|
-
file: { type: 'string', description: 'Repository-relative path, for example server/router.js.' },
|
|
429
|
-
includeSource: { type: 'boolean', description: 'Also return the full file text. Defaults to false.' },
|
|
430
|
-
},
|
|
431
|
-
required: ['file'],
|
|
332
|
+
{
|
|
333
|
+
name: 'onboarder_tour',
|
|
334
|
+
description: 'A guided reading order for a new contributor: the handful of files worth reading first, each with '
|
|
335
|
+
+ 'the reason it matters and how many files depend on it. The fastest way to understand an '
|
|
336
|
+
+ 'unfamiliar repository.',
|
|
337
|
+
inputSchema: { type: 'object', properties: { path: PATH_PROPERTY }, required: [] },
|
|
338
|
+
async run(args) { return tourPayload(await resolve(args)); },
|
|
432
339
|
},
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
imports: a.facts.importsOf[rel] || [],
|
|
442
|
-
importedBy: a.facts.importers[rel] || [],
|
|
443
|
-
metrics: {
|
|
444
|
-
loc: file.loc,
|
|
445
|
-
complexity: file.complexity,
|
|
446
|
-
exports: (file.exports || []).length,
|
|
447
|
-
functions: (file.functions || []).map((fn) => fn.name),
|
|
340
|
+
{
|
|
341
|
+
name: 'onboarder_health',
|
|
342
|
+
description: 'Maintainability report: an overall score and letter grade, what the score is made of, and the '
|
|
343
|
+
+ 'riskiest files with their size, complexity, dependency counts and blast radius.',
|
|
344
|
+
inputSchema: {
|
|
345
|
+
type: 'object',
|
|
346
|
+
properties: { path: PATH_PROPERTY, limit: LIMIT_PROPERTY },
|
|
347
|
+
required: [],
|
|
448
348
|
},
|
|
449
|
-
|
|
450
|
-
// Source last, and only when asked for: it is by far the largest part of
|
|
451
|
-
// the answer, and a model that wanted the structure does not need it.
|
|
452
|
-
...(args.includeSource ? { source: (await readFileText(a, rel)).text } : {}),
|
|
453
|
-
};
|
|
454
|
-
},
|
|
455
|
-
},
|
|
456
|
-
|
|
457
|
-
{
|
|
458
|
-
name: 'onboarder_explain_folder',
|
|
459
|
-
|
|
460
|
-
description:
|
|
461
|
-
'Explain one folder: what lives in it, which folders it imports, its entry points and hubs, and a '
|
|
462
|
-
+ 'written summary of what the folder is for. Pass an empty string for the repository root.',
|
|
463
|
-
inputSchema: {
|
|
464
|
-
type: 'object',
|
|
465
|
-
properties: {
|
|
466
|
-
path: PATH_PROPERTY,
|
|
467
|
-
folder: { type: 'string', description: 'Repository-relative folder, for example server/mcp. Use "" for the root.' },
|
|
468
|
-
},
|
|
469
|
-
required: ['folder'],
|
|
349
|
+
async run(args) { return healthPayload(await resolve(args), limitOf(args.limit, 20)); },
|
|
470
350
|
},
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
// directory beneath it: a tree two levels down is noise in a summary.
|
|
483
|
-
const depth = folder ? folder.split('/').length : 0;
|
|
484
|
-
const subfolders = [...new Set(paths
|
|
485
|
-
.map((p) => p.split('/').slice(0, -1).join('/'))
|
|
486
|
-
.filter((d) => d && d.split('/').length === depth + 1))];
|
|
487
|
-
|
|
488
|
-
return {
|
|
489
|
-
folder: folder || '.',
|
|
490
|
-
fileCount: paths.length,
|
|
491
|
-
parsedFileCount: parsed.length,
|
|
492
|
-
subfolders: subfolders.slice(0, 40),
|
|
493
|
-
files: parsed.map((f) => docFileRow(f.path, a.scan, a.facts)),
|
|
494
|
-
// Which folders this one imports, and which import it. Counts are import
|
|
495
|
-
// edges, so `server/mcp → shared/analyzer: 6` means six files in here
|
|
496
|
-
// reach in there — not six files in there.
|
|
497
|
-
...folderCrossings(a, folder),
|
|
498
|
-
documentation: folderStaticDoc(folder, a.scan, a.facts, subfolders.length),
|
|
499
|
-
summary: explainFolder(folder, a.scan, a.facts),
|
|
500
|
-
};
|
|
351
|
+
{
|
|
352
|
+
name: 'onboarder_security',
|
|
353
|
+
description: 'Static security findings from the built-in rules: hardcoded secrets, unsafe calls, injection '
|
|
354
|
+
+ 'patterns and the rest, ordered by severity. These are the built-in rules only — use '
|
|
355
|
+
+ 'onboarder_deep_analysis for the external scanners.',
|
|
356
|
+
inputSchema: {
|
|
357
|
+
type: 'object',
|
|
358
|
+
properties: { path: PATH_PROPERTY, limit: LIMIT_PROPERTY },
|
|
359
|
+
required: [],
|
|
360
|
+
},
|
|
361
|
+
async run(args) { return securityPayload(await resolve(args), limitOf(args.limit, 50, 500)); },
|
|
501
362
|
},
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
file: { type: 'string', description: 'Repository-relative path.' },
|
|
514
|
-
},
|
|
515
|
-
required: ['file'],
|
|
363
|
+
{
|
|
364
|
+
name: 'onboarder_history',
|
|
365
|
+
description: 'What git knows: commit count, contributors, and the files with the highest churn weighted against '
|
|
366
|
+
+ 'complexity — the change-risk hotspots, plus files only one person has ever touched and files '
|
|
367
|
+
+ 'that keep changing together without importing each other.',
|
|
368
|
+
inputSchema: {
|
|
369
|
+
type: 'object',
|
|
370
|
+
properties: { path: PATH_PROPERTY, limit: LIMIT_PROPERTY },
|
|
371
|
+
required: [],
|
|
372
|
+
},
|
|
373
|
+
async run(args) { return historyPayload(await resolve(args), limitOf(args.limit, 25)); },
|
|
516
374
|
},
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
375
|
+
{
|
|
376
|
+
name: 'onboarder_dependencies',
|
|
377
|
+
description: 'The dependency inventory: languages, package managers, every declared dependency with version, '
|
|
378
|
+
+ 'category and documentation link, the project license with its compliance counts, and the CI '
|
|
379
|
+
+ 'workflows with their triggers and jobs.',
|
|
380
|
+
inputSchema: { type: 'object', properties: { path: PATH_PROPERTY }, required: [] },
|
|
381
|
+
async run(args) { return dependenciesPayload(await resolve(args)); },
|
|
520
382
|
},
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
383
|
+
{
|
|
384
|
+
name: 'onboarder_explain_file',
|
|
385
|
+
description: 'Explain one file: its role in the codebase, what it imports, what imports it, its exports, its '
|
|
386
|
+
+ 'metrics, and a written summary. Set includeSource to get the file text as well.',
|
|
387
|
+
inputSchema: {
|
|
388
|
+
type: 'object',
|
|
389
|
+
properties: {
|
|
390
|
+
path: PATH_PROPERTY,
|
|
391
|
+
file: { type: 'string', description: 'Repository-relative path, for example server/router.js.' },
|
|
392
|
+
includeSource: { type: 'boolean', description: 'Also return the full file text. Defaults to false.' },
|
|
393
|
+
},
|
|
394
|
+
required: ['file'],
|
|
395
|
+
},
|
|
396
|
+
async run(args) {
|
|
397
|
+
const a = await resolve(args);
|
|
398
|
+
const { rel, file } = fileIn(a, args.file);
|
|
399
|
+
return {
|
|
400
|
+
path: rel,
|
|
401
|
+
role: fileFactsLine(rel, a.scan, a.facts),
|
|
402
|
+
documentation: fileStaticDoc(rel, a.scan, a.facts),
|
|
403
|
+
row: docFileRow(rel, a.scan, a.facts),
|
|
404
|
+
imports: a.facts.importsOf[rel] || [],
|
|
405
|
+
importedBy: a.facts.importers[rel] || [],
|
|
406
|
+
metrics: {
|
|
407
|
+
loc: file.loc,
|
|
408
|
+
complexity: file.complexity,
|
|
409
|
+
exports: (file.exports || []).length,
|
|
410
|
+
functions: (file.functions || []).map((fn) => fn.name),
|
|
411
|
+
},
|
|
412
|
+
summary: explainFile(rel, file, a.facts),
|
|
413
|
+
// Source last, and only when asked for: it is by far the largest part of
|
|
414
|
+
// the answer, and a model that wanted the structure does not need it.
|
|
415
|
+
...(args.includeSource ? { source: (await readFileText(a, rel)).text } : {}),
|
|
416
|
+
};
|
|
417
|
+
},
|
|
539
418
|
},
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
419
|
+
{
|
|
420
|
+
name: 'onboarder_explain_folder',
|
|
421
|
+
description: 'Explain one folder: what lives in it, which folders it imports, its entry points and hubs, and a '
|
|
422
|
+
+ 'written summary of what the folder is for. Pass an empty string for the repository root.',
|
|
423
|
+
inputSchema: {
|
|
424
|
+
type: 'object',
|
|
425
|
+
properties: {
|
|
426
|
+
path: PATH_PROPERTY,
|
|
427
|
+
folder: { type: 'string', description: 'Repository-relative folder, for example server/mcp. Use "" for the root.' },
|
|
428
|
+
},
|
|
429
|
+
required: ['folder'],
|
|
430
|
+
},
|
|
431
|
+
async run(args) {
|
|
432
|
+
const a = await resolve(args);
|
|
433
|
+
const folder = String(args.folder ?? '').trim();
|
|
434
|
+
const paths = filesUnder(a, folder);
|
|
435
|
+
if (!paths.length && folder)
|
|
436
|
+
throw new ToolError(`No files under "${folder}".`);
|
|
437
|
+
const parsed = parsedFilesUnder(a, folder);
|
|
438
|
+
if (!parsed.length && folder) {
|
|
439
|
+
throw new ToolError(`Nothing under "${folder}" was parsed as source — use onboarder_list_files instead.`);
|
|
440
|
+
}
|
|
441
|
+
// Subfolders are the directories directly inside `folder`, not every
|
|
442
|
+
// directory beneath it: a tree two levels down is noise in a summary.
|
|
443
|
+
const depth = folder ? folder.split('/').length : 0;
|
|
444
|
+
const subfolders = [...new Set(paths
|
|
445
|
+
.map((p) => p.split('/').slice(0, -1).join('/'))
|
|
446
|
+
.filter((d) => d && d.split('/').length === depth + 1))];
|
|
447
|
+
return {
|
|
448
|
+
folder: folder || '.',
|
|
449
|
+
fileCount: paths.length,
|
|
450
|
+
parsedFileCount: parsed.length,
|
|
451
|
+
subfolders: subfolders.slice(0, 40),
|
|
452
|
+
files: parsed.map((f) => docFileRow(f.path, a.scan, a.facts)),
|
|
453
|
+
// Which folders this one imports, and which import it. Counts are import
|
|
454
|
+
// edges, so `server/mcp → shared/analyzer: 6` means six files in here
|
|
455
|
+
// reach in there — not six files in there.
|
|
456
|
+
...folderCrossings(a, folder),
|
|
457
|
+
documentation: folderStaticDoc(folder, a.scan, a.facts, subfolders.length),
|
|
458
|
+
summary: explainFolder(folder, a.scan, a.facts),
|
|
459
|
+
};
|
|
460
|
+
},
|
|
565
461
|
},
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
required: ['query'],
|
|
462
|
+
{
|
|
463
|
+
name: 'onboarder_read_file',
|
|
464
|
+
description: 'The text of one file inside the repository. Paths outside the repository are refused, and files over '
|
|
465
|
+
+ '200 KB are refused rather than truncated — use onboarder_search to find the part you need.',
|
|
466
|
+
inputSchema: {
|
|
467
|
+
type: 'object',
|
|
468
|
+
properties: {
|
|
469
|
+
path: PATH_PROPERTY,
|
|
470
|
+
file: { type: 'string', description: 'Repository-relative path.' },
|
|
471
|
+
},
|
|
472
|
+
required: ['file'],
|
|
473
|
+
},
|
|
474
|
+
async run(args) {
|
|
475
|
+
const a = await resolve(args);
|
|
476
|
+
return readFileText(a, args.file);
|
|
477
|
+
},
|
|
583
478
|
},
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
479
|
+
{
|
|
480
|
+
name: 'onboarder_list_files',
|
|
481
|
+
description: 'List the files in the repository, or in one folder. Filter by a substring of the path, by extension, '
|
|
482
|
+
+ 'and by whether the file is a test.',
|
|
483
|
+
inputSchema: {
|
|
484
|
+
type: 'object',
|
|
485
|
+
properties: {
|
|
486
|
+
path: PATH_PROPERTY,
|
|
487
|
+
folder: { type: 'string', description: 'Repository-relative folder. Use "" for the whole repository.' },
|
|
488
|
+
contains: { type: 'string', description: 'Keep only paths containing this text.' },
|
|
489
|
+
extension: { type: 'string', description: 'Keep only this extension, with or without the dot, e.g. "js".' },
|
|
490
|
+
tests: { type: 'boolean', description: 'true keeps only test files, false excludes them.' },
|
|
491
|
+
limit: LIMIT_PROPERTY,
|
|
492
|
+
},
|
|
493
|
+
required: [],
|
|
494
|
+
},
|
|
495
|
+
async run(args) {
|
|
496
|
+
const a = await resolve(args);
|
|
497
|
+
const limit = limitOf(args.limit, 200);
|
|
498
|
+
const folder = String(args.folder ?? '').trim();
|
|
499
|
+
const needle = String(args.contains || '').toLowerCase();
|
|
500
|
+
const ext = String(args.extension || '').trim().replace(/^\./, '').toLowerCase();
|
|
501
|
+
const isTest = (p) => /(^|\/)(tests?|__tests__)\//i.test(p) || /\.(test|spec)\./i.test(p);
|
|
502
|
+
// A folder listing spans subfolders and includes assets: "what is in here"
|
|
503
|
+
// is a question about the repository, not about what the parser understood.
|
|
504
|
+
let files = filesUnder(a, folder);
|
|
505
|
+
if (needle)
|
|
506
|
+
files = files.filter((p) => p.toLowerCase().includes(needle));
|
|
507
|
+
if (ext)
|
|
508
|
+
files = files.filter((p) => p.toLowerCase().endsWith('.' + ext));
|
|
509
|
+
if (args.tests === true)
|
|
510
|
+
files = files.filter(isTest);
|
|
511
|
+
if (args.tests === false)
|
|
512
|
+
files = files.filter((p) => !isTest(p));
|
|
513
|
+
return {
|
|
514
|
+
root: a.root,
|
|
515
|
+
folder: folder || '.',
|
|
516
|
+
total: files.length,
|
|
517
|
+
truncated: files.length > limit,
|
|
518
|
+
// `allFiles` rows are sometimes just a path string rather than a parsed
|
|
519
|
+
// file object, so both are mapped to their path and nothing else.
|
|
520
|
+
files: files.slice(0, limit).map((f) => (typeof f === 'string' ? f : f.path)),
|
|
521
|
+
};
|
|
522
|
+
},
|
|
601
523
|
},
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
items: { type: 'string', enum: TOOL_DEFS.map((d) => d.id) },
|
|
617
|
-
description: 'Which analyzers to run. Omit to run every installed one.',
|
|
524
|
+
{
|
|
525
|
+
name: 'onboarder_search',
|
|
526
|
+
description: "Search the repository the way the app's search palette does: bare words rank by relevance, and the "
|
|
527
|
+
+ 'full query language works too — path:, ext:, is:test, is:source, "exact phrases", -exclude, '
|
|
528
|
+
+ '/regex/, and case: for case sensitivity.',
|
|
529
|
+
inputSchema: {
|
|
530
|
+
type: 'object',
|
|
531
|
+
properties: {
|
|
532
|
+
path: PATH_PROPERTY,
|
|
533
|
+
query: { type: 'string', description: 'The search query.' },
|
|
534
|
+
limit: LIMIT_PROPERTY,
|
|
535
|
+
caseSensitive: { type: 'boolean', description: 'Match case exactly. Defaults to false.' },
|
|
536
|
+
},
|
|
537
|
+
required: ['query'],
|
|
618
538
|
},
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
539
|
+
async run(args) {
|
|
540
|
+
const a = await resolve(args);
|
|
541
|
+
const out = searchDocuments(a.searchIndex, String(args.query || ''), {
|
|
542
|
+
limit: limitOf(args.limit, 20),
|
|
543
|
+
caseSensitive: !!args.caseSensitive,
|
|
544
|
+
});
|
|
545
|
+
// A regex that would not compile comes back as `error` with zero results.
|
|
546
|
+
// Passing that through as-is would tell the agent the code is not there,
|
|
547
|
+
// which is the one conclusion it must not draw from a typo in its own
|
|
548
|
+
// query — so it is raised as a tool error the model can see and correct.
|
|
549
|
+
if (out.error)
|
|
550
|
+
throw new ToolError(out.error);
|
|
551
|
+
return {
|
|
552
|
+
query: out.query,
|
|
553
|
+
total: out.total,
|
|
554
|
+
indexed: out.indexed,
|
|
555
|
+
results: out.results.map((r) => ({ path: r.path, line: r.line, snippet: r.snippet })),
|
|
556
|
+
};
|
|
623
557
|
},
|
|
624
|
-
},
|
|
625
|
-
required: [],
|
|
626
558
|
},
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
559
|
+
{
|
|
560
|
+
name: 'onboarder_deep_analysis',
|
|
561
|
+
description: 'Run the optional external analyzers (semgrep, gitleaks, knip, and the rest) over the repository and '
|
|
562
|
+
+ 'return their findings normalized. Slower than the built-in tools, and it only covers the analyzers '
|
|
563
|
+
+ 'that are actually installed. Call onboarder_analyzer_status first to see which those are.',
|
|
564
|
+
inputSchema: {
|
|
565
|
+
type: 'object',
|
|
566
|
+
properties: {
|
|
567
|
+
path: PATH_PROPERTY,
|
|
568
|
+
tools: {
|
|
569
|
+
type: 'array',
|
|
570
|
+
items: { type: 'string', enum: TOOL_DEFS.map((d) => d.id) },
|
|
571
|
+
description: 'Which analyzers to run. Omit to run every installed one.',
|
|
572
|
+
},
|
|
573
|
+
kinds: {
|
|
574
|
+
type: 'array',
|
|
575
|
+
items: { type: 'string', enum: ['security', 'dead-code'] },
|
|
576
|
+
description: 'Narrow to a purpose.',
|
|
577
|
+
},
|
|
578
|
+
},
|
|
579
|
+
required: [],
|
|
580
|
+
},
|
|
581
|
+
async run(args) {
|
|
582
|
+
const a = await resolve(args);
|
|
583
|
+
return runExternalAnalysis(a.root, { tools: args.tools, kinds: args.kinds });
|
|
584
|
+
},
|
|
630
585
|
},
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
// disagree about whether semgrep is installed.
|
|
644
|
-
return { analyzers: Object.values(toolsStatus()) };
|
|
586
|
+
{
|
|
587
|
+
name: 'onboarder_analyzer_status',
|
|
588
|
+
description: 'Which external analyzers are installed on this machine, what each one is for, and how to install the '
|
|
589
|
+
+ 'ones that are missing. Takes no arguments.',
|
|
590
|
+
inputSchema: { type: 'object', properties: {}, required: [] },
|
|
591
|
+
async run() {
|
|
592
|
+
// `toolsStatus` is keyed by id — the shape the engines panel and the HTTP
|
|
593
|
+
// route both expect. It becomes a list here so an agent does not have to
|
|
594
|
+
// know that, but the same fields survive, so this and `/api/tools` cannot
|
|
595
|
+
// disagree about whether semgrep is installed.
|
|
596
|
+
return { analyzers: Object.values(toolsStatus()) };
|
|
597
|
+
},
|
|
645
598
|
},
|
|
646
|
-
},
|
|
647
599
|
];
|
|
648
|
-
|
|
649
600
|
const BY_NAME = new Map(TOOLS.map((t) => [t.name, t]));
|
|
650
|
-
|
|
651
601
|
// The MCP wire shape, which is not the same as the internal one: the handler
|
|
652
602
|
// stays off the table, and the schema is named `inputSchema` exactly as the spec
|
|
653
603
|
// requires.
|
|
654
604
|
export function toolDefinitions() {
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
605
|
+
return TOOLS.map((t) => ({
|
|
606
|
+
name: t.name,
|
|
607
|
+
description: t.description,
|
|
608
|
+
inputSchema: t.inputSchema,
|
|
609
|
+
}));
|
|
660
610
|
}
|
|
661
|
-
|
|
662
611
|
export function findTool(name) {
|
|
663
|
-
|
|
612
|
+
return BY_NAME.get(name) || null;
|
|
664
613
|
}
|
|
665
|
-
|
|
666
614
|
export async function callTool(name, args) {
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
615
|
+
const tool = findTool(name);
|
|
616
|
+
if (!tool)
|
|
617
|
+
throw new ToolError(`No tool called "${name}".`);
|
|
618
|
+
return await tool.run(args || {});
|
|
670
619
|
}
|
|
671
|
-
|
|
672
620
|
// What the top-bar button polls. The runner fills in `running`, `pid` and the
|
|
673
621
|
// rest; this is the part that is the same either way.
|
|
674
622
|
export function mcpStatus() {
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
623
|
+
return {
|
|
624
|
+
transport: 'stdio',
|
|
625
|
+
toolCount: TOOLS.length,
|
|
626
|
+
tools: TOOLS.map((t) => t.name),
|
|
627
|
+
cachedRepositories: cachedRoots(),
|
|
628
|
+
lastRoot,
|
|
629
|
+
analyzers: toolsStatus(),
|
|
630
|
+
};
|
|
683
631
|
}
|
|
632
|
+
//# sourceMappingURL=tools.js.map
|