@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
@@ -11,6 +11,8 @@
11
11
  import path from 'node:path';
12
12
  import { performance } from 'node:perf_hooks';
13
13
  import { bulkNodeIdsByFile } from '../../../../db/index.js';
14
+ import { debug } from '../../../../infrastructure/logger.js';
15
+ import { toErrorMessage } from '../../../../shared/errors.js';
14
16
  import type {
15
17
  BetterSqlite3Database,
16
18
  ExtractorOutput,
@@ -36,35 +38,31 @@ interface PrecomputedFileData {
36
38
  _reverseDepOnly?: boolean;
37
39
  }
38
40
 
39
- // ── Native fast-path ─────────────────────────────────────────────────
41
+ // ── Native fast-path helpers ─────────────────────────────────────────
40
42
 
41
- function tryNativeInsert(ctx: PipelineContext): boolean {
42
- // Use NativeDatabase persistent connection (Phase 6.15+).
43
- // Standalone napi functions were removed in 6.17 — falls through to JS if nativeDb unavailable.
44
- if (!ctx.nativeDb?.bulkInsertNodes) return false;
45
-
46
- const { allSymbols, filesToParse, metadataUpdates, rootDir, removed } = ctx;
47
-
48
- // Marshal allSymbols → InsertNodesBatch[]
49
- const batches: Array<{
50
- file: string;
51
- definitions: Array<{
43
+ /** Shape of a marshaled batch for native bulk insert. */
44
+ interface InsertNodesBatch {
45
+ file: string;
46
+ definitions: Array<{
47
+ name: string;
48
+ kind: string;
49
+ line: number;
50
+ endLine?: number;
51
+ visibility?: string;
52
+ children: Array<{
52
53
  name: string;
53
54
  kind: string;
54
55
  line: number;
55
56
  endLine?: number;
56
57
  visibility?: string;
57
- children: Array<{
58
- name: string;
59
- kind: string;
60
- line: number;
61
- endLine?: number;
62
- visibility?: string;
63
- }>;
64
58
  }>;
65
- exports: Array<{ name: string; kind: string; line: number }>;
66
- }> = [];
59
+ }>;
60
+ exports: Array<{ name: string; kind: string; line: number }>;
61
+ }
67
62
 
63
+ /** Marshal allSymbols into the batch format expected by native bulkInsertNodes. */
64
+ function marshalSymbolBatches(allSymbols: Map<string, ExtractorOutput>): InsertNodesBatch[] {
65
+ const batches: InsertNodesBatch[] = [];
68
66
  for (const [relPath, symbols] of allSymbols) {
69
67
  batches.push({
70
68
  file: relPath,
@@ -89,14 +87,18 @@ function tryNativeInsert(ctx: PipelineContext): boolean {
89
87
  })),
90
88
  });
91
89
  }
90
+ return batches;
91
+ }
92
92
 
93
- // Build file hash entries
94
- const precomputedData = new Map<string, PrecomputedFileData>();
95
- for (const item of filesToParse) {
96
- if (item.relPath) precomputedData.set(item.relPath, item as PrecomputedFileData);
97
- }
98
-
93
+ /** Build file hash entries from parsed symbols and precomputed/metadata sources. */
94
+ function buildFileHashes(
95
+ allSymbols: Map<string, ExtractorOutput>,
96
+ precomputedData: Map<string, PrecomputedFileData>,
97
+ metadataUpdates: MetadataUpdate[],
98
+ rootDir: string,
99
+ ): Array<{ file: string; hash: string; mtime: number; size: number }> {
99
100
  const fileHashes: Array<{ file: string; hash: string; mtime: number; size: number }> = [];
101
+
100
102
  for (const [relPath] of allSymbols) {
101
103
  const precomputed = precomputedData.get(relPath);
102
104
  if (precomputed?._reverseDepOnly) {
@@ -119,7 +121,8 @@ function tryNativeInsert(ctx: PipelineContext): boolean {
119
121
  let code: string | null;
120
122
  try {
121
123
  code = readFileSafe(absPath);
122
- } catch {
124
+ } catch (e) {
125
+ debug(`buildFileHashes: readFileSafe failed for ${relPath}: ${toErrorMessage(e)}`);
123
126
  code = null;
124
127
  }
125
128
  if (code !== null) {
@@ -138,6 +141,24 @@ function tryNativeInsert(ctx: PipelineContext): boolean {
138
141
  fileHashes.push({ file: item.relPath, hash: item.hash, mtime, size });
139
142
  }
140
143
 
144
+ return fileHashes;
145
+ }
146
+
147
+ // ── Native fast-path ─────────────────────────────────────────────────
148
+
149
+ function tryNativeInsert(ctx: PipelineContext): boolean {
150
+ if (!ctx.nativeDb?.bulkInsertNodes) return false;
151
+
152
+ const { allSymbols, filesToParse, metadataUpdates, rootDir, removed } = ctx;
153
+
154
+ const batches = marshalSymbolBatches(allSymbols);
155
+
156
+ const precomputedData = new Map<string, PrecomputedFileData>();
157
+ for (const item of filesToParse) {
158
+ if (item.relPath) precomputedData.set(item.relPath, item as PrecomputedFileData);
159
+ }
160
+ const fileHashes = buildFileHashes(allSymbols, precomputedData, metadataUpdates, rootDir);
161
+
141
162
  // WAL guard: same suspendJsDb/resumeJsDb pattern used by feature modules
142
163
  // (ast, cfg, complexity, dataflow). Checkpoint JS side before native write,
143
164
  // then checkpoint native side after, so neither library reads WAL frames
@@ -151,8 +172,10 @@ function tryNativeInsert(ctx: PipelineContext): boolean {
151
172
  } finally {
152
173
  try {
153
174
  ctx.nativeDb?.exec('PRAGMA wal_checkpoint(TRUNCATE)');
154
- } catch {
155
- /* ignore — nativeDb may already be closed */
175
+ } catch (e) {
176
+ debug(
177
+ `tryNativeInsert: WAL checkpoint failed (nativeDb may already be closed): ${toErrorMessage(e)}`,
178
+ );
156
179
  }
157
180
  }
158
181
  return result;
@@ -324,7 +347,8 @@ function updateFileHashes(
324
347
  let code: string | null;
325
348
  try {
326
349
  code = readFileSafe(absPath);
327
- } catch {
350
+ } catch (e) {
351
+ debug(`updateFileHashes: readFileSafe failed for ${relPath}: ${toErrorMessage(e)}`);
328
352
  code = null;
329
353
  }
330
354
  if (code !== null) {
@@ -364,8 +388,8 @@ export async function insertNodes(ctx: PipelineContext): Promise<void> {
364
388
  // Removed-file hash cleanup is handled inside the native call
365
389
  return;
366
390
  }
367
- } catch {
368
- // Native insert failed fall through to JS implementation
391
+ } catch (e) {
392
+ debug(`insertNodes: native insert failed, falling back to JS: ${toErrorMessage(e)}`);
369
393
  }
370
394
  }
371
395
 
@@ -380,7 +404,8 @@ export async function insertNodes(ctx: PipelineContext): Promise<void> {
380
404
  upsertHash = ctx.db.prepare(
381
405
  'INSERT OR REPLACE INTO file_hashes (file, hash, mtime, size) VALUES (?, ?, ?, ?)',
382
406
  );
383
- } catch {
407
+ } catch (e) {
408
+ debug(`insertNodes: file_hashes prepare failed (table may not exist): ${toErrorMessage(e)}`);
384
409
  upsertHash = null;
385
410
  }
386
411
 
@@ -1,6 +1,7 @@
1
1
  import path from 'node:path';
2
2
  import { performance } from 'node:perf_hooks';
3
3
  import { debug } from '../../../../infrastructure/logger.js';
4
+ import { normalizePath } from '../../../../shared/constants.js';
4
5
  import type { Import } from '../../../../types.js';
5
6
  import { parseFilesAuto } from '../../../parser.js';
6
7
  import { resolveImportPath, resolveImportsBatch } from '../../resolve.js';
@@ -12,20 +13,10 @@ interface ReexportEntry {
12
13
  wildcardReexport: boolean;
13
14
  }
14
15
 
15
- export async function resolveImports(ctx: PipelineContext): Promise<void> {
16
- const { db, fileSymbols, rootDir, aliases, allFiles, isFullBuild, engineOpts } = ctx;
17
- const t0 = performance.now();
18
- const batchInputs: Array<{ fromFile: string; importSource: string }> = [];
19
- for (const [relPath, symbols] of fileSymbols) {
20
- const absFile = path.join(rootDir, relPath);
21
- for (const imp of symbols.imports) {
22
- batchInputs.push({ fromFile: absFile, importSource: imp.source });
23
- }
24
- }
25
- ctx.batchResolved = resolveImportsBatch(batchInputs, rootDir, aliases, allFiles);
26
- ctx.timing.resolveMs = performance.now() - t0;
27
-
16
+ /** Collect reexport entries from fileSymbols into the reexportMap. */
17
+ function buildReexportMap(ctx: PipelineContext): void {
28
18
  ctx.reexportMap = new Map<string, ReexportEntry[]>();
19
+ const { fileSymbols, rootDir } = ctx;
29
20
  for (const [relPath, symbols] of fileSymbols) {
30
21
  const reexports = symbols.imports.filter((imp) => imp.reexport);
31
22
  if (reexports.length > 0) {
@@ -39,110 +30,141 @@ export async function resolveImports(ctx: PipelineContext): Promise<void> {
39
30
  );
40
31
  }
41
32
  }
33
+ }
42
34
 
43
- ctx.barrelOnlyFiles = new Set<string>();
44
- if (!isFullBuild) {
45
- // Collect the set of changed file paths to scope barrel re-parsing.
46
- const changedRelPaths = new Set<string>(fileSymbols.keys());
47
-
48
- // For small incremental builds (≤5 files), only re-parse barrel files
49
- // that are related to the changed files — either re-exporting from them
50
- // or imported by them. For larger changes, re-parse all barrels.
51
- let barrelCandidates: Array<{ file: string }>;
52
- if (changedRelPaths.size <= 5) {
53
- // All known barrel files (has at least one reexport edge)
54
- const allBarrelFiles = new Set(
55
- (
56
- db
57
- .prepare(
58
- `SELECT DISTINCT n1.file FROM edges e
59
- JOIN nodes n1 ON e.source_id = n1.id
60
- WHERE e.kind = 'reexports' AND n1.kind = 'file'`,
61
- )
62
- .all() as Array<{ file: string }>
63
- ).map((r) => r.file),
64
- );
35
+ /**
36
+ * Find barrel files related to changed files for scoped re-parsing.
37
+ * For small incremental builds (<=5 files), only barrels that re-export from
38
+ * or are imported by the changed files. For larger changes, all barrels.
39
+ */
40
+ function findBarrelCandidates(ctx: PipelineContext): Array<{ file: string }> {
41
+ const { db, fileSymbols, rootDir, aliases } = ctx;
42
+ const changedRelPaths = new Set<string>(fileSymbols.keys());
65
43
 
66
- const barrels = new Set<string>();
67
-
68
- // Find barrels imported by changed files using parsed import data
69
- // (can't query DB edges — they were purged for the changed files).
70
- for (const relPath of changedRelPaths) {
71
- const symbols = fileSymbols.get(relPath);
72
- if (!symbols) continue;
73
- for (const imp of symbols.imports) {
74
- const resolved = ctx.batchResolved?.get(`${path.join(rootDir, relPath)}|${imp.source}`);
75
- const target =
76
- resolved ??
77
- resolveImportPath(path.join(rootDir, relPath), imp.source, rootDir, aliases);
78
- if (allBarrelFiles.has(target)) barrels.add(target);
79
- }
80
- }
44
+ const SMALL_CHANGE_THRESHOLD = 5;
45
+ if (changedRelPaths.size <= SMALL_CHANGE_THRESHOLD) {
46
+ const allBarrelFiles = new Set(
47
+ (
48
+ db
49
+ .prepare(
50
+ `SELECT DISTINCT n1.file FROM edges e
51
+ JOIN nodes n1 ON e.source_id = n1.id
52
+ WHERE e.kind = 'reexports' AND n1.kind = 'file'`,
53
+ )
54
+ .all() as Array<{ file: string }>
55
+ ).map((r) => r.file),
56
+ );
81
57
 
82
- // Also find barrels that re-export from the changed files
83
- const reexportSourceStmt = db.prepare(
84
- `SELECT DISTINCT n1.file FROM edges e
85
- JOIN nodes n1 ON e.source_id = n1.id
86
- JOIN nodes n2 ON e.target_id = n2.id
87
- WHERE e.kind = 'reexports' AND n1.kind = 'file' AND n2.file = ?`,
88
- );
89
- for (const relPath of changedRelPaths) {
90
- for (const row of reexportSourceStmt.all(relPath) as Array<{ file: string }>) {
91
- barrels.add(row.file);
92
- }
58
+ const barrels = new Set<string>();
59
+
60
+ // Find barrels imported by changed files using parsed import data
61
+ // (can't query DB edges -- they were purged for the changed files).
62
+ for (const relPath of changedRelPaths) {
63
+ const symbols = fileSymbols.get(relPath);
64
+ if (!symbols) continue;
65
+ for (const imp of symbols.imports) {
66
+ const resolved = ctx.batchResolved?.get(
67
+ `${normalizePath(path.join(rootDir, relPath))}|${imp.source}`,
68
+ );
69
+ const target =
70
+ resolved ?? resolveImportPath(path.join(rootDir, relPath), imp.source, rootDir, aliases);
71
+ if (allBarrelFiles.has(target)) barrels.add(target);
93
72
  }
94
- barrelCandidates = [...barrels].map((file) => ({ file }));
95
- } else {
96
- barrelCandidates = db
97
- .prepare(
98
- `SELECT DISTINCT n1.file FROM edges e
99
- JOIN nodes n1 ON e.source_id = n1.id
100
- WHERE e.kind = 'reexports' AND n1.kind = 'file'`,
101
- )
102
- .all() as Array<{ file: string }>;
103
73
  }
104
74
 
105
- // Batch-parse all barrel candidates at once instead of one-by-one
106
- const barrelPaths: string[] = [];
107
- for (const { file: relPath } of barrelCandidates) {
108
- if (!fileSymbols.has(relPath)) {
109
- barrelPaths.push(path.join(rootDir, relPath));
75
+ // Also find barrels that re-export from the changed files
76
+ const reexportSourceStmt = db.prepare(
77
+ `SELECT DISTINCT n1.file FROM edges e
78
+ JOIN nodes n1 ON e.source_id = n1.id
79
+ JOIN nodes n2 ON e.target_id = n2.id
80
+ WHERE e.kind = 'reexports' AND n1.kind = 'file' AND n2.file = ?`,
81
+ );
82
+ for (const relPath of changedRelPaths) {
83
+ for (const row of reexportSourceStmt.all(relPath) as Array<{ file: string }>) {
84
+ barrels.add(row.file);
110
85
  }
111
86
  }
87
+ return [...barrels].map((file) => ({ file }));
88
+ }
112
89
 
113
- if (barrelPaths.length > 0) {
114
- const deleteOutgoingEdges = db.prepare(
115
- 'DELETE FROM edges WHERE source_id IN (SELECT id FROM nodes WHERE file = ?)',
116
- );
90
+ return db
91
+ .prepare(
92
+ `SELECT DISTINCT n1.file FROM edges e
93
+ JOIN nodes n1 ON e.source_id = n1.id
94
+ WHERE e.kind = 'reexports' AND n1.kind = 'file'`,
95
+ )
96
+ .all() as Array<{ file: string }>;
97
+ }
117
98
 
118
- try {
119
- const barrelSymbols = await parseFilesAuto(barrelPaths, rootDir, engineOpts);
120
- for (const [relPath, fileSym] of barrelSymbols) {
121
- deleteOutgoingEdges.run(relPath);
122
- fileSymbols.set(relPath, fileSym);
123
- ctx.barrelOnlyFiles.add(relPath);
124
- const reexports = fileSym.imports.filter((imp: Import) => imp.reexport);
125
- if (reexports.length > 0) {
126
- ctx.reexportMap.set(
127
- relPath,
128
- reexports.map((imp: Import) => ({
129
- source: getResolved(ctx, path.join(rootDir, relPath), imp.source),
130
- names: imp.names,
131
- wildcardReexport: imp.wildcardReexport || false,
132
- })),
133
- );
134
- }
135
- }
136
- } catch (e: unknown) {
137
- debug(`Barrel re-parse failed (non-fatal): ${(e as Error).message}`);
99
+ /** Re-parse barrel files and update fileSymbols/reexportMap with fresh data. */
100
+ async function reparseBarrelFiles(
101
+ ctx: PipelineContext,
102
+ barrelCandidates: Array<{ file: string }>,
103
+ ): Promise<void> {
104
+ const { db, fileSymbols, rootDir, engineOpts } = ctx;
105
+
106
+ const barrelPaths: string[] = [];
107
+ for (const { file: relPath } of barrelCandidates) {
108
+ if (!fileSymbols.has(relPath)) {
109
+ barrelPaths.push(path.join(rootDir, relPath));
110
+ }
111
+ }
112
+
113
+ if (barrelPaths.length === 0) return;
114
+
115
+ const deleteOutgoingEdges = db.prepare(
116
+ 'DELETE FROM edges WHERE source_id IN (SELECT id FROM nodes WHERE file = ?)',
117
+ );
118
+
119
+ try {
120
+ const barrelSymbols = await parseFilesAuto(barrelPaths, rootDir, engineOpts);
121
+ for (const [relPath, fileSym] of barrelSymbols) {
122
+ deleteOutgoingEdges.run(relPath);
123
+ fileSymbols.set(relPath, fileSym);
124
+ ctx.barrelOnlyFiles.add(relPath);
125
+ const reexports = fileSym.imports.filter((imp: Import) => imp.reexport);
126
+ if (reexports.length > 0) {
127
+ ctx.reexportMap.set(
128
+ relPath,
129
+ reexports.map((imp: Import) => ({
130
+ source: getResolved(ctx, path.join(rootDir, relPath), imp.source),
131
+ names: imp.names,
132
+ wildcardReexport: imp.wildcardReexport || false,
133
+ })),
134
+ );
138
135
  }
139
136
  }
137
+ } catch (e: unknown) {
138
+ debug(`Barrel re-parse failed (non-fatal): ${(e as Error).message}`);
139
+ }
140
+ }
141
+
142
+ export async function resolveImports(ctx: PipelineContext): Promise<void> {
143
+ const { fileSymbols, rootDir, aliases, allFiles, isFullBuild } = ctx;
144
+ const t0 = performance.now();
145
+
146
+ const batchInputs: Array<{ fromFile: string; importSource: string }> = [];
147
+ for (const [relPath, symbols] of fileSymbols) {
148
+ const absFile = path.join(rootDir, relPath);
149
+ for (const imp of symbols.imports) {
150
+ batchInputs.push({ fromFile: absFile, importSource: imp.source });
151
+ }
152
+ }
153
+ ctx.batchResolved = resolveImportsBatch(batchInputs, rootDir, aliases, allFiles);
154
+ ctx.timing.resolveMs = performance.now() - t0;
155
+
156
+ buildReexportMap(ctx);
157
+
158
+ ctx.barrelOnlyFiles = new Set<string>();
159
+ if (!isFullBuild) {
160
+ const barrelCandidates = findBarrelCandidates(ctx);
161
+ await reparseBarrelFiles(ctx, barrelCandidates);
140
162
  }
141
163
  }
142
164
 
143
165
  export function getResolved(ctx: PipelineContext, absFile: string, importSource: string): string {
144
166
  if (ctx.batchResolved) {
145
- const key = `${absFile}|${importSource}`;
167
+ const key = `${normalizePath(absFile)}|${importSource}`;
146
168
  const hit = ctx.batchResolved.get(key);
147
169
  if (hit !== undefined) return hit;
148
170
  }
@@ -1,9 +1,14 @@
1
- import { tarjan } from '../../graph/algorithms/tarjan.js';
2
- import { buildDependencyGraph } from '../../graph/builders/dependency.js';
3
- import { CodeGraph } from '../../graph/model.js';
1
+ import { getCallableNodes, getCallEdges, getFileNodesAll, getImportEdges } from '../../db/index.js';
4
2
  import { loadNative } from '../../infrastructure/native.js';
3
+ import { isTestFile } from '../../infrastructure/test-filter.js';
5
4
  import type { BetterSqlite3Database } from '../../types.js';
6
5
 
6
+ /**
7
+ * Find cycles using Tarjan's SCC algorithm.
8
+ *
9
+ * Builds a label-based adjacency list directly from DB rows — no intermediate
10
+ * CodeGraph construction. This is O(V + E) with minimal memory overhead.
11
+ */
7
12
  export function findCycles(
8
13
  db: BetterSqlite3Database,
9
14
  opts: { fileLevel?: boolean; noTests?: boolean } = {},
@@ -11,40 +16,122 @@ export function findCycles(
11
16
  const fileLevel = opts.fileLevel !== false;
12
17
  const noTests = opts.noTests || false;
13
18
 
14
- const graph = buildDependencyGraph(db, { fileLevel, noTests });
19
+ const edges: Array<{ source: string; target: string }> = [];
20
+ const seen = new Set<string>();
15
21
 
16
- const idToLabel = new Map<string, string>();
17
- for (const [id, attrs] of graph.nodes()) {
18
- if (fileLevel) {
19
- idToLabel.set(id, attrs.file as string);
20
- } else {
21
- idToLabel.set(id, `${attrs.label}|${attrs.file}`);
22
+ if (fileLevel) {
23
+ let nodes = getFileNodesAll(db);
24
+ if (noTests) nodes = nodes.filter((n) => !isTestFile(n.file));
25
+ const nodeIds = new Set<number>();
26
+ const idToFile = new Map<number, string>();
27
+ for (const n of nodes) {
28
+ nodeIds.add(n.id);
29
+ idToFile.set(n.id, n.file);
30
+ }
31
+ for (const e of getImportEdges(db)) {
32
+ if (!nodeIds.has(e.source_id) || !nodeIds.has(e.target_id)) continue;
33
+ if (e.source_id === e.target_id) continue;
34
+ const src = idToFile.get(e.source_id)!;
35
+ const tgt = idToFile.get(e.target_id)!;
36
+ const key = `${src}\0${tgt}`;
37
+ if (seen.has(key)) continue;
38
+ seen.add(key);
39
+ edges.push({ source: src, target: tgt });
40
+ }
41
+ } else {
42
+ let nodes = getCallableNodes(db);
43
+ if (noTests) nodes = nodes.filter((n) => !isTestFile(n.file));
44
+ const nodeIds = new Set<number>();
45
+ const idToLabel = new Map<number, string>();
46
+ for (const n of nodes) {
47
+ nodeIds.add(n.id);
48
+ idToLabel.set(n.id, `${n.name}|${n.file}`);
49
+ }
50
+ for (const e of getCallEdges(db)) {
51
+ if (!nodeIds.has(e.source_id) || !nodeIds.has(e.target_id)) continue;
52
+ if (e.source_id === e.target_id) continue;
53
+ const src = idToLabel.get(e.source_id)!;
54
+ const tgt = idToLabel.get(e.target_id)!;
55
+ const key = `${src}\0${tgt}`;
56
+ if (seen.has(key)) continue;
57
+ seen.add(key);
58
+ edges.push({ source: src, target: tgt });
22
59
  }
23
60
  }
24
61
 
25
- const edges = graph.toEdgeArray().map((e) => ({
26
- source: idToLabel.get(e.source) ?? e.source,
27
- target: idToLabel.get(e.target) ?? e.target,
28
- }));
29
-
30
62
  const native = loadNative();
31
63
  if (native) {
32
64
  return native.detectCycles(edges) as string[][];
33
65
  }
34
66
 
35
- const labelGraph = new CodeGraph();
36
- for (const { source, target } of edges) {
37
- labelGraph.addEdge(source, target);
38
- }
39
- return tarjan(labelGraph);
67
+ return tarjanFromEdges(edges);
40
68
  }
41
69
 
42
70
  export function findCyclesJS(edges: Array<{ source: string; target: string }>): string[][] {
43
- const graph = new CodeGraph();
71
+ return tarjanFromEdges(edges);
72
+ }
73
+
74
+ /**
75
+ * Run Tarjan's SCC on a flat edge list. Returns SCCs with length > 1 (cycles).
76
+ * Uses a simple adjacency-list Map instead of a full CodeGraph.
77
+ */
78
+ function tarjanFromEdges(edges: Array<{ source: string; target: string }>): string[][] {
79
+ const adj = new Map<string, string[]>();
80
+ const allNodes = new Set<string>();
44
81
  for (const { source, target } of edges) {
45
- graph.addEdge(source, target);
82
+ allNodes.add(source);
83
+ allNodes.add(target);
84
+ let list = adj.get(source);
85
+ if (!list) {
86
+ list = [];
87
+ adj.set(source, list);
88
+ }
89
+ list.push(target);
46
90
  }
47
- return tarjan(graph);
91
+
92
+ let index = 0;
93
+ const stack: string[] = [];
94
+ const onStack = new Set<string>();
95
+ const indices = new Map<string, number>();
96
+ const lowlinks = new Map<string, number>();
97
+ const sccs: string[][] = [];
98
+
99
+ function strongconnect(v: string): void {
100
+ indices.set(v, index);
101
+ lowlinks.set(v, index);
102
+ index++;
103
+ stack.push(v);
104
+ onStack.add(v);
105
+
106
+ const successors = adj.get(v);
107
+ if (successors) {
108
+ for (const w of successors) {
109
+ if (!indices.has(w)) {
110
+ strongconnect(w);
111
+ lowlinks.set(v, Math.min(lowlinks.get(v)!, lowlinks.get(w)!));
112
+ } else if (onStack.has(w)) {
113
+ lowlinks.set(v, Math.min(lowlinks.get(v)!, indices.get(w)!));
114
+ }
115
+ }
116
+ }
117
+
118
+ if (lowlinks.get(v) === indices.get(v)) {
119
+ const scc: string[] = [];
120
+ let w: string | undefined;
121
+ do {
122
+ w = stack.pop()!;
123
+ onStack.delete(w);
124
+ scc.push(w);
125
+ } while (w !== v);
126
+ if (scc.length > 1) sccs.push(scc);
127
+ }
128
+ }
129
+
130
+ for (const id of allNodes) {
131
+ if (!indices.has(id)) strongconnect(id);
132
+ }
133
+
134
+ return sccs;
48
135
  }
49
136
 
50
137
  export function formatCycles(cycles: string[][]): string {
@@ -565,7 +565,7 @@ export function resolveImportsBatch(
565
565
  // Native resolver's .js → .ts remap fails on unnormalized paths —
566
566
  // apply JS-side fallback (same fix as resolveImportPath).
567
567
  const resolved = remapJsToTs(normalized, rootDir);
568
- map.set(`${r.fromFile}|${r.importSource}`, resolved);
568
+ map.set(`${normalizePath(r.fromFile)}|${r.importSource}`, resolved);
569
569
  }
570
570
  return map;
571
571
  } catch (e) {