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
|
@@ -1,272 +1,250 @@
|
|
|
1
1
|
// Pattern reading: turns the raw import graph into observations about the
|
|
2
2
|
// repo's shape — its depth layers, its load-bearing files, its smells.
|
|
3
3
|
// Everything here is pure functions over scan + facts.
|
|
4
|
-
|
|
5
4
|
// Assigns every reachable file a depth: the length of the longest import
|
|
6
5
|
// chain from an entry point. Files no entry can reach land in `unreachable`.
|
|
7
6
|
// Cycles are survived by capping how often a file may be relaxed.
|
|
8
7
|
export function computeLayers(scan, facts) {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
}
|
|
18
|
-
if (!seeds.length) {
|
|
19
|
-
// Last resort: the most depended-upon files.
|
|
20
|
-
seeds = facts.hubs.slice(0, 3).map((h) => h.path);
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
const depth = new Map();
|
|
24
|
-
const visits = new Map();
|
|
25
|
-
const queue = [];
|
|
26
|
-
for (const s of seeds) {
|
|
27
|
-
depth.set(s, 0);
|
|
28
|
-
queue.push(s);
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
while (queue.length) {
|
|
32
|
-
const cur = queue.shift();
|
|
33
|
-
const d = depth.get(cur);
|
|
34
|
-
visits.set(cur, (visits.get(cur) || 0) + 1);
|
|
35
|
-
if (visits.get(cur) > 4) continue; // circular chains give up here
|
|
36
|
-
const next = d + 1;
|
|
37
|
-
if (next > MAX_DEPTH) continue; // and nobody sinks past the basement
|
|
38
|
-
for (const nxt of facts.importsOf[cur] || []) {
|
|
39
|
-
if ((depth.get(nxt) ?? -1) < next) {
|
|
40
|
-
depth.set(nxt, next);
|
|
41
|
-
queue.push(nxt);
|
|
42
|
-
}
|
|
8
|
+
let seeds = facts.entries.slice();
|
|
9
|
+
if (!seeds.length) {
|
|
10
|
+
// No recognized entry points — start from the tops of the import chains:
|
|
11
|
+
// files that pull others in but are pulled in by nothing.
|
|
12
|
+
seeds = scan.files
|
|
13
|
+
.filter((f) => (facts.fanOut[f.path] || 0) > 0 && (facts.fanIn[f.path] || 0) === 0)
|
|
14
|
+
.map((f) => f.path)
|
|
15
|
+
.slice(0, 5);
|
|
43
16
|
}
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
17
|
+
if (!seeds.length) {
|
|
18
|
+
// Last resort: the most depended-upon files.
|
|
19
|
+
seeds = facts.hubs.slice(0, 3).map((h) => h.path);
|
|
20
|
+
}
|
|
21
|
+
const depth = new Map();
|
|
22
|
+
const visits = new Map();
|
|
23
|
+
const queue = [];
|
|
24
|
+
for (const s of seeds) {
|
|
25
|
+
depth.set(s, 0);
|
|
26
|
+
queue.push(s);
|
|
27
|
+
}
|
|
28
|
+
while (queue.length) {
|
|
29
|
+
const cur = queue.shift();
|
|
30
|
+
const d = depth.get(cur);
|
|
31
|
+
visits.set(cur, (visits.get(cur) || 0) + 1);
|
|
32
|
+
if (visits.get(cur) > 4)
|
|
33
|
+
continue; // circular chains give up here
|
|
34
|
+
const next = d + 1;
|
|
35
|
+
if (next > MAX_DEPTH)
|
|
36
|
+
continue; // and nobody sinks past the basement
|
|
37
|
+
for (const nxt of facts.importsOf[cur] || []) {
|
|
38
|
+
if ((depth.get(nxt) ?? -1) < next) {
|
|
39
|
+
depth.set(nxt, next);
|
|
40
|
+
queue.push(nxt);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
// Depths can skip values (cycle members sink several floors at once), and
|
|
45
|
+
// for..of over a sparse array hands holes to sort() — compact first.
|
|
46
|
+
const sparse = [];
|
|
47
|
+
for (const [path, d] of depth) {
|
|
48
|
+
(sparse[d] ||= []).push(path);
|
|
49
|
+
}
|
|
50
|
+
const layers = sparse.filter(Boolean);
|
|
51
|
+
for (const l of layers)
|
|
52
|
+
l.sort((a, b) => a.localeCompare(b));
|
|
53
|
+
const unreachable = scan.files
|
|
54
|
+
.map((f) => f.path)
|
|
55
|
+
.filter((p) => !depth.has(p))
|
|
56
|
+
.sort();
|
|
57
|
+
return { layers, unreachable, seeds };
|
|
61
58
|
}
|
|
62
|
-
|
|
63
59
|
const MAX_DEPTH = 12;
|
|
64
|
-
|
|
65
60
|
// Written observations, in the order a senior dev would mention them.
|
|
66
61
|
// Each: { id, tone: 'good' | 'note' | 'warn', severity: 'info' | 'low' | 'medium' | 'high', paths: string[], title, detail }.
|
|
67
62
|
export function detectPatterns(scan, facts, manifest, layersInfo) {
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
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
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
63
|
+
const out = [];
|
|
64
|
+
const topHub = facts.hubs[0];
|
|
65
|
+
if (topHub && topHub.fanIn >= 5) {
|
|
66
|
+
out.push({
|
|
67
|
+
id: 'hub-and-spoke',
|
|
68
|
+
tone: 'note',
|
|
69
|
+
severity: 'info',
|
|
70
|
+
paths: [topHub.path],
|
|
71
|
+
title: 'Hub and spoke',
|
|
72
|
+
detail: `\`${topHub.path}\` holds ${topHub.fanIn} dependents — this repo orbits a core. Learn that file and most of the rest makes sense.`,
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
const god = scan.files
|
|
76
|
+
.filter((f) => (facts.fanIn[f.path] || 0) + (facts.fanOut[f.path] || 0) >= 12)
|
|
77
|
+
.sort((a, b) => ((facts.fanIn[b.path] || 0) + (facts.fanOut[b.path] || 0)) - ((facts.fanIn[a.path] || 0) + (facts.fanOut[a.path] || 0)));
|
|
78
|
+
if (god.length) {
|
|
79
|
+
out.push({
|
|
80
|
+
id: 'god-files',
|
|
81
|
+
tone: 'warn',
|
|
82
|
+
severity: 'medium',
|
|
83
|
+
paths: god.map((f) => f.path),
|
|
84
|
+
title: god.length > 1 ? 'God files' : 'God file',
|
|
85
|
+
detail: `${god.slice(0, 3).map((f) => `\`${f.path}\``).join(', ')} — heavily connected in both directions. Everyone's problem, nobody's owner. Touch gently.`,
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
if (layersInfo && layersInfo.layers.length >= 3) {
|
|
89
|
+
out.push({
|
|
90
|
+
id: 'layered-shape',
|
|
91
|
+
tone: 'good',
|
|
92
|
+
severity: 'info',
|
|
93
|
+
paths: [],
|
|
94
|
+
title: 'A layered shape',
|
|
95
|
+
detail: `The code reads in ${layersInfo.layers.length} strata from the door to the deepest leaf. That is an architecture someone can draw — always a good sign.`,
|
|
96
|
+
});
|
|
97
|
+
if (layersInfo.unreachable.length) {
|
|
98
|
+
out.push({
|
|
99
|
+
id: 'unreachable',
|
|
100
|
+
tone: 'note',
|
|
101
|
+
severity: 'info',
|
|
102
|
+
paths: layersInfo.unreachable,
|
|
103
|
+
title: 'Off the beaten path',
|
|
104
|
+
detail: `${layersInfo.unreachable.length} files can't be reached from any entry point — utilities awaiting adoption, or furniture from an older layout.`,
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
if (facts.cycles.length) {
|
|
109
|
+
out.push({
|
|
110
|
+
id: 'cycles',
|
|
111
|
+
tone: 'warn',
|
|
112
|
+
severity: 'high',
|
|
113
|
+
paths: Array.from(new Set(facts.cycles.flat())),
|
|
114
|
+
title: 'Circular dependencies',
|
|
115
|
+
detail: `${facts.cycles.length} ${facts.cycles.length === 1 ? 'loop' : 'loops'} where files import each other; the biggest tangles ${facts.cycles[0].length} files. Untangle before a big refactor.`,
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
const barrels = scan.files.filter((f) => /^index\.[jt]sx?$/.test(f.name) &&
|
|
119
|
+
f.functions.length === 0 &&
|
|
120
|
+
(facts.fanIn[f.path] || 0) >= 2 &&
|
|
121
|
+
(facts.fanOut[f.path] || 0) >= 2);
|
|
122
|
+
if (barrels.length) {
|
|
123
|
+
out.push({
|
|
124
|
+
id: 'barrels',
|
|
125
|
+
tone: 'note',
|
|
126
|
+
severity: 'info',
|
|
127
|
+
paths: barrels.map((f) => f.path),
|
|
128
|
+
title: 'Barrel files',
|
|
129
|
+
detail: `${barrels.slice(0, 3).map((f) => `\`${f.path}\``).join(', ')} mostly re-export other modules. Convenient for imports, one extra hop when you're hunting a definition.`,
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
const testCount = scan.files.filter((f) => /(^|[._-])(test|spec|tests|__tests__)([._-]|\/|$)/i.test(f.path)).length;
|
|
133
|
+
if (testCount > 0) {
|
|
134
|
+
const pct = Math.round((testCount / Math.max(1, scan.stats.filesParsed)) * 100);
|
|
135
|
+
out.push({
|
|
136
|
+
id: 'test-shadow',
|
|
137
|
+
tone: 'good',
|
|
138
|
+
severity: 'info',
|
|
139
|
+
paths: [],
|
|
140
|
+
title: 'A test shadow',
|
|
141
|
+
detail: `${testCount} test ${testCount === 1 ? 'file' : 'files'} against ${scan.stats.filesParsed} code files (~${pct}%). Read tests to learn intended behavior, code to learn actual behavior.`,
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
else {
|
|
145
|
+
out.push({
|
|
146
|
+
id: 'no-tests',
|
|
147
|
+
tone: 'warn',
|
|
148
|
+
severity: 'medium',
|
|
149
|
+
paths: [],
|
|
150
|
+
title: 'No tests in sight',
|
|
151
|
+
detail: 'Nothing matches the usual test/spec naming. Onboarding risk: intended behavior lives only inside the code.',
|
|
152
|
+
});
|
|
115
153
|
}
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
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
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
});
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
if (facts.depsDrift?.undeclaredImported?.length) {
|
|
193
|
-
out.push({
|
|
194
|
-
id: 'undeclared-deps',
|
|
195
|
-
tone: 'warn',
|
|
196
|
-
severity: 'high',
|
|
197
|
-
paths: [],
|
|
198
|
-
title: 'Undeclared dependencies',
|
|
199
|
-
detail: `Imported packages not listed in package manifest: ${facts.depsDrift.undeclaredImported.slice(0, 4).map((d) => `\`${d}\``).join(', ')}. Risk of build failure in CI/clean environments.`,
|
|
200
|
-
});
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
if (facts.depsDrift?.unusedDeclared?.length) {
|
|
204
|
-
out.push({
|
|
205
|
-
id: 'unused-deps',
|
|
206
|
-
tone: 'note',
|
|
207
|
-
severity: 'low',
|
|
208
|
-
paths: [],
|
|
209
|
-
title: 'Unused dependencies',
|
|
210
|
-
detail: `Declared dependencies not directly imported in source: ${facts.depsDrift.unusedDeclared.slice(0, 5).map((d) => `\`${d}\``).join(', ')}.`,
|
|
211
|
-
});
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
if (facts.orphans.length) {
|
|
215
|
-
out.push({
|
|
216
|
-
id: 'orphans',
|
|
217
|
-
tone: 'note',
|
|
218
|
-
severity: 'info',
|
|
219
|
-
paths: facts.orphans,
|
|
220
|
-
title: 'Unclaimed territory',
|
|
221
|
-
detail: `${facts.orphans.length} ${facts.orphans.length === 1 ? 'file is' : 'files are'} imported by nothing. Dead code, or doors the naming rules didn't recognize.`,
|
|
222
|
-
});
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
const topExt = scan.externals[0];
|
|
226
|
-
if (topExt && topExt.usedBy.length >= 3) {
|
|
227
|
-
out.push({
|
|
228
|
-
id: 'top-external',
|
|
229
|
-
tone: 'note',
|
|
230
|
-
severity: 'info',
|
|
231
|
-
paths: topExt.usedBy,
|
|
232
|
-
title: 'Heaviest outside anchor',
|
|
233
|
-
detail: `\`${topExt.name}\` is pulled in by ${topExt.usedBy.length} files — the external dependency this repo would miss most.`,
|
|
234
|
-
});
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
if (manifest?.services?.length) {
|
|
238
|
-
out.push({
|
|
239
|
-
id: 'services',
|
|
240
|
-
tone: 'note',
|
|
241
|
-
severity: 'info',
|
|
242
|
-
paths: [],
|
|
243
|
-
title: 'Runs as services',
|
|
244
|
-
detail: `${manifest.services.length} ${manifest.services.length === 1 ? 'service' : 'services'} declared: ${manifest.services.map((s) => s.name).join(', ')}.`,
|
|
245
|
-
});
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
return out;
|
|
154
|
+
if (facts.untestedHubs?.length) {
|
|
155
|
+
out.push({
|
|
156
|
+
id: 'untested-hubs',
|
|
157
|
+
tone: 'warn',
|
|
158
|
+
severity: 'high',
|
|
159
|
+
paths: facts.untestedHubs.map((h) => h.path),
|
|
160
|
+
title: 'Untested hubs',
|
|
161
|
+
detail: `${facts.untestedHubs.length} load-bearing ${facts.untestedHubs.length === 1 ? 'file is' : 'files are'} not reachable through test imports. Check whether tests exercise these files before changing them.`,
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
if (facts.deadExports?.length) {
|
|
165
|
+
const deadFiles = Array.from(new Set(facts.deadExports.map((d) => d.file)));
|
|
166
|
+
out.push({
|
|
167
|
+
id: 'dead-exports',
|
|
168
|
+
tone: 'note',
|
|
169
|
+
severity: 'low',
|
|
170
|
+
paths: deadFiles,
|
|
171
|
+
title: 'Unused exports',
|
|
172
|
+
detail: `${facts.deadExports.length} exported ${facts.deadExports.length === 1 ? 'symbol is' : 'symbols are'} never imported anywhere in the project. Potential dead code or unexposed public API.`,
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
if (facts.depsDrift?.undeclaredImported?.length) {
|
|
176
|
+
out.push({
|
|
177
|
+
id: 'undeclared-deps',
|
|
178
|
+
tone: 'warn',
|
|
179
|
+
severity: 'high',
|
|
180
|
+
paths: [],
|
|
181
|
+
title: 'Undeclared dependencies',
|
|
182
|
+
detail: `Imported packages not listed in package manifest: ${facts.depsDrift.undeclaredImported.slice(0, 4).map((d) => `\`${d}\``).join(', ')}. Risk of build failure in CI/clean environments.`,
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
if (facts.depsDrift?.unusedDeclared?.length) {
|
|
186
|
+
out.push({
|
|
187
|
+
id: 'unused-deps',
|
|
188
|
+
tone: 'note',
|
|
189
|
+
severity: 'low',
|
|
190
|
+
paths: [],
|
|
191
|
+
title: 'Unused dependencies',
|
|
192
|
+
detail: `Declared dependencies not directly imported in source: ${facts.depsDrift.unusedDeclared.slice(0, 5).map((d) => `\`${d}\``).join(', ')}.`,
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
if (facts.orphans.length) {
|
|
196
|
+
out.push({
|
|
197
|
+
id: 'orphans',
|
|
198
|
+
tone: 'note',
|
|
199
|
+
severity: 'info',
|
|
200
|
+
paths: facts.orphans,
|
|
201
|
+
title: 'Unclaimed territory',
|
|
202
|
+
detail: `${facts.orphans.length} ${facts.orphans.length === 1 ? 'file is' : 'files are'} imported by nothing. Dead code, or doors the naming rules didn't recognize.`,
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
const topExt = scan.externals[0];
|
|
206
|
+
if (topExt && topExt.usedBy.length >= 3) {
|
|
207
|
+
out.push({
|
|
208
|
+
id: 'top-external',
|
|
209
|
+
tone: 'note',
|
|
210
|
+
severity: 'info',
|
|
211
|
+
paths: topExt.usedBy,
|
|
212
|
+
title: 'Heaviest outside anchor',
|
|
213
|
+
detail: `\`${topExt.name}\` is pulled in by ${topExt.usedBy.length} files — the external dependency this repo would miss most.`,
|
|
214
|
+
});
|
|
215
|
+
}
|
|
216
|
+
if (manifest?.services?.length) {
|
|
217
|
+
out.push({
|
|
218
|
+
id: 'services',
|
|
219
|
+
tone: 'note',
|
|
220
|
+
severity: 'info',
|
|
221
|
+
paths: [],
|
|
222
|
+
title: 'Runs as services',
|
|
223
|
+
detail: `${manifest.services.length} ${manifest.services.length === 1 ? 'service' : 'services'} declared: ${manifest.services.map((s) => s.name).join(', ')}.`,
|
|
224
|
+
});
|
|
225
|
+
}
|
|
226
|
+
return out;
|
|
249
227
|
}
|
|
250
|
-
|
|
251
228
|
// Folder-to-folder traffic as a matrix for the heat grid. Folders are the
|
|
252
229
|
// top-level ones, ordered by how much traffic they see in total.
|
|
253
230
|
export function couplingMatrix(scan, facts, maxFolders = 8) {
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
231
|
+
const traffic = new Map();
|
|
232
|
+
for (const fe of facts.folderEdges) {
|
|
233
|
+
traffic.set(fe.from, (traffic.get(fe.from) || 0) + fe.count);
|
|
234
|
+
traffic.set(fe.to, (traffic.get(fe.to) || 0) + fe.count);
|
|
235
|
+
}
|
|
236
|
+
const folders = [...traffic.entries()]
|
|
237
|
+
.sort((a, b) => b[1] - a[1])
|
|
238
|
+
.slice(0, maxFolders)
|
|
239
|
+
.map(([name]) => name);
|
|
240
|
+
const counts = new Map();
|
|
241
|
+
let max = 1;
|
|
242
|
+
for (const fe of facts.folderEdges) {
|
|
243
|
+
if (!folders.includes(fe.from) || !folders.includes(fe.to))
|
|
244
|
+
continue;
|
|
245
|
+
counts.set(fe.from + '->' + fe.to, fe.count);
|
|
246
|
+
max = Math.max(max, fe.count);
|
|
247
|
+
}
|
|
248
|
+
return { folders, counts, max };
|
|
272
249
|
}
|
|
250
|
+
//# sourceMappingURL=patterns.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"patterns.js","sourceRoot":"","sources":["../../src/shared/analyzer/patterns.ts"],"names":[],"mappings":"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","sourcesContent":["// Pattern reading: turns the raw import graph into observations about the\n// repo's shape — its depth layers, its load-bearing files, its smells.\n// Everything here is pure functions over scan + facts.\n\n// Assigns every reachable file a depth: the length of the longest import\n// chain from an entry point. Files no entry can reach land in `unreachable`.\n// Cycles are survived by capping how often a file may be relaxed.\nexport function computeLayers(scan, facts) {\n let seeds = facts.entries.slice();\n if (!seeds.length) {\n // No recognized entry points — start from the tops of the import chains:\n // files that pull others in but are pulled in by nothing.\n seeds = scan.files\n .filter((f) => (facts.fanOut[f.path] || 0) > 0 && (facts.fanIn[f.path] || 0) === 0)\n .map((f) => f.path)\n .slice(0, 5);\n }\n if (!seeds.length) {\n // Last resort: the most depended-upon files.\n seeds = facts.hubs.slice(0, 3).map((h) => h.path);\n }\n\n const depth = new Map();\n const visits = new Map();\n const queue = [];\n for (const s of seeds) {\n depth.set(s, 0);\n queue.push(s);\n }\n\n while (queue.length) {\n const cur = queue.shift();\n const d = depth.get(cur);\n visits.set(cur, (visits.get(cur) || 0) + 1);\n if (visits.get(cur) > 4) continue; // circular chains give up here\n const next = d + 1;\n if (next > MAX_DEPTH) continue; // and nobody sinks past the basement\n for (const nxt of facts.importsOf[cur] || []) {\n if ((depth.get(nxt) ?? -1) < next) {\n depth.set(nxt, next);\n queue.push(nxt);\n }\n }\n }\n\n // Depths can skip values (cycle members sink several floors at once), and\n // for..of over a sparse array hands holes to sort() — compact first.\n const sparse = [];\n for (const [path, d] of depth) {\n (sparse[d] ||= []).push(path);\n }\n const layers = sparse.filter(Boolean);\n for (const l of layers) l.sort((a, b) => a.localeCompare(b));\n\n const unreachable = scan.files\n .map((f) => f.path)\n .filter((p) => !depth.has(p))\n .sort();\n\n return { layers, unreachable, seeds };\n}\n\nconst MAX_DEPTH = 12;\n\n// Written observations, in the order a senior dev would mention them.\n// Each: { id, tone: 'good' | 'note' | 'warn', severity: 'info' | 'low' | 'medium' | 'high', paths: string[], title, detail }.\nexport function detectPatterns(scan, facts, manifest, layersInfo) {\n const out = [];\n const topHub = facts.hubs[0];\n\n if (topHub && topHub.fanIn >= 5) {\n out.push({\n id: 'hub-and-spoke',\n tone: 'note',\n severity: 'info',\n paths: [topHub.path],\n title: 'Hub and spoke',\n detail: `\\`${topHub.path}\\` holds ${topHub.fanIn} dependents — this repo orbits a core. Learn that file and most of the rest makes sense.`,\n });\n }\n\n const god = scan.files\n .filter((f) => (facts.fanIn[f.path] || 0) + (facts.fanOut[f.path] || 0) >= 12)\n .sort((a, b) => ((facts.fanIn[b.path] || 0) + (facts.fanOut[b.path] || 0)) - ((facts.fanIn[a.path] || 0) + (facts.fanOut[a.path] || 0)));\n\n if (god.length) {\n out.push({\n id: 'god-files',\n tone: 'warn',\n severity: 'medium',\n paths: god.map((f) => f.path),\n title: god.length > 1 ? 'God files' : 'God file',\n detail: `${god.slice(0, 3).map((f) => `\\`${f.path}\\``).join(', ')} — heavily connected in both directions. Everyone's problem, nobody's owner. Touch gently.`,\n });\n }\n\n if (layersInfo && layersInfo.layers.length >= 3) {\n out.push({\n id: 'layered-shape',\n tone: 'good',\n severity: 'info',\n paths: [],\n title: 'A layered shape',\n detail: `The code reads in ${layersInfo.layers.length} strata from the door to the deepest leaf. That is an architecture someone can draw — always a good sign.`,\n });\n if (layersInfo.unreachable.length) {\n out.push({\n id: 'unreachable',\n tone: 'note',\n severity: 'info',\n paths: layersInfo.unreachable,\n title: 'Off the beaten path',\n detail: `${layersInfo.unreachable.length} files can't be reached from any entry point — utilities awaiting adoption, or furniture from an older layout.`,\n });\n }\n }\n\n if (facts.cycles.length) {\n out.push({\n id: 'cycles',\n tone: 'warn',\n severity: 'high',\n paths: Array.from(new Set(facts.cycles.flat())),\n title: 'Circular dependencies',\n detail: `${facts.cycles.length} ${facts.cycles.length === 1 ? 'loop' : 'loops'} where files import each other; the biggest tangles ${facts.cycles[0].length} files. Untangle before a big refactor.`,\n });\n }\n\n const barrels = scan.files.filter(\n (f) =>\n /^index\\.[jt]sx?$/.test(f.name) &&\n f.functions.length === 0 &&\n (facts.fanIn[f.path] || 0) >= 2 &&\n (facts.fanOut[f.path] || 0) >= 2\n );\n if (barrels.length) {\n out.push({\n id: 'barrels',\n tone: 'note',\n severity: 'info',\n paths: barrels.map((f) => f.path),\n title: 'Barrel files',\n detail: `${barrels.slice(0, 3).map((f) => `\\`${f.path}\\``).join(', ')} mostly re-export other modules. Convenient for imports, one extra hop when you're hunting a definition.`,\n });\n }\n\n const testCount = scan.files.filter((f) => /(^|[._-])(test|spec|tests|__tests__)([._-]|\\/|$)/i.test(f.path)).length;\n if (testCount > 0) {\n const pct = Math.round((testCount / Math.max(1, scan.stats.filesParsed)) * 100);\n out.push({\n id: 'test-shadow',\n tone: 'good',\n severity: 'info',\n paths: [],\n title: 'A test shadow',\n detail: `${testCount} test ${testCount === 1 ? 'file' : 'files'} against ${scan.stats.filesParsed} code files (~${pct}%). Read tests to learn intended behavior, code to learn actual behavior.`,\n });\n } else {\n out.push({\n id: 'no-tests',\n tone: 'warn',\n severity: 'medium',\n paths: [],\n title: 'No tests in sight',\n detail: 'Nothing matches the usual test/spec naming. Onboarding risk: intended behavior lives only inside the code.',\n });\n }\n\n if (facts.untestedHubs?.length) {\n out.push({\n id: 'untested-hubs',\n tone: 'warn',\n severity: 'high',\n paths: facts.untestedHubs.map((h) => h.path),\n title: 'Untested hubs',\n detail: `${facts.untestedHubs.length} load-bearing ${facts.untestedHubs.length === 1 ? 'file is' : 'files are'} not reachable through test imports. Check whether tests exercise these files before changing them.`,\n });\n }\n\n if (facts.deadExports?.length) {\n const deadFiles = Array.from(new Set(facts.deadExports.map((d) => d.file)));\n out.push({\n id: 'dead-exports',\n tone: 'note',\n severity: 'low',\n paths: deadFiles,\n title: 'Unused exports',\n detail: `${facts.deadExports.length} exported ${facts.deadExports.length === 1 ? 'symbol is' : 'symbols are'} never imported anywhere in the project. Potential dead code or unexposed public API.`,\n });\n }\n\n if (facts.depsDrift?.undeclaredImported?.length) {\n out.push({\n id: 'undeclared-deps',\n tone: 'warn',\n severity: 'high',\n paths: [],\n title: 'Undeclared dependencies',\n detail: `Imported packages not listed in package manifest: ${facts.depsDrift.undeclaredImported.slice(0, 4).map((d) => `\\`${d}\\``).join(', ')}. Risk of build failure in CI/clean environments.`,\n });\n }\n\n if (facts.depsDrift?.unusedDeclared?.length) {\n out.push({\n id: 'unused-deps',\n tone: 'note',\n severity: 'low',\n paths: [],\n title: 'Unused dependencies',\n detail: `Declared dependencies not directly imported in source: ${facts.depsDrift.unusedDeclared.slice(0, 5).map((d) => `\\`${d}\\``).join(', ')}.`,\n });\n }\n\n if (facts.orphans.length) {\n out.push({\n id: 'orphans',\n tone: 'note',\n severity: 'info',\n paths: facts.orphans,\n title: 'Unclaimed territory',\n detail: `${facts.orphans.length} ${facts.orphans.length === 1 ? 'file is' : 'files are'} imported by nothing. Dead code, or doors the naming rules didn't recognize.`,\n });\n }\n\n const topExt = scan.externals[0];\n if (topExt && topExt.usedBy.length >= 3) {\n out.push({\n id: 'top-external',\n tone: 'note',\n severity: 'info',\n paths: topExt.usedBy,\n title: 'Heaviest outside anchor',\n detail: `\\`${topExt.name}\\` is pulled in by ${topExt.usedBy.length} files — the external dependency this repo would miss most.`,\n });\n }\n\n if (manifest?.services?.length) {\n out.push({\n id: 'services',\n tone: 'note',\n severity: 'info',\n paths: [],\n title: 'Runs as services',\n detail: `${manifest.services.length} ${manifest.services.length === 1 ? 'service' : 'services'} declared: ${manifest.services.map((s) => s.name).join(', ')}.`,\n });\n }\n\n return out;\n}\n\n// Folder-to-folder traffic as a matrix for the heat grid. Folders are the\n// top-level ones, ordered by how much traffic they see in total.\nexport function couplingMatrix(scan, facts, maxFolders = 8) {\n const traffic = new Map();\n for (const fe of facts.folderEdges) {\n traffic.set(fe.from, (traffic.get(fe.from) || 0) + fe.count);\n traffic.set(fe.to, (traffic.get(fe.to) || 0) + fe.count);\n }\n const folders = [...traffic.entries()]\n .sort((a, b) => b[1] - a[1])\n .slice(0, maxFolders)\n .map(([name]) => name);\n\n const counts = new Map();\n let max = 1;\n for (const fe of facts.folderEdges) {\n if (!folders.includes(fe.from) || !folders.includes(fe.to)) continue;\n counts.set(fe.from + '->' + fe.to, fe.count);\n max = Math.max(max, fe.count);\n }\n return { folders, counts, max };\n}\n"]}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import type { FileSource, ScanOptions, ImportEdge, ScannedFile } from '../contracts.js';
|
|
2
|
+
export { DEFAULT_LIMITS } from './scanControl.js';
|
|
3
|
+
export declare function scanRepo(source: FileSource, options?: ScanOptions): Promise<{
|
|
4
|
+
root: string;
|
|
5
|
+
name: any;
|
|
6
|
+
scannedAt: string;
|
|
7
|
+
stats: {
|
|
8
|
+
filesTotal: number;
|
|
9
|
+
filesParsed: number;
|
|
10
|
+
skipped: number;
|
|
11
|
+
skips: {
|
|
12
|
+
ignored: number;
|
|
13
|
+
vendorDir: number;
|
|
14
|
+
notSource: number;
|
|
15
|
+
listFailed: number;
|
|
16
|
+
notCode: number;
|
|
17
|
+
readFailed: number;
|
|
18
|
+
tooLarge: number;
|
|
19
|
+
analyzeFailed: number;
|
|
20
|
+
};
|
|
21
|
+
truncated: any;
|
|
22
|
+
edgeCount: number;
|
|
23
|
+
imports: {
|
|
24
|
+
total: number;
|
|
25
|
+
internal: number;
|
|
26
|
+
external: number;
|
|
27
|
+
unresolved: number;
|
|
28
|
+
confidence: number;
|
|
29
|
+
worst: any[];
|
|
30
|
+
};
|
|
31
|
+
tookMs: number;
|
|
32
|
+
languages: Record<string, number>;
|
|
33
|
+
languageLoc: Record<string, number>;
|
|
34
|
+
};
|
|
35
|
+
allFiles: string[];
|
|
36
|
+
files: ScannedFile[];
|
|
37
|
+
edges: ImportEdge[];
|
|
38
|
+
externals: {
|
|
39
|
+
name: any;
|
|
40
|
+
usedBy: any[];
|
|
41
|
+
}[];
|
|
42
|
+
folders: {
|
|
43
|
+
path: string;
|
|
44
|
+
name: string;
|
|
45
|
+
depth: number;
|
|
46
|
+
fileCount: number;
|
|
47
|
+
loc: number;
|
|
48
|
+
comment: number;
|
|
49
|
+
blank: number;
|
|
50
|
+
size: number;
|
|
51
|
+
langs: Record<string, number>;
|
|
52
|
+
hasReadme: boolean;
|
|
53
|
+
docRatio: number;
|
|
54
|
+
}[];
|
|
55
|
+
workflows: any[];
|
|
56
|
+
sbom: any;
|
|
57
|
+
license: any;
|
|
58
|
+
licenseReport: any;
|
|
59
|
+
} & {
|
|
60
|
+
tempId?: string;
|
|
61
|
+
gitUrl?: string;
|
|
62
|
+
}>;
|