@optave/codegraph 3.8.0 → 3.8.1

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 (296) hide show
  1. package/README.md +9 -8
  2. package/dist/ast-analysis/engine.d.ts.map +1 -1
  3. package/dist/ast-analysis/engine.js +95 -87
  4. package/dist/ast-analysis/engine.js.map +1 -1
  5. package/dist/ast-analysis/metrics.d.ts +0 -3
  6. package/dist/ast-analysis/metrics.d.ts.map +1 -1
  7. package/dist/ast-analysis/metrics.js +30 -13
  8. package/dist/ast-analysis/metrics.js.map +1 -1
  9. package/dist/ast-analysis/shared.d.ts.map +1 -1
  10. package/dist/ast-analysis/shared.js +24 -19
  11. package/dist/ast-analysis/shared.js.map +1 -1
  12. package/dist/ast-analysis/visitor-utils.d.ts.map +1 -1
  13. package/dist/ast-analysis/visitor-utils.js +55 -39
  14. package/dist/ast-analysis/visitor-utils.js.map +1 -1
  15. package/dist/ast-analysis/visitor.d.ts.map +1 -1
  16. package/dist/ast-analysis/visitor.js +91 -70
  17. package/dist/ast-analysis/visitor.js.map +1 -1
  18. package/dist/ast-analysis/visitors/ast-store-visitor.d.ts.map +1 -1
  19. package/dist/ast-analysis/visitors/ast-store-visitor.js +54 -58
  20. package/dist/ast-analysis/visitors/ast-store-visitor.js.map +1 -1
  21. package/dist/ast-analysis/visitors/complexity-visitor.d.ts.map +1 -1
  22. package/dist/ast-analysis/visitors/complexity-visitor.js +32 -39
  23. package/dist/ast-analysis/visitors/complexity-visitor.js.map +1 -1
  24. package/dist/ast-analysis/visitors/dataflow-visitor.d.ts.map +1 -1
  25. package/dist/ast-analysis/visitors/dataflow-visitor.js +57 -38
  26. package/dist/ast-analysis/visitors/dataflow-visitor.js.map +1 -1
  27. package/dist/cli/commands/watch.d.ts.map +1 -1
  28. package/dist/cli/commands/watch.js +16 -2
  29. package/dist/cli/commands/watch.js.map +1 -1
  30. package/dist/db/connection.d.ts.map +1 -1
  31. package/dist/db/connection.js +29 -26
  32. package/dist/db/connection.js.map +1 -1
  33. package/dist/db/query-builder.d.ts.map +1 -1
  34. package/dist/db/query-builder.js +16 -5
  35. package/dist/db/query-builder.js.map +1 -1
  36. package/dist/db/repository/base.d.ts +10 -0
  37. package/dist/db/repository/base.d.ts.map +1 -1
  38. package/dist/db/repository/base.js +17 -0
  39. package/dist/db/repository/base.js.map +1 -1
  40. package/dist/db/repository/native-repository.d.ts +6 -1
  41. package/dist/db/repository/native-repository.d.ts.map +1 -1
  42. package/dist/db/repository/native-repository.js +77 -1
  43. package/dist/db/repository/native-repository.js.map +1 -1
  44. package/dist/db/repository/nodes.d.ts.map +1 -1
  45. package/dist/db/repository/nodes.js +8 -4
  46. package/dist/db/repository/nodes.js.map +1 -1
  47. package/dist/db/repository/sqlite-repository.d.ts +3 -0
  48. package/dist/db/repository/sqlite-repository.d.ts.map +1 -1
  49. package/dist/db/repository/sqlite-repository.js +26 -0
  50. package/dist/db/repository/sqlite-repository.js.map +1 -1
  51. package/dist/domain/analysis/brief.d.ts.map +1 -1
  52. package/dist/domain/analysis/brief.js +13 -17
  53. package/dist/domain/analysis/brief.js.map +1 -1
  54. package/dist/domain/analysis/context.d.ts.map +1 -1
  55. package/dist/domain/analysis/context.js +14 -11
  56. package/dist/domain/analysis/context.js.map +1 -1
  57. package/dist/domain/analysis/dependencies.d.ts.map +1 -1
  58. package/dist/domain/analysis/dependencies.js +53 -52
  59. package/dist/domain/analysis/dependencies.js.map +1 -1
  60. package/dist/domain/analysis/fn-impact.d.ts +2 -7
  61. package/dist/domain/analysis/fn-impact.d.ts.map +1 -1
  62. package/dist/domain/analysis/fn-impact.js +33 -31
  63. package/dist/domain/analysis/fn-impact.js.map +1 -1
  64. package/dist/domain/analysis/implementations.d.ts.map +1 -1
  65. package/dist/domain/analysis/implementations.js +11 -19
  66. package/dist/domain/analysis/implementations.js.map +1 -1
  67. package/dist/domain/analysis/module-map.d.ts.map +1 -1
  68. package/dist/domain/analysis/module-map.js +55 -76
  69. package/dist/domain/analysis/module-map.js.map +1 -1
  70. package/dist/domain/analysis/query-helpers.d.ts +7 -0
  71. package/dist/domain/analysis/query-helpers.d.ts.map +1 -1
  72. package/dist/domain/analysis/query-helpers.js +15 -1
  73. package/dist/domain/analysis/query-helpers.js.map +1 -1
  74. package/dist/domain/graph/builder/pipeline.d.ts.map +1 -1
  75. package/dist/domain/graph/builder/pipeline.js +255 -105
  76. package/dist/domain/graph/builder/pipeline.js.map +1 -1
  77. package/dist/domain/graph/builder/stages/build-edges.d.ts.map +1 -1
  78. package/dist/domain/graph/builder/stages/build-edges.js +22 -17
  79. package/dist/domain/graph/builder/stages/build-edges.js.map +1 -1
  80. package/dist/domain/graph/builder/stages/detect-changes.js +2 -2
  81. package/dist/domain/graph/builder/stages/detect-changes.js.map +1 -1
  82. package/dist/domain/graph/builder/stages/finalize.js +2 -2
  83. package/dist/domain/graph/builder/stages/finalize.js.map +1 -1
  84. package/dist/domain/graph/builder/stages/insert-nodes.d.ts.map +1 -1
  85. package/dist/domain/graph/builder/stages/insert-nodes.js +32 -21
  86. package/dist/domain/graph/builder/stages/insert-nodes.js.map +1 -1
  87. package/dist/domain/graph/builder/stages/resolve-imports.d.ts.map +1 -1
  88. package/dist/domain/graph/builder/stages/resolve-imports.js +95 -84
  89. package/dist/domain/graph/builder/stages/resolve-imports.js.map +1 -1
  90. package/dist/domain/graph/cycles.d.ts +6 -0
  91. package/dist/domain/graph/cycles.d.ts.map +1 -1
  92. package/dist/domain/graph/cycles.js +114 -22
  93. package/dist/domain/graph/cycles.js.map +1 -1
  94. package/dist/domain/graph/resolve.js +1 -1
  95. package/dist/domain/graph/resolve.js.map +1 -1
  96. package/dist/domain/graph/watcher.d.ts +2 -0
  97. package/dist/domain/graph/watcher.d.ts.map +1 -1
  98. package/dist/domain/graph/watcher.js +170 -75
  99. package/dist/domain/graph/watcher.js.map +1 -1
  100. package/dist/domain/parser.d.ts +0 -5
  101. package/dist/domain/parser.d.ts.map +1 -1
  102. package/dist/domain/parser.js +13 -28
  103. package/dist/domain/parser.js.map +1 -1
  104. package/dist/domain/search/generator.js +1 -1
  105. package/dist/domain/search/generator.js.map +1 -1
  106. package/dist/domain/search/models.d.ts +4 -3
  107. package/dist/domain/search/models.d.ts.map +1 -1
  108. package/dist/domain/search/models.js +18 -5
  109. package/dist/domain/search/models.js.map +1 -1
  110. package/dist/domain/search/search/hybrid.d.ts.map +1 -1
  111. package/dist/domain/search/search/hybrid.js +29 -18
  112. package/dist/domain/search/search/hybrid.js.map +1 -1
  113. package/dist/extractors/go.js +36 -33
  114. package/dist/extractors/go.js.map +1 -1
  115. package/dist/extractors/helpers.d.ts.map +1 -1
  116. package/dist/extractors/helpers.js +40 -29
  117. package/dist/extractors/helpers.js.map +1 -1
  118. package/dist/extractors/java.js +58 -46
  119. package/dist/extractors/java.js.map +1 -1
  120. package/dist/extractors/javascript.js +46 -45
  121. package/dist/extractors/javascript.js.map +1 -1
  122. package/dist/extractors/kotlin.js +84 -78
  123. package/dist/extractors/kotlin.js.map +1 -1
  124. package/dist/extractors/python.js +29 -24
  125. package/dist/extractors/python.js.map +1 -1
  126. package/dist/extractors/rust.js +41 -32
  127. package/dist/extractors/rust.js.map +1 -1
  128. package/dist/extractors/solidity.js +58 -67
  129. package/dist/extractors/solidity.js.map +1 -1
  130. package/dist/extractors/swift.js +83 -81
  131. package/dist/extractors/swift.js.map +1 -1
  132. package/dist/extractors/zig.js +58 -60
  133. package/dist/extractors/zig.js.map +1 -1
  134. package/dist/features/ast.d.ts +16 -14
  135. package/dist/features/ast.d.ts.map +1 -1
  136. package/dist/features/ast.js +83 -81
  137. package/dist/features/ast.js.map +1 -1
  138. package/dist/features/audit.d.ts.map +1 -1
  139. package/dist/features/audit.js +8 -6
  140. package/dist/features/audit.js.map +1 -1
  141. package/dist/features/branch-compare.d.ts.map +1 -1
  142. package/dist/features/branch-compare.js +69 -72
  143. package/dist/features/branch-compare.js.map +1 -1
  144. package/dist/features/communities.d.ts.map +1 -1
  145. package/dist/features/communities.js +19 -7
  146. package/dist/features/communities.js.map +1 -1
  147. package/dist/features/complexity.d.ts.map +1 -1
  148. package/dist/features/complexity.js +120 -125
  149. package/dist/features/complexity.js.map +1 -1
  150. package/dist/features/dataflow.d.ts.map +1 -1
  151. package/dist/features/dataflow.js +136 -137
  152. package/dist/features/dataflow.js.map +1 -1
  153. package/dist/features/flow.d.ts.map +1 -1
  154. package/dist/features/flow.js +84 -79
  155. package/dist/features/flow.js.map +1 -1
  156. package/dist/features/structure-query.d.ts.map +1 -1
  157. package/dist/features/structure-query.js +69 -65
  158. package/dist/features/structure-query.js.map +1 -1
  159. package/dist/graph/algorithms/leiden/optimiser.d.ts.map +1 -1
  160. package/dist/graph/algorithms/leiden/optimiser.js +70 -55
  161. package/dist/graph/algorithms/leiden/optimiser.js.map +1 -1
  162. package/dist/graph/algorithms/leiden/partition.d.ts.map +1 -1
  163. package/dist/graph/algorithms/leiden/partition.js +288 -266
  164. package/dist/graph/algorithms/leiden/partition.js.map +1 -1
  165. package/dist/graph/model.d.ts.map +1 -1
  166. package/dist/graph/model.js +5 -1
  167. package/dist/graph/model.js.map +1 -1
  168. package/dist/infrastructure/config.d.ts.map +1 -1
  169. package/dist/infrastructure/config.js +6 -4
  170. package/dist/infrastructure/config.js.map +1 -1
  171. package/dist/infrastructure/suppress.d.ts +25 -0
  172. package/dist/infrastructure/suppress.d.ts.map +1 -0
  173. package/dist/infrastructure/suppress.js +43 -0
  174. package/dist/infrastructure/suppress.js.map +1 -0
  175. package/dist/mcp/server.d.ts.map +1 -1
  176. package/dist/mcp/server.js +29 -24
  177. package/dist/mcp/server.js.map +1 -1
  178. package/dist/presentation/dataflow.d.ts.map +1 -1
  179. package/dist/presentation/dataflow.js +47 -38
  180. package/dist/presentation/dataflow.js.map +1 -1
  181. package/dist/presentation/diff-impact-mermaid.d.ts.map +1 -1
  182. package/dist/presentation/diff-impact-mermaid.js +60 -51
  183. package/dist/presentation/diff-impact-mermaid.js.map +1 -1
  184. package/dist/presentation/queries-cli/exports.d.ts.map +1 -1
  185. package/dist/presentation/queries-cli/exports.js +20 -14
  186. package/dist/presentation/queries-cli/exports.js.map +1 -1
  187. package/dist/presentation/queries-cli/impact.d.ts.map +1 -1
  188. package/dist/presentation/queries-cli/impact.js +15 -13
  189. package/dist/presentation/queries-cli/impact.js.map +1 -1
  190. package/dist/presentation/queries-cli/inspect.d.ts.map +1 -1
  191. package/dist/presentation/queries-cli/inspect.js +101 -79
  192. package/dist/presentation/queries-cli/inspect.js.map +1 -1
  193. package/dist/presentation/queries-cli/overview.d.ts.map +1 -1
  194. package/dist/presentation/queries-cli/overview.js +25 -16
  195. package/dist/presentation/queries-cli/overview.js.map +1 -1
  196. package/dist/presentation/queries-cli/path.js +26 -20
  197. package/dist/presentation/queries-cli/path.js.map +1 -1
  198. package/dist/presentation/result-formatter.d.ts +10 -0
  199. package/dist/presentation/result-formatter.d.ts.map +1 -1
  200. package/dist/presentation/result-formatter.js +16 -1
  201. package/dist/presentation/result-formatter.js.map +1 -1
  202. package/dist/presentation/viewer.d.ts.map +1 -1
  203. package/dist/presentation/viewer.js +18 -12
  204. package/dist/presentation/viewer.js.map +1 -1
  205. package/dist/shared/errors.d.ts +5 -0
  206. package/dist/shared/errors.d.ts.map +1 -1
  207. package/dist/shared/errors.js +5 -0
  208. package/dist/shared/errors.js.map +1 -1
  209. package/dist/shared/hierarchy.d.ts +8 -2
  210. package/dist/shared/hierarchy.d.ts.map +1 -1
  211. package/dist/shared/hierarchy.js +42 -1
  212. package/dist/shared/hierarchy.js.map +1 -1
  213. package/dist/shared/normalize.d.ts +6 -1
  214. package/dist/shared/normalize.d.ts.map +1 -1
  215. package/dist/shared/normalize.js +20 -12
  216. package/dist/shared/normalize.js.map +1 -1
  217. package/dist/shared/paginate.d.ts +0 -9
  218. package/dist/shared/paginate.d.ts.map +1 -1
  219. package/dist/shared/paginate.js +0 -15
  220. package/dist/shared/paginate.js.map +1 -1
  221. package/dist/types.d.ts +10 -4
  222. package/dist/types.d.ts.map +1 -1
  223. package/package.json +7 -7
  224. package/src/ast-analysis/engine.ts +126 -105
  225. package/src/ast-analysis/metrics.ts +33 -11
  226. package/src/ast-analysis/shared.ts +33 -24
  227. package/src/ast-analysis/visitor-utils.ts +52 -32
  228. package/src/ast-analysis/visitor.ts +132 -71
  229. package/src/ast-analysis/visitors/ast-store-visitor.ts +53 -50
  230. package/src/ast-analysis/visitors/complexity-visitor.ts +35 -40
  231. package/src/ast-analysis/visitors/dataflow-visitor.ts +87 -43
  232. package/src/cli/commands/watch.ts +16 -2
  233. package/src/db/connection.ts +29 -28
  234. package/src/db/query-builder.ts +15 -3
  235. package/src/db/repository/base.ts +20 -0
  236. package/src/db/repository/native-repository.ts +79 -1
  237. package/src/db/repository/nodes.ts +13 -8
  238. package/src/db/repository/sqlite-repository.ts +29 -0
  239. package/src/domain/analysis/brief.ts +15 -25
  240. package/src/domain/analysis/context.ts +17 -10
  241. package/src/domain/analysis/dependencies.ts +67 -76
  242. package/src/domain/analysis/fn-impact.ts +36 -43
  243. package/src/domain/analysis/implementations.ts +11 -17
  244. package/src/domain/analysis/module-map.ts +58 -92
  245. package/src/domain/analysis/query-helpers.ts +18 -1
  246. package/src/domain/graph/builder/pipeline.ts +286 -97
  247. package/src/domain/graph/builder/stages/build-edges.ts +22 -18
  248. package/src/domain/graph/builder/stages/detect-changes.ts +2 -2
  249. package/src/domain/graph/builder/stages/finalize.ts +2 -2
  250. package/src/domain/graph/builder/stages/insert-nodes.ts +59 -34
  251. package/src/domain/graph/builder/stages/resolve-imports.ts +122 -100
  252. package/src/domain/graph/cycles.ts +110 -23
  253. package/src/domain/graph/resolve.ts +1 -1
  254. package/src/domain/graph/watcher.ts +202 -96
  255. package/src/domain/parser.ts +14 -26
  256. package/src/domain/search/generator.ts +1 -1
  257. package/src/domain/search/models.ts +17 -4
  258. package/src/domain/search/search/hybrid.ts +69 -51
  259. package/src/extractors/go.ts +43 -33
  260. package/src/extractors/helpers.ts +37 -23
  261. package/src/extractors/java.ts +66 -47
  262. package/src/extractors/javascript.ts +45 -46
  263. package/src/extractors/kotlin.ts +84 -77
  264. package/src/extractors/python.ts +31 -25
  265. package/src/extractors/rust.ts +37 -29
  266. package/src/extractors/solidity.ts +57 -61
  267. package/src/extractors/swift.ts +81 -80
  268. package/src/extractors/zig.ts +58 -61
  269. package/src/features/ast.ts +130 -110
  270. package/src/features/audit.ts +8 -6
  271. package/src/features/branch-compare.ts +105 -79
  272. package/src/features/communities.ts +25 -10
  273. package/src/features/complexity.ts +171 -134
  274. package/src/features/dataflow.ts +165 -175
  275. package/src/features/flow.ts +129 -92
  276. package/src/features/structure-query.ts +79 -64
  277. package/src/graph/algorithms/leiden/optimiser.ts +99 -55
  278. package/src/graph/algorithms/leiden/partition.ts +359 -294
  279. package/src/graph/model.ts +6 -1
  280. package/src/infrastructure/config.ts +6 -4
  281. package/src/infrastructure/suppress.ts +47 -0
  282. package/src/mcp/server.ts +53 -37
  283. package/src/presentation/dataflow.ts +50 -44
  284. package/src/presentation/diff-impact-mermaid.ts +104 -62
  285. package/src/presentation/queries-cli/exports.ts +21 -13
  286. package/src/presentation/queries-cli/impact.ts +15 -13
  287. package/src/presentation/queries-cli/inspect.ts +100 -81
  288. package/src/presentation/queries-cli/overview.ts +26 -16
  289. package/src/presentation/queries-cli/path.ts +33 -25
  290. package/src/presentation/result-formatter.ts +19 -1
  291. package/src/presentation/viewer.ts +42 -14
  292. package/src/shared/errors.ts +6 -0
  293. package/src/shared/hierarchy.ts +50 -2
  294. package/src/shared/normalize.ts +31 -12
  295. package/src/shared/paginate.ts +0 -17
  296. package/src/types.ts +24 -4
