@optave/codegraph 3.1.2 → 3.1.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (194) hide show
  1. package/README.md +19 -21
  2. package/package.json +10 -7
  3. package/src/analysis/context.js +408 -0
  4. package/src/analysis/dependencies.js +341 -0
  5. package/src/analysis/exports.js +130 -0
  6. package/src/analysis/impact.js +463 -0
  7. package/src/analysis/module-map.js +322 -0
  8. package/src/analysis/roles.js +45 -0
  9. package/src/analysis/symbol-lookup.js +232 -0
  10. package/src/ast-analysis/shared.js +5 -4
  11. package/src/batch.js +2 -1
  12. package/src/builder/context.js +85 -0
  13. package/src/builder/helpers.js +218 -0
  14. package/src/builder/incremental.js +178 -0
  15. package/src/builder/pipeline.js +130 -0
  16. package/src/builder/stages/build-edges.js +297 -0
  17. package/src/builder/stages/build-structure.js +113 -0
  18. package/src/builder/stages/collect-files.js +44 -0
  19. package/src/builder/stages/detect-changes.js +413 -0
  20. package/src/builder/stages/finalize.js +139 -0
  21. package/src/builder/stages/insert-nodes.js +195 -0
  22. package/src/builder/stages/parse-files.js +28 -0
  23. package/src/builder/stages/resolve-imports.js +143 -0
  24. package/src/builder/stages/run-analyses.js +44 -0
  25. package/src/builder.js +10 -1472
  26. package/src/cfg.js +1 -2
  27. package/src/cli/commands/ast.js +26 -0
  28. package/src/cli/commands/audit.js +46 -0
  29. package/src/cli/commands/batch.js +68 -0
  30. package/src/cli/commands/branch-compare.js +21 -0
  31. package/src/cli/commands/build.js +26 -0
  32. package/src/cli/commands/cfg.js +30 -0
  33. package/src/cli/commands/check.js +79 -0
  34. package/src/cli/commands/children.js +31 -0
  35. package/src/cli/commands/co-change.js +65 -0
  36. package/src/cli/commands/communities.js +23 -0
  37. package/src/cli/commands/complexity.js +45 -0
  38. package/src/cli/commands/context.js +34 -0
  39. package/src/cli/commands/cycles.js +28 -0
  40. package/src/cli/commands/dataflow.js +32 -0
  41. package/src/cli/commands/deps.js +16 -0
  42. package/src/cli/commands/diff-impact.js +30 -0
  43. package/src/cli/commands/embed.js +30 -0
  44. package/src/cli/commands/export.js +75 -0
  45. package/src/cli/commands/exports.js +18 -0
  46. package/src/cli/commands/flow.js +36 -0
  47. package/src/cli/commands/fn-impact.js +30 -0
  48. package/src/cli/commands/impact.js +16 -0
  49. package/src/cli/commands/info.js +76 -0
  50. package/src/cli/commands/map.js +19 -0
  51. package/src/cli/commands/mcp.js +18 -0
  52. package/src/cli/commands/models.js +19 -0
  53. package/src/cli/commands/owners.js +25 -0
  54. package/src/cli/commands/path.js +36 -0
  55. package/src/cli/commands/plot.js +80 -0
  56. package/src/cli/commands/query.js +49 -0
  57. package/src/cli/commands/registry.js +100 -0
  58. package/src/cli/commands/roles.js +34 -0
  59. package/src/cli/commands/search.js +42 -0
  60. package/src/cli/commands/sequence.js +32 -0
  61. package/src/cli/commands/snapshot.js +61 -0
  62. package/src/cli/commands/stats.js +15 -0
  63. package/src/cli/commands/structure.js +32 -0
  64. package/src/cli/commands/triage.js +78 -0
  65. package/src/cli/commands/watch.js +12 -0
  66. package/src/cli/commands/where.js +24 -0
  67. package/src/cli/index.js +118 -0
  68. package/src/cli/shared/options.js +39 -0
  69. package/src/cli/shared/output.js +1 -0
  70. package/src/cli.js +11 -1514
  71. package/src/commands/check.js +5 -5
  72. package/src/commands/manifesto.js +3 -3
  73. package/src/commands/structure.js +1 -1
  74. package/src/communities.js +15 -87
  75. package/src/complexity.js +1 -1
  76. package/src/cycles.js +30 -85
  77. package/src/dataflow.js +1 -2
  78. package/src/db/connection.js +4 -4
  79. package/src/db/migrations.js +41 -0
  80. package/src/db/query-builder.js +6 -5
  81. package/src/db/repository/base.js +201 -0
  82. package/src/db/repository/cached-stmt.js +19 -0
  83. package/src/db/repository/cfg.js +27 -38
  84. package/src/db/repository/cochange.js +16 -3
  85. package/src/db/repository/complexity.js +11 -6
  86. package/src/db/repository/dataflow.js +6 -1
  87. package/src/db/repository/edges.js +120 -98
  88. package/src/db/repository/embeddings.js +14 -3
  89. package/src/db/repository/graph-read.js +32 -9
  90. package/src/db/repository/in-memory-repository.js +584 -0
  91. package/src/db/repository/index.js +6 -1
  92. package/src/db/repository/nodes.js +110 -40
  93. package/src/db/repository/sqlite-repository.js +219 -0
  94. package/src/db.js +5 -0
  95. package/src/embeddings/generator.js +163 -0
  96. package/src/embeddings/index.js +13 -0
  97. package/src/embeddings/models.js +218 -0
  98. package/src/embeddings/search/cli-formatter.js +151 -0
  99. package/src/embeddings/search/filters.js +46 -0
  100. package/src/embeddings/search/hybrid.js +121 -0
  101. package/src/embeddings/search/keyword.js +68 -0
  102. package/src/embeddings/search/prepare.js +66 -0
  103. package/src/embeddings/search/semantic.js +145 -0
  104. package/src/embeddings/stores/fts5.js +27 -0
  105. package/src/embeddings/stores/sqlite-blob.js +24 -0
  106. package/src/embeddings/strategies/source.js +14 -0
  107. package/src/embeddings/strategies/structured.js +43 -0
  108. package/src/embeddings/strategies/text-utils.js +43 -0
  109. package/src/errors.js +78 -0
  110. package/src/export.js +217 -520
  111. package/src/extractors/csharp.js +10 -2
  112. package/src/extractors/go.js +3 -1
  113. package/src/extractors/helpers.js +71 -0
  114. package/src/extractors/java.js +9 -2
  115. package/src/extractors/javascript.js +38 -1
  116. package/src/extractors/php.js +3 -1
  117. package/src/extractors/python.js +14 -3
  118. package/src/extractors/rust.js +3 -1
  119. package/src/graph/algorithms/bfs.js +49 -0
  120. package/src/graph/algorithms/centrality.js +16 -0
  121. package/src/graph/algorithms/index.js +5 -0
  122. package/src/graph/algorithms/louvain.js +26 -0
  123. package/src/graph/algorithms/shortest-path.js +41 -0
  124. package/src/graph/algorithms/tarjan.js +49 -0
  125. package/src/graph/builders/dependency.js +91 -0
  126. package/src/graph/builders/index.js +3 -0
  127. package/src/graph/builders/structure.js +40 -0
  128. package/src/graph/builders/temporal.js +33 -0
  129. package/src/graph/classifiers/index.js +2 -0
  130. package/src/graph/classifiers/risk.js +85 -0
  131. package/src/graph/classifiers/roles.js +64 -0
  132. package/src/graph/index.js +13 -0
  133. package/src/graph/model.js +230 -0
  134. package/src/index.js +33 -204
  135. package/src/infrastructure/result-formatter.js +2 -21
  136. package/src/mcp/index.js +2 -0
  137. package/src/mcp/middleware.js +26 -0
  138. package/src/mcp/server.js +128 -0
  139. package/src/mcp/tool-registry.js +801 -0
  140. package/src/mcp/tools/ast-query.js +14 -0
  141. package/src/mcp/tools/audit.js +21 -0
  142. package/src/mcp/tools/batch-query.js +11 -0
  143. package/src/mcp/tools/branch-compare.js +10 -0
  144. package/src/mcp/tools/cfg.js +21 -0
  145. package/src/mcp/tools/check.js +43 -0
  146. package/src/mcp/tools/co-changes.js +20 -0
  147. package/src/mcp/tools/code-owners.js +12 -0
  148. package/src/mcp/tools/communities.js +15 -0
  149. package/src/mcp/tools/complexity.js +18 -0
  150. package/src/mcp/tools/context.js +17 -0
  151. package/src/mcp/tools/dataflow.js +26 -0
  152. package/src/mcp/tools/diff-impact.js +24 -0
  153. package/src/mcp/tools/execution-flow.js +26 -0
  154. package/src/mcp/tools/export-graph.js +57 -0
  155. package/src/mcp/tools/file-deps.js +12 -0
  156. package/src/mcp/tools/file-exports.js +13 -0
  157. package/src/mcp/tools/find-cycles.js +15 -0
  158. package/src/mcp/tools/fn-impact.js +15 -0
  159. package/src/mcp/tools/impact-analysis.js +12 -0
  160. package/src/mcp/tools/index.js +71 -0
  161. package/src/mcp/tools/list-functions.js +14 -0
  162. package/src/mcp/tools/list-repos.js +11 -0
  163. package/src/mcp/tools/module-map.js +6 -0
  164. package/src/mcp/tools/node-roles.js +14 -0
  165. package/src/mcp/tools/path.js +12 -0
  166. package/src/mcp/tools/query.js +30 -0
  167. package/src/mcp/tools/semantic-search.js +65 -0
  168. package/src/mcp/tools/sequence.js +17 -0
  169. package/src/mcp/tools/structure.js +15 -0
  170. package/src/mcp/tools/symbol-children.js +14 -0
  171. package/src/mcp/tools/triage.js +35 -0
  172. package/src/mcp/tools/where.js +13 -0
  173. package/src/mcp.js +2 -1470
  174. package/src/native.js +34 -10
  175. package/src/parser.js +53 -2
  176. package/src/presentation/colors.js +44 -0
  177. package/src/presentation/export.js +444 -0
  178. package/src/presentation/result-formatter.js +21 -0
  179. package/src/presentation/sequence-renderer.js +43 -0
  180. package/src/presentation/table.js +47 -0
  181. package/src/presentation/viewer.js +634 -0
  182. package/src/queries.js +35 -2276
  183. package/src/resolve.js +1 -1
  184. package/src/sequence.js +2 -38
  185. package/src/shared/file-utils.js +153 -0
  186. package/src/shared/generators.js +125 -0
  187. package/src/shared/hierarchy.js +27 -0
  188. package/src/shared/normalize.js +59 -0
  189. package/src/snapshot.js +6 -5
  190. package/src/structure.js +15 -40
  191. package/src/triage.js +20 -72
  192. package/src/viewer.js +35 -656
  193. package/src/watcher.js +8 -148
  194. package/src/embedder.js +0 -1097
