@optave/codegraph 3.1.3 → 3.1.5
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 +38 -84
- package/package.json +13 -8
- package/src/ast-analysis/engine.js +32 -12
- package/src/ast-analysis/shared.js +6 -5
- package/src/cli/commands/ast.js +22 -0
- package/src/cli/commands/audit.js +45 -0
- package/src/cli/commands/batch.js +68 -0
- package/src/cli/commands/branch-compare.js +21 -0
- package/src/cli/commands/build.js +26 -0
- package/src/cli/commands/cfg.js +26 -0
- package/src/cli/commands/check.js +74 -0
- package/src/cli/commands/children.js +28 -0
- package/src/cli/commands/co-change.js +67 -0
- package/src/cli/commands/communities.js +19 -0
- package/src/cli/commands/complexity.js +46 -0
- package/src/cli/commands/context.js +30 -0
- package/src/cli/commands/cycles.js +32 -0
- package/src/cli/commands/dataflow.js +28 -0
- package/src/cli/commands/deps.js +12 -0
- package/src/cli/commands/diff-impact.js +26 -0
- package/src/cli/commands/embed.js +30 -0
- package/src/cli/commands/export.js +78 -0
- package/src/cli/commands/exports.js +14 -0
- package/src/cli/commands/flow.js +32 -0
- package/src/cli/commands/fn-impact.js +26 -0
- package/src/cli/commands/impact.js +12 -0
- package/src/cli/commands/info.js +76 -0
- package/src/cli/commands/map.js +19 -0
- package/src/cli/commands/mcp.js +18 -0
- package/src/cli/commands/models.js +19 -0
- package/src/cli/commands/owners.js +25 -0
- package/src/cli/commands/path.js +36 -0
- package/src/cli/commands/plot.js +89 -0
- package/src/cli/commands/query.js +45 -0
- package/src/cli/commands/registry.js +100 -0
- package/src/cli/commands/roles.js +30 -0
- package/src/cli/commands/search.js +42 -0
- package/src/cli/commands/sequence.js +28 -0
- package/src/cli/commands/snapshot.js +66 -0
- package/src/cli/commands/stats.js +15 -0
- package/src/cli/commands/structure.js +33 -0
- package/src/cli/commands/triage.js +78 -0
- package/src/cli/commands/watch.js +12 -0
- package/src/cli/commands/where.js +20 -0
- package/src/cli/index.js +124 -0
- package/src/cli/shared/open-graph.js +13 -0
- package/src/cli/shared/options.js +59 -0
- package/src/cli/shared/output.js +1 -0
- package/src/cli.js +11 -1522
- package/src/db/connection.js +130 -7
- package/src/{db.js → db/index.js} +17 -5
- package/src/db/migrations.js +42 -1
- package/src/db/query-builder.js +20 -12
- package/src/db/repository/base.js +201 -0
- package/src/db/repository/graph-read.js +7 -4
- package/src/db/repository/in-memory-repository.js +575 -0
- package/src/db/repository/index.js +5 -1
- package/src/db/repository/nodes.js +60 -6
- package/src/db/repository/sqlite-repository.js +219 -0
- package/src/domain/analysis/context.js +408 -0
- package/src/domain/analysis/dependencies.js +341 -0
- package/src/domain/analysis/exports.js +134 -0
- package/src/domain/analysis/impact.js +466 -0
- package/src/domain/analysis/module-map.js +322 -0
- package/src/domain/analysis/roles.js +45 -0
- package/src/domain/analysis/symbol-lookup.js +238 -0
- package/src/domain/graph/builder/context.js +85 -0
- package/src/domain/graph/builder/helpers.js +218 -0
- package/src/domain/graph/builder/incremental.js +178 -0
- package/src/domain/graph/builder/pipeline.js +130 -0
- package/src/domain/graph/builder/stages/build-edges.js +297 -0
- package/src/domain/graph/builder/stages/build-structure.js +113 -0
- package/src/domain/graph/builder/stages/collect-files.js +44 -0
- package/src/domain/graph/builder/stages/detect-changes.js +413 -0
- package/src/domain/graph/builder/stages/finalize.js +139 -0
- package/src/domain/graph/builder/stages/insert-nodes.js +195 -0
- package/src/domain/graph/builder/stages/parse-files.js +28 -0
- package/src/domain/graph/builder/stages/resolve-imports.js +143 -0
- package/src/domain/graph/builder/stages/run-analyses.js +44 -0
- package/src/domain/graph/builder.js +11 -0
- package/src/{change-journal.js → domain/graph/change-journal.js} +1 -1
- package/src/domain/graph/cycles.js +82 -0
- package/src/{journal.js → domain/graph/journal.js} +1 -1
- package/src/{resolve.js → domain/graph/resolve.js} +3 -3
- package/src/{watcher.js → domain/graph/watcher.js} +10 -150
- package/src/{parser.js → domain/parser.js} +5 -5
- package/src/domain/queries.js +48 -0
- package/src/domain/search/generator.js +163 -0
- package/src/domain/search/index.js +13 -0
- package/src/domain/search/models.js +218 -0
- package/src/domain/search/search/cli-formatter.js +151 -0
- package/src/domain/search/search/filters.js +46 -0
- package/src/domain/search/search/hybrid.js +121 -0
- package/src/domain/search/search/keyword.js +68 -0
- package/src/domain/search/search/prepare.js +66 -0
- package/src/domain/search/search/semantic.js +145 -0
- package/src/domain/search/stores/fts5.js +27 -0
- package/src/domain/search/stores/sqlite-blob.js +24 -0
- package/src/domain/search/strategies/source.js +14 -0
- package/src/domain/search/strategies/structured.js +43 -0
- package/src/domain/search/strategies/text-utils.js +43 -0
- package/src/extractors/csharp.js +10 -2
- package/src/extractors/go.js +3 -1
- package/src/extractors/helpers.js +71 -0
- package/src/extractors/java.js +9 -2
- package/src/extractors/javascript.js +39 -2
- package/src/extractors/php.js +3 -1
- package/src/extractors/python.js +14 -3
- package/src/extractors/rust.js +3 -1
- package/src/{ast.js → features/ast.js} +8 -8
- package/src/{audit.js → features/audit.js} +16 -44
- package/src/{batch.js → features/batch.js} +6 -5
- package/src/{boundaries.js → features/boundaries.js} +2 -2
- package/src/{branch-compare.js → features/branch-compare.js} +3 -3
- package/src/{cfg.js → features/cfg.js} +11 -12
- package/src/{check.js → features/check.js} +13 -30
- package/src/{cochange.js → features/cochange.js} +5 -5
- package/src/{communities.js → features/communities.js} +18 -90
- package/src/{complexity.js → features/complexity.js} +13 -13
- package/src/{dataflow.js → features/dataflow.js} +12 -13
- package/src/features/export.js +378 -0
- package/src/{flow.js → features/flow.js} +4 -4
- package/src/features/graph-enrichment.js +327 -0
- package/src/{manifesto.js → features/manifesto.js} +6 -6
- package/src/{owners.js → features/owners.js} +2 -2
- package/src/{sequence.js → features/sequence.js} +16 -52
- package/src/{snapshot.js → features/snapshot.js} +8 -7
- package/src/{structure.js → features/structure.js} +20 -45
- package/src/{triage.js → features/triage.js} +27 -79
- package/src/graph/algorithms/bfs.js +49 -0
- package/src/graph/algorithms/centrality.js +16 -0
- package/src/graph/algorithms/index.js +5 -0
- package/src/graph/algorithms/louvain.js +26 -0
- package/src/graph/algorithms/shortest-path.js +41 -0
- package/src/graph/algorithms/tarjan.js +49 -0
- package/src/graph/builders/dependency.js +110 -0
- package/src/graph/builders/index.js +3 -0
- package/src/graph/builders/structure.js +40 -0
- package/src/graph/builders/temporal.js +33 -0
- package/src/graph/classifiers/index.js +2 -0
- package/src/graph/classifiers/risk.js +85 -0
- package/src/graph/classifiers/roles.js +64 -0
- package/src/graph/index.js +13 -0
- package/src/graph/model.js +230 -0
- package/src/index.cjs +16 -0
- package/src/index.js +42 -219
- package/src/{native.js → infrastructure/native.js} +3 -1
- package/src/infrastructure/result-formatter.js +2 -21
- package/src/mcp/index.js +2 -0
- package/src/mcp/middleware.js +26 -0
- package/src/mcp/server.js +128 -0
- package/src/{mcp.js → mcp/tool-registry.js} +6 -675
- package/src/mcp/tools/ast-query.js +14 -0
- package/src/mcp/tools/audit.js +21 -0
- package/src/mcp/tools/batch-query.js +11 -0
- package/src/mcp/tools/branch-compare.js +12 -0
- package/src/mcp/tools/cfg.js +21 -0
- package/src/mcp/tools/check.js +43 -0
- package/src/mcp/tools/co-changes.js +20 -0
- package/src/mcp/tools/code-owners.js +12 -0
- package/src/mcp/tools/communities.js +15 -0
- package/src/mcp/tools/complexity.js +18 -0
- package/src/mcp/tools/context.js +17 -0
- package/src/mcp/tools/dataflow.js +26 -0
- package/src/mcp/tools/diff-impact.js +24 -0
- package/src/mcp/tools/execution-flow.js +26 -0
- package/src/mcp/tools/export-graph.js +57 -0
- package/src/mcp/tools/file-deps.js +12 -0
- package/src/mcp/tools/file-exports.js +13 -0
- package/src/mcp/tools/find-cycles.js +15 -0
- package/src/mcp/tools/fn-impact.js +15 -0
- package/src/mcp/tools/impact-analysis.js +12 -0
- package/src/mcp/tools/index.js +71 -0
- package/src/mcp/tools/list-functions.js +14 -0
- package/src/mcp/tools/list-repos.js +11 -0
- package/src/mcp/tools/module-map.js +6 -0
- package/src/mcp/tools/node-roles.js +14 -0
- package/src/mcp/tools/path.js +12 -0
- package/src/mcp/tools/query.js +30 -0
- package/src/mcp/tools/semantic-search.js +65 -0
- package/src/mcp/tools/sequence.js +17 -0
- package/src/mcp/tools/structure.js +15 -0
- package/src/mcp/tools/symbol-children.js +14 -0
- package/src/mcp/tools/triage.js +35 -0
- package/src/mcp/tools/where.js +13 -0
- package/src/{commands → presentation}/audit.js +2 -2
- package/src/{commands → presentation}/batch.js +1 -1
- package/src/{commands → presentation}/branch-compare.js +2 -2
- package/src/{commands → presentation}/cfg.js +1 -1
- package/src/{commands → presentation}/check.js +6 -6
- package/src/presentation/colors.js +44 -0
- package/src/{commands → presentation}/communities.js +1 -1
- package/src/{commands → presentation}/complexity.js +1 -1
- package/src/{commands → presentation}/dataflow.js +1 -1
- package/src/presentation/export.js +444 -0
- package/src/{commands → presentation}/flow.js +2 -2
- package/src/{commands → presentation}/manifesto.js +4 -4
- package/src/{commands → presentation}/owners.js +1 -1
- package/src/presentation/queries-cli/exports.js +46 -0
- package/src/presentation/queries-cli/impact.js +198 -0
- package/src/presentation/queries-cli/index.js +5 -0
- package/src/presentation/queries-cli/inspect.js +334 -0
- package/src/presentation/queries-cli/overview.js +197 -0
- package/src/presentation/queries-cli/path.js +58 -0
- package/src/presentation/queries-cli.js +27 -0
- package/src/{commands → presentation}/query.js +1 -1
- package/src/presentation/result-formatter.js +144 -0
- package/src/presentation/sequence-renderer.js +43 -0
- package/src/{commands → presentation}/sequence.js +2 -2
- package/src/{commands → presentation}/structure.js +2 -2
- package/src/presentation/table.js +47 -0
- package/src/{commands → presentation}/triage.js +1 -1
- package/src/{viewer.js → presentation/viewer.js} +68 -382
- package/src/{constants.js → shared/constants.js} +1 -1
- package/src/shared/errors.js +78 -0
- package/src/shared/file-utils.js +153 -0
- package/src/shared/generators.js +125 -0
- package/src/shared/hierarchy.js +27 -0
- package/src/shared/normalize.js +59 -0
- package/src/builder.js +0 -1486
- package/src/cycles.js +0 -137
- package/src/embedder.js +0 -1097
- package/src/export.js +0 -681
- package/src/queries-cli.js +0 -866
- package/src/queries.js +0 -2289
- /package/src/{config.js → infrastructure/config.js} +0 -0
- /package/src/{logger.js → infrastructure/logger.js} +0 -0
- /package/src/{registry.js → infrastructure/registry.js} +0 -0
- /package/src/{update-check.js → infrastructure/update-check.js} +0 -0
- /package/src/{commands → presentation}/cochange.js +0 -0
- /package/src/{kinds.js → shared/kinds.js} +0 -0
- /package/src/{paginate.js → shared/paginate.js} +0 -0
|
@@ -0,0 +1,378 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import { isTestFile } from '../infrastructure/test-filter.js';
|
|
3
|
+
import {
|
|
4
|
+
renderFileLevelDOT,
|
|
5
|
+
renderFileLevelGraphML,
|
|
6
|
+
renderFileLevelMermaid,
|
|
7
|
+
renderFileLevelNeo4jCSV,
|
|
8
|
+
renderFunctionLevelDOT,
|
|
9
|
+
renderFunctionLevelGraphML,
|
|
10
|
+
renderFunctionLevelMermaid,
|
|
11
|
+
renderFunctionLevelNeo4jCSV,
|
|
12
|
+
} from '../presentation/export.js';
|
|
13
|
+
import { paginateResult } from '../shared/paginate.js';
|
|
14
|
+
|
|
15
|
+
const DEFAULT_MIN_CONFIDENCE = 0.5;
|
|
16
|
+
|
|
17
|
+
// ─── Shared data loaders ─────────────────────────────────────────────
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Load file-level edges from DB with filtering.
|
|
21
|
+
* @param {object} db
|
|
22
|
+
* @param {object} opts
|
|
23
|
+
* @param {boolean} [opts.includeKind] - Include edge_kind in SELECT DISTINCT
|
|
24
|
+
* @param {boolean} [opts.includeConfidence] - Include confidence (adds a column to DISTINCT — use only when needed)
|
|
25
|
+
* @returns {{ edges: Array, totalEdges: number }}
|
|
26
|
+
*/
|
|
27
|
+
function loadFileLevelEdges(
|
|
28
|
+
db,
|
|
29
|
+
{ noTests, minConfidence, limit, includeKind = false, includeConfidence = false },
|
|
30
|
+
) {
|
|
31
|
+
const minConf = minConfidence ?? DEFAULT_MIN_CONFIDENCE;
|
|
32
|
+
const kindClause = includeKind ? ', e.kind AS edge_kind' : '';
|
|
33
|
+
const confidenceClause = includeConfidence ? ', e.confidence' : '';
|
|
34
|
+
let edges = db
|
|
35
|
+
.prepare(
|
|
36
|
+
`
|
|
37
|
+
SELECT DISTINCT n1.file AS source, n2.file AS target${kindClause}${confidenceClause}
|
|
38
|
+
FROM edges e
|
|
39
|
+
JOIN nodes n1 ON e.source_id = n1.id
|
|
40
|
+
JOIN nodes n2 ON e.target_id = n2.id
|
|
41
|
+
WHERE n1.file != n2.file AND e.kind IN ('imports', 'imports-type', 'calls')
|
|
42
|
+
AND e.confidence >= ?
|
|
43
|
+
`,
|
|
44
|
+
)
|
|
45
|
+
.all(minConf);
|
|
46
|
+
if (noTests) edges = edges.filter((e) => !isTestFile(e.source) && !isTestFile(e.target));
|
|
47
|
+
const totalEdges = edges.length;
|
|
48
|
+
if (limit && edges.length > limit) edges = edges.slice(0, limit);
|
|
49
|
+
return { edges, totalEdges };
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Load function-level edges from DB with filtering.
|
|
54
|
+
* Returns the maximal field set needed by any serializer.
|
|
55
|
+
* @returns {{ edges: Array, totalEdges: number }}
|
|
56
|
+
*/
|
|
57
|
+
function loadFunctionLevelEdges(db, { noTests, minConfidence, limit }) {
|
|
58
|
+
const minConf = minConfidence ?? DEFAULT_MIN_CONFIDENCE;
|
|
59
|
+
let edges = db
|
|
60
|
+
.prepare(
|
|
61
|
+
`
|
|
62
|
+
SELECT n1.id AS source_id, n1.name AS source_name, n1.kind AS source_kind,
|
|
63
|
+
n1.file AS source_file, n1.line AS source_line, n1.role AS source_role,
|
|
64
|
+
n2.id AS target_id, n2.name AS target_name, n2.kind AS target_kind,
|
|
65
|
+
n2.file AS target_file, n2.line AS target_line, n2.role AS target_role,
|
|
66
|
+
e.kind AS edge_kind, e.confidence
|
|
67
|
+
FROM edges e
|
|
68
|
+
JOIN nodes n1 ON e.source_id = n1.id
|
|
69
|
+
JOIN nodes n2 ON e.target_id = n2.id
|
|
70
|
+
WHERE n1.kind IN ('function', 'method', 'class', 'interface', 'type', 'struct', 'enum', 'trait', 'record', 'module')
|
|
71
|
+
AND n2.kind IN ('function', 'method', 'class', 'interface', 'type', 'struct', 'enum', 'trait', 'record', 'module')
|
|
72
|
+
AND e.kind = 'calls'
|
|
73
|
+
AND e.confidence >= ?
|
|
74
|
+
`,
|
|
75
|
+
)
|
|
76
|
+
.all(minConf);
|
|
77
|
+
if (noTests)
|
|
78
|
+
edges = edges.filter((e) => !isTestFile(e.source_file) && !isTestFile(e.target_file));
|
|
79
|
+
const totalEdges = edges.length;
|
|
80
|
+
if (limit && edges.length > limit) edges = edges.slice(0, limit);
|
|
81
|
+
return { edges, totalEdges };
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Load directory groupings for file-level graphs.
|
|
86
|
+
* Uses DB directory nodes if available, falls back to path.dirname().
|
|
87
|
+
* @returns {Array<{ name: string, files: Array<{ path: string, basename: string }>, cohesion: number|null }>}
|
|
88
|
+
*/
|
|
89
|
+
function loadDirectoryGroups(db, allFiles) {
|
|
90
|
+
const hasDirectoryNodes =
|
|
91
|
+
db.prepare("SELECT COUNT(*) as c FROM nodes WHERE kind = 'directory'").get().c > 0;
|
|
92
|
+
|
|
93
|
+
const dirs = new Map();
|
|
94
|
+
|
|
95
|
+
if (hasDirectoryNodes) {
|
|
96
|
+
const dbDirs = db
|
|
97
|
+
.prepare(`
|
|
98
|
+
SELECT n.id, n.name, nm.cohesion
|
|
99
|
+
FROM nodes n
|
|
100
|
+
LEFT JOIN node_metrics nm ON n.id = nm.node_id
|
|
101
|
+
WHERE n.kind = 'directory'
|
|
102
|
+
`)
|
|
103
|
+
.all();
|
|
104
|
+
|
|
105
|
+
for (const d of dbDirs) {
|
|
106
|
+
const containedFiles = db
|
|
107
|
+
.prepare(`
|
|
108
|
+
SELECT n.name FROM edges e
|
|
109
|
+
JOIN nodes n ON e.target_id = n.id
|
|
110
|
+
WHERE e.source_id = ? AND e.kind = 'contains' AND n.kind = 'file'
|
|
111
|
+
`)
|
|
112
|
+
.all(d.id)
|
|
113
|
+
.map((r) => r.name)
|
|
114
|
+
.filter((f) => allFiles.has(f));
|
|
115
|
+
|
|
116
|
+
if (containedFiles.length > 0) {
|
|
117
|
+
dirs.set(d.name, { files: containedFiles, cohesion: d.cohesion ?? null });
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
} else {
|
|
121
|
+
for (const file of allFiles) {
|
|
122
|
+
const dir = path.dirname(file) || '.';
|
|
123
|
+
if (!dirs.has(dir)) dirs.set(dir, { files: [], cohesion: null });
|
|
124
|
+
dirs.get(dir).files.push(file);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
return [...dirs]
|
|
129
|
+
.sort((a, b) => a[0].localeCompare(b[0]))
|
|
130
|
+
.map(([name, info]) => ({
|
|
131
|
+
name,
|
|
132
|
+
files: info.files.map((f) => ({ path: f, basename: path.basename(f) })),
|
|
133
|
+
cohesion: info.cohesion,
|
|
134
|
+
}));
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Load directory groupings for Mermaid file-level graphs (simplified — no cohesion, string arrays).
|
|
139
|
+
*/
|
|
140
|
+
function loadMermaidDirectoryGroups(db, allFiles) {
|
|
141
|
+
const hasDirectoryNodes =
|
|
142
|
+
db.prepare("SELECT COUNT(*) as c FROM nodes WHERE kind = 'directory'").get().c > 0;
|
|
143
|
+
|
|
144
|
+
const dirs = new Map();
|
|
145
|
+
|
|
146
|
+
if (hasDirectoryNodes) {
|
|
147
|
+
const dbDirs = db.prepare("SELECT id, name FROM nodes WHERE kind = 'directory'").all();
|
|
148
|
+
for (const d of dbDirs) {
|
|
149
|
+
const containedFiles = db
|
|
150
|
+
.prepare(`
|
|
151
|
+
SELECT n.name FROM edges e
|
|
152
|
+
JOIN nodes n ON e.target_id = n.id
|
|
153
|
+
WHERE e.source_id = ? AND e.kind = 'contains' AND n.kind = 'file'
|
|
154
|
+
`)
|
|
155
|
+
.all(d.id)
|
|
156
|
+
.map((r) => r.name)
|
|
157
|
+
.filter((f) => allFiles.has(f));
|
|
158
|
+
if (containedFiles.length > 0) dirs.set(d.name, containedFiles);
|
|
159
|
+
}
|
|
160
|
+
} else {
|
|
161
|
+
for (const file of allFiles) {
|
|
162
|
+
const dir = path.dirname(file) || '.';
|
|
163
|
+
if (!dirs.has(dir)) dirs.set(dir, []);
|
|
164
|
+
dirs.get(dir).push(file);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
return [...dirs]
|
|
169
|
+
.sort((a, b) => a[0].localeCompare(b[0]))
|
|
170
|
+
.map(([name, files]) => ({ name, files }));
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Load node roles for Mermaid function-level styling.
|
|
175
|
+
* @returns {Map<string, string>} "file::name" → role
|
|
176
|
+
*/
|
|
177
|
+
function loadNodeRoles(db, edges) {
|
|
178
|
+
const roles = new Map();
|
|
179
|
+
const seen = new Set();
|
|
180
|
+
for (const e of edges) {
|
|
181
|
+
for (const [file, name] of [
|
|
182
|
+
[e.source_file, e.source_name],
|
|
183
|
+
[e.target_file, e.target_name],
|
|
184
|
+
]) {
|
|
185
|
+
const key = `${file}::${name}`;
|
|
186
|
+
if (seen.has(key)) continue;
|
|
187
|
+
seen.add(key);
|
|
188
|
+
const row = db
|
|
189
|
+
.prepare('SELECT role FROM nodes WHERE file = ? AND name = ? AND role IS NOT NULL LIMIT 1')
|
|
190
|
+
.get(file, name);
|
|
191
|
+
if (row?.role) roles.set(key, row.role);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
return roles;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// ─── Public API ──────────────────────────────────────────────────────
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* Export the dependency graph in DOT (Graphviz) format.
|
|
201
|
+
*/
|
|
202
|
+
export function exportDOT(db, opts = {}) {
|
|
203
|
+
const fileLevel = opts.fileLevel !== false;
|
|
204
|
+
const noTests = opts.noTests || false;
|
|
205
|
+
const minConfidence = opts.minConfidence;
|
|
206
|
+
const limit = opts.limit;
|
|
207
|
+
|
|
208
|
+
if (fileLevel) {
|
|
209
|
+
const { edges, totalEdges } = loadFileLevelEdges(db, { noTests, minConfidence, limit });
|
|
210
|
+
const allFiles = new Set();
|
|
211
|
+
for (const { source, target } of edges) {
|
|
212
|
+
allFiles.add(source);
|
|
213
|
+
allFiles.add(target);
|
|
214
|
+
}
|
|
215
|
+
const dirs = loadDirectoryGroups(db, allFiles);
|
|
216
|
+
return renderFileLevelDOT({ dirs, edges, totalEdges, limit });
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
const { edges, totalEdges } = loadFunctionLevelEdges(db, { noTests, minConfidence, limit });
|
|
220
|
+
return renderFunctionLevelDOT({ edges, totalEdges, limit });
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* Export the dependency graph in Mermaid format.
|
|
225
|
+
*/
|
|
226
|
+
export function exportMermaid(db, opts = {}) {
|
|
227
|
+
const fileLevel = opts.fileLevel !== false;
|
|
228
|
+
const noTests = opts.noTests || false;
|
|
229
|
+
const minConfidence = opts.minConfidence;
|
|
230
|
+
const direction = opts.direction || 'LR';
|
|
231
|
+
const limit = opts.limit;
|
|
232
|
+
|
|
233
|
+
if (fileLevel) {
|
|
234
|
+
const { edges, totalEdges } = loadFileLevelEdges(db, {
|
|
235
|
+
noTests,
|
|
236
|
+
minConfidence,
|
|
237
|
+
limit,
|
|
238
|
+
includeKind: true,
|
|
239
|
+
});
|
|
240
|
+
const allFiles = new Set();
|
|
241
|
+
for (const { source, target } of edges) {
|
|
242
|
+
allFiles.add(source);
|
|
243
|
+
allFiles.add(target);
|
|
244
|
+
}
|
|
245
|
+
const dirs = loadMermaidDirectoryGroups(db, allFiles);
|
|
246
|
+
return renderFileLevelMermaid({ direction, dirs, edges, totalEdges, limit });
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
const { edges, totalEdges } = loadFunctionLevelEdges(db, { noTests, minConfidence, limit });
|
|
250
|
+
const roles = loadNodeRoles(db, edges);
|
|
251
|
+
return renderFunctionLevelMermaid({ direction, edges, roles, totalEdges, limit });
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
/**
|
|
255
|
+
* Export as JSON adjacency list.
|
|
256
|
+
*/
|
|
257
|
+
export function exportJSON(db, opts = {}) {
|
|
258
|
+
const noTests = opts.noTests || false;
|
|
259
|
+
const minConf = opts.minConfidence ?? DEFAULT_MIN_CONFIDENCE;
|
|
260
|
+
|
|
261
|
+
let nodes = db
|
|
262
|
+
.prepare(`
|
|
263
|
+
SELECT id, name, kind, file, line FROM nodes WHERE kind = 'file'
|
|
264
|
+
`)
|
|
265
|
+
.all();
|
|
266
|
+
if (noTests) nodes = nodes.filter((n) => !isTestFile(n.file));
|
|
267
|
+
|
|
268
|
+
let edges = db
|
|
269
|
+
.prepare(`
|
|
270
|
+
SELECT DISTINCT n1.file AS source, n2.file AS target, e.kind, e.confidence
|
|
271
|
+
FROM edges e
|
|
272
|
+
JOIN nodes n1 ON e.source_id = n1.id
|
|
273
|
+
JOIN nodes n2 ON e.target_id = n2.id
|
|
274
|
+
WHERE n1.file != n2.file AND e.confidence >= ?
|
|
275
|
+
`)
|
|
276
|
+
.all(minConf);
|
|
277
|
+
if (noTests) edges = edges.filter((e) => !isTestFile(e.source) && !isTestFile(e.target));
|
|
278
|
+
|
|
279
|
+
const base = { nodes, edges };
|
|
280
|
+
return paginateResult(base, 'edges', { limit: opts.limit, offset: opts.offset });
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
/**
|
|
284
|
+
* Export the dependency graph in GraphML (XML) format.
|
|
285
|
+
*/
|
|
286
|
+
export function exportGraphML(db, opts = {}) {
|
|
287
|
+
const fileLevel = opts.fileLevel !== false;
|
|
288
|
+
const noTests = opts.noTests || false;
|
|
289
|
+
const minConfidence = opts.minConfidence;
|
|
290
|
+
const limit = opts.limit;
|
|
291
|
+
|
|
292
|
+
if (fileLevel) {
|
|
293
|
+
const { edges } = loadFileLevelEdges(db, { noTests, minConfidence, limit });
|
|
294
|
+
return renderFileLevelGraphML({ edges });
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
const { edges } = loadFunctionLevelEdges(db, { noTests, minConfidence, limit });
|
|
298
|
+
return renderFunctionLevelGraphML({ edges });
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
/**
|
|
302
|
+
* Export the dependency graph in TinkerPop GraphSON v3 format.
|
|
303
|
+
*/
|
|
304
|
+
export function exportGraphSON(db, opts = {}) {
|
|
305
|
+
const noTests = opts.noTests || false;
|
|
306
|
+
const minConf = opts.minConfidence ?? DEFAULT_MIN_CONFIDENCE;
|
|
307
|
+
|
|
308
|
+
let nodes = db
|
|
309
|
+
.prepare(`
|
|
310
|
+
SELECT id, name, kind, file, line, role FROM nodes
|
|
311
|
+
WHERE kind IN ('function', 'method', 'class', 'interface', 'type', 'struct', 'enum', 'trait', 'record', 'module', 'file')
|
|
312
|
+
`)
|
|
313
|
+
.all();
|
|
314
|
+
if (noTests) nodes = nodes.filter((n) => !isTestFile(n.file));
|
|
315
|
+
|
|
316
|
+
let edges = db
|
|
317
|
+
.prepare(`
|
|
318
|
+
SELECT e.rowid AS id, n1.id AS outV, n2.id AS inV, e.kind, e.confidence
|
|
319
|
+
FROM edges e
|
|
320
|
+
JOIN nodes n1 ON e.source_id = n1.id
|
|
321
|
+
JOIN nodes n2 ON e.target_id = n2.id
|
|
322
|
+
WHERE e.confidence >= ?
|
|
323
|
+
`)
|
|
324
|
+
.all(minConf);
|
|
325
|
+
if (noTests) {
|
|
326
|
+
const nodeIds = new Set(nodes.map((n) => n.id));
|
|
327
|
+
edges = edges.filter((e) => nodeIds.has(e.outV) && nodeIds.has(e.inV));
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
const vertices = nodes.map((n) => ({
|
|
331
|
+
id: n.id,
|
|
332
|
+
label: n.kind,
|
|
333
|
+
properties: {
|
|
334
|
+
name: [{ id: 0, value: n.name }],
|
|
335
|
+
file: [{ id: 0, value: n.file }],
|
|
336
|
+
...(n.line != null ? { line: [{ id: 0, value: n.line }] } : {}),
|
|
337
|
+
...(n.role ? { role: [{ id: 0, value: n.role }] } : {}),
|
|
338
|
+
},
|
|
339
|
+
}));
|
|
340
|
+
|
|
341
|
+
const gEdges = edges.map((e) => ({
|
|
342
|
+
id: e.id,
|
|
343
|
+
label: e.kind,
|
|
344
|
+
inV: e.inV,
|
|
345
|
+
outV: e.outV,
|
|
346
|
+
properties: {
|
|
347
|
+
confidence: e.confidence,
|
|
348
|
+
},
|
|
349
|
+
}));
|
|
350
|
+
|
|
351
|
+
const base = { vertices, edges: gEdges };
|
|
352
|
+
return paginateResult(base, 'edges', { limit: opts.limit, offset: opts.offset });
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
/**
|
|
356
|
+
* Export the dependency graph as Neo4j bulk-import CSV files.
|
|
357
|
+
* Returns { nodes: string, relationships: string }.
|
|
358
|
+
*/
|
|
359
|
+
export function exportNeo4jCSV(db, opts = {}) {
|
|
360
|
+
const fileLevel = opts.fileLevel !== false;
|
|
361
|
+
const noTests = opts.noTests || false;
|
|
362
|
+
const minConfidence = opts.minConfidence;
|
|
363
|
+
const limit = opts.limit;
|
|
364
|
+
|
|
365
|
+
if (fileLevel) {
|
|
366
|
+
const { edges } = loadFileLevelEdges(db, {
|
|
367
|
+
noTests,
|
|
368
|
+
minConfidence,
|
|
369
|
+
limit,
|
|
370
|
+
includeKind: true,
|
|
371
|
+
includeConfidence: true,
|
|
372
|
+
});
|
|
373
|
+
return renderFileLevelNeo4jCSV({ edges });
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
const { edges } = loadFunctionLevelEdges(db, { noTests, minConfidence, limit });
|
|
377
|
+
return renderFunctionLevelNeo4jCSV({ edges });
|
|
378
|
+
}
|
|
@@ -5,10 +5,10 @@
|
|
|
5
5
|
* framework entry points (routes, commands, events) through their call chains.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
import { openReadonlyOrFail } from '
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
8
|
+
import { openReadonlyOrFail } from '../db/index.js';
|
|
9
|
+
import { CORE_SYMBOL_KINDS, findMatchingNodes } from '../domain/queries.js';
|
|
10
|
+
import { isTestFile } from '../infrastructure/test-filter.js';
|
|
11
|
+
import { paginateResult } from '../shared/paginate.js';
|
|
12
12
|
import { FRAMEWORK_ENTRY_PREFIXES } from './structure.js';
|
|
13
13
|
|
|
14
14
|
/**
|