@@ -1,11 +1,4 @@
1
- import {
2
- findCallees,
3
- findCallers,
4
- findFileNodes,
5
- findImportSources,
6
- findImportTargets,
7
- findNodesByFile,
8
- } from '../../db/index.js';
1
+ import { findFileNodes, type Repository } from '../../db/index.js';
9
2
  import { cachedStmt } from '../../db/repository/cached-stmt.js';
10
3
  import { isTestFile } from '../../infrastructure/test-filter.js';
11
4
  import { resolveMethodViaHierarchy } from '../../shared/hierarchy.js';
@@ -18,13 +11,11 @@ import type {
18
11
  RelatedNodeRow,
19
12
  StmtCache,
20
13
  } from '../../types.js';
21
- import { withReadonlyDb } from './query-helpers.js';
14
+ import { withReadonlyDb, withRepo } from './query-helpers.js';
22
15
  import { findMatchingNodes } from './symbol-lookup.js';
23
16
 
24
- type UpstreamRow = { id: number; name: string; kind: string; file: string; line: number };
25
17
  type NodeByIdRow = { name: string; kind: string; file: string; line: number };
26
18
 
27
- const _upstreamStmtCache: StmtCache<UpstreamRow> = new WeakMap();
28
19
  const _nodeByIdStmtCache: StmtCache<NodeByIdRow> = new WeakMap();
