@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
@@ -1,297 +0,0 @@
1
- /**
2
- * Stage: buildEdges
3
- *
4
- * Builds import, call, receiver, extends, and implements edges.
5
- * Uses pre-loaded node lookup maps (N+1 optimization).
6
- */
7
- import path from 'node:path';
8
- import { performance } from 'node:perf_hooks';
9
- import { getNodeId } from '../../db.js';
10
- import { loadNative } from '../../native.js';
11
- import { computeConfidence } from '../../resolve.js';
12
- import { BUILTIN_RECEIVERS, batchInsertEdges } from '../helpers.js';
13
- import { getResolved, isBarrelFile, resolveBarrelExport } from './resolve-imports.js';
14
-
15
- /**
16
- * @param {import('../context.js').PipelineContext} ctx
17
- */
18
- export async function buildEdges(ctx) {
19
- const { db, fileSymbols, barrelOnlyFiles, rootDir, engineName } = ctx;
20
-
21
- const getNodeIdStmt = {
22
- get: (name, kind, file, line) => {
23
- const id = getNodeId(db, name, kind, file, line);
24
- return id != null ? { id } : undefined;
25
- },
26
- };
27
-
28
- // Pre-load all nodes into lookup maps
29
- const allNodes = db
30
- .prepare(
31
- `SELECT id, name, kind, file, line FROM nodes WHERE kind IN ('function','method','class','interface','struct','type','module','enum','trait')`,
32
- )
33
- .all();
34
- ctx.nodesByName = new Map();
35
- for (const node of allNodes) {
36
- if (!ctx.nodesByName.has(node.name)) ctx.nodesByName.set(node.name, []);
37
- ctx.nodesByName.get(node.name).push(node);
38
- }
39
- ctx.nodesByNameAndFile = new Map();
40
- for (const node of allNodes) {
41
- const key = `${node.name}|${node.file}`;
42
- if (!ctx.nodesByNameAndFile.has(key)) ctx.nodesByNameAndFile.set(key, []);
43
- ctx.nodesByNameAndFile.get(key).push(node);
44
- }
45
-
46
- const t0 = performance.now();
47
- const buildEdgesTx = db.transaction(() => {
48
- const allEdgeRows = [];
49
-
50
- // ── Import edges ────────────────────────────────────────────────
51
- for (const [relPath, symbols] of fileSymbols) {
52
- if (barrelOnlyFiles.has(relPath)) continue;
53
- const fileNodeRow = getNodeIdStmt.get(relPath, 'file', relPath, 0);
54
- if (!fileNodeRow) continue;
55
- const fileNodeId = fileNodeRow.id;
56
-
57
- for (const imp of symbols.imports) {
58
- const resolvedPath = getResolved(ctx, path.join(rootDir, relPath), imp.source);
59
- const targetRow = getNodeIdStmt.get(resolvedPath, 'file', resolvedPath, 0);
60
- if (targetRow) {
61
- const edgeKind = imp.reexport
62
- ? 'reexports'
63
- : imp.typeOnly
64
- ? 'imports-type'
65
- : imp.dynamicImport
66
- ? 'dynamic-imports'
67
- : 'imports';
68
- allEdgeRows.push([fileNodeId, targetRow.id, edgeKind, 1.0, 0]);
69
-
70
- if (!imp.reexport && isBarrelFile(ctx, resolvedPath)) {
71
- const resolvedSources = new Set();
72
- for (const name of imp.names) {
73
- const cleanName = name.replace(/^\*\s+as\s+/, '');
74
- const actualSource = resolveBarrelExport(ctx, resolvedPath, cleanName);
75
- if (
76
- actualSource &&
77
- actualSource !== resolvedPath &&
78
- !resolvedSources.has(actualSource)
79
- ) {
80
- resolvedSources.add(actualSource);
81
- const actualRow = getNodeIdStmt.get(actualSource, 'file', actualSource, 0);
82
- if (actualRow) {
83
- allEdgeRows.push([
84
- fileNodeId,
85
- actualRow.id,
86
- edgeKind === 'imports-type'
87
- ? 'imports-type'
88
- : edgeKind === 'dynamic-imports'
89
- ? 'dynamic-imports'
90
- : 'imports',
91
- 0.9,
92
- 0,
93
- ]);
94
- }
95
- }
96
- }
97
- }
98
- }
99
- }
100
- }
101
-
102
- // ── Call/receiver/extends/implements edges ───────────────────────
103
- const native = engineName === 'native' ? loadNative() : null;
104
- if (native?.buildCallEdges) {
105
- const nativeFiles = [];
106
- for (const [relPath, symbols] of fileSymbols) {
107
- if (barrelOnlyFiles.has(relPath)) continue;
108
- const fileNodeRow = getNodeIdStmt.get(relPath, 'file', relPath, 0);
109
- if (!fileNodeRow) continue;
110
-
111
- const importedNames = [];
112
- for (const imp of symbols.imports) {
113
- const resolvedPath = getResolved(ctx, path.join(rootDir, relPath), imp.source);
114
- for (const name of imp.names) {
115
- const cleanName = name.replace(/^\*\s+as\s+/, '');
116
- let targetFile = resolvedPath;
117
- if (isBarrelFile(ctx, resolvedPath)) {
118
- const actual = resolveBarrelExport(ctx, resolvedPath, cleanName);
119
- if (actual) targetFile = actual;
120
- }
121
- importedNames.push({ name: cleanName, file: targetFile });
122
- }
123
- }
124
-
125
- nativeFiles.push({
126
- file: relPath,
127
- fileNodeId: fileNodeRow.id,
128
- definitions: symbols.definitions.map((d) => ({
129
- name: d.name,
130
- kind: d.kind,
131
- line: d.line,
132
- endLine: d.endLine ?? null,
133
- })),
134
- calls: symbols.calls,
135
- importedNames,
136
- classes: symbols.classes,
137
- });
138
- }
139
-
140
- const nativeEdges = native.buildCallEdges(nativeFiles, allNodes, [...BUILTIN_RECEIVERS]);
141
- for (const e of nativeEdges) {
142
- allEdgeRows.push([e.sourceId, e.targetId, e.kind, e.confidence, e.dynamic]);
143
- }
144
- } else {
145
- // JS fallback
146
- for (const [relPath, symbols] of fileSymbols) {
147
- if (barrelOnlyFiles.has(relPath)) continue;
148
- const fileNodeRow = getNodeIdStmt.get(relPath, 'file', relPath, 0);
149
- if (!fileNodeRow) continue;
150
-
151
- const importedNames = new Map();
152
- for (const imp of symbols.imports) {
153
- const resolvedPath = getResolved(ctx, path.join(rootDir, relPath), imp.source);
154
- for (const name of imp.names) {
155
- const cleanName = name.replace(/^\*\s+as\s+/, '');
156
- importedNames.set(cleanName, resolvedPath);
157
- }
158
- }
159
-
160
- const seenCallEdges = new Set();
161
- for (const call of symbols.calls) {
162
- if (call.receiver && BUILTIN_RECEIVERS.has(call.receiver)) continue;
163
- let caller = null;
164
- let callerSpan = Infinity;
165
- for (const def of symbols.definitions) {
166
- if (def.line <= call.line) {
167
- const end = def.endLine || Infinity;
168
- if (call.line <= end) {
169
- const span = end - def.line;
170
- if (span < callerSpan) {
171
- const row = getNodeIdStmt.get(def.name, def.kind, relPath, def.line);
172
- if (row) {
173
- caller = row;
174
- callerSpan = span;
175
- }
176
- }
177
- } else if (!caller) {
178
- const row = getNodeIdStmt.get(def.name, def.kind, relPath, def.line);
179
- if (row) caller = row;
180
- }
181
- }
182
- }
183
- if (!caller) caller = fileNodeRow;
184
-
185
- const isDynamic = call.dynamic ? 1 : 0;
186
- let targets;
187
- const importedFrom = importedNames.get(call.name);
188
-
189
- if (importedFrom) {
190
- targets = ctx.nodesByNameAndFile.get(`${call.name}|${importedFrom}`) || [];
191
- if (targets.length === 0 && isBarrelFile(ctx, importedFrom)) {
192
- const actualSource = resolveBarrelExport(ctx, importedFrom, call.name);
193
- if (actualSource) {
194
- targets = ctx.nodesByNameAndFile.get(`${call.name}|${actualSource}`) || [];
195
- }
196
- }
197
- }
198
- if (!targets || targets.length === 0) {
199
- targets = ctx.nodesByNameAndFile.get(`${call.name}|${relPath}`) || [];
200
- if (targets.length === 0) {
201
- const methodCandidates = (ctx.nodesByName.get(call.name) || []).filter(
202
- (n) => n.name.endsWith(`.${call.name}`) && n.kind === 'method',
203
- );
204
- if (methodCandidates.length > 0) {
205
- targets = methodCandidates;
206
- } else if (
207
- !call.receiver ||
208
- call.receiver === 'this' ||
209
- call.receiver === 'self' ||
210
- call.receiver === 'super'
211
- ) {
212
- targets = (ctx.nodesByName.get(call.name) || []).filter(
213
- (n) => computeConfidence(relPath, n.file, null) >= 0.5,
214
- );
215
- }
216
- }
217
- }
218
-
219
- if (targets.length > 1) {
220
- targets.sort((a, b) => {
221
- const confA = computeConfidence(relPath, a.file, importedFrom);
222
- const confB = computeConfidence(relPath, b.file, importedFrom);
223
- return confB - confA;
224
- });
225
- }
226
-
227
- for (const t of targets) {
228
- const edgeKey = `${caller.id}|${t.id}`;
229
- if (t.id !== caller.id && !seenCallEdges.has(edgeKey)) {
230
- seenCallEdges.add(edgeKey);
231
- const confidence = computeConfidence(relPath, t.file, importedFrom);
232
- allEdgeRows.push([caller.id, t.id, 'calls', confidence, isDynamic]);
233
- }
234
- }
235
-
236
- // Receiver edge
237
- if (
238
- call.receiver &&
239
- !BUILTIN_RECEIVERS.has(call.receiver) &&
240
- call.receiver !== 'this' &&
241
- call.receiver !== 'self' &&
242
- call.receiver !== 'super'
243
- ) {
244
- const receiverKinds = new Set(['class', 'struct', 'interface', 'type', 'module']);
245
- const samefile = ctx.nodesByNameAndFile.get(`${call.receiver}|${relPath}`) || [];
246
- const candidates =
247
- samefile.length > 0 ? samefile : ctx.nodesByName.get(call.receiver) || [];
248
- const receiverNodes = candidates.filter((n) => receiverKinds.has(n.kind));
249
- if (receiverNodes.length > 0 && caller) {
250
- const recvTarget = receiverNodes[0];
251
- const recvKey = `recv|${caller.id}|${recvTarget.id}`;
252
- if (!seenCallEdges.has(recvKey)) {
253
- seenCallEdges.add(recvKey);
254
- allEdgeRows.push([caller.id, recvTarget.id, 'receiver', 0.7, 0]);
255
- }
256
- }
257
- }
258
- }
259
-
260
- // Class extends edges
261
- for (const cls of symbols.classes) {
262
- if (cls.extends) {
263
- const sourceRow = (ctx.nodesByNameAndFile.get(`${cls.name}|${relPath}`) || []).find(
264
- (n) => n.kind === 'class',
265
- );
266
- const targetCandidates = ctx.nodesByName.get(cls.extends) || [];
267
- const targetRows = targetCandidates.filter((n) => n.kind === 'class');
268
- if (sourceRow) {
269
- for (const t of targetRows) {
270
- allEdgeRows.push([sourceRow.id, t.id, 'extends', 1.0, 0]);
271
- }
272
- }
273
- }
274
-
275
- if (cls.implements) {
276
- const sourceRow = (ctx.nodesByNameAndFile.get(`${cls.name}|${relPath}`) || []).find(
277
- (n) => n.kind === 'class',
278
- );
279
- const targetCandidates = ctx.nodesByName.get(cls.implements) || [];
280
- const targetRows = targetCandidates.filter(
281
- (n) => n.kind === 'interface' || n.kind === 'class',
282
- );
283
- if (sourceRow) {
284
- for (const t of targetRows) {
285
- allEdgeRows.push([sourceRow.id, t.id, 'implements', 1.0, 0]);
286
- }
287
- }
288
- }
289
- }
290
- }
291
- }
292
-
293
- batchInsertEdges(db, allEdgeRows);
294
- });
295
- buildEdgesTx();
296
- ctx.timing.edgesMs = performance.now() - t0;
297
- }
@@ -1,195 +0,0 @@
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
- }
package/src/mcp.js DELETED
@@ -1,2 +0,0 @@
1
- export { startMCPServer } from './mcp/server.js';
2
- export { buildToolList, TOOLS } from './mcp/tool-registry.js';