@optave/codegraph 3.1.2 → 3.1.4

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 (194) hide show
  1. package/README.md +19 -21
  2. package/package.json +10 -7
  3. package/src/analysis/context.js +408 -0
  4. package/src/analysis/dependencies.js +341 -0
  5. package/src/analysis/exports.js +130 -0
  6. package/src/analysis/impact.js +463 -0
  7. package/src/analysis/module-map.js +322 -0
  8. package/src/analysis/roles.js +45 -0
  9. package/src/analysis/symbol-lookup.js +232 -0
  10. package/src/ast-analysis/shared.js +5 -4
  11. package/src/batch.js +2 -1
  12. package/src/builder/context.js +85 -0
  13. package/src/builder/helpers.js +218 -0
  14. package/src/builder/incremental.js +178 -0
  15. package/src/builder/pipeline.js +130 -0
  16. package/src/builder/stages/build-edges.js +297 -0
  17. package/src/builder/stages/build-structure.js +113 -0
  18. package/src/builder/stages/collect-files.js +44 -0
  19. package/src/builder/stages/detect-changes.js +413 -0
  20. package/src/builder/stages/finalize.js +139 -0
  21. package/src/builder/stages/insert-nodes.js +195 -0
  22. package/src/builder/stages/parse-files.js +28 -0
  23. package/src/builder/stages/resolve-imports.js +143 -0
  24. package/src/builder/stages/run-analyses.js +44 -0
  25. package/src/builder.js +10 -1472
  26. package/src/cfg.js +1 -2
  27. package/src/cli/commands/ast.js +26 -0
  28. package/src/cli/commands/audit.js +46 -0
  29. package/src/cli/commands/batch.js +68 -0
  30. package/src/cli/commands/branch-compare.js +21 -0
  31. package/src/cli/commands/build.js +26 -0
  32. package/src/cli/commands/cfg.js +30 -0
  33. package/src/cli/commands/check.js +79 -0
  34. package/src/cli/commands/children.js +31 -0
  35. package/src/cli/commands/co-change.js +65 -0
  36. package/src/cli/commands/communities.js +23 -0
  37. package/src/cli/commands/complexity.js +45 -0
  38. package/src/cli/commands/context.js +34 -0
  39. package/src/cli/commands/cycles.js +28 -0
  40. package/src/cli/commands/dataflow.js +32 -0
  41. package/src/cli/commands/deps.js +16 -0
  42. package/src/cli/commands/diff-impact.js +30 -0
  43. package/src/cli/commands/embed.js +30 -0
  44. package/src/cli/commands/export.js +75 -0
  45. package/src/cli/commands/exports.js +18 -0
  46. package/src/cli/commands/flow.js +36 -0
  47. package/src/cli/commands/fn-impact.js +30 -0
  48. package/src/cli/commands/impact.js +16 -0
  49. package/src/cli/commands/info.js +76 -0
  50. package/src/cli/commands/map.js +19 -0
  51. package/src/cli/commands/mcp.js +18 -0
  52. package/src/cli/commands/models.js +19 -0
  53. package/src/cli/commands/owners.js +25 -0
  54. package/src/cli/commands/path.js +36 -0
  55. package/src/cli/commands/plot.js +80 -0
  56. package/src/cli/commands/query.js +49 -0
  57. package/src/cli/commands/registry.js +100 -0
  58. package/src/cli/commands/roles.js +34 -0
  59. package/src/cli/commands/search.js +42 -0
  60. package/src/cli/commands/sequence.js +32 -0
  61. package/src/cli/commands/snapshot.js +61 -0
  62. package/src/cli/commands/stats.js +15 -0
  63. package/src/cli/commands/structure.js +32 -0
  64. package/src/cli/commands/triage.js +78 -0
  65. package/src/cli/commands/watch.js +12 -0
  66. package/src/cli/commands/where.js +24 -0
  67. package/src/cli/index.js +118 -0
  68. package/src/cli/shared/options.js +39 -0
  69. package/src/cli/shared/output.js +1 -0
  70. package/src/cli.js +11 -1514
  71. package/src/commands/check.js +5 -5
  72. package/src/commands/manifesto.js +3 -3
  73. package/src/commands/structure.js +1 -1
  74. package/src/communities.js +15 -87
  75. package/src/complexity.js +1 -1
  76. package/src/cycles.js +30 -85
  77. package/src/dataflow.js +1 -2
  78. package/src/db/connection.js +4 -4
  79. package/src/db/migrations.js +41 -0
  80. package/src/db/query-builder.js +6 -5
  81. package/src/db/repository/base.js +201 -0
  82. package/src/db/repository/cached-stmt.js +19 -0
  83. package/src/db/repository/cfg.js +27 -38
  84. package/src/db/repository/cochange.js +16 -3
  85. package/src/db/repository/complexity.js +11 -6
  86. package/src/db/repository/dataflow.js +6 -1
  87. package/src/db/repository/edges.js +120 -98
  88. package/src/db/repository/embeddings.js +14 -3
  89. package/src/db/repository/graph-read.js +32 -9
  90. package/src/db/repository/in-memory-repository.js +584 -0
  91. package/src/db/repository/index.js +6 -1
  92. package/src/db/repository/nodes.js +110 -40
  93. package/src/db/repository/sqlite-repository.js +219 -0
  94. package/src/db.js +5 -0
  95. package/src/embeddings/generator.js +163 -0
  96. package/src/embeddings/index.js +13 -0
  97. package/src/embeddings/models.js +218 -0
  98. package/src/embeddings/search/cli-formatter.js +151 -0
  99. package/src/embeddings/search/filters.js +46 -0
  100. package/src/embeddings/search/hybrid.js +121 -0
  101. package/src/embeddings/search/keyword.js +68 -0
  102. package/src/embeddings/search/prepare.js +66 -0
  103. package/src/embeddings/search/semantic.js +145 -0
  104. package/src/embeddings/stores/fts5.js +27 -0
  105. package/src/embeddings/stores/sqlite-blob.js +24 -0
  106. package/src/embeddings/strategies/source.js +14 -0
  107. package/src/embeddings/strategies/structured.js +43 -0
  108. package/src/embeddings/strategies/text-utils.js +43 -0
  109. package/src/errors.js +78 -0
  110. package/src/export.js +217 -520
  111. package/src/extractors/csharp.js +10 -2
  112. package/src/extractors/go.js +3 -1
  113. package/src/extractors/helpers.js +71 -0
  114. package/src/extractors/java.js +9 -2
  115. package/src/extractors/javascript.js +38 -1
  116. package/src/extractors/php.js +3 -1
  117. package/src/extractors/python.js +14 -3
  118. package/src/extractors/rust.js +3 -1
  119. package/src/graph/algorithms/bfs.js +49 -0
  120. package/src/graph/algorithms/centrality.js +16 -0
  121. package/src/graph/algorithms/index.js +5 -0
  122. package/src/graph/algorithms/louvain.js +26 -0
  123. package/src/graph/algorithms/shortest-path.js +41 -0
  124. package/src/graph/algorithms/tarjan.js +49 -0
  125. package/src/graph/builders/dependency.js +91 -0
  126. package/src/graph/builders/index.js +3 -0
  127. package/src/graph/builders/structure.js +40 -0
  128. package/src/graph/builders/temporal.js +33 -0
  129. package/src/graph/classifiers/index.js +2 -0
  130. package/src/graph/classifiers/risk.js +85 -0
  131. package/src/graph/classifiers/roles.js +64 -0
  132. package/src/graph/index.js +13 -0
  133. package/src/graph/model.js +230 -0
  134. package/src/index.js +33 -204
  135. package/src/infrastructure/result-formatter.js +2 -21
  136. package/src/mcp/index.js +2 -0
  137. package/src/mcp/middleware.js +26 -0
  138. package/src/mcp/server.js +128 -0
  139. package/src/mcp/tool-registry.js +801 -0
  140. package/src/mcp/tools/ast-query.js +14 -0
  141. package/src/mcp/tools/audit.js +21 -0
  142. package/src/mcp/tools/batch-query.js +11 -0
  143. package/src/mcp/tools/branch-compare.js +10 -0
  144. package/src/mcp/tools/cfg.js +21 -0
  145. package/src/mcp/tools/check.js +43 -0
  146. package/src/mcp/tools/co-changes.js +20 -0
  147. package/src/mcp/tools/code-owners.js +12 -0
  148. package/src/mcp/tools/communities.js +15 -0
  149. package/src/mcp/tools/complexity.js +18 -0
  150. package/src/mcp/tools/context.js +17 -0
  151. package/src/mcp/tools/dataflow.js +26 -0
  152. package/src/mcp/tools/diff-impact.js +24 -0
  153. package/src/mcp/tools/execution-flow.js +26 -0
  154. package/src/mcp/tools/export-graph.js +57 -0
  155. package/src/mcp/tools/file-deps.js +12 -0
  156. package/src/mcp/tools/file-exports.js +13 -0
  157. package/src/mcp/tools/find-cycles.js +15 -0
  158. package/src/mcp/tools/fn-impact.js +15 -0
  159. package/src/mcp/tools/impact-analysis.js +12 -0
  160. package/src/mcp/tools/index.js +71 -0
  161. package/src/mcp/tools/list-functions.js +14 -0
  162. package/src/mcp/tools/list-repos.js +11 -0
  163. package/src/mcp/tools/module-map.js +6 -0
  164. package/src/mcp/tools/node-roles.js +14 -0
  165. package/src/mcp/tools/path.js +12 -0
  166. package/src/mcp/tools/query.js +30 -0
  167. package/src/mcp/tools/semantic-search.js +65 -0
  168. package/src/mcp/tools/sequence.js +17 -0
  169. package/src/mcp/tools/structure.js +15 -0
  170. package/src/mcp/tools/symbol-children.js +14 -0
  171. package/src/mcp/tools/triage.js +35 -0
  172. package/src/mcp/tools/where.js +13 -0
  173. package/src/mcp.js +2 -1470
  174. package/src/native.js +34 -10
  175. package/src/parser.js +53 -2
  176. package/src/presentation/colors.js +44 -0
  177. package/src/presentation/export.js +444 -0
  178. package/src/presentation/result-formatter.js +21 -0
  179. package/src/presentation/sequence-renderer.js +43 -0
  180. package/src/presentation/table.js +47 -0
  181. package/src/presentation/viewer.js +634 -0
  182. package/src/queries.js +35 -2276
  183. package/src/resolve.js +1 -1
  184. package/src/sequence.js +2 -38
  185. package/src/shared/file-utils.js +153 -0
  186. package/src/shared/generators.js +125 -0
  187. package/src/shared/hierarchy.js +27 -0
  188. package/src/shared/normalize.js +59 -0
  189. package/src/snapshot.js +6 -5
  190. package/src/structure.js +15 -40
  191. package/src/triage.js +20 -72
  192. package/src/viewer.js +35 -656
  193. package/src/watcher.js +8 -148
  194. package/src/embedder.js +0 -1097
