@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,21 +1,13 @@
1
1
  import path from 'node:path';
2
2
  import { openReadonlyOrFail, openReadonlyWithNative, testFilterSQL } from '../../db/index.js';
3
- import { cachedStmt } from '../../db/repository/cached-stmt.js';
4
3
  import { loadConfig } from '../../infrastructure/config.js';
5
4
  import { debug } from '../../infrastructure/logger.js';
6
5
  import { isTestFile } from '../../infrastructure/test-filter.js';
7
6
  import { DEAD_ROLE_PREFIX } from '../../shared/kinds.js';
8
- import type { BetterSqlite3Database, StmtCache } from '../../types.js';
7
+ import type { BetterSqlite3Database } from '../../types.js';
9
8
  import { findCycles } from '../graph/cycles.js';
10
9
  import { LANGUAGE_REGISTRY } from '../parser.js';
11
10
 
12
- // ---------------------------------------------------------------------------
13
- // Statement caches (one prepared statement per db instance)
14
- // ---------------------------------------------------------------------------
15
-
16
- const _fileNodesStmtCache: StmtCache<{ id: number; file: string }> = new WeakMap();
17
- const _allNodesStmtCache: StmtCache<{ id: number; file: string }> = new WeakMap();
18
-
19
11
  export const FALSE_POSITIVE_NAMES = new Set([
20
12
  'run',
21
13
  'get',
@@ -52,48 +44,11 @@ export const FALSE_POSITIVE_CALLER_THRESHOLD = 20;
52
44
  // Section helpers
53
45
  // ---------------------------------------------------------------------------
54
46
 
55
- const _fileNodesStmt: StmtCache<{ id: number; file: string }> = new WeakMap();
56
- const _allNodesIdFileStmt: StmtCache<{ id: number; file: string }> = new WeakMap();
57
-
58
- function buildTestFileIds(db: BetterSqlite3Database): Set<number> {
59
- const allFileNodes = cachedStmt(
60
- _fileNodesStmt,
61
- db,
62
- "SELECT id, file FROM nodes WHERE kind = 'file'",
63
- ).all();
64
- const testFileIds = new Set<number>();
65
- const testFiles = new Set<string>();
66
- for (const n of allFileNodes) {
67
- if (isTestFile(n.file)) {
68
- testFileIds.add(n.id);
69
- testFiles.add(n.file);
70
- }
71
- }
72
- const allNodes = cachedStmt(_allNodesIdFileStmt, db, 'SELECT id, file FROM nodes').all();
73
- for (const n of allNodes) {
74
- if (testFiles.has(n.file)) testFileIds.add(n.id);
75
- }
76
- return testFileIds;
77
- }
78
-
79
- function countNodesByKind(db: BetterSqlite3Database, testFileIds: Set<number> | null) {
80
- let nodeRows: Array<{ kind: string; c: number }>;
81
- if (testFileIds) {
82
- const allNodes = db.prepare('SELECT id, kind, file FROM nodes').all() as Array<{
83
- id: number;
84
- kind: string;
85
- file: string;
86
- }>;
87
- const filtered = allNodes.filter((n) => !testFileIds.has(n.id));
88
- const counts: Record<string, number> = {};
89
- for (const n of filtered) counts[n.kind] = (counts[n.kind] || 0) + 1;
90
- nodeRows = Object.entries(counts).map(([kind, c]) => ({ kind, c }));
91
- } else {
92
- nodeRows = db.prepare('SELECT kind, COUNT(*) as c FROM nodes GROUP BY kind').all() as Array<{
93
- kind: string;
94
- c: number;
95
- }>;
96
- }
47
+ function countNodesByKind(db: BetterSqlite3Database, noTests: boolean) {
48
+ const testFilter = testFilterSQL('file', noTests);
49
+ const nodeRows = db
50
+ .prepare(`SELECT kind, COUNT(*) as c FROM nodes WHERE 1=1 ${testFilter} GROUP BY kind`)
51
+ .all() as Array<{ kind: string; c: number }>;
97
52
  const byKind: Record<string, number> = {};
98
53
  let total = 0;
99
54
  for (const r of nodeRows) {
@@ -103,20 +58,21 @@ function countNodesByKind(db: BetterSqlite3Database, testFileIds: Set<number> |
103
58
  return { total, byKind };
104
59
  }
105
60
 
106
- function countEdgesByKind(db: BetterSqlite3Database, testFileIds: Set<number> | null) {
61
+ function countEdgesByKind(db: BetterSqlite3Database, noTests: boolean) {
107
62
  let edgeRows: Array<{ kind: string; c: number }>;
108
- if (testFileIds) {
109
- const allEdges = db.prepare('SELECT source_id, target_id, kind FROM edges').all() as Array<{
110
- source_id: number;
111
- target_id: number;
112
- kind: string;
113
- }>;
114
- const filtered = allEdges.filter(
115
- (e) => !testFileIds.has(e.source_id) && !testFileIds.has(e.target_id),
116
- );
117
- const counts: Record<string, number> = {};
118
- for (const e of filtered) counts[e.kind] = (counts[e.kind] || 0) + 1;
119
- edgeRows = Object.entries(counts).map(([kind, c]) => ({ kind, c }));
63
+ if (noTests) {
64
+ // Join edges with source node to filter out test files in SQL
65
+ const srcFilter = testFilterSQL('ns.file', true);
66
+ const tgtFilter = testFilterSQL('nt.file', true);
67
+ edgeRows = db
68
+ .prepare(`
69
+ SELECT e.kind, COUNT(*) as c FROM edges e
70
+ JOIN nodes ns ON e.source_id = ns.id
71
+ JOIN nodes nt ON e.target_id = nt.id
72
+ WHERE 1=1 ${srcFilter} ${tgtFilter}
73
+ GROUP BY e.kind
74
+ `)
75
+ .all() as Array<{ kind: string; c: number }>;
120
76
  } else {
121
77
  edgeRows = db.prepare('SELECT kind, COUNT(*) as c FROM edges GROUP BY kind').all() as Array<{
122
78
  kind: string;
@@ -157,16 +113,25 @@ function findHotspots(db: BetterSqlite3Database, noTests: boolean, limit: number
157
113
  const hotspotRows = db
158
114
  .prepare(`
159
115
  SELECT n.file,
160
- (SELECT COUNT(*) FROM edges WHERE target_id = n.id) as fan_in,
161
- (SELECT COUNT(*) FROM edges WHERE source_id = n.id) as fan_out
116
+ COALESCE(fi.cnt, 0) as fan_in,
117
+ COALESCE(fo.cnt, 0) as fan_out
162
118
  FROM nodes n
119
+ LEFT JOIN (
120
+ SELECT target_id, COUNT(*) AS cnt FROM edges
121
+ WHERE kind NOT IN ('contains', 'parameter_of', 'receiver')
122
+ GROUP BY target_id
123
+ ) fi ON fi.target_id = n.id
124
+ LEFT JOIN (
125
+ SELECT source_id, COUNT(*) AS cnt FROM edges
126
+ WHERE kind NOT IN ('contains', 'parameter_of', 'receiver')
127
+ GROUP BY source_id
128
+ ) fo ON fo.source_id = n.id
163
129
  WHERE n.kind = 'file' ${testFilter}
164
- ORDER BY (SELECT COUNT(*) FROM edges WHERE target_id = n.id)
165
- + (SELECT COUNT(*) FROM edges WHERE source_id = n.id) DESC
130
+ ORDER BY COALESCE(fi.cnt, 0) + COALESCE(fo.cnt, 0) DESC
131
+ LIMIT ?
166
132
  `)
167
- .all() as Array<{ file: string; fan_in: number; fan_out: number }>;
168
- const filtered = noTests ? hotspotRows.filter((r) => !isTestFile(r.file)) : hotspotRows;
169
- return filtered.slice(0, limit).map((r) => ({
133
+ .all(limit) as Array<{ file: string; fan_in: number; fan_out: number }>;
134
+ return hotspotRows.map((r) => ({
170
135
  file: r.file,
171
136
  fanIn: r.fan_in,
172
137
  fanOut: r.fan_out,
@@ -275,20 +240,12 @@ function computeQualityMetrics(
275
240
  }
276
241
 
277
242
  function countRoles(db: BetterSqlite3Database, noTests: boolean) {
278
- let roleRows: Array<{ role: string; c: number }>;
279
- if (noTests) {
280
- const allRoleNodes = db
281
- .prepare('SELECT role, file FROM nodes WHERE role IS NOT NULL')
282
- .all() as Array<{ role: string; file: string }>;
283
- const filtered = allRoleNodes.filter((n) => !isTestFile(n.file));
284
- const counts: Record<string, number> = {};
285
- for (const n of filtered) counts[n.role] = (counts[n.role] || 0) + 1;
286
- roleRows = Object.entries(counts).map(([role, c]) => ({ role, c }));
287
- } else {
288
- roleRows = db
289
- .prepare('SELECT role, COUNT(*) as c FROM nodes WHERE role IS NOT NULL GROUP BY role')
290
- .all() as Array<{ role: string; c: number }>;
291
- }
243
+ const testFilter = testFilterSQL('file', noTests);
244
+ const roleRows = db
245
+ .prepare(
246
+ `SELECT role, COUNT(*) as c FROM nodes WHERE role IS NOT NULL ${testFilter} GROUP BY role`,
247
+ )
248
+ .all() as Array<{ role: string; c: number }>;
292
249
  const roles: Record<string, number> & { dead?: number } = {};
293
250
  let deadTotal = 0;
294
251
  for (const r of roleRows) {
@@ -344,13 +301,23 @@ export function moduleMapData(customDbPath: string, limit = 20, opts: { noTests?
344
301
 
345
302
  const nodes = db
346
303
  .prepare(`
347
- SELECT n.*,
348
- (SELECT COUNT(*) FROM edges WHERE source_id = n.id AND kind NOT IN ('contains', 'parameter_of', 'receiver')) as out_edges,
349
- (SELECT COUNT(*) FROM edges WHERE target_id = n.id AND kind NOT IN ('contains', 'parameter_of', 'receiver')) as in_edges
304
+ SELECT n.file,
305
+ COALESCE(fo.cnt, 0) as out_edges,
306
+ COALESCE(fi.cnt, 0) as in_edges
350
307
  FROM nodes n
308
+ LEFT JOIN (
309
+ SELECT source_id, COUNT(*) AS cnt FROM edges
310
+ WHERE kind NOT IN ('contains', 'parameter_of', 'receiver')
311
+ GROUP BY source_id
312
+ ) fo ON fo.source_id = n.id
313
+ LEFT JOIN (
314
+ SELECT target_id, COUNT(*) AS cnt FROM edges
315
+ WHERE kind NOT IN ('contains', 'parameter_of', 'receiver')
316
+ GROUP BY target_id
317
+ ) fi ON fi.target_id = n.id
351
318
  WHERE n.kind = 'file'
352
319
  ${testFilter}
353
- ORDER BY (SELECT COUNT(*) FROM edges WHERE target_id = n.id AND kind NOT IN ('contains', 'parameter_of', 'receiver')) DESC
320
+ ORDER BY COALESCE(fi.cnt, 0) DESC
354
321
  LIMIT ?
355
322
  `)
356
323
  .all(limit) as Array<{ file: string; in_edges: number; out_edges: number }>;
@@ -486,10 +453,9 @@ export function statsData(customDbPath: string, opts: { noTests?: boolean; confi
486
453
 
487
454
  // ── JS fallback ───────────────────────────────────────────────────
488
455
  const testFilter = testFilterSQL('n.file', noTests);
489
- const testFileIds = noTests ? buildTestFileIds(db) : null;
490
456
 
491
- const { total: totalNodes, byKind: nodesByKind } = countNodesByKind(db, testFileIds);
492
- const { total: totalEdges, byKind: edgesByKind } = countEdgesByKind(db, testFileIds);
457
+ const { total: totalNodes, byKind: nodesByKind } = countNodesByKind(db, noTests);
458
+ const { total: totalEdges, byKind: edgesByKind } = countEdgesByKind(db, noTests);
493
459
 
494
460
  const hotspots = findHotspots(db, noTests, 5);
495
461
  const embeddings = getEmbeddingsInfo(db);
@@ -1,4 +1,4 @@
1
- import { openReadonlyOrFail } from '../../db/index.js';
1
+ import { openReadonlyOrFail, openRepo, type Repository } from '../../db/index.js';
2
2
  import { loadConfig } from '../../infrastructure/config.js';
3
3
  import type { BetterSqlite3Database, CodegraphConfig } from '../../types.js';
4
4
 
@@ -19,6 +19,23 @@ export function withReadonlyDb<T>(
19
19
  }
20
20
  }
21
21
 
22
+ /**
23
+ * Open a Repository (native-first, falling back to better-sqlite3), run `fn`,
24
+ * and close on completion. Mirrors `withReadonlyDb` but routes queries through
25
+ * the native Rust engine when available.
26
+ */
27
+ export function withRepo<T>(
28
+ customDbPath: string | undefined,
29
+ fn: (repo: InstanceType<typeof Repository>) => T,
30
+ ): T {
31
+ const { repo, close } = openRepo(customDbPath);
32
+ try {
33
+ return fn(repo);
34
+ } finally {
35
+ close();
36
+ }
37
+ }
38
+
22
39
  /**
23
40
  * Resolve common analysis options into a normalized form.
24
41
  * Shared across fn-impact, context, dependencies, and exports modules.