@optave/codegraph 3.1.4 → 3.2.0

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 (210) hide show
  1. package/README.md +29 -72
  2. package/package.json +10 -8
  3. package/src/ast-analysis/engine.js +260 -246
  4. package/src/ast-analysis/shared.js +2 -14
  5. package/src/ast-analysis/visitors/cfg-visitor.js +635 -649
  6. package/src/ast-analysis/visitors/complexity-visitor.js +135 -139
  7. package/src/ast-analysis/visitors/dataflow-visitor.js +230 -224
  8. package/src/cli/commands/ast.js +4 -7
  9. package/src/cli/commands/audit.js +11 -11
  10. package/src/cli/commands/batch.js +6 -5
  11. package/src/cli/commands/branch-compare.js +1 -1
  12. package/src/cli/commands/brief.js +12 -0
  13. package/src/cli/commands/build.js +1 -1
  14. package/src/cli/commands/cfg.js +5 -8
  15. package/src/cli/commands/check.js +28 -36
  16. package/src/cli/commands/children.js +9 -7
  17. package/src/cli/commands/co-change.js +5 -3
  18. package/src/cli/commands/communities.js +2 -6
  19. package/src/cli/commands/complexity.js +5 -3
  20. package/src/cli/commands/context.js +9 -8
  21. package/src/cli/commands/cycles.js +12 -8
  22. package/src/cli/commands/dataflow.js +5 -8
  23. package/src/cli/commands/deps.js +9 -8
  24. package/src/cli/commands/diff-impact.js +2 -6
  25. package/src/cli/commands/embed.js +1 -1
  26. package/src/cli/commands/export.js +34 -31
  27. package/src/cli/commands/exports.js +2 -6
  28. package/src/cli/commands/flow.js +5 -8
  29. package/src/cli/commands/fn-impact.js +9 -8
  30. package/src/cli/commands/impact.js +2 -6
  31. package/src/cli/commands/info.js +2 -2
  32. package/src/cli/commands/map.js +1 -1
  33. package/src/cli/commands/mcp.js +1 -1
  34. package/src/cli/commands/models.js +1 -1
  35. package/src/cli/commands/owners.js +5 -3
  36. package/src/cli/commands/path.js +2 -2
  37. package/src/cli/commands/plot.js +40 -31
  38. package/src/cli/commands/query.js +9 -8
  39. package/src/cli/commands/registry.js +2 -2
  40. package/src/cli/commands/roles.js +5 -8
  41. package/src/cli/commands/search.js +9 -3
  42. package/src/cli/commands/sequence.js +5 -8
  43. package/src/cli/commands/snapshot.js +6 -1
  44. package/src/cli/commands/stats.js +1 -1
  45. package/src/cli/commands/structure.js +5 -4
  46. package/src/cli/commands/triage.js +41 -30
  47. package/src/cli/commands/watch.js +1 -1
  48. package/src/cli/commands/where.js +2 -6
  49. package/src/cli/index.js +11 -5
  50. package/src/cli/shared/open-graph.js +13 -0
  51. package/src/cli/shared/options.js +22 -2
  52. package/src/cli.js +1 -1
  53. package/src/db/connection.js +140 -11
  54. package/src/{db.js → db/index.js} +12 -5
  55. package/src/db/migrations.js +42 -65
  56. package/src/db/query-builder.js +72 -9
  57. package/src/db/repository/base.js +1 -1
  58. package/src/db/repository/graph-read.js +3 -3
  59. package/src/db/repository/in-memory-repository.js +30 -28
  60. package/src/db/repository/nodes.js +10 -17
  61. package/src/domain/analysis/brief.js +155 -0
  62. package/src/domain/analysis/context.js +392 -0
  63. package/src/domain/analysis/dependencies.js +395 -0
  64. package/src/{analysis → domain/analysis}/exports.js +11 -6
  65. package/src/domain/analysis/impact.js +581 -0
  66. package/src/domain/analysis/module-map.js +348 -0
  67. package/src/{analysis → domain/analysis}/roles.js +12 -9
  68. package/src/{analysis → domain/analysis}/symbol-lookup.js +19 -11
  69. package/src/{builder → domain/graph/builder}/helpers.js +4 -4
  70. package/src/{builder → domain/graph/builder}/incremental.js +119 -93
  71. package/src/domain/graph/builder/pipeline.js +156 -0
  72. package/src/domain/graph/builder/stages/build-edges.js +376 -0
  73. package/src/{builder → domain/graph/builder}/stages/build-structure.js +4 -4
  74. package/src/{builder → domain/graph/builder}/stages/collect-files.js +2 -2
  75. package/src/{builder → domain/graph/builder}/stages/detect-changes.js +204 -183
  76. package/src/{builder → domain/graph/builder}/stages/finalize.js +4 -4
  77. package/src/domain/graph/builder/stages/insert-nodes.js +203 -0
  78. package/src/{builder → domain/graph/builder}/stages/parse-files.js +2 -2
  79. package/src/{builder → domain/graph/builder}/stages/resolve-imports.js +1 -1
  80. package/src/{builder → domain/graph/builder}/stages/run-analyses.js +2 -2
  81. package/src/{change-journal.js → domain/graph/change-journal.js} +1 -1
  82. package/src/{cycles.js → domain/graph/cycles.js} +4 -4
  83. package/src/{journal.js → domain/graph/journal.js} +1 -1
  84. package/src/{resolve.js → domain/graph/resolve.js} +2 -2
  85. package/src/{watcher.js → domain/graph/watcher.js} +7 -7
  86. package/src/{parser.js → domain/parser.js} +24 -15
  87. package/src/{queries.js → domain/queries.js} +17 -16
  88. package/src/{embeddings → domain/search}/generator.js +3 -3
  89. package/src/{embeddings → domain/search}/models.js +2 -2
  90. package/src/{embeddings → domain/search}/search/cli-formatter.js +1 -1
  91. package/src/{embeddings → domain/search}/search/filters.js +9 -5
  92. package/src/{embeddings → domain/search}/search/hybrid.js +1 -1
  93. package/src/{embeddings → domain/search}/search/keyword.js +13 -6
  94. package/src/{embeddings → domain/search}/search/prepare.js +15 -7
  95. package/src/{embeddings → domain/search}/search/semantic.js +1 -1
  96. package/src/{embeddings → domain/search}/strategies/structured.js +1 -1
  97. package/src/extractors/csharp.js +224 -207
  98. package/src/extractors/go.js +176 -172
  99. package/src/extractors/hcl.js +94 -78
  100. package/src/extractors/java.js +213 -207
  101. package/src/extractors/javascript.js +275 -305
  102. package/src/extractors/php.js +234 -221
  103. package/src/extractors/python.js +252 -250
  104. package/src/extractors/ruby.js +192 -185
  105. package/src/extractors/rust.js +182 -167
  106. package/src/{ast.js → features/ast.js} +13 -11
  107. package/src/{audit.js → features/audit.js} +20 -46
  108. package/src/{batch.js → features/batch.js} +5 -5
  109. package/src/{boundaries.js → features/boundaries.js} +100 -85
  110. package/src/{branch-compare.js → features/branch-compare.js} +3 -3
  111. package/src/{cfg.js → features/cfg.js} +141 -150
  112. package/src/{check.js → features/check.js} +13 -30
  113. package/src/{cochange.js → features/cochange.js} +5 -5
  114. package/src/{communities.js → features/communities.js} +72 -57
  115. package/src/{complexity.js → features/complexity.js} +154 -143
  116. package/src/{dataflow.js → features/dataflow.js} +155 -158
  117. package/src/{export.js → features/export.js} +6 -6
  118. package/src/{flow.js → features/flow.js} +4 -4
  119. package/src/{viewer.js → features/graph-enrichment.js} +8 -8
  120. package/src/{manifesto.js → features/manifesto.js} +15 -12
  121. package/src/{owners.js → features/owners.js} +6 -5
  122. package/src/features/sequence.js +300 -0
  123. package/src/features/shared/find-nodes.js +31 -0
  124. package/src/{snapshot.js → features/snapshot.js} +3 -3
  125. package/src/{structure.js → features/structure.js} +139 -108
  126. package/src/features/triage.js +141 -0
  127. package/src/graph/builders/dependency.js +33 -14
  128. package/src/graph/classifiers/risk.js +3 -2
  129. package/src/graph/classifiers/roles.js +6 -3
  130. package/src/index.cjs +16 -0
  131. package/src/index.js +40 -39
  132. package/src/{native.js → infrastructure/native.js} +1 -1
  133. package/src/mcp/middleware.js +1 -1
  134. package/src/mcp/server.js +68 -59
  135. package/src/mcp/tool-registry.js +15 -2
  136. package/src/mcp/tools/ast-query.js +1 -1
  137. package/src/mcp/tools/audit.js +1 -1
  138. package/src/mcp/tools/batch-query.js +1 -1
  139. package/src/mcp/tools/branch-compare.js +3 -1
  140. package/src/mcp/tools/brief.js +8 -0
  141. package/src/mcp/tools/cfg.js +1 -1
  142. package/src/mcp/tools/check.js +3 -3
  143. package/src/mcp/tools/co-changes.js +1 -1
  144. package/src/mcp/tools/code-owners.js +1 -1
  145. package/src/mcp/tools/communities.js +1 -1
  146. package/src/mcp/tools/complexity.js +1 -1
  147. package/src/mcp/tools/dataflow.js +2 -2
  148. package/src/mcp/tools/execution-flow.js +2 -2
  149. package/src/mcp/tools/export-graph.js +2 -2
  150. package/src/mcp/tools/find-cycles.js +2 -2
  151. package/src/mcp/tools/index.js +2 -0
  152. package/src/mcp/tools/list-repos.js +1 -1
  153. package/src/mcp/tools/sequence.js +1 -1
  154. package/src/mcp/tools/structure.js +1 -1
  155. package/src/mcp/tools/triage.js +2 -2
  156. package/src/{commands → presentation}/audit.js +2 -2
  157. package/src/{commands → presentation}/batch.js +1 -1
  158. package/src/{commands → presentation}/branch-compare.js +2 -2
  159. package/src/presentation/brief.js +51 -0
  160. package/src/{commands → presentation}/cfg.js +1 -1
  161. package/src/{commands → presentation}/check.js +2 -2
  162. package/src/{commands → presentation}/communities.js +1 -1
  163. package/src/{commands → presentation}/complexity.js +1 -1
  164. package/src/{commands → presentation}/dataflow.js +1 -1
  165. package/src/{commands → presentation}/flow.js +2 -2
  166. package/src/{commands → presentation}/manifesto.js +1 -1
  167. package/src/{commands → presentation}/owners.js +1 -1
  168. package/src/presentation/queries-cli/exports.js +53 -0
  169. package/src/presentation/queries-cli/impact.js +214 -0
  170. package/src/presentation/queries-cli/index.js +5 -0
  171. package/src/presentation/queries-cli/inspect.js +329 -0
  172. package/src/presentation/queries-cli/overview.js +196 -0
  173. package/src/presentation/queries-cli/path.js +65 -0
  174. package/src/presentation/queries-cli.js +27 -0
  175. package/src/{commands → presentation}/query.js +1 -1
  176. package/src/presentation/result-formatter.js +126 -3
  177. package/src/{commands → presentation}/sequence.js +2 -2
  178. package/src/{commands → presentation}/structure.js +1 -1
  179. package/src/presentation/table.js +0 -8
  180. package/src/{commands → presentation}/triage.js +1 -1
  181. package/src/{constants.js → shared/constants.js} +1 -1
  182. package/src/shared/file-utils.js +2 -2
  183. package/src/shared/generators.js +9 -5
  184. package/src/shared/hierarchy.js +1 -1
  185. package/src/{kinds.js → shared/kinds.js} +1 -1
  186. package/src/analysis/context.js +0 -408
  187. package/src/analysis/dependencies.js +0 -341
  188. package/src/analysis/impact.js +0 -463
  189. package/src/analysis/module-map.js +0 -322
  190. package/src/builder/pipeline.js +0 -130
  191. package/src/builder/stages/build-edges.js +0 -297
  192. package/src/builder/stages/insert-nodes.js +0 -195
  193. package/src/mcp.js +0 -2
  194. package/src/queries-cli.js +0 -866
  195. package/src/sequence.js +0 -289
  196. package/src/triage.js +0 -126
  197. /package/src/{builder → domain/graph/builder}/context.js +0 -0
  198. /package/src/{builder.js → domain/graph/builder.js} +0 -0
  199. /package/src/{embeddings → domain/search}/index.js +0 -0
  200. /package/src/{embeddings → domain/search}/stores/fts5.js +0 -0
  201. /package/src/{embeddings → domain/search}/stores/sqlite-blob.js +0 -0
  202. /package/src/{embeddings → domain/search}/strategies/source.js +0 -0
  203. /package/src/{embeddings → domain/search}/strategies/text-utils.js +0 -0
  204. /package/src/{config.js → infrastructure/config.js} +0 -0
  205. /package/src/{logger.js → infrastructure/logger.js} +0 -0
  206. /package/src/{registry.js → infrastructure/registry.js} +0 -0
  207. /package/src/{update-check.js → infrastructure/update-check.js} +0 -0
  208. /package/src/{commands → presentation}/cochange.js +0 -0
  209. /package/src/{errors.js → shared/errors.js} +0 -0
  210. /package/src/{paginate.js → shared/paginate.js} +0 -0
