@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
@@ -0,0 +1,201 @@
1
+ /**
2
+ * Abstract Repository base class.
3
+ *
4
+ * Defines the contract for all graph data access. Every method throws
5
+ * "not implemented" by default — concrete subclasses override what they support.
6
+ */
7
+ export class Repository {
8
+ // ── Node lookups ────────────────────────────────────────────────────
9
+ /** @param {number} id @returns {object|undefined} */
10
+ findNodeById(_id) {
11
+ throw new Error('not implemented');
12
+ }
13
+
14
+ /** @param {string} file @returns {object[]} */
15
+ findNodesByFile(_file) {
16
+ throw new Error('not implemented');
17
+ }
18
+
19
+ /** @param {string} fileLike @returns {object[]} */
20
+ findFileNodes(_fileLike) {
21
+ throw new Error('not implemented');
22
+ }
23
+
24
+ /** @param {string} namePattern @param {object} [opts] @returns {object[]} */
25
+ findNodesWithFanIn(_namePattern, _opts) {
26
+ throw new Error('not implemented');
27
+ }
28
+
29
+ /** @returns {number} */
30
+ countNodes() {
31
+ throw new Error('not implemented');
32
+ }
33
+
34
+ /** @returns {number} */
35
+ countEdges() {
36
+ throw new Error('not implemented');
37
+ }
38
+
39
+ /** @returns {number} */
40
+ countFiles() {
41
+ throw new Error('not implemented');
42
+ }
43
+
44
+ /** @param {string} name @param {string} kind @param {string} file @param {number} line @returns {number|undefined} */
45
+ getNodeId(_name, _kind, _file, _line) {
46
+ throw new Error('not implemented');
47
+ }
48
+
49
+ /** @param {string} name @param {string} file @param {number} line @returns {number|undefined} */
50
+ getFunctionNodeId(_name, _file, _line) {
51
+ throw new Error('not implemented');
52
+ }
53
+
54
+ /** @param {string} file @returns {{ id: number, name: string, kind: string, line: number }[]} */
55
+ bulkNodeIdsByFile(_file) {
56
+ throw new Error('not implemented');
57
+ }
58
+
59
+ /** @param {number} parentId @returns {object[]} */
60
+ findNodeChildren(_parentId) {
61
+ throw new Error('not implemented');
62
+ }
63
+
64
+ /** @param {string} scopeName @param {object} [opts] @returns {object[]} */
65
+ findNodesByScope(_scopeName, _opts) {
66
+ throw new Error('not implemented');
67
+ }
68
+
69
+ /** @param {string} qualifiedName @param {object} [opts] @returns {object[]} */
70
+ findNodeByQualifiedName(_qualifiedName, _opts) {
71
+ throw new Error('not implemented');
72
+ }
73
+
74
+ /** @param {object} [opts] @returns {object[]} */
75
+ listFunctionNodes(_opts) {
76
+ throw new Error('not implemented');
77
+ }
78
+
79
+ /** @param {object} [opts] @returns {IterableIterator} */
80
+ iterateFunctionNodes(_opts) {
81
+ throw new Error('not implemented');
82
+ }
83
+
84
+ /** @param {object} [opts] @returns {object[]} */
85
+ findNodesForTriage(_opts) {
86
+ throw new Error('not implemented');
87
+ }
88
+
89
+ // ── Edge queries ────────────────────────────────────────────────────
90
+ /** @param {number} nodeId @returns {object[]} */
91
+ findCallees(_nodeId) {
92
+ throw new Error('not implemented');
93
+ }
94
+
95
+ /** @param {number} nodeId @returns {object[]} */
96
+ findCallers(_nodeId) {
97
+ throw new Error('not implemented');
98
+ }
99
+
100
+ /** @param {number} nodeId @returns {object[]} */
101
+ findDistinctCallers(_nodeId) {
102
+ throw new Error('not implemented');
103
+ }
104
+
105
+ /** @param {number} nodeId @returns {object[]} */
106
+ findAllOutgoingEdges(_nodeId) {
107
+ throw new Error('not implemented');
108
+ }
109
+
110
+ /** @param {number} nodeId @returns {object[]} */
111
+ findAllIncomingEdges(_nodeId) {
112
+ throw new Error('not implemented');
113
+ }
114
+
115
+ /** @param {number} nodeId @returns {string[]} */
116
+ findCalleeNames(_nodeId) {
117
+ throw new Error('not implemented');
118
+ }
119
+
120
+ /** @param {number} nodeId @returns {string[]} */
121
+ findCallerNames(_nodeId) {
122
+ throw new Error('not implemented');
123
+ }
124
+
125
+ /** @param {number} nodeId @returns {{ file: string, edge_kind: string }[]} */
126
+ findImportTargets(_nodeId) {
127
+ throw new Error('not implemented');
128
+ }
129
+
130
+ /** @param {number} nodeId @returns {{ file: string, edge_kind: string }[]} */
131
+ findImportSources(_nodeId) {
132
+ throw new Error('not implemented');
133
+ }
134
+
135
+ /** @param {number} nodeId @returns {object[]} */
136
+ findImportDependents(_nodeId) {
137
+ throw new Error('not implemented');
138
+ }
139
+
140
+ /** @param {string} file @returns {Set<number>} */
141
+ findCrossFileCallTargets(_file) {
142
+ throw new Error('not implemented');
143
+ }
144
+
145
+ /** @param {number} nodeId @param {string} file @returns {number} */
146
+ countCrossFileCallers(_nodeId, _file) {
147
+ throw new Error('not implemented');
148
+ }
149
+
150
+ /** @param {number} classNodeId @returns {Set<number>} */
151
+ getClassHierarchy(_classNodeId) {
152
+ throw new Error('not implemented');
153
+ }
154
+
155
+ /** @param {string} file @returns {{ caller_name: string, callee_name: string }[]} */
156
+ findIntraFileCallEdges(_file) {
157
+ throw new Error('not implemented');
158
+ }
159
+
160
+ // ── Graph-read queries ──────────────────────────────────────────────
161
+ /** @returns {{ id: number, name: string, kind: string, file: string }[]} */
162
+ getCallableNodes() {
163
+ throw new Error('not implemented');
164
+ }
165
+
166
+ /** @returns {{ source_id: number, target_id: number }[]} */
167
+ getCallEdges() {
168
+ throw new Error('not implemented');
169
+ }
170
+
171
+ /** @returns {{ id: number, name: string, file: string }[]} */
172
+ getFileNodesAll() {
173
+ throw new Error('not implemented');
174
+ }
175
+
176
+ /** @returns {{ source_id: number, target_id: number }[]} */
177
+ getImportEdges() {
178
+ throw new Error('not implemented');
179
+ }
180
+
181
+ // ── Optional table checks (default: false/undefined) ────────────────
182
+ /** @returns {boolean} */
183
+ hasCfgTables() {
184
+ throw new Error('not implemented');
185
+ }
186
+
187
+ /** @returns {boolean} */
188
+ hasEmbeddings() {
189
+ throw new Error('not implemented');
190
+ }
191
+
192
+ /** @returns {boolean} */
193
+ hasDataflowTable() {
194
+ throw new Error('not implemented');
195
+ }
196
+
197
+ /** @param {number} nodeId @returns {object|undefined} */
198
+ getComplexityForNode(_nodeId) {
199
+ throw new Error('not implemented');
200
+ }
201
+ }
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Resolve a cached prepared statement, compiling on first use per db.
3
+ * Each `cache` WeakMap must always be called with the same `sql` —
4
+ * the sql argument is only used on the first compile; subsequent calls
5
+ * return the cached statement regardless of the sql passed.
6
+ *
7
+ * @param {WeakMap} cache - WeakMap keyed by db instance
8
+ * @param {object} db - better-sqlite3 database instance
9
+ * @param {string} sql - SQL to compile on first use
10
+ * @returns {object} prepared statement
11
+ */
12
+ export function cachedStmt(cache, db, sql) {
13
+ let stmt = cache.get(db);
14
+ if (!stmt) {
15
+ stmt = db.prepare(sql);
16
+ cache.set(db, stmt);
17
+ }
18
+ return stmt;
19
+ }
@@ -1,5 +1,6 @@
1
+ import { cachedStmt } from './cached-stmt.js';
2
+
1
3
  // ─── 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
