@optave/codegraph 3.1.0 → 3.1.2

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.
Files changed (83) hide show
  1. package/README.md +5 -5
  2. package/grammars/tree-sitter-go.wasm +0 -0
  3. package/package.json +8 -9
  4. package/src/ast-analysis/engine.js +365 -0
  5. package/src/ast-analysis/metrics.js +118 -0
  6. package/src/ast-analysis/rules/csharp.js +201 -0
  7. package/src/ast-analysis/rules/go.js +182 -0
  8. package/src/ast-analysis/rules/index.js +82 -0
  9. package/src/ast-analysis/rules/java.js +175 -0
  10. package/src/ast-analysis/rules/javascript.js +246 -0
  11. package/src/ast-analysis/rules/php.js +219 -0
  12. package/src/ast-analysis/rules/python.js +196 -0
  13. package/src/ast-analysis/rules/ruby.js +204 -0
  14. package/src/ast-analysis/rules/rust.js +173 -0
  15. package/src/ast-analysis/shared.js +223 -0
  16. package/src/ast-analysis/visitor-utils.js +176 -0
  17. package/src/ast-analysis/visitor.js +162 -0
  18. package/src/ast-analysis/visitors/ast-store-visitor.js +150 -0
  19. package/src/ast-analysis/visitors/cfg-visitor.js +792 -0
  20. package/src/ast-analysis/visitors/complexity-visitor.js +243 -0
  21. package/src/ast-analysis/visitors/dataflow-visitor.js +358 -0
  22. package/src/ast.js +26 -166
  23. package/src/audit.js +2 -88
  24. package/src/batch.js +0 -25
  25. package/src/boundaries.js +1 -1
  26. package/src/branch-compare.js +82 -172
  27. package/src/builder.js +48 -184
  28. package/src/cfg.js +148 -1174
  29. package/src/check.js +1 -84
  30. package/src/cli.js +118 -197
  31. package/src/cochange.js +1 -39
  32. package/src/commands/audit.js +88 -0
  33. package/src/commands/batch.js +26 -0
  34. package/src/commands/branch-compare.js +97 -0
  35. package/src/commands/cfg.js +55 -0
  36. package/src/commands/check.js +82 -0
  37. package/src/commands/cochange.js +37 -0
  38. package/src/commands/communities.js +69 -0
  39. package/src/commands/complexity.js +77 -0
  40. package/src/commands/dataflow.js +110 -0
  41. package/src/commands/flow.js +70 -0
  42. package/src/commands/manifesto.js +77 -0
  43. package/src/commands/owners.js +52 -0
  44. package/src/commands/query.js +21 -0
  45. package/src/commands/sequence.js +33 -0
  46. package/src/commands/structure.js +64 -0
  47. package/src/commands/triage.js +49 -0
  48. package/src/communities.js +22 -96
  49. package/src/complexity.js +234 -1591
  50. package/src/cycles.js +1 -1
  51. package/src/dataflow.js +274 -1352
  52. package/src/db/connection.js +88 -0
  53. package/src/db/migrations.js +312 -0
  54. package/src/db/query-builder.js +280 -0
  55. package/src/db/repository/build-stmts.js +104 -0
  56. package/src/db/repository/cfg.js +83 -0
  57. package/src/db/repository/cochange.js +41 -0
  58. package/src/db/repository/complexity.js +15 -0
  59. package/src/db/repository/dataflow.js +12 -0
  60. package/src/db/repository/edges.js +259 -0
  61. package/src/db/repository/embeddings.js +40 -0
  62. package/src/db/repository/graph-read.js +39 -0
  63. package/src/db/repository/index.js +42 -0
  64. package/src/db/repository/nodes.js +236 -0
  65. package/src/db.js +58 -399
  66. package/src/embedder.js +158 -174
  67. package/src/export.js +1 -1
  68. package/src/extractors/javascript.js +130 -5
  69. package/src/flow.js +153 -222
  70. package/src/index.js +53 -16
  71. package/src/infrastructure/result-formatter.js +21 -0
  72. package/src/infrastructure/test-filter.js +7 -0
  73. package/src/kinds.js +50 -0
  74. package/src/manifesto.js +1 -82
  75. package/src/mcp.js +37 -20
  76. package/src/owners.js +127 -182
  77. package/src/queries-cli.js +866 -0
  78. package/src/queries.js +1271 -2416
  79. package/src/sequence.js +179 -223
  80. package/src/structure.js +211 -269
  81. package/src/triage.js +117 -212
  82. package/src/viewer.js +1 -1
  83. package/src/watcher.js +7 -4