@@ -1,3 +1,21 @@
1
+ import { cachedStmt } from './cached-stmt.js';
2
+
3
+ // ─── Prepared-statement caches (one per db instance) ────────────────────
4
+ const _findCalleesStmt = new WeakMap();
5
+ const _findCallersStmt = new WeakMap();
6
+ const _findDistinctCallersStmt = new WeakMap();
7
+ const _findAllOutgoingStmt = new WeakMap();
8
+ const _findAllIncomingStmt = new WeakMap();
9
+ const _findCalleeNamesStmt = new WeakMap();
10
+ const _findCallerNamesStmt = new WeakMap();
11
+ const _findImportTargetsStmt = new WeakMap();
12
+ const _findImportSourcesStmt = new WeakMap();
13
+ const _findImportDependentsStmt = new WeakMap();
14
+ const _findCrossFileCallTargetsStmt = new WeakMap();
15
+ const _countCrossFileCallersStmt = new WeakMap();
16
+ const _getClassAncestorsStmt = new WeakMap();
17
+ const _findIntraFileCallEdgesStmt = new WeakMap();
18
+
1
19
  // ─── Call-edge queries ──────────────────────────────────────────────────
2
20
 
3
21
  /**
@@ -8,13 +26,13 @@
8
26
  * @returns {{ id: number, name: string, kind: string, file: string, line: number, end_line: number|null }[]}
9
27
  */