4
  const _getCfgBlocksStmt = new WeakMap();
4
5
  const _getCfgEdgesStmt = new WeakMap();
5
6
  const _deleteCfgEdgesStmt = new WeakMap();
@@ -26,16 +27,13 @@ export function hasCfgTables(db) {
26
27
  * @returns {object[]}
27
28
  */
28
29
  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);
30
+ return cachedStmt(
31
+ _getCfgBlocksStmt,
32
+ db,
33
+ `SELECT id, block_index, block_type, start_line, end_line, label
34
+ FROM cfg_blocks WHERE function_node_id = ?
35
+ ORDER BY block_index`,
36
+ ).all(functionNodeId);
39
37
  }
40
38
 
41
39
  /**
@@ -45,21 +43,18 @@ export function getCfgBlocks(db, functionNodeId) {
45
43
  * @returns {object[]}
46
44
  */
47
45
  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);
46
+ return cachedStmt(
47
+ _getCfgEdgesStmt,
48
+ db,
49
+ `SELECT e.kind,
50
+ sb.block_index AS source_index, sb.block_type AS source_type,
51
+ tb.block_index AS target_index, tb.block_type AS target_type
52
+ FROM cfg_edges e
53
+ JOIN cfg_blocks sb ON e.source_block_id = sb.id
54
+ JOIN cfg_blocks tb ON e.target_block_id = tb.id
55
+ WHERE e.function_node_id = ?
56
+ ORDER BY sb.block_index, tb.block_index`,
57
+ ).all(functionNodeId);
63
58
  }