29
20
 
30
21
  export function fileDepsData(
@@ -32,21 +23,21 @@ export function fileDepsData(
32
23
  customDbPath: string,
33
24
  opts: { noTests?: boolean; limit?: number; offset?: number } = {},
34
25
  ) {
35
- return withReadonlyDb(customDbPath, (db) => {
26
+ return withRepo(customDbPath, (repo) => {
36
27
  const noTests = opts.noTests || false;
37
- const fileNodes = findFileNodes(db, `%${file}%`) as NodeRow[];
28
+ const fileNodes = repo.findFileNodes(`%${file}%`) as NodeRow[];
38
29
  if (fileNodes.length === 0) {
39
30
  return { file, results: [] };
40
31
  }
41
32
 
42
33
  const results = fileNodes.map((fn) => {
43
- let importsTo = findImportTargets(db, fn.id) as ImportEdgeRow[];
34
+ let importsTo = repo.findImportTargets(fn.id) as ImportEdgeRow[];
44
35
  if (noTests) importsTo = importsTo.filter((i) => !isTestFile(i.file));
45
36
 
46
- let importedBy = findImportSources(db, fn.id) as ImportEdgeRow[];
37
+ let importedBy = repo.findImportSources(fn.id) as ImportEdgeRow[];
47
38
  if (noTests) importedBy = importedBy.filter((i) => !isTestFile(i.file));
48
39
 
49
- const defs = findNodesByFile(db, fn.file) as NodeRow[];
40
+ const defs = repo.findNodesByFile(fn.file) as NodeRow[];
50
41
 
51
42
  return {
52
43
  file: fn.file,
@@ -64,9 +55,11 @@ export function fileDepsData(
64
55
  /**
65
56
  * BFS transitive caller traversal starting from `callers` of `nodeId`.
66
57
  * Returns an object keyed by depth (2..depth) -> array of caller descriptors.
58
+ *
59
+ * Uses Repository.findCallers() so it works with both native and WASM engines.
67
60
  */
68
61
  function buildTransitiveCallers(
69
- db: BetterSqlite3Database,
62
+ repo: InstanceType<typeof Repository>,
70
63
  callers: Array<{ id: number; name: string; kind: string; file: string; line: number }>,
71
64
  nodeId: number,
72
65
  depth: number,
@@ -81,28 +74,12 @@ function buildTransitiveCallers(
81
74
  const visited = new Set([nodeId]);
82
75
  let frontier = callers;
83
76
 
84
- const upstreamStmt = cachedStmt(
85
- _upstreamStmtCache,
86
- db,
87
- `
88
- SELECT n.id, n.name, n.kind, n.file, n.line
89
- FROM edges e JOIN nodes n ON e.source_id = n.id
90
- WHERE e.target_id = ? AND e.kind = 'calls'
91
- `,
92
- );
93
-
94
77
  for (let d = 2; d <= depth; d++) {
95
78
  const nextFrontier: typeof frontier = [];
96
79
  for (const f of frontier) {
97
80
  if (visited.has(f.id)) continue;
98
81
  visited.add(f.id);
99
- const upstream = upstreamStmt.all(f.id) as Array<{
100
- id: number;
101
- name: string;
102
- kind: string;
103
- file: string;
104
- line: number;
105
- }>;
82
+ const upstream = repo.findCallers(f.id) as RelatedNodeRow[];
106
83
  for (const u of upstream) {
107
84
  if (noTests && isTestFile(u.file)) continue;
108
85
  if (!visited.has(u.id)) {
@@ -125,6 +102,59 @@ function buildTransitiveCallers(
125
102
  return transitiveCallers;
126
103
  }
127
104
 
105
+ function collectCallersWithHierarchy(
106
+ repo: InstanceType<typeof Repository>,
107
+ node: NodeRow,
108
+ noTests: boolean,
109
+ ): Array<RelatedNodeRow & { viaHierarchy?: string }> {
110
+ let callers: Array<RelatedNodeRow & { viaHierarchy?: string }> = repo.findCallers(
111
+ node.id,
112
+ ) as RelatedNodeRow[];
113
+
114
+ if (node.kind === 'method' && node.name.includes('.')) {
115
+ const methodName = node.name.split('.').pop()!;
116
+ const relatedMethods = resolveMethodViaHierarchy(repo, methodName);
117
+ for (const rm of relatedMethods) {
118
+ if (rm.id === node.id) continue;
119
+ const extraCallers = repo.findCallers(rm.id) as RelatedNodeRow[];
120
+ callers.push(...extraCallers.map((c) => ({ ...c, viaHierarchy: rm.name })));
121
+ }
122
+ }
123
+ if (noTests) callers = callers.filter((c) => !isTestFile(c.file));
124
+ return callers;
125
+ }
126
+
127
+ function buildNodeDepsResult(
128
+ repo: InstanceType<typeof Repository>,
129
+ node: NodeRow,
130
+ hc: Map<string, string | null>,
131
+ depth: number,
132
+ noTests: boolean,
133
+ ) {
134
+ const callees = repo.findCallees(node.id) as RelatedNodeRow[];
135
+ const filteredCallees = noTests ? callees.filter((c) => !isTestFile(c.file)) : callees;
136
+ const callers = collectCallersWithHierarchy(repo, node, noTests);
137
+ const transitiveCallers = buildTransitiveCallers(repo, callers, node.id, depth, noTests);
138
+
139
+ return {
140
+ ...normalizeSymbol(node, repo, hc),
141
+ callees: filteredCallees.map((c) => ({
142
+ name: c.name,
143
+ kind: c.kind,
144
+ file: c.file,
145
+ line: c.line,
146
+ })),
147
+ callers: callers.map((c) => ({
148
+ name: c.name,
149
+ kind: c.kind,
150
+ file: c.file,
151
+ line: c.line,
152
+ viaHierarchy: c.viaHierarchy || undefined,
153
+ })),
154
+ transitiveCallers,
155
+ };
156
+ }
157
+
128
158
  export function fnDepsData(
129
159
  name: string,
130
160
  customDbPath: string,
@@ -137,56 +167,17 @@ export function fnDepsData(
137
167
  offset?: number;
138
168
  } = {},
139
169
  ) {
140
- return withReadonlyDb(customDbPath, (db) => {
170
+ return withRepo(customDbPath, (repo) => {
141
171
  const depth = opts.depth || 3;
142
172
  const noTests = opts.noTests || false;
143
173
  const hc = new Map();
144
174
 
145
- const nodes = findMatchingNodes(db, name, { noTests, file: opts.file, kind: opts.kind });
175
+ const nodes = findMatchingNodes(repo, name, { noTests, file: opts.file, kind: opts.kind });
146
176
  if (nodes.length === 0) {
147
177
  return { name, results: [] };
148
178
  }
149
179
 
150
- const results = nodes.map((node) => {
151
- const callees = findCallees(db, node.id) as RelatedNodeRow[];
152
- const filteredCallees = noTests ? callees.filter((c) => !isTestFile(c.file)) : callees;
153
-
154
- let callers: Array<RelatedNodeRow & { viaHierarchy?: string }> = findCallers(
155
- db,
156
- node.id,
157
- ) as RelatedNodeRow[];
158
-
159
- if (node.kind === 'method' && node.name.includes('.')) {
160
- const methodName = node.name.split('.').pop()!;
161
- const relatedMethods = resolveMethodViaHierarchy(db, methodName);
162
- for (const rm of relatedMethods) {
163
- if (rm.id === node.id) continue;
164
- const extraCallers = findCallers(db, rm.id) as RelatedNodeRow[];
165
- callers.push(...extraCallers.map((c) => ({ ...c, viaHierarchy: rm.name })));
166
- }
167
- }
168
- if (noTests) callers = callers.filter((c) => !isTestFile(c.file));
169
-
170
- const transitiveCallers = buildTransitiveCallers(db, callers, node.id, depth, noTests);
171
-
172
- return {
173
- ...normalizeSymbol(node, db, hc),
174
- callees: filteredCallees.map((c) => ({
175
- name: c.name,
176
- kind: c.kind,
177
- file: c.file,
178
- line: c.line,
179
- })),
180
- callers: callers.map((c) => ({
181
- name: c.name,
182
- kind: c.kind,
183
- file: c.file,
184
- line: c.line,
185
- viaHierarchy: c.viaHierarchy || undefined,
186
- })),
187
- transitiveCallers,
188
- };
189
- });
180
+ const results = nodes.map((node) => buildNodeDepsResult(repo, node, hc, depth, noTests));
190
181
 
191
182
  const base = { name, results };
192
183
  return paginateResult(base, 'results', { limit: opts.limit, offset: opts.offset });
@@ -1,40 +1,32 @@
1
- import {
2
- findDistinctCallers,
3
- findFileNodes,
4
- findImplementors,
5
- findImportDependents,
6
- findNodeById,
7
- } from '../../db/index.js';
1
+ import { Repository, SqliteRepository } from '../../db/index.js';
8
2
  import { isTestFile } from '../../infrastructure/test-filter.js';
9
3
  import { normalizeSymbol } from '../../shared/normalize.js';
10
4
  import { paginateResult } from '../../shared/paginate.js';
11
5
  import type { BetterSqlite3Database, NodeRow, RelatedNodeRow } from '../../types.js';
12
- import { resolveAnalysisOpts, withReadonlyDb } from './query-helpers.js';
6
+ import { resolveAnalysisOpts, withRepo } from './query-helpers.js';
13
7
  import { findMatchingNodes } from './symbol-lookup.js';
14
8
 
9
+ /** Cache so repeated raw-db calls reuse the same SqliteRepository (preserves per-instance memoization). */
10
+ const repoCache = new WeakMap<BetterSqlite3Database, InstanceType<typeof SqliteRepository>>();
11
+
12
+ /** Coerce a raw db handle or Repository into a Repository instance. */
13
+ function toRepo(
14
+ dbOrRepo: BetterSqlite3Database | InstanceType<typeof Repository>,
15
+ ): InstanceType<typeof Repository> {
16
+ if (dbOrRepo instanceof Repository) return dbOrRepo;
17
+ const db = dbOrRepo as BetterSqlite3Database;
18
+ let repo = repoCache.get(db);
19
+ if (!repo) {
20
+ repo = new SqliteRepository(db);
21
+ repoCache.set(db, repo);
22
+ }
23
+ return repo;
24
+ }
25
+
15
26
  // --- Shared BFS: transitive callers ---
16
27
 
17
28
  const INTERFACE_LIKE_KINDS = new Set(['interface', 'trait']);
18
29
 
19
- /**
20
- * Check whether the graph contains any 'implements' edges.
21
- * Cached per db handle so the query runs at most once per connection.
22
- */
23
- const _hasImplementsCache: WeakMap<BetterSqlite3Database, boolean> = new WeakMap();
24
- function hasImplementsEdges(db: BetterSqlite3Database): boolean {
25
- if (_hasImplementsCache.has(db)) return _hasImplementsCache.get(db)!;
26
- const row = db.prepare("SELECT 1 FROM edges WHERE kind = 'implements' LIMIT 1").get();
27
- const result = !!row;
28
- _hasImplementsCache.set(db, result);
29
- return result;
30
- }
31
-
32
- /**
33
- * BFS traversal to find transitive callers of a node.
34
- * When an interface/trait node is encountered (either as the start node or
35
- * during traversal), its concrete implementors are also added to the frontier
36
- * so that changes to an interface signature propagate to all implementors.
37
- */
38
30
  type BfsLevel = Array<{
39
31
  name: string;
40
32
  kind: string;
@@ -76,7 +68,7 @@ function recordImplementor(
76
68
 
77
69
  /** Expand implementors for an interface/trait node into the BFS frontier. */
78
70
  function expandImplementors(
79
- db: BetterSqlite3Database,
71
+ repo: InstanceType<typeof Repository>,
80
72
  nodeId: number,
81
73
  depth: number,
82
74
  visited: Set<number>,
@@ -85,14 +77,14 @@ function expandImplementors(
85
77
  noTests: boolean,
86
78
  onVisit?: BfsOnVisit,
87
79
  ): void {
88
- const impls = findImplementors(db, nodeId) as RelatedNodeRow[];
80
+ const impls = repo.findImplementors(nodeId) as RelatedNodeRow[];
89
81
  for (const impl of impls) {
90
82
  recordImplementor(impl, nodeId, depth, visited, frontier, levels, noTests, onVisit);
91
83
  }
92
84
  }
93
85
 
94
86
  export function bfsTransitiveCallers(
95
- db: BetterSqlite3Database,
87
+ dbOrRepo: BetterSqlite3Database | InstanceType<typeof Repository>,
96
88
  startId: number,
97
89
  {
98
90
  noTests = false,
@@ -106,7 +98,8 @@ export function bfsTransitiveCallers(
106
98
  onVisit?: BfsOnVisit;
107
99
  } = {},
108
100
  ) {
109
- const resolveImplementors = includeImplementors && hasImplementsEdges(db);
101
+ const repo = toRepo(dbOrRepo);
102
+ const resolveImplementors = includeImplementors && repo.hasImplementsEdges();
110
103
  const visited = new Set([startId]);
111
104
  const levels: BfsLevels = {};
112
105
  let frontier = [startId];
@@ -114,9 +107,9 @@ export function bfsTransitiveCallers(
114
107
  // Seed: if start node is an interface/trait, include its implementors at depth 1
115
108
  const implNextFrontier: number[] = [];
116
109
  if (resolveImplementors) {
117
- const startNode = findNodeById(db, startId) as NodeRow | undefined;
110
+ const startNode = repo.findNodeById(startId) as NodeRow | undefined;
118
111
  if (startNode && INTERFACE_LIKE_KINDS.has(startNode.kind)) {
119
- expandImplementors(db, startId, 1, visited, implNextFrontier, levels, noTests, onVisit);
112
+ expandImplementors(repo, startId, 1, visited, implNextFrontier, levels, noTests, onVisit);
120
113
  }
121
114
  }
122
115
 
@@ -126,7 +119,7 @@ export function bfsTransitiveCallers(
126
119
  }
127
120
  const nextFrontier: number[] = [];
128
121
  for (const fid of frontier) {
129
- const callers = findDistinctCallers(db, fid) as RelatedNodeRow[];
122
+ const callers = repo.findDistinctCallers(fid) as RelatedNodeRow[];
130
123
  for (const c of callers) {
131
124
  if (!visited.has(c.id) && (!noTests || !isTestFile(c.file))) {
132
125
  visited.add(c.id);
@@ -136,7 +129,7 @@ export function bfsTransitiveCallers(
136
129
  if (onVisit) onVisit(c, fid, d);
137
130
  }
138
131
  if (resolveImplementors && INTERFACE_LIKE_KINDS.has(c.kind)) {
139
- expandImplementors(db, c.id, d + 1, visited, nextFrontier, levels, noTests, onVisit);
132
+ expandImplementors(repo, c.id, d + 1, visited, nextFrontier, levels, noTests, onVisit);
140
133
  }
141
134
  }
142
135
  }
@@ -152,9 +145,9 @@ export function impactAnalysisData(
152
145
  customDbPath: string,
153
146
  opts: { noTests?: boolean } = {},
154
147
  ) {
155
- return withReadonlyDb(customDbPath, (db) => {
148
+ return withRepo(customDbPath, (repo) => {
156
149
  const noTests = opts.noTests || false;
157
- const fileNodes = findFileNodes(db, `%${file}%`) as NodeRow[];
150
+ const fileNodes = repo.findFileNodes(`%${file}%`) as NodeRow[];
158
151
  if (fileNodes.length === 0) {
159
152
  return { file, sources: [], levels: {}, totalDependents: 0 };
160
153
  }
@@ -172,7 +165,7 @@ export function impactAnalysisData(
172
165
  while (queue.length > 0) {
173
166
  const current = queue.shift()!;
174
167
  const level = levels.get(current)!;
175
- const dependents = findImportDependents(db, current) as RelatedNodeRow[];
168
+ const dependents = repo.findImportDependents(current) as RelatedNodeRow[];
176
169
  for (const dep of dependents) {
177
170
  if (!visited.has(dep.id) && (!noTests || !isTestFile(dep.file))) {
178
171
  visited.add(dep.id);
@@ -186,7 +179,7 @@ export function impactAnalysisData(
186
179
  for (const [id, level] of levels) {
187
180
  if (level === 0) continue;
188
181
  if (!byLevel[level]) byLevel[level] = [];
189
- const node = findNodeById(db, id) as NodeRow | undefined;
182
+ const node = repo.findNodeById(id) as NodeRow | undefined;
190
183
  if (node) byLevel[level].push({ file: node.file });
191
184
  }
192
185
 
@@ -213,12 +206,12 @@ export function fnImpactData(
213
206
  config?: any;
214
207
  } = {},
215
208
  ) {
216
- return withReadonlyDb(customDbPath, (db) => {
209
+ return withRepo(customDbPath, (repo) => {
217
210
  const { noTests, config } = resolveAnalysisOpts(opts);
218
211
  const maxDepth = opts.depth || config.analysis?.fnImpactDepth || 5;
219
212
  const hc = new Map();
220
213
 
221
- const nodes = findMatchingNodes(db, name, { noTests, file: opts.file, kind: opts.kind });
214
+ const nodes = findMatchingNodes(repo, name, { noTests, file: opts.file, kind: opts.kind });
222
215
  if (nodes.length === 0) {
223
216
  return { name, results: [] };
224
217
  }
@@ -226,13 +219,13 @@ export function fnImpactData(
226
219
  const includeImplementors = opts.includeImplementors !== false;
227
220
 
228
221
  const results = nodes.map((node) => {
229
- const { levels, totalDependents } = bfsTransitiveCallers(db, node.id, {
222
+ const { levels, totalDependents } = bfsTransitiveCallers(repo, node.id, {
230
223
  noTests,
231
224
  maxDepth,
232
225
  includeImplementors,
233
226
  });
234
227
  return {
235
- ...normalizeSymbol(node, db, hc),
228
+ ...normalizeSymbol(node, repo, hc),
236
229
  levels,
237
230
  totalDependents,
238
231
  };
@@ -1,9 +1,9 @@
1
- import { findImplementors, findInterfaces, openReadonlyOrFail } from '../../db/index.js';
2
1
  import { isTestFile } from '../../infrastructure/test-filter.js';
3
2
  import { CORE_SYMBOL_KINDS } from '../../shared/kinds.js';
4
3
  import { normalizeSymbol } from '../../shared/normalize.js';
5
4
  import { paginateResult } from '../../shared/paginate.js';
6
5
  import type { RelatedNodeRow } from '../../types.js';
6
+ import { withRepo } from './query-helpers.js';
7
7
  import { findMatchingNodes } from './symbol-lookup.js';
8
8
 
9
9
  /**
@@ -14,12 +14,11 @@ export function implementationsData(
14
14
  customDbPath: string,
15
15
  opts: { noTests?: boolean; file?: string; kind?: string; limit?: number; offset?: number } = {},
16
16
  ) {
17
- const db = openReadonlyOrFail(customDbPath);
18
- try {
17
+ return withRepo(customDbPath, (repo) => {
19
18
  const noTests = opts.noTests || false;
20
19
  const hc = new Map();
21
20
 
22
- const nodes = findMatchingNodes(db, name, {
21
+ const nodes = findMatchingNodes(repo, name, {
23
22
  noTests,
24
23
  file: opts.file,
25
24
  kind: opts.kind,
@@ -30,11 +29,11 @@ export function implementationsData(
30
29
  }
31
30
 
32
31
  const results = nodes.map((node) => {
33
- let implementors = findImplementors(db, node.id) as RelatedNodeRow[];
32
+ let implementors = repo.findImplementors(node.id) as RelatedNodeRow[];
34
33
  if (noTests) implementors = implementors.filter((n) => !isTestFile(n.file));
35
34
 
36
35
  return {
37
- ...normalizeSymbol(node, db, hc),
36
+ ...normalizeSymbol(node, repo, hc),
38
37
  implementors: implementors.map((impl) => ({
39
38
  name: impl.name,
40
39
  kind: impl.kind,
@@ -46,9 +45,7 @@ export function implementationsData(
46
45
 
47
46
  const base = { name, results };
48
47
  return paginateResult(base, 'results', { limit: opts.limit, offset: opts.offset });
49
- } finally {
50
- db.close();
51
- }
48
+ });
52
49
  }
53
50
 
54
51
  /**
@@ -59,12 +56,11 @@ export function interfacesData(
59
56
  customDbPath: string,
60
57
  opts: { noTests?: boolean; file?: string; kind?: string; limit?: number; offset?: number } = {},
61
58
  ) {
62
- const db = openReadonlyOrFail(customDbPath);
63
- try {
59
+ return withRepo(customDbPath, (repo) => {
64
60
  const noTests = opts.noTests || false;
65
61
  const hc = new Map();
66
62
 
67
- const nodes = findMatchingNodes(db, name, {
63
+ const nodes = findMatchingNodes(repo, name, {
68
64
  noTests,
69
65
  file: opts.file,
70
66
  kind: opts.kind,
@@ -75,11 +71,11 @@ export function interfacesData(
75
71
  }
76
72
 
77
73
  const results = nodes.map((node) => {
78
- let interfaces = findInterfaces(db, node.id) as RelatedNodeRow[];
74
+ let interfaces = repo.findInterfaces(node.id) as RelatedNodeRow[];
79
75
  if (noTests) interfaces = interfaces.filter((n) => !isTestFile(n.file));
80
76
 
81
77
  return {
82
- ...normalizeSymbol(node, db, hc),
78
+ ...normalizeSymbol(node, repo, hc),
83
79
  interfaces: interfaces.map((iface) => ({
84
80
  name: iface.name,
85
81
  kind: iface.kind,
@@ -91,7 +87,5 @@ export function interfacesData(
91
87
 
92
88
  const base = { name, results };
93
89
  return paginateResult(base, 'results', { limit: opts.limit, offset: opts.offset });
94
- } finally {
95
- db.close();
96
- }
90
+ });
97
91
  }