10
28
  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);
29
+ return cachedStmt(
30
+ _findCalleesStmt,
31
+ db,
32
+ `SELECT DISTINCT n.id, n.name, n.kind, n.file, n.line, n.end_line
33
+ FROM edges e JOIN nodes n ON e.target_id = n.id
34
+ WHERE e.source_id = ? AND e.kind = 'calls'`,
35
+ ).all(nodeId);
18
36
  }
19
37
 
20
38
  /**
@@ -24,13 +42,13 @@ export function findCallees(db, nodeId) {
24
42
  * @returns {{ id: number, name: string, kind: string, file: string, line: number }[]}
25
43
  */
26
44
  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);
45
+ return cachedStmt(
46
+ _findCallersStmt,
47
+ db,
48
+ `SELECT n.id, n.name, n.kind, n.file, n.line
49
+ FROM edges e JOIN nodes n ON e.source_id = n.id
50
+ WHERE e.target_id = ? AND e.kind = 'calls'`,
51
+ ).all(nodeId);
34
52
  }
35
53
 
36
54
  /**
@@ -40,13 +58,13 @@ export function findCallers(db, nodeId) {
40
58
  * @returns {{ id: number, name: string, kind: string, file: string, line: number }[]}
41
59
  */
42
60
  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);
61
+ return cachedStmt(
62
+ _findDistinctCallersStmt,
63
+ db,
64
+ `SELECT DISTINCT n.id, n.name, n.kind, n.file, n.line
65
+ FROM edges e JOIN nodes n ON e.source_id = n.id
66
+ WHERE e.target_id = ? AND e.kind = 'calls'`,
67
+ ).all(nodeId);
50
68
  }