@@ -6,16 +6,127 @@
6
6
  */
7
7
  import fs from 'node:fs';
8
8
  import path from 'node:path';
9
- import { normalizePath } from '../constants.js';
10
- import { warn } from '../logger.js';
11
- import { parseFileIncremental } from '../parser.js';
9
+ import { warn } from '../../../infrastructure/logger.js';
10
+ import { normalizePath } from '../../../shared/constants.js';
11
+ import { parseFileIncremental } from '../../parser.js';
12
12
  import { computeConfidence, resolveImportPath } from '../resolve.js';
13
13
  import { BUILTIN_RECEIVERS, readFileSafe } from './helpers.js';
14
14
 
15
+ // ── Node insertion ──────────────────────────────────────────────────────
16
+
17
+ function insertFileNodes(stmts, relPath, symbols) {
18
+ stmts.insertNode.run(relPath, 'file', relPath, 0, null);
19
+ for (const def of symbols.definitions) {
20
+ stmts.insertNode.run(def.name, def.kind, relPath, def.line, def.endLine || null);
21
+ }
22
+ for (const exp of symbols.exports) {
23
+ stmts.insertNode.run(exp.name, exp.kind, relPath, exp.line, null);
24
+ }
25
+ }
26
+
27
+ // ── Import edge building ────────────────────────────────────────────────
28
+
29
+ function buildImportEdges(stmts, relPath, symbols, rootDir, fileNodeId, aliases) {
30
+ let edgesAdded = 0;
31
+ for (const imp of symbols.imports) {
32
+ const resolvedPath = resolveImportPath(
33
+ path.join(rootDir, relPath),
34
+ imp.source,
35
+ rootDir,
36
+ aliases,
37
+ );
38
+ const targetRow = stmts.getNodeId.get(resolvedPath, 'file', resolvedPath, 0);
39
+ if (targetRow) {
40
+ const edgeKind = imp.reexport ? 'reexports' : imp.typeOnly ? 'imports-type' : 'imports';
41
+ stmts.insertEdge.run(fileNodeId, targetRow.id, edgeKind, 1.0, 0);
42
+ edgesAdded++;
43
+ }
44
+ }
45
+ return edgesAdded;
46
+ }
47
+
48
+ function buildImportedNamesMap(symbols, rootDir, relPath, aliases) {
49
+ const importedNames = new Map();
50
+ for (const imp of symbols.imports) {
51
+ const resolvedPath = resolveImportPath(
52
+ path.join(rootDir, relPath),
53
+ imp.source,
54
+ rootDir,
55
+ aliases,
56
+ );
57
+ for (const name of imp.names) {
58
+ importedNames.set(name.replace(/^\*\s+as\s+/, ''), resolvedPath);
59
+ }
60
+ }
61
+ return importedNames;
62
+ }
63
+
64
+ // ── Call edge building ──────────────────────────────────────────────────
65
+
66
+ function findCaller(call, definitions, relPath, stmts) {
67
+ let caller = null;
68
+ let callerSpan = Infinity;
69
+ for (const def of definitions) {
70
+ if (def.line <= call.line) {
71
+ const end = def.endLine || Infinity;
72
+ if (call.line <= end) {
73
+ const span = end - def.line;
74
+ if (span < callerSpan) {
75
+ const row = stmts.getNodeId.get(def.name, def.kind, relPath, def.line);
76
+ if (row) {
77
+ caller = row;
78
+ callerSpan = span;
79
+ }
80
+ }
81
+ } else if (!caller) {
82
+ const row = stmts.getNodeId.get(def.name, def.kind, relPath, def.line);
83
+ if (row) caller = row;
84
+ }
85
+ }
86
+ }
87
+ return caller;
88
+ }
89
+
90
+ function resolveCallTargets(stmts, call, relPath, importedNames) {
91
+ const importedFrom = importedNames.get(call.name);
92
+ let targets;
93
+ if (importedFrom) {
94
+ targets = stmts.findNodeInFile.all(call.name, importedFrom);
95
+ }
96
+ if (!targets || targets.length === 0) {
97
+ targets = stmts.findNodeInFile.all(call.name, relPath);
98
+ if (targets.length === 0) {
99
+ targets = stmts.findNodeByName.all(call.name);
100
+ }
101
+ }
102
+ return { targets, importedFrom };
103
+ }
104
+
105
+ function buildCallEdges(stmts, relPath, symbols, fileNodeRow, importedNames) {
106
+ let edgesAdded = 0;
107
+ for (const call of symbols.calls) {
108
+ if (call.receiver && BUILTIN_RECEIVERS.has(call.receiver)) continue;
109
+
110
+ const caller = findCaller(call, symbols.definitions, relPath, stmts) || fileNodeRow;
111
+ const { targets, importedFrom } = resolveCallTargets(stmts, call, relPath, importedNames);
112
+
113
+ for (const t of targets) {
114
+ if (t.id !== caller.id) {
115
+ const confidence = computeConfidence(relPath, t.file, importedFrom ?? null);
116
+ stmts.insertEdge.run(caller.id, t.id, 'calls', confidence, call.dynamic ? 1 : 0);
117
+ edgesAdded++;
118
+ }
119
+ }
120
+ }
121
+ return edgesAdded;
122
+ }
123
+
124
+ // ── Main entry point ────────────────────────────────────────────────────
125
+
15
126
  /**
16
127
  * Parse a single file and update the database incrementally.
17
128
  *
18
- * @param {import('better-sqlite3').Database} db
129
+ * @param {import('better-sqlite3').Database} _db
19
130
  * @param {string} rootDir - Absolute root directory
20
131
  * @param {string} filePath - Absolute file path
21
132
  * @param {object} stmts - Prepared DB statements
@@ -61,105 +172,20 @@ export async function rebuildFile(_db, rootDir, filePath, stmts, engineOpts, cac
61
172
  const symbols = await parseFileIncremental(cache, filePath, code, engineOpts);
62
173
  if (!symbols) return null;
63
174
 
64
- // Insert nodes
65
- stmts.insertNode.run(relPath, 'file', relPath, 0, null);
66
- for (const def of symbols.definitions) {
67
- stmts.insertNode.run(def.name, def.kind, relPath, def.line, def.endLine || null);
68
- }
69
- for (const exp of symbols.exports) {
70
- stmts.insertNode.run(exp.name, exp.kind, relPath, exp.line, null);
71
- }
175
+ insertFileNodes(stmts, relPath, symbols);
72
176
 
73
177
  const newNodes = stmts.countNodes.get(relPath)?.c || 0;
74
178
  const newSymbols = diffSymbols ? stmts.listSymbols.all(relPath) : [];
75
179
 
76
- let edgesAdded = 0;
77
180
  const fileNodeRow = stmts.getNodeId.get(relPath, 'file', relPath, 0);
78
181
  if (!fileNodeRow)
79
182
  return { file: relPath, nodesAdded: newNodes, nodesRemoved: oldNodes, edgesAdded: 0 };
80
- const fileNodeId = fileNodeRow.id;
81
183
 
82
- // Load aliases for import resolution
83
184
  const aliases = { baseUrl: null, paths: {} };
84
185
 
85
- // Import edges
86
- for (const imp of symbols.imports) {
87
- const resolvedPath = resolveImportPath(
88
- path.join(rootDir, relPath),
89
- imp.source,
90
- rootDir,
91
- aliases,
92
- );
93
- const targetRow = stmts.getNodeId.get(resolvedPath, 'file', resolvedPath, 0);
94
- if (targetRow) {
95
- const edgeKind = imp.reexport ? 'reexports' : imp.typeOnly ? 'imports-type' : 'imports';
96
- stmts.insertEdge.run(fileNodeId, targetRow.id, edgeKind, 1.0, 0);
97
- edgesAdded++;
98
- }
99
- }
100
-
101
- // Build import name → resolved file mapping
102
- const importedNames = new Map();
103
- for (const imp of symbols.imports) {
104
- const resolvedPath = resolveImportPath(
105
- path.join(rootDir, relPath),
106
- imp.source,
107
- rootDir,
108
- aliases,
109
- );
110
- for (const name of imp.names) {
111
- importedNames.set(name.replace(/^\*\s+as\s+/, ''), resolvedPath);
112
- }
113
- }
114
-
115
- // Call edges
116
- for (const call of symbols.calls) {
117
- if (call.receiver && BUILTIN_RECEIVERS.has(call.receiver)) continue;
118
-
119
- let caller = null;
120
- let callerSpan = Infinity;
121
- for (const def of symbols.definitions) {
122
- if (def.line <= call.line) {
123
- const end = def.endLine || Infinity;
124
- if (call.line <= end) {
125
- const span = end - def.line;
126
- if (span < callerSpan) {
127
- const row = stmts.getNodeId.get(def.name, def.kind, relPath, def.line);
128
- if (row) {
129
- caller = row;
130
- callerSpan = span;
131
- }
132
- }
133
- } else if (!caller) {
134
- const row = stmts.getNodeId.get(def.name, def.kind, relPath, def.line);
135
- if (row) caller = row;
136
- }
137
- }
138
- }
139
- if (!caller) caller = fileNodeRow;
140
-
141
- const importedFrom = importedNames.get(call.name);
142
- let targets;
143
- if (importedFrom) {
144
- targets = stmts.findNodeInFile.all(call.name, importedFrom);
145
- }
146
- if (!targets || targets.length === 0) {
147
- targets = stmts.findNodeInFile.all(call.name, relPath);
148
- if (targets.length === 0) {
149
- targets = stmts.findNodeByName.all(call.name);
150
- }
151
- }
152
-
153
- for (const t of targets) {
154
- if (t.id !== caller.id) {
155
- const confidence = importedFrom
156
- ? computeConfidence(relPath, t.file, importedFrom)
157
- : computeConfidence(relPath, t.file, null);
158
- stmts.insertEdge.run(caller.id, t.id, 'calls', confidence, call.dynamic ? 1 : 0);
159
- edgesAdded++;
160
- }
161
- }
162
- }
186
+ let edgesAdded = buildImportEdges(stmts, relPath, symbols, rootDir, fileNodeRow.id, aliases);
187
+ const importedNames = buildImportedNamesMap(symbols, rootDir, relPath, aliases);
188
+ edgesAdded += buildCallEdges(stmts, relPath, symbols, fileNodeRow, importedNames);
163
189
 
164
190
  const symbolDiff = diffSymbols ? diffSymbols(oldSymbols, newSymbols) : null;
165
191
  const event = oldNodes === 0 ? 'added' : 'modified';
@@ -0,0 +1,156 @@
1
+ /**
2
+ * Pipeline orchestrator — runs build stages sequentially through a shared PipelineContext.
3
+ *
4
+ * This is the heart of the builder refactor (ROADMAP 3.9): the monolithic buildGraph()
5
+ * is decomposed into independently testable stages that communicate via PipelineContext.
6
+ */
7
+ import path from 'node:path';
8
+ import { performance } from 'node:perf_hooks';
9
+ import { closeDb, getBuildMeta, initSchema, MIGRATIONS, openDb } from '../../../db/index.js';
10
+ import { loadConfig } from '../../../infrastructure/config.js';
11
+ import { info } from '../../../infrastructure/logger.js';
12
+ import { getActiveEngine } from '../../parser.js';
13
+ import { PipelineContext } from './context.js';
14
+ import { loadPathAliases } from './helpers.js';
15
+ import { buildEdges } from './stages/build-edges.js';
16
+ import { buildStructure } from './stages/build-structure.js';
17
+ // Pipeline stages
18
+ import { collectFiles } from './stages/collect-files.js';
19
+ import { detectChanges } from './stages/detect-changes.js';
20
+ import { finalize } from './stages/finalize.js';
21
+ import { insertNodes } from './stages/insert-nodes.js';
22
+ import { parseFiles } from './stages/parse-files.js';
23
+ import { resolveImports } from './stages/resolve-imports.js';
24
+ import { runAnalyses } from './stages/run-analyses.js';
25
+
26
+ // ── Setup helpers ───────────────────────────────────────────────────────
27
+
28
+ function initializeEngine(ctx) {
29
+ ctx.engineOpts = {
30
+ engine: ctx.opts.engine || 'auto',
31
+ dataflow: ctx.opts.dataflow !== false,
32
+ ast: ctx.opts.ast !== false,
33
+ };
34
+ const { name: engineName, version: engineVersion } = getActiveEngine(ctx.engineOpts);
35
+ ctx.engineName = engineName;
36
+ ctx.engineVersion = engineVersion;
37
+ info(`Using ${engineName} engine${engineVersion ? ` (v${engineVersion})` : ''}`);
38
+ }
39
+
40
+ function checkEngineSchemaMismatch(ctx) {
41
+ ctx.schemaVersion = MIGRATIONS[MIGRATIONS.length - 1].version;
42
+ ctx.forceFullRebuild = false;
43
+ if (!ctx.incremental) return;
44
+
45
+ const prevEngine = getBuildMeta(ctx.db, 'engine');
46
+ if (prevEngine && prevEngine !== ctx.engineName) {
47
+ info(`Engine changed (${prevEngine} → ${ctx.engineName}), promoting to full rebuild.`);
48
+ ctx.forceFullRebuild = true;
49
+ }
50
+ const prevSchema = getBuildMeta(ctx.db, 'schema_version');
51
+ if (prevSchema && Number(prevSchema) !== ctx.schemaVersion) {
52
+ info(
53
+ `Schema version changed (${prevSchema} → ${ctx.schemaVersion}), promoting to full rebuild.`,
54
+ );
55
+ ctx.forceFullRebuild = true;
56
+ }
57
+ }
58
+
59
+ function loadAliases(ctx) {
60
+ ctx.aliases = loadPathAliases(ctx.rootDir);
61
+ if (ctx.config.aliases) {
62
+ for (const [key, value] of Object.entries(ctx.config.aliases)) {
63
+ const pattern = key.endsWith('/') ? `${key}*` : key;
64
+ const target = path.resolve(ctx.rootDir, value);
65
+ ctx.aliases.paths[pattern] = [target.endsWith('/') ? `${target}*` : `${target}/*`];
66
+ }
67
+ }
68
+ if (ctx.aliases.baseUrl || Object.keys(ctx.aliases.paths).length > 0) {
69
+ info(
70
+ `Loaded path aliases: baseUrl=${ctx.aliases.baseUrl || 'none'}, ${Object.keys(ctx.aliases.paths).length} path mappings`,
71
+ );
72
+ }
73
+ }
74
+
75
+ function setupPipeline(ctx) {
76
+ ctx.rootDir = path.resolve(ctx.rootDir);
77
+ ctx.dbPath = path.join(ctx.rootDir, '.codegraph', 'graph.db');
78
+ ctx.db = openDb(ctx.dbPath);
79
+ initSchema(ctx.db);
80
+
81
+ ctx.config = loadConfig(ctx.rootDir);
82
+ ctx.incremental =
83
+ ctx.opts.incremental !== false && ctx.config.build && ctx.config.build.incremental !== false;
84
+
85
+ initializeEngine(ctx);
86
+ checkEngineSchemaMismatch(ctx);
87
+ loadAliases(ctx);
88
+
89
+ ctx.timing.setupMs = performance.now() - ctx.buildStart;
90
+ }
91
+
92
+ function formatTimingResult(ctx) {
93
+ return {
94
+ phases: {
95
+ setupMs: +ctx.timing.setupMs.toFixed(1),
96
+ parseMs: +(ctx.timing.parseMs ?? 0).toFixed(1),
97
+ insertMs: +(ctx.timing.insertMs ?? 0).toFixed(1),
98
+ resolveMs: +(ctx.timing.resolveMs ?? 0).toFixed(1),
99
+ edgesMs: +(ctx.timing.edgesMs ?? 0).toFixed(1),
100
+ structureMs: +(ctx.timing.structureMs ?? 0).toFixed(1),
101
+ rolesMs: +(ctx.timing.rolesMs ?? 0).toFixed(1),
102
+ astMs: +(ctx.timing.astMs ?? 0).toFixed(1),
103
+ complexityMs: +(ctx.timing.complexityMs ?? 0).toFixed(1),
104
+ ...(ctx.timing.cfgMs != null && { cfgMs: +ctx.timing.cfgMs.toFixed(1) }),
105
+ ...(ctx.timing.dataflowMs != null && { dataflowMs: +ctx.timing.dataflowMs.toFixed(1) }),
106
+ finalizeMs: +(ctx.timing.finalizeMs ?? 0).toFixed(1),
107
+ },
108
+ };
109
+ }
110
+
111
+ // ── Pipeline stages execution ───────────────────────────────────────────
112
+
113
+ async function runPipelineStages(ctx) {
114
+ await collectFiles(ctx);
115
+ await detectChanges(ctx);
116
+
117
+ if (ctx.earlyExit) return;
118
+
119
+ await parseFiles(ctx);
120
+ await insertNodes(ctx);
121
+ await resolveImports(ctx);
122
+ await buildEdges(ctx);
123
+ await buildStructure(ctx);
124
+ await runAnalyses(ctx);
125
+ await finalize(ctx);
126
+ }
127
+
128
+ // ── Main entry point ────────────────────────────────────────────────────
129
+
130
+ /**
131
+ * Build the dependency graph for a codebase.
132
+ *
133
+ * Signature and return value are identical to the original monolithic buildGraph().
134
+ *
135
+ * @param {string} rootDir - Root directory to scan
136
+ * @param {object} [opts] - Build options
137
+ * @returns {Promise<{ phases: object } | undefined>}
138
+ */
139
+ export async function buildGraph(rootDir, opts = {}) {
140
+ const ctx = new PipelineContext();
141
+ ctx.buildStart = performance.now();
142
+ ctx.opts = opts;
143
+ ctx.rootDir = rootDir;
144
+
145
+ try {
146
+ setupPipeline(ctx);
147
+ await runPipelineStages(ctx);
148
+ } catch (err) {
149
+ if (!ctx.earlyExit) closeDb(ctx.db);
150
+ throw err;
151
+ }
152
+
153
+ if (ctx.earlyExit) return;
154
+
155
+ return formatTimingResult(ctx);
156
+ }