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