51
69
 
52
70
  // ─── All-edge queries (no kind filter) ─────────────────────────────────
@@ -58,13 +76,13 @@ export function findDistinctCallers(db, nodeId) {
58
76
  * @returns {{ name: string, kind: string, file: string, line: number, edge_kind: string }[]}
59
77
  */
60
78
  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);
79
+ return cachedStmt(
80
+ _findAllOutgoingStmt,
81
+ db,
82
+ `SELECT n.name, n.kind, n.file, n.line, e.kind AS edge_kind
83
+ FROM edges e JOIN nodes n ON e.target_id = n.id
84
+ WHERE e.source_id = ?`,
85
+ ).all(nodeId);
68
86
  }
69
87
 
70
88
  /**
@@ -74,13 +92,13 @@ export function findAllOutgoingEdges(db, nodeId) {
74
92
  * @returns {{ name: string, kind: string, file: string, line: number, edge_kind: string }[]}
75
93
  */
76
94
  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);
95
+ return cachedStmt(
96
+ _findAllIncomingStmt,
97
+ db,
98
+ `SELECT n.name, n.kind, n.file, n.line, e.kind AS edge_kind
99
+ FROM edges e JOIN nodes n ON e.source_id = n.id
100
+ WHERE e.target_id = ?`,
101
+ ).all(nodeId);
84
102
  }
85
103
 
86
104
  // ─── Name-only callee/caller lookups (for embedder) ────────────────────
@@ -92,13 +110,14 @@ export function findAllIncomingEdges(db, nodeId) {
92
110
  * @returns {string[]}
93
111
  */
94
112
  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
- )
113
+ return cachedStmt(
114
+ _findCalleeNamesStmt,
115
+ db,
116
+ `SELECT DISTINCT n.name
117
+ FROM edges e JOIN nodes n ON e.target_id = n.id
118
+ WHERE e.source_id = ? AND e.kind = 'calls'
119
+ ORDER BY n.name`,
120
+ )
102
121
  .all(nodeId)
103
122
  .map((r) => r.name);
104
123
  }
@@ -110,13 +129,14 @@ export function findCalleeNames(db, nodeId) {
110
129
  * @returns {string[]}
111
130
  */
112
131
  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
- )
132
+ return cachedStmt(
133
+ _findCallerNamesStmt,
134
+ db,
135
+ `SELECT DISTINCT n.name
136
+ FROM edges e JOIN nodes n ON e.source_id = n.id
137
+ WHERE e.target_id = ? AND e.kind = 'calls'
138
+ ORDER BY n.name`,
139
+ )
120
140
  .all(nodeId)
121
141
  .map((r) => r.name);
122
142
  }
@@ -130,13 +150,13 @@ export function findCallerNames(db, nodeId) {
130
150
  * @returns {{ file: string, edge_kind: string }[]}
131
151
  */
132
152
  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);
153
+ return cachedStmt(
154
+ _findImportTargetsStmt,
155
+ db,
156
+ `SELECT n.file, e.kind AS edge_kind
157
+ FROM edges e JOIN nodes n ON e.target_id = n.id
158
+ WHERE e.source_id = ? AND e.kind IN ('imports', 'imports-type')`,
159
+ ).all(nodeId);
140
160
  }
141
161
 
142
162
  /**
@@ -146,13 +166,13 @@ export function findImportTargets(db, nodeId) {
146
166
  * @returns {{ file: string, edge_kind: string }[]}
147
167
  */
148
168
  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);