64
59
 
65
60
  /**
@@ -68,16 +63,10 @@ export function getCfgEdges(db, functionNodeId) {
68
63
  * @param {number} functionNodeId
69
64
  */
70
65
  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);
66
+ cachedStmt(_deleteCfgEdgesStmt, db, 'DELETE FROM cfg_edges WHERE function_node_id = ?').run(
67
+ functionNodeId,
68
+ );
69
+ cachedStmt(_deleteCfgBlocksStmt, db, 'DELETE FROM cfg_blocks WHERE function_node_id = ?').run(
70
+ functionNodeId,
71
+ );
83
72
  }
@@ -1,3 +1,10 @@
1
+ import { cachedStmt } from './cached-stmt.js';
2
+
3
+ // ─── Statement caches (one prepared statement per db instance) ────────────
4
+ const _hasCoChangesStmt = new WeakMap();
5
+ const _getCoChangeMetaStmt = new WeakMap();
6
+ const _upsertCoChangeMetaStmt = new WeakMap();
7
+
1
8
  /**
2
9
  * Check whether the co_changes table has data.
3
10
  * @param {object} db
@@ -5,7 +12,7 @@
5
12
  */
6
13
  export function hasCoChanges(db) {
7
14
  try {
8
- return !!db.prepare('SELECT 1 FROM co_changes LIMIT 1').get();
15
+ return !!cachedStmt(_hasCoChangesStmt, db, 'SELECT 1 FROM co_changes LIMIT 1').get();
9
16
  } catch {
10
17
  return false;
11
18
  }
@@ -19,7 +26,11 @@ export function hasCoChanges(db) {
19
26
  export function getCoChangeMeta(db) {
20
27
  const meta = {};
21
28
  try {
22
- for (const row of db.prepare('SELECT key, value FROM co_change_meta').all()) {
29
+ for (const row of cachedStmt(
30
+ _getCoChangeMetaStmt,
31
+ db,
32
+ 'SELECT key, value FROM co_change_meta',
33
+ ).all()) {
23
34
  meta[row.key] = row.value;
24
35
  }
25
36
  } catch {
@@ -35,7 +46,9 @@ export function getCoChangeMeta(db) {
35
46
  * @param {string} value
36
47
  */
37
48
  export function upsertCoChangeMeta(db, key, value) {
38
- db.prepare(
49
+ cachedStmt(
50
+ _upsertCoChangeMetaStmt,
51
+ db,
39
52
  'INSERT INTO co_change_meta (key, value) VALUES (?, ?) ON CONFLICT(key) DO UPDATE SET value = excluded.value',
40
53
  ).run(key, value);
41
54
  }
@@ -1,3 +1,8 @@
1
+ import { cachedStmt } from './cached-stmt.js';
2
+
3
+ // ─── Statement caches (one prepared statement per db instance) ────────────
4
+ const _getComplexityForNodeStmt = new WeakMap();
5
+
1
6
  /**
2
7
  * Get complexity metrics for a node.
3
8
  * Used by contextData and explainFunctionImpl in queries.js.
@@ -6,10 +11,10 @@
6
11
  * @returns {{ cognitive: number, cyclomatic: number, max_nesting: number, maintainability_index: number, halstead_volume: number }|undefined}
7
12
  */
8
13
  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);
14
+ return cachedStmt(
15
+ _getComplexityForNodeStmt,
16
+ db,
17
+ `SELECT cognitive, cyclomatic, max_nesting, maintainability_index, halstead_volume
18
+ FROM function_complexity WHERE node_id = ?`,
19
+ ).get(nodeId);
15
20
  }
@@ -1,3 +1,8 @@
1
+ import { cachedStmt } from './cached-stmt.js';
2
+
3
+ // ─── Statement caches (one prepared statement per db instance) ────────────
4
+ const _hasDataflowTableStmt = new WeakMap();
5
+
1
6
  /**
2
7
  * Check whether the dataflow table exists and has data.
3
8
  * @param {object} db
@@ -5,7 +10,7 @@
5
10
  */
6
11
  export function hasDataflowTable(db) {
7
12
  try {
8
- return db.prepare('SELECT COUNT(*) AS c FROM dataflow').get().c > 0;
13
+ return cachedStmt(_hasDataflowTableStmt, db, 'SELECT COUNT(*) AS c FROM dataflow').get().c > 0;
9
14
  } catch {
10
15
  return false;
11
16
  }