@@ -0,0 +1,195 @@
1
+ /**
2
+ * Stage: insertNodes
3
+ *
4
+ * Batch-inserts file nodes, definitions, exports, children, and contains/parameter_of edges.
5
+ * Updates file hashes for incremental builds.
6
+ */
7
+ import path from 'node:path';
8
+ import { performance } from 'node:perf_hooks';
9
+ import { bulkNodeIdsByFile } from '../../db.js';
10
+ import {
11
+ batchInsertEdges,
12
+ batchInsertNodes,
13
+ fileHash,
14
+ fileStat,
15
+ readFileSafe,
16
+ } from '../helpers.js';
17
+
18
+ /**
19
+ * @param {import('../context.js').PipelineContext} ctx
20
+ */
21
+ export async function insertNodes(ctx) {
22
+ const { db, allSymbols, filesToParse, metadataUpdates, rootDir, removed } = ctx;
23
+
24
+ // Build lookup from incremental data (pre-computed hashes + stats)
25
+ const precomputedData = new Map();
26
+ for (const item of filesToParse) {
27
+ if (item.relPath) {
28
+ precomputedData.set(item.relPath, item);
29
+ }
30
+ }
31
+
32
+ const bulkGetNodeIds = { all: (file) => bulkNodeIdsByFile(db, file) };
33
+
34
+ // Prepare hash upsert
35
+ let upsertHash;
36
+ try {
37
+ upsertHash = db.prepare(
38
+ 'INSERT OR REPLACE INTO file_hashes (file, hash, mtime, size) VALUES (?, ?, ?, ?)',
39
+ );
40
+ } catch {
41
+ upsertHash = null;
42
+ }
43
+
44
+ // Populate fileSymbols before the transaction so it is a pure input
45
+ // to (rather than a side-effect of) the DB write — avoids partial
46
+ // population if the transaction rolls back.
47
+ for (const [relPath, symbols] of allSymbols) {
48
+ ctx.fileSymbols.set(relPath, symbols);
49
+ }
50
+
51
+ const insertAll = db.transaction(() => {
52
+ // Phase 1: Batch insert all file nodes + definitions + exports
53
+ // Row format: [name, kind, file, line, end_line, parent_id, qualified_name, scope, visibility]
54
+ const phase1Rows = [];
55
+ for (const [relPath, symbols] of allSymbols) {
56
+ phase1Rows.push([relPath, 'file', relPath, 0, null, null, null, null, null]);
57
+ for (const def of symbols.definitions) {
58
+ // Methods already have 'Class.method' as name — use as qualified_name.
59
+ // For methods, scope is the class portion; for top-level defs, scope is null.
60
+ const dotIdx = def.name.lastIndexOf('.');
61
+ const scope = dotIdx !== -1 ? def.name.slice(0, dotIdx) : null;
62
+ phase1Rows.push([
63
+ def.name,
64
+ def.kind,
65
+ relPath,
66
+ def.line,
67
+ def.endLine || null,
68
+ null,
69
+ def.name,
70
+ scope,
71
+ def.visibility || null,
72
+ ]);
73
+ }
74
+ for (const exp of symbols.exports) {
75
+ phase1Rows.push([exp.name, exp.kind, relPath, exp.line, null, null, exp.name, null, null]);
76
+ }
77
+ }
78
+ batchInsertNodes(db, phase1Rows);
79
+
80
+ // Phase 1b: Mark exported symbols
81
+ const markExported = db.prepare(
82
+ 'UPDATE nodes SET exported = 1 WHERE name = ? AND kind = ? AND file = ? AND line = ?',
83
+ );
84
+ for (const [relPath, symbols] of allSymbols) {
85
+ for (const exp of symbols.exports) {
86
+ markExported.run(exp.name, exp.kind, relPath, exp.line);
87
+ }
88
+ }
89
+
90
+ // Phase 3: Batch insert children (needs parent IDs from Phase 2)
91
+ const childRows = [];
92
+ for (const [relPath, symbols] of allSymbols) {
93
+ const nodeIdMap = new Map();
94
+ for (const row of bulkGetNodeIds.all(relPath)) {
95
+ nodeIdMap.set(`${row.name}|${row.kind}|${row.line}`, row.id);
96
+ }
97
+ for (const def of symbols.definitions) {
98
+ if (!def.children?.length) continue;
99
+ const defId = nodeIdMap.get(`${def.name}|${def.kind}|${def.line}`);
100
+ if (!defId) continue;
101
+ for (const child of def.children) {
102
+ const qualifiedName = `${def.name}.${child.name}`;
103
+ childRows.push([
104
+ child.name,
105
+ child.kind,
106
+ relPath,
107
+ child.line,
108
+ child.endLine || null,
109
+ defId,
110
+ qualifiedName,
111
+ def.name,
112
+ child.visibility || null,
113
+ ]);
114
+ }
115
+ }
116
+ }
117
+ batchInsertNodes(db, childRows);
118
+
119
+ // Phase 5: Batch insert contains/parameter_of edges
120
+ const edgeRows = [];
121
+ for (const [relPath, symbols] of allSymbols) {
122
+ const nodeIdMap = new Map();
123
+ for (const row of bulkGetNodeIds.all(relPath)) {
124
+ nodeIdMap.set(`${row.name}|${row.kind}|${row.line}`, row.id);
125
+ }
126
+ const fileId = nodeIdMap.get(`${relPath}|file|0`);
127
+ for (const def of symbols.definitions) {
128
+ const defId = nodeIdMap.get(`${def.name}|${def.kind}|${def.line}`);
129
+ if (fileId && defId) {
130
+ edgeRows.push([fileId, defId, 'contains', 1.0, 0]);
131
+ }
132
+ if (def.children?.length && defId) {
133
+ for (const child of def.children) {
134
+ const childId = nodeIdMap.get(`${child.name}|${child.kind}|${child.line}`);
135
+ if (childId) {
136
+ edgeRows.push([defId, childId, 'contains', 1.0, 0]);
137
+ if (child.kind === 'parameter') {
138
+ edgeRows.push([childId, defId, 'parameter_of', 1.0, 0]);
139
+ }
140
+ }
141
+ }
142
+ }
143
+ }
144
+
145
+ // Update file hash — skip reverse-dep files (unchanged)
146
+ if (upsertHash) {
147
+ const precomputed = precomputedData.get(relPath);
148
+ if (precomputed?._reverseDepOnly) {
149
+ // no-op: file unchanged, hash already correct
150
+ } else if (precomputed?.hash) {
151
+ const stat = precomputed.stat || fileStat(path.join(rootDir, relPath));
152
+ const mtime = stat ? Math.floor(stat.mtimeMs) : 0;
153
+ const size = stat ? stat.size : 0;
154
+ upsertHash.run(relPath, precomputed.hash, mtime, size);
155
+ } else {
156
+ const absPath = path.join(rootDir, relPath);
157
+ let code;
158
+ try {
159
+ code = readFileSafe(absPath);
160
+ } catch {
161
+ code = null;
162
+ }
163
+ if (code !== null) {
164
+ const stat = fileStat(absPath);
165
+ const mtime = stat ? Math.floor(stat.mtimeMs) : 0;
166
+ const size = stat ? stat.size : 0;
167
+ upsertHash.run(relPath, fileHash(code), mtime, size);
168
+ }
169
+ }
170
+ }
171
+ }
172
+ batchInsertEdges(db, edgeRows);
173
+
174
+ // Also update metadata-only entries (self-heal mtime/size without re-parse)
175
+ if (upsertHash) {
176
+ for (const item of metadataUpdates) {
177
+ const mtime = item.stat ? Math.floor(item.stat.mtimeMs) : 0;
178
+ const size = item.stat ? item.stat.size : 0;
179
+ upsertHash.run(item.relPath, item.hash, mtime, size);
180
+ }
181
+ }
182
+ });
183
+
184
+ const t0 = performance.now();
185
+ insertAll();
186
+ ctx.timing.insertMs = performance.now() - t0;
187
+
188
+ // Clean up removed file hashes
189
+ if (upsertHash && removed.length > 0) {
190
+ const deleteHash = db.prepare('DELETE FROM file_hashes WHERE file = ?');
191
+ for (const relPath of removed) {
192
+ deleteHash.run(relPath);
193
+ }
194
+ }
195
+ }
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Stage: parseFiles
3
+ *
4
+ * Parses source files via parseFilesAuto (native or WASM engine).
5
+ * Populates ctx.allSymbols, ctx.fileSymbols, ctx.filesToParse.
6
+ */
7
+ import { performance } from 'node:perf_hooks';
8
+ import { info } from '../../logger.js';
9
+ import { parseFilesAuto } from '../../parser.js';
10
+
11
+ /**
12
+ * @param {import('../context.js').PipelineContext} ctx
13
+ */
14
+ export async function parseFiles(ctx) {
15
+ const { allFiles, parseChanges, isFullBuild, engineOpts, rootDir } = ctx;
16
+
17
+ ctx.filesToParse = isFullBuild ? allFiles.map((f) => ({ file: f })) : parseChanges;
18
+ ctx.fileSymbols = new Map();
19
+
20
+ const filePaths = ctx.filesToParse.map((item) => item.file);
21
+ const t0 = performance.now();
22
+ ctx.allSymbols = await parseFilesAuto(filePaths, rootDir, engineOpts);
23
+ ctx.timing.parseMs = performance.now() - t0;
24
+
25
+ const parsed = ctx.allSymbols.size;
26
+ const skipped = ctx.filesToParse.length - parsed;
27
+ info(`Parsed ${parsed} files (${skipped} skipped)`);
28
+ }
@@ -0,0 +1,143 @@
1
+ /**
2
+ * Stage: resolveImports
3
+ *
4
+ * Batch import resolution + barrel/re-export map construction.
5
+ * For incremental builds, loads unchanged barrel files for resolution.
6
+ */
7
+ import path from 'node:path';
8
+ import { performance } from 'node:perf_hooks';
9
+ import { parseFilesAuto } from '../../parser.js';
10
+ import { resolveImportPath, resolveImportsBatch } from '../../resolve.js';
11
+
12
+ /**
13
+ * @param {import('../context.js').PipelineContext} ctx
14
+ */
15
+ export async function resolveImports(ctx) {
16
+ const { db, fileSymbols, rootDir, aliases, allFiles, isFullBuild, engineOpts } = ctx;
17
+
18
+ // Collect all (fromFile, importSource) pairs and resolve in one native call
19
+ const t0 = performance.now();
20
+ const batchInputs = [];
21
+ for (const [relPath, symbols] of fileSymbols) {
22
+ const absFile = path.join(rootDir, relPath);
23
+ for (const imp of symbols.imports) {
24
+ batchInputs.push({ fromFile: absFile, importSource: imp.source });
25
+ }
26
+ }
27
+ ctx.batchResolved = resolveImportsBatch(batchInputs, rootDir, aliases, allFiles);
28
+ ctx.timing.resolveMs = performance.now() - t0;
29
+
30
+ // Build re-export map for barrel resolution
31
+ ctx.reexportMap = new Map();
32
+ for (const [relPath, symbols] of fileSymbols) {
33
+ const reexports = symbols.imports.filter((imp) => imp.reexport);
34
+ if (reexports.length > 0) {
35
+ ctx.reexportMap.set(
36
+ relPath,
37
+ reexports.map((imp) => ({
38
+ source: getResolved(ctx, path.join(rootDir, relPath), imp.source),
39
+ names: imp.names,
40
+ wildcardReexport: imp.wildcardReexport || false,
41
+ })),
42
+ );
43
+ }
44
+ }
45
+
46
+ // For incremental builds, load unchanged barrel files into reexportMap
47
+ ctx.barrelOnlyFiles = new Set();
48
+ if (!isFullBuild) {
49
+ const barrelCandidates = db
50
+ .prepare(
51
+ `SELECT DISTINCT n1.file FROM edges e
52
+ JOIN nodes n1 ON e.source_id = n1.id
53
+ WHERE e.kind = 'reexports' AND n1.kind = 'file'`,
54
+ )
55
+ .all();
56
+ for (const { file: relPath } of barrelCandidates) {
57
+ if (fileSymbols.has(relPath)) continue;
58
+ const absPath = path.join(rootDir, relPath);
59
+ try {
60
+ const symbols = await parseFilesAuto([absPath], rootDir, engineOpts);
61
+ const fileSym = symbols.get(relPath);
62
+ if (fileSym) {
63
+ fileSymbols.set(relPath, fileSym);
64
+ ctx.barrelOnlyFiles.add(relPath);
65
+ const reexports = fileSym.imports.filter((imp) => imp.reexport);
66
+ if (reexports.length > 0) {
67
+ ctx.reexportMap.set(
68
+ relPath,
69
+ reexports.map((imp) => ({
70
+ source: getResolved(ctx, absPath, imp.source),
71
+ names: imp.names,
72
+ wildcardReexport: imp.wildcardReexport || false,
73
+ })),
74
+ );
75
+ }
76
+ }
77
+ } catch {
78
+ /* skip if unreadable */
79
+ }
80
+ }
81
+ }
82
+ }
83
+
84
+ /**
85
+ * Resolve an import source, preferring batch results.
86
+ * Exported so other stages (build-edges) can reuse it.
87
+ */
88
+ export function getResolved(ctx, absFile, importSource) {
89
+ if (ctx.batchResolved) {
90
+ const key = `${absFile}|${importSource}`;
91
+ const hit = ctx.batchResolved.get(key);
92
+ if (hit !== undefined) return hit;
93
+ }
94
+ return resolveImportPath(absFile, importSource, ctx.rootDir, ctx.aliases);
95
+ }
96
+
97
+ /**
98
+ * Check if a file is a barrel (re-export hub).
99
+ */
100
+ export function isBarrelFile(ctx, relPath) {
101
+ const symbols = ctx.fileSymbols.get(relPath);
102
+ if (!symbols) return false;
103
+ const reexports = symbols.imports.filter((imp) => imp.reexport);
104
+ if (reexports.length === 0) return false;
105
+ const ownDefs = symbols.definitions.length;
106
+ return reexports.length >= ownDefs;
107
+ }
108
+
109
+ /**
110
+ * Resolve a symbol through barrel re-export chains.
111
+ */
112
+ export function resolveBarrelExport(ctx, barrelPath, symbolName, visited = new Set()) {
113
+ if (visited.has(barrelPath)) return null;
114
+ visited.add(barrelPath);
115
+ const reexports = ctx.reexportMap.get(barrelPath);
116
+ if (!reexports) return null;
117
+
118
+ for (const re of reexports) {
119
+ if (re.names.length > 0 && !re.wildcardReexport) {
120
+ if (re.names.includes(symbolName)) {
121
+ const targetSymbols = ctx.fileSymbols.get(re.source);
122
+ if (targetSymbols) {
123
+ const hasDef = targetSymbols.definitions.some((d) => d.name === symbolName);
124
+ if (hasDef) return re.source;
125
+ const deeper = resolveBarrelExport(ctx, re.source, symbolName, visited);
126
+ if (deeper) return deeper;
127
+ }
128
+ return re.source;
129
+ }
130
+ continue;
131
+ }
132
+ if (re.wildcardReexport || re.names.length === 0) {
133
+ const targetSymbols = ctx.fileSymbols.get(re.source);
134
+ if (targetSymbols) {
135
+ const hasDef = targetSymbols.definitions.some((d) => d.name === symbolName);
136
+ if (hasDef) return re.source;
137
+ const deeper = resolveBarrelExport(ctx, re.source, symbolName, visited);
138
+ if (deeper) return deeper;
139
+ }
140
+ }
141
+ }
142
+ return null;
143
+ }
@@ -0,0 +1,44 @@
1
+ /**
2
+ * Stage: runAnalyses
3
+ *
4
+ * Dispatches to the unified AST analysis engine (AST nodes, complexity, CFG, dataflow).
5
+ * Filters out reverse-dep files for incremental builds.
6
+ */
7
+ import { debug, warn } from '../../logger.js';
8
+
9
+ /**
10
+ * @param {import('../context.js').PipelineContext} ctx
11
+ */
12
+ export async function runAnalyses(ctx) {
13
+ const { db, allSymbols, rootDir, opts, engineOpts, isFullBuild, filesToParse } = ctx;
14
+
15
+ // For incremental builds, exclude reverse-dep-only files
16
+ let astComplexitySymbols = allSymbols;
17
+ if (!isFullBuild) {
18
+ const reverseDepFiles = new Set(
19
+ filesToParse.filter((item) => item._reverseDepOnly).map((item) => item.relPath),
20
+ );
21
+ if (reverseDepFiles.size > 0) {
22
+ astComplexitySymbols = new Map();
23
+ for (const [relPath, symbols] of allSymbols) {
24
+ if (!reverseDepFiles.has(relPath)) {
25
+ astComplexitySymbols.set(relPath, symbols);
26
+ }
27
+ }
28
+ debug(
29
+ `AST/complexity/CFG/dataflow: processing ${astComplexitySymbols.size} changed files (skipping ${reverseDepFiles.size} reverse-deps)`,
30
+ );
31
+ }
32
+ }
33
+
34
+ const { runAnalyses: runAnalysesFn } = await import('../../ast-analysis/engine.js');
35
+ try {
36
+ const analysisTiming = await runAnalysesFn(db, astComplexitySymbols, rootDir, opts, engineOpts);
37
+ ctx.timing.astMs = analysisTiming.astMs;
38
+ ctx.timing.complexityMs = analysisTiming.complexityMs;
39
+ ctx.timing.cfgMs = analysisTiming.cfgMs;
40
+ ctx.timing.dataflowMs = analysisTiming.dataflowMs;
41
+ } catch (err) {
42
+ warn(`Analysis engine failed (AST/complexity/CFG/dataflow may be incomplete): ${err.message}`);
43
+ }
44
+ }