169
+ return cachedStmt(
170
+ _findImportSourcesStmt,
171
+ db,
172
+ `SELECT n.file, e.kind AS edge_kind
173
+ FROM edges e JOIN nodes n ON e.source_id = n.id
174
+ WHERE e.target_id = ? AND e.kind IN ('imports', 'imports-type')`,
175
+ ).all(nodeId);
156
176
  }
157
177
 
158
178
  /**
@@ -163,12 +183,12 @@ export function findImportSources(db, nodeId) {
163
183
  * @returns {object[]}
164
184
  */
165
185
  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);
186
+ return cachedStmt(
187
+ _findImportDependentsStmt,
188
+ db,
189
+ `SELECT n.* FROM edges e JOIN nodes n ON e.source_id = n.id
190
+ WHERE e.target_id = ? AND e.kind IN ('imports', 'imports-type')`,
191
+ ).all(nodeId);
172
192
  }
173
193
 
174
194
  // ─── Cross-file and hierarchy queries ──────────────────────────────────
@@ -182,13 +202,14 @@ export function findImportDependents(db, nodeId) {
182
202
  */
183
203
  export function findCrossFileCallTargets(db, file) {
184
204
  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
- )
205
+ cachedStmt(
206
+ _findCrossFileCallTargetsStmt,
207
+ db,
208
+ `SELECT DISTINCT e.target_id FROM edges e
209
+ JOIN nodes caller ON e.source_id = caller.id
210
+ JOIN nodes target ON e.target_id = target.id
211
+ WHERE target.file = ? AND caller.file != ? AND e.kind = 'calls'`,
212
+ )
192
213
  .all(file, file)
193
214
  .map((r) => r.target_id),
194
215
  );
@@ -203,12 +224,12 @@ export function findCrossFileCallTargets(db, file) {
203
224
  * @returns {number}
204
225
  */
205
226
  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;
227
+ return cachedStmt(
228
+ _countCrossFileCallersStmt,
229
+ db,
230
+ `SELECT COUNT(*) AS cnt FROM edges e JOIN nodes n ON e.source_id = n.id
231
+ WHERE e.target_id = ? AND e.kind = 'calls' AND n.file != ?`,
232
+ ).get(nodeId, file).cnt;
212
233
  }
213
234
 
214
235
  /**
@@ -220,14 +241,15 @@ export function countCrossFileCallers(db, nodeId, file) {
220
241
  export function getClassHierarchy(db, classNodeId) {
221
242
  const ancestors = new Set();
222
243
  const queue = [classNodeId];
244
+ const stmt = cachedStmt(
245
+ _getClassAncestorsStmt,
246
+ db,
247
+ `SELECT n.id, n.name FROM edges e JOIN nodes n ON e.target_id = n.id
248
+ WHERE e.source_id = ? AND e.kind = 'extends'`,
249
+ );
223
250
  while (queue.length > 0) {
224
251
  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);
252
+ const parents = stmt.all(current);
231
253
  for (const p of parents) {
232
254
  if (!ancestors.has(p.id)) {
233
255
  ancestors.add(p.id);
@@ -246,14 +268,14 @@ export function getClassHierarchy(db, classNodeId) {
246
268
  * @returns {{ caller_name: string, callee_name: string }[]}
247
269
  */
248
270
  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);
271
+ return cachedStmt(
272
+ _findIntraFileCallEdgesStmt,
273
+ db,
274
+ `SELECT caller.name AS caller_name, callee.name AS callee_name
275
+ FROM edges e
276
+ JOIN nodes caller ON e.source_id = caller.id
277
+ JOIN nodes callee ON e.target_id = callee.id
278
+ WHERE caller.file = ? AND callee.file = ? AND e.kind = 'calls'
279
+ ORDER BY caller.line`,
280
+ ).all(file, file);
259
281
  }
@@ -1,3 +1,10 @@
1
+ import { cachedStmt } from './cached-stmt.js';
2
+
3
+ // ─── Statement caches (one prepared statement per db instance) ────────────
4
+ const _hasEmbeddingsStmt = new WeakMap();
5
+ const _getEmbeddingCountStmt = new WeakMap();
6
+ const _getEmbeddingMetaStmt = new WeakMap();
7
+
1
8
  /**
2
9
  * Check whether the embeddings table has data.
3
10
  * @param {object} db
@@ -5,7 +12,7 @@
5
12
  */