@@ -0,0 +1,104 @@
1
+ /**
2
+ * Prepare all purge statements once, returning an object of runnable stmts.
3
+ * Optional tables are wrapped in try/catch — if the table doesn't exist,
4
+ * that slot is set to null.
5
+ *
6
+ * @param {object} db - Open read-write database handle
7
+ * @returns {object} prepared statements (some may be null)
8
+ */
9
+ function preparePurgeStmts(db) {
10
+ const tryPrepare = (sql) => {
11
+ try {
12
+ return db.prepare(sql);
13
+ } catch {
14
+ return null;
15
+ }
16
+ };
17
+
18
+ return {
19
+ embeddings: tryPrepare(
20
+ 'DELETE FROM embeddings WHERE node_id IN (SELECT id FROM nodes WHERE file = ?)',
21
+ ),
22
+ cfgEdges: tryPrepare(
23
+ 'DELETE FROM cfg_edges WHERE function_node_id IN (SELECT id FROM nodes WHERE file = ?)',
24
+ ),
25
+ cfgBlocks: tryPrepare(
26
+ 'DELETE FROM cfg_blocks WHERE function_node_id IN (SELECT id FROM nodes WHERE file = ?)',
27
+ ),
28
+ dataflow: tryPrepare(
29
+ 'DELETE FROM dataflow WHERE source_id IN (SELECT id FROM nodes WHERE file = ?) OR target_id IN (SELECT id FROM nodes WHERE file = ?)',
30
+ ),
31
+ complexity: tryPrepare(
32
+ 'DELETE FROM function_complexity WHERE node_id IN (SELECT id FROM nodes WHERE file = ?)',
33
+ ),
34
+ nodeMetrics: tryPrepare(
35
+ 'DELETE FROM node_metrics WHERE node_id IN (SELECT id FROM nodes WHERE file = ?)',
36
+ ),
37
+ astNodes: tryPrepare('DELETE FROM ast_nodes WHERE file = ?'),
38
+ // Core tables — always exist
39
+ edges: db.prepare(
40
+ 'DELETE FROM edges WHERE source_id IN (SELECT id FROM nodes WHERE file = @f) OR target_id IN (SELECT id FROM nodes WHERE file = @f)',
41
+ ),
42
+ nodes: db.prepare('DELETE FROM nodes WHERE file = ?'),
43
+ fileHashes: tryPrepare('DELETE FROM file_hashes WHERE file = ?'),
44
+ };
45
+ }
46
+
47
+ /**
48
+ * Cascade-delete all graph data for a single file across all tables.
49
+ * Order: dependent tables first, then edges, then nodes, then hashes.
50
+ *
51
+ * @param {object} db - Open read-write database handle
52
+ * @param {string} file - Relative file path to purge
53
+ * @param {object} [opts]
54
+ * @param {boolean} [opts.purgeHashes=true] - Also delete file_hashes entry
55
+ */
56
+ export function purgeFileData(db, file, opts = {}) {
57
+ const stmts = preparePurgeStmts(db);
58
+ runPurge(stmts, file, opts);
59
+ }
60
+
61
+ /**
62
+ * Run purge using pre-prepared statements for a single file.
63
+ * @param {object} stmts - Prepared statements from preparePurgeStmts
64
+ * @param {string} file - Relative file path to purge
65
+ * @param {object} [opts]
66
+ * @param {boolean} [opts.purgeHashes=true]
67
+ */
68
+ function runPurge(stmts, file, opts = {}) {
69
+ const { purgeHashes = true } = opts;
70
+
71
+ // Optional tables
72
+ stmts.embeddings?.run(file);
73
+ stmts.cfgEdges?.run(file);
74
+ stmts.cfgBlocks?.run(file);
75
+ stmts.dataflow?.run(file, file);
76
+ stmts.complexity?.run(file);
77
+ stmts.nodeMetrics?.run(file);
78
+ stmts.astNodes?.run(file);
79
+
80
+ // Core tables
81
+ stmts.edges.run({ f: file });
82
+ stmts.nodes.run(file);
83
+
84
+ if (purgeHashes) {
85
+ stmts.fileHashes?.run(file);
86
+ }
87
+ }
88
+
89
+ /**
90
+ * Purge all graph data for multiple files.
91
+ * Prepares statements once and loops over files for efficiency.
92
+ *
93
+ * @param {object} db - Open read-write database handle
94
+ * @param {string[]} files - Relative file paths to purge
95
+ * @param {object} [opts]
96
+ * @param {boolean} [opts.purgeHashes=true]
97
+ */
98
+ export function purgeFilesData(db, files, opts = {}) {
99
+ if (!files || files.length === 0) return;
100
+ const stmts = preparePurgeStmts(db);
101
+ for (const file of files) {
102
+ runPurge(stmts, file, opts);
103
+ }
104
+ }
@@ -0,0 +1,83 @@
1
+ // ─── Statement caches (one prepared statement per db instance) ────────────
2
+ // WeakMap keys on the db object so statements are GC'd when the db closes.
3
+ const _getCfgBlocksStmt = new WeakMap();
4
+ const _getCfgEdgesStmt = new WeakMap();
5
+ const _deleteCfgEdgesStmt = new WeakMap();
6
+ const _deleteCfgBlocksStmt = new WeakMap();
7
+
8
+ /**
9
+ * Check whether CFG tables exist.
10
+ * @param {object} db
11
+ * @returns {boolean}
12
+ */
13
+ export function hasCfgTables(db) {
14
+ try {
15
+ db.prepare('SELECT 1 FROM cfg_blocks LIMIT 0').get();
16
+ return true;
17
+ } catch {
18
+ return false;
19
+ }
20
+ }
21
+
22
+ /**
23
+ * Get CFG blocks for a function node.
24
+ * @param {object} db
25
+ * @param {number} functionNodeId
26
+ * @returns {object[]}
27
+ */
28
+ export function getCfgBlocks(db, functionNodeId) {
29
+ let stmt = _getCfgBlocksStmt.get(db);
30
+ if (!stmt) {
31
+ stmt = db.prepare(
32
+ `SELECT id, block_index, block_type, start_line, end_line, label
33
+ FROM cfg_blocks WHERE function_node_id = ?
34
+ ORDER BY block_index`,
35
+ );
36
+ _getCfgBlocksStmt.set(db, stmt);
37
+ }
38
+ return stmt.all(functionNodeId);
39
+ }
40
+
41
+ /**
42
+ * Get CFG edges for a function node (with block info).
43
+ * @param {object} db
44
+ * @param {number} functionNodeId
45
+ * @returns {object[]}
46
+ */
47
+ export function getCfgEdges(db, functionNodeId) {
48
+ let stmt = _getCfgEdgesStmt.get(db);
49
+ if (!stmt) {
50
+ stmt = db.prepare(
51
+ `SELECT e.kind,
52
+ sb.block_index AS source_index, sb.block_type AS source_type,
53
+ tb.block_index AS target_index, tb.block_type AS target_type
54
+ FROM cfg_edges e
55
+ JOIN cfg_blocks sb ON e.source_block_id = sb.id
56
+ JOIN cfg_blocks tb ON e.target_block_id = tb.id
57
+ WHERE e.function_node_id = ?
58
+ ORDER BY sb.block_index, tb.block_index`,
59
+ );
60
+ _getCfgEdgesStmt.set(db, stmt);
61
+ }
62
+ return stmt.all(functionNodeId);
63
+ }
64
+
65
+ /**
66
+ * Delete all CFG data for a function node.
67
+ * @param {object} db
68
+ * @param {number} functionNodeId
69
+ */
70
+ export function deleteCfgForNode(db, functionNodeId) {
71
+ let delEdges = _deleteCfgEdgesStmt.get(db);
72
+ if (!delEdges) {
73
+ delEdges = db.prepare('DELETE FROM cfg_edges WHERE function_node_id = ?');
74
+ _deleteCfgEdgesStmt.set(db, delEdges);
75
+ }
76
+ let delBlocks = _deleteCfgBlocksStmt.get(db);
77
+ if (!delBlocks) {
78
+ delBlocks = db.prepare('DELETE FROM cfg_blocks WHERE function_node_id = ?');
79
+ _deleteCfgBlocksStmt.set(db, delBlocks);
80
+ }
81
+ delEdges.run(functionNodeId);
82
+ delBlocks.run(functionNodeId);
83
+ }
@@ -0,0 +1,41 @@
1
+ /**
2
+ * Check whether the co_changes table has data.
3
+ * @param {object} db
4
+ * @returns {boolean}
5
+ */
6
+ export function hasCoChanges(db) {
7
+ try {
8
+ return !!db.prepare('SELECT 1 FROM co_changes LIMIT 1').get();
9
+ } catch {
10
+ return false;
11
+ }
12
+ }
13
+
14
+ /**
15
+ * Get all co-change metadata as a key-value map.
16
+ * @param {object} db
17
+ * @returns {Record<string, string>}
18
+ */
19
+ export function getCoChangeMeta(db) {
20
+ const meta = {};
21
+ try {
22
+ for (const row of db.prepare('SELECT key, value FROM co_change_meta').all()) {
23
+ meta[row.key] = row.value;
24
+ }
25
+ } catch {
26
+ /* table may not exist */
27
+ }
28
+ return meta;
29
+ }
30
+
31
+ /**
32
+ * Upsert a co-change metadata key-value pair.
33
+ * @param {object} db
34
+ * @param {string} key
35
+ * @param {string} value
36
+ */
37
+ export function upsertCoChangeMeta(db, key, value) {
38
+ db.prepare(
39
+ 'INSERT INTO co_change_meta (key, value) VALUES (?, ?) ON CONFLICT(key) DO UPDATE SET value = excluded.value',
40
+ ).run(key, value);
41
+ }
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Get complexity metrics for a node.
3
+ * Used by contextData and explainFunctionImpl in queries.js.
4
+ * @param {object} db
5
+ * @param {number} nodeId
6
+ * @returns {{ cognitive: number, cyclomatic: number, max_nesting: number, maintainability_index: number, halstead_volume: number }|undefined}
7
+ */
8
+ export function getComplexityForNode(db, nodeId) {
9
+ return db
10
+ .prepare(
11
+ `SELECT cognitive, cyclomatic, max_nesting, maintainability_index, halstead_volume
12
+ FROM function_complexity WHERE node_id = ?`,
13
+ )
14
+ .get(nodeId);
15
+ }
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Check whether the dataflow table exists and has data.
3
+ * @param {object} db
4
+ * @returns {boolean}
5
+ */
6
+ export function hasDataflowTable(db) {
7
+ try {
8
+ return db.prepare('SELECT COUNT(*) AS c FROM dataflow').get().c > 0;
9
+ } catch {
10
+ return false;
11
+ }
12
+ }
@@ -0,0 +1,259 @@
1
+ // ─── Call-edge queries ──────────────────────────────────────────────────
2
+
3
+ /**
4
+ * Find all callees of a node (outgoing 'calls' edges).
5
+ * Returns full node info including end_line for source display.
6
+ * @param {object} db
7
+ * @param {number} nodeId
8
+ * @returns {{ id: number, name: string, kind: string, file: string, line: number, end_line: number|null }[]}
9
+ */
10
+ export function findCallees(db, nodeId) {
11
+ return db
12
+ .prepare(
13
+ `SELECT DISTINCT n.id, n.name, n.kind, n.file, n.line, n.end_line
14
+ FROM edges e JOIN nodes n ON e.target_id = n.id
15
+ WHERE e.source_id = ? AND e.kind = 'calls'`,
16
+ )
17
+ .all(nodeId);
18
+ }
19
+
20
+ /**
21
+ * Find all callers of a node (incoming 'calls' edges).
22
+ * @param {object} db
23
+ * @param {number} nodeId
24
+ * @returns {{ id: number, name: string, kind: string, file: string, line: number }[]}
25
+ */
26
+ export function findCallers(db, nodeId) {
27
+ return db
28
+ .prepare(
29
+ `SELECT n.id, n.name, n.kind, n.file, n.line
30
+ FROM edges e JOIN nodes n ON e.source_id = n.id
31
+ WHERE e.target_id = ? AND e.kind = 'calls'`,
32
+ )
33
+ .all(nodeId);
34
+ }
35
+
36
+ /**
37
+ * Find distinct callers of a node (for impact analysis BFS).
38
+ * @param {object} db
39
+ * @param {number} nodeId
40
+ * @returns {{ id: number, name: string, kind: string, file: string, line: number }[]}
41
+ */
42
+ export function findDistinctCallers(db, nodeId) {
43
+ return db
44
+ .prepare(
45
+ `SELECT DISTINCT n.id, n.name, n.kind, n.file, n.line
46
+ FROM edges e JOIN nodes n ON e.source_id = n.id
47
+ WHERE e.target_id = ? AND e.kind = 'calls'`,
48
+ )
49
+ .all(nodeId);
50
+ }
51
+
52
+ // ─── All-edge queries (no kind filter) ─────────────────────────────────
53
+
54
+ /**
55
+ * Find all outgoing edges with edge kind (for queryNameData).
56
+ * @param {object} db
57
+ * @param {number} nodeId
58
+ * @returns {{ name: string, kind: string, file: string, line: number, edge_kind: string }[]}
59
+ */
60
+ export function findAllOutgoingEdges(db, nodeId) {
61
+ return db
62
+ .prepare(
63
+ `SELECT n.name, n.kind, n.file, n.line, e.kind AS edge_kind
64
+ FROM edges e JOIN nodes n ON e.target_id = n.id
65
+ WHERE e.source_id = ?`,
66
+ )
67
+ .all(nodeId);
68
+ }
69
+
70
+ /**
71
+ * Find all incoming edges with edge kind (for queryNameData).
72
+ * @param {object} db
73
+ * @param {number} nodeId
74
+ * @returns {{ name: string, kind: string, file: string, line: number, edge_kind: string }[]}
75
+ */
76
+ export function findAllIncomingEdges(db, nodeId) {
77
+ return db
78
+ .prepare(
79
+ `SELECT n.name, n.kind, n.file, n.line, e.kind AS edge_kind
80
+ FROM edges e JOIN nodes n ON e.source_id = n.id
81
+ WHERE e.target_id = ?`,
82
+ )
83
+ .all(nodeId);
84
+ }
85
+
86
+ // ─── Name-only callee/caller lookups (for embedder) ────────────────────
87
+
88
+ /**
89
+ * Get distinct callee names for a node, sorted alphabetically.
90
+ * @param {object} db
91
+ * @param {number} nodeId
92
+ * @returns {string[]}
93
+ */
94
+ export function findCalleeNames(db, nodeId) {
95
+ return db
96
+ .prepare(
97
+ `SELECT DISTINCT n.name
98
+ FROM edges e JOIN nodes n ON e.target_id = n.id
99
+ WHERE e.source_id = ? AND e.kind = 'calls'
100
+ ORDER BY n.name`,
101
+ )
102
+ .all(nodeId)
103
+ .map((r) => r.name);
104
+ }
105
+
106
+ /**
107
+ * Get distinct caller names for a node, sorted alphabetically.
108
+ * @param {object} db
109
+ * @param {number} nodeId
110
+ * @returns {string[]}
111
+ */
112
+ export function findCallerNames(db, nodeId) {
113
+ return db
114
+ .prepare(
115
+ `SELECT DISTINCT n.name
116
+ FROM edges e JOIN nodes n ON e.source_id = n.id
117
+ WHERE e.target_id = ? AND e.kind = 'calls'
118
+ ORDER BY n.name`,
119
+ )
120
+ .all(nodeId)
121
+ .map((r) => r.name);
122
+ }
123
+
124
+ // ─── Import-edge queries ───────────────────────────────────────────────
125
+
126
+ /**
127
+ * Find outgoing import edges (files this node imports).
128
+ * @param {object} db
129
+ * @param {number} nodeId
130
+ * @returns {{ file: string, edge_kind: string }[]}
131
+ */
132
+ export function findImportTargets(db, nodeId) {
133
+ return db
134
+ .prepare(
135
+ `SELECT n.file, e.kind AS edge_kind
136
+ FROM edges e JOIN nodes n ON e.target_id = n.id
137
+ WHERE e.source_id = ? AND e.kind IN ('imports', 'imports-type')`,
138
+ )
139
+ .all(nodeId);
140
+ }
141
+
142
+ /**
143
+ * Find incoming import edges (files that import this node).
144
+ * @param {object} db
145
+ * @param {number} nodeId
146
+ * @returns {{ file: string, edge_kind: string }[]}
147
+ */
148
+ export function findImportSources(db, nodeId) {
149
+ return db
150
+ .prepare(
151
+ `SELECT n.file, e.kind AS edge_kind
152
+ FROM edges e JOIN nodes n ON e.source_id = n.id
153
+ WHERE e.target_id = ? AND e.kind IN ('imports', 'imports-type')`,
154
+ )
155
+ .all(nodeId);
156
+ }
157
+
158
+ /**
159
+ * Find nodes that import a given node (BFS-ready, returns full node info).
160
+ * Used by impactAnalysisData for transitive import traversal.
161
+ * @param {object} db
162
+ * @param {number} nodeId
163
+ * @returns {object[]}
164
+ */
165
+ export function findImportDependents(db, nodeId) {
166
+ return db
167
+ .prepare(
168
+ `SELECT n.* FROM edges e JOIN nodes n ON e.source_id = n.id
169
+ WHERE e.target_id = ? AND e.kind IN ('imports', 'imports-type')`,
170
+ )
171
+ .all(nodeId);
172
+ }
173
+
174
+ // ─── Cross-file and hierarchy queries ──────────────────────────────────
175
+
176
+ /**
177
+ * Get IDs of symbols in a file that are called from other files.
178
+ * Used for "exported" detection in explain/where/exports.
179
+ * @param {object} db
180
+ * @param {string} file
181
+ * @returns {Set<number>}
182
+ */
183
+ export function findCrossFileCallTargets(db, file) {
184
+ return new Set(
185
+ db
186
+ .prepare(
187
+ `SELECT DISTINCT e.target_id FROM edges e
188
+ JOIN nodes caller ON e.source_id = caller.id
189
+ JOIN nodes target ON e.target_id = target.id
190
+ WHERE target.file = ? AND caller.file != ? AND e.kind = 'calls'`,
191
+ )
192
+ .all(file, file)
193
+ .map((r) => r.target_id),
194
+ );
195
+ }
196
+
197
+ /**
198
+ * Count callers that are in a different file than the target node.
199
+ * Used by whereSymbolImpl to determine if a symbol is exported.
200
+ * @param {object} db
201
+ * @param {number} nodeId
202
+ * @param {string} file - The target node's file
203
+ * @returns {number}
204
+ */
205
+ export function countCrossFileCallers(db, nodeId, file) {
206
+ return db
207
+ .prepare(
208
+ `SELECT COUNT(*) AS cnt FROM edges e JOIN nodes n ON e.source_id = n.id
209
+ WHERE e.target_id = ? AND e.kind = 'calls' AND n.file != ?`,
210
+ )
211
+ .get(nodeId, file).cnt;
212
+ }
213
+
214
+ /**
215
+ * Get all ancestor class IDs via extends edges (BFS).
216
+ * @param {object} db
217
+ * @param {number} classNodeId
218
+ * @returns {Set<number>}
219
+ */
220
+ export function getClassHierarchy(db, classNodeId) {
221
+ const ancestors = new Set();
222
+ const queue = [classNodeId];
223
+ while (queue.length > 0) {
224
+ const current = queue.shift();
225
+ const parents = db
226
+ .prepare(
227
+ `SELECT n.id, n.name FROM edges e JOIN nodes n ON e.target_id = n.id
228
+ WHERE e.source_id = ? AND e.kind = 'extends'`,
229
+ )
230
+ .all(current);
231
+ for (const p of parents) {
232
+ if (!ancestors.has(p.id)) {
233
+ ancestors.add(p.id);
234
+ queue.push(p.id);
235
+ }
236
+ }
237
+ }
238
+ return ancestors;
239
+ }
240
+
241
+ /**
242
+ * Find intra-file call edges (caller → callee within the same file).
243
+ * Used by explainFileImpl for data flow visualization.
244
+ * @param {object} db
245
+ * @param {string} file
246
+ * @returns {{ caller_name: string, callee_name: string }[]}
247
+ */
248
+ export function findIntraFileCallEdges(db, file) {
249
+ return db
250
+ .prepare(
251
+ `SELECT caller.name AS caller_name, callee.name AS callee_name
252
+ FROM edges e
253
+ JOIN nodes caller ON e.source_id = caller.id
254
+ JOIN nodes callee ON e.target_id = callee.id
255
+ WHERE caller.file = ? AND callee.file = ? AND e.kind = 'calls'
256
+ ORDER BY caller.line`,
257
+ )
258
+ .all(file, file);
259
+ }
@@ -0,0 +1,40 @@
1
+ /**
2
+ * Check whether the embeddings table has data.
3
+ * @param {object} db
4
+ * @returns {boolean}
5
+ */
6
+ export function hasEmbeddings(db) {
7
+ try {
8
+ return !!db.prepare('SELECT 1 FROM embeddings LIMIT 1').get();
9
+ } catch {
10
+ return false;
11
+ }
12
+ }
13
+
14
+ /**
15
+ * Get the count of embeddings.
16
+ * @param {object} db
17
+ * @returns {number}
18
+ */
19
+ export function getEmbeddingCount(db) {
20
+ try {
21
+ return db.prepare('SELECT COUNT(*) AS c FROM embeddings').get().c;
22
+ } catch {
23
+ return 0;
24
+ }
25
+ }
26
+
27
+ /**
28
+ * Get a single embedding metadata value by key.
29
+ * @param {object} db
30
+ * @param {string} key
31
+ * @returns {string|undefined}
32
+ */
33
+ export function getEmbeddingMeta(db, key) {
34
+ try {
35
+ const row = db.prepare('SELECT value FROM embedding_meta WHERE key = ?').get(key);
36
+ return row?.value;
37
+ } catch {
38
+ return undefined;
39
+ }
40
+ }
@@ -0,0 +1,39 @@
1
+ /**
2
+ * Get callable nodes (function/method/class) for community detection.
3
+ * @param {object} db
4
+ * @returns {{ id: number, name: string, kind: string, file: string }[]}
5
+ */
6
+ export function getCallableNodes(db) {
7
+ return db
8
+ .prepare("SELECT id, name, kind, file FROM nodes WHERE kind IN ('function','method','class')")
9
+ .all();
10
+ }
11
+
12
+ /**
13
+ * Get all 'calls' edges.
14
+ * @param {object} db
15
+ * @returns {{ source_id: number, target_id: number }[]}
16
+ */
17
+ export function getCallEdges(db) {
18
+ return db.prepare("SELECT source_id, target_id FROM edges WHERE kind = 'calls'").all();
19
+ }
20
+
21
+ /**
22
+ * Get all file-kind nodes.
23
+ * @param {object} db
24
+ * @returns {{ id: number, name: string, file: string }[]}
25
+ */
26
+ export function getFileNodesAll(db) {
27
+ return db.prepare("SELECT id, name, file FROM nodes WHERE kind = 'file'").all();
28
+ }
29
+
30
+ /**
31
+ * Get all import edges.
32
+ * @param {object} db
33
+ * @returns {{ source_id: number, target_id: number }[]}
34
+ */
35
+ export function getImportEdges(db) {
36
+ return db
37
+ .prepare("SELECT source_id, target_id FROM edges WHERE kind IN ('imports','imports-type')")
38
+ .all();
39
+ }
@@ -0,0 +1,42 @@
1
+ // Barrel re-export for repository/ modules.
2
+
3
+ export { purgeFileData, purgeFilesData } from './build-stmts.js';
4
+ export { deleteCfgForNode, getCfgBlocks, getCfgEdges, hasCfgTables } from './cfg.js';
5
+ export { getCoChangeMeta, hasCoChanges, upsertCoChangeMeta } from './cochange.js';
6
+
7
+ export { getComplexityForNode } from './complexity.js';
8
+ export { hasDataflowTable } from './dataflow.js';
9
+ export {
10
+ countCrossFileCallers,
11
+ findAllIncomingEdges,
12
+ findAllOutgoingEdges,
13
+ findCalleeNames,
14
+ findCallees,
15
+ findCallerNames,
16
+ findCallers,
17
+ findCrossFileCallTargets,
18
+ findDistinctCallers,
19
+ findImportDependents,
20
+ findImportSources,
21
+ findImportTargets,
22
+ findIntraFileCallEdges,
23
+ getClassHierarchy,
24
+ } from './edges.js';
25
+ export { getEmbeddingCount, getEmbeddingMeta, hasEmbeddings } from './embeddings.js';
26
+ export { getCallableNodes, getCallEdges, getFileNodesAll, getImportEdges } from './graph-read.js';
27
+ export {
28
+ bulkNodeIdsByFile,
29
+ countEdges,
30
+ countFiles,
31
+ countNodes,
32
+ findFileNodes,
33
+ findNodeById,
34
+ findNodeChildren,
35
+ findNodesByFile,
36
+ findNodesForTriage,
37
+ findNodesWithFanIn,
38
+ getFunctionNodeId,
39
+ getNodeId,
40
+ iterateFunctionNodes,
41
+ listFunctionNodes,
42
+ } from './nodes.js';