6
13
  export function hasEmbeddings(db) {
7
14
  try {
8
- return !!db.prepare('SELECT 1 FROM embeddings LIMIT 1').get();
15
+ return !!cachedStmt(_hasEmbeddingsStmt, db, 'SELECT 1 FROM embeddings LIMIT 1').get();
9
16
  } catch {
10
17
  return false;
11
18
  }
@@ -18,7 +25,7 @@ export function hasEmbeddings(db) {
18
25
  */
19
26
  export function getEmbeddingCount(db) {
20
27
  try {
21
- return db.prepare('SELECT COUNT(*) AS c FROM embeddings').get().c;
28
+ return cachedStmt(_getEmbeddingCountStmt, db, 'SELECT COUNT(*) AS c FROM embeddings').get().c;
22
29
  } catch {
23
30
  return 0;
24
31
  }
@@ -32,7 +39,11 @@ export function getEmbeddingCount(db) {
32
39
  */
33
40
  export function getEmbeddingMeta(db, key) {
34
41
  try {
35
- const row = db.prepare('SELECT value FROM embedding_meta WHERE key = ?').get(key);
42
+ const row = cachedStmt(
43
+ _getEmbeddingMetaStmt,
44
+ db,
45
+ 'SELECT value FROM embedding_meta WHERE key = ?',
46
+ ).get(key);
36
47
  return row?.value;
37
48
  } catch {
38
49
  return undefined;
@@ -1,12 +1,25 @@
1
+ import { CORE_SYMBOL_KINDS } from '../../kinds.js';
2
+ import { cachedStmt } from './cached-stmt.js';
3
+
4
+ // ─── Statement caches (one prepared statement per db instance) ────────────
5
+ const _getCallableNodesStmt = new WeakMap();
6
+ const _getCallEdgesStmt = new WeakMap();
7
+ const _getFileNodesAllStmt = new WeakMap();
8
+ const _getImportEdgesStmt = new WeakMap();
9
+
10
+ const CALLABLE_KINDS_SQL = CORE_SYMBOL_KINDS.map((k) => `'${k}'`).join(',');
11
+
1
12
  /**
2
- * Get callable nodes (function/method/class) for community detection.
13
+ * Get callable nodes (all core symbol kinds) for graph construction.
3
14
  * @param {object} db
4
15
  * @returns {{ id: number, name: string, kind: string, file: string }[]}
5
16
  */
6
17
  export function getCallableNodes(db) {
7
- return db
8
- .prepare("SELECT id, name, kind, file FROM nodes WHERE kind IN ('function','method','class')")
9
- .all();
18
+ return cachedStmt(
19
+ _getCallableNodesStmt,
20
+ db,
21
+ `SELECT id, name, kind, file FROM nodes WHERE kind IN (${CALLABLE_KINDS_SQL})`,
22
+ ).all();
10
23
  }
11
24
 
12
25
  /**
@@ -15,7 +28,11 @@ export function getCallableNodes(db) {
15
28
  * @returns {{ source_id: number, target_id: number }[]}
16
29
  */
17
30
  export function getCallEdges(db) {
18
- return db.prepare("SELECT source_id, target_id FROM edges WHERE kind = 'calls'").all();
31
+ return cachedStmt(
32
+ _getCallEdgesStmt,
33
+ db,
34
+ "SELECT source_id, target_id FROM edges WHERE kind = 'calls'",
35
+ ).all();
19
36
  }
20
37
 
21
38
  /**
@@ -24,7 +41,11 @@ export function getCallEdges(db) {
24
41
  * @returns {{ id: number, name: string, file: string }[]}
25
42
  */
26
43
  export function getFileNodesAll(db) {
27
- return db.prepare("SELECT id, name, file FROM nodes WHERE kind = 'file'").all();
44
+ return cachedStmt(
45
+ _getFileNodesAllStmt,
46
+ db,
47
+ "SELECT id, name, file FROM nodes WHERE kind = 'file'",
48
+ ).all();
28
49
  }
29
50
 
30
51
  /**
@@ -33,7 +54,9 @@ export function getFileNodesAll(db) {
33
54
  * @returns {{ source_id: number, target_id: number }[]}
34
55
  */
35
56
  export function getImportEdges(db) {
36
- return db
37
- .prepare("SELECT source_id, target_id FROM edges WHERE kind IN ('imports','imports-type')")
38
- .all();
57
+ return cachedStmt(
58
+ _getImportEdgesStmt,
59
+ db,
60
+ "SELECT source_id, target_id FROM edges WHERE kind IN ('imports','imports-type')",
61
+ ).all();
39
62
  }