@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.
- package/README.md +29 -72
- package/package.json +10 -8
- package/src/ast-analysis/engine.js +260 -246
- package/src/ast-analysis/shared.js +2 -14
- package/src/ast-analysis/visitors/cfg-visitor.js +635 -649
- package/src/ast-analysis/visitors/complexity-visitor.js +135 -139
- package/src/ast-analysis/visitors/dataflow-visitor.js +230 -224
- package/src/cli/commands/ast.js +4 -7
- package/src/cli/commands/audit.js +11 -11
- package/src/cli/commands/batch.js +6 -5
- package/src/cli/commands/branch-compare.js +1 -1
- package/src/cli/commands/brief.js +12 -0
- package/src/cli/commands/build.js +1 -1
- package/src/cli/commands/cfg.js +5 -8
- package/src/cli/commands/check.js +28 -36
- package/src/cli/commands/children.js +9 -7
- package/src/cli/commands/co-change.js +5 -3
- package/src/cli/commands/communities.js +2 -6
- package/src/cli/commands/complexity.js +5 -3
- package/src/cli/commands/context.js +9 -8
- package/src/cli/commands/cycles.js +12 -8
- package/src/cli/commands/dataflow.js +5 -8
- package/src/cli/commands/deps.js +9 -8
- package/src/cli/commands/diff-impact.js +2 -6
- package/src/cli/commands/embed.js +1 -1
- package/src/cli/commands/export.js +34 -31
- package/src/cli/commands/exports.js +2 -6
- package/src/cli/commands/flow.js +5 -8
- package/src/cli/commands/fn-impact.js +9 -8
- package/src/cli/commands/impact.js +2 -6
- package/src/cli/commands/info.js +2 -2
- package/src/cli/commands/map.js +1 -1
- package/src/cli/commands/mcp.js +1 -1
- package/src/cli/commands/models.js +1 -1
- package/src/cli/commands/owners.js +5 -3
- package/src/cli/commands/path.js +2 -2
- package/src/cli/commands/plot.js +40 -31
- package/src/cli/commands/query.js +9 -8
- package/src/cli/commands/registry.js +2 -2
- package/src/cli/commands/roles.js +5 -8
- package/src/cli/commands/search.js +9 -3
- package/src/cli/commands/sequence.js +5 -8
- package/src/cli/commands/snapshot.js +6 -1
- package/src/cli/commands/stats.js +1 -1
- package/src/cli/commands/structure.js +5 -4
- package/src/cli/commands/triage.js +41 -30
- package/src/cli/commands/watch.js +1 -1
- package/src/cli/commands/where.js +2 -6
- package/src/cli/index.js +11 -5
- package/src/cli/shared/open-graph.js +13 -0
- package/src/cli/shared/options.js +22 -2
- package/src/cli.js +1 -1
- package/src/db/connection.js +140 -11
- package/src/{db.js → db/index.js} +12 -5
- package/src/db/migrations.js +42 -65
- package/src/db/query-builder.js +72 -9
- package/src/db/repository/base.js +1 -1
- package/src/db/repository/graph-read.js +3 -3
- package/src/db/repository/in-memory-repository.js +30 -28
- package/src/db/repository/nodes.js +10 -17
- package/src/domain/analysis/brief.js +155 -0
- package/src/domain/analysis/context.js +392 -0
- package/src/domain/analysis/dependencies.js +395 -0
- package/src/{analysis → domain/analysis}/exports.js +11 -6
- package/src/domain/analysis/impact.js +581 -0
- package/src/domain/analysis/module-map.js +348 -0
- package/src/{analysis → domain/analysis}/roles.js +12 -9
- package/src/{analysis → domain/analysis}/symbol-lookup.js +19 -11
- package/src/{builder → domain/graph/builder}/helpers.js +4 -4
- package/src/{builder → domain/graph/builder}/incremental.js +119 -93
- package/src/domain/graph/builder/pipeline.js +156 -0
- package/src/domain/graph/builder/stages/build-edges.js +376 -0
- package/src/{builder → domain/graph/builder}/stages/build-structure.js +4 -4
- package/src/{builder → domain/graph/builder}/stages/collect-files.js +2 -2
- package/src/{builder → domain/graph/builder}/stages/detect-changes.js +204 -183
- package/src/{builder → domain/graph/builder}/stages/finalize.js +4 -4
- package/src/domain/graph/builder/stages/insert-nodes.js +203 -0
- package/src/{builder → domain/graph/builder}/stages/parse-files.js +2 -2
- package/src/{builder → domain/graph/builder}/stages/resolve-imports.js +1 -1
- package/src/{builder → domain/graph/builder}/stages/run-analyses.js +2 -2
- package/src/{change-journal.js → domain/graph/change-journal.js} +1 -1
- package/src/{cycles.js → domain/graph/cycles.js} +4 -4
- package/src/{journal.js → domain/graph/journal.js} +1 -1
- package/src/{resolve.js → domain/graph/resolve.js} +2 -2
- package/src/{watcher.js → domain/graph/watcher.js} +7 -7
- package/src/{parser.js → domain/parser.js} +24 -15
- package/src/{queries.js → domain/queries.js} +17 -16
- package/src/{embeddings → domain/search}/generator.js +3 -3
- package/src/{embeddings → domain/search}/models.js +2 -2
- package/src/{embeddings → domain/search}/search/cli-formatter.js +1 -1
- package/src/{embeddings → domain/search}/search/filters.js +9 -5
- package/src/{embeddings → domain/search}/search/hybrid.js +1 -1
- package/src/{embeddings → domain/search}/search/keyword.js +13 -6
- package/src/{embeddings → domain/search}/search/prepare.js +15 -7
- package/src/{embeddings → domain/search}/search/semantic.js +1 -1
- package/src/{embeddings → domain/search}/strategies/structured.js +1 -1
- package/src/extractors/csharp.js +224 -207
- package/src/extractors/go.js +176 -172
- package/src/extractors/hcl.js +94 -78
- package/src/extractors/java.js +213 -207
- package/src/extractors/javascript.js +275 -305
- package/src/extractors/php.js +234 -221
- package/src/extractors/python.js +252 -250
- package/src/extractors/ruby.js +192 -185
- package/src/extractors/rust.js +182 -167
- package/src/{ast.js → features/ast.js} +13 -11
- package/src/{audit.js → features/audit.js} +20 -46
- package/src/{batch.js → features/batch.js} +5 -5
- package/src/{boundaries.js → features/boundaries.js} +100 -85
- package/src/{branch-compare.js → features/branch-compare.js} +3 -3
- package/src/{cfg.js → features/cfg.js} +141 -150
- package/src/{check.js → features/check.js} +13 -30
- package/src/{cochange.js → features/cochange.js} +5 -5
- package/src/{communities.js → features/communities.js} +72 -57
- package/src/{complexity.js → features/complexity.js} +154 -143
- package/src/{dataflow.js → features/dataflow.js} +155 -158
- package/src/{export.js → features/export.js} +6 -6
- package/src/{flow.js → features/flow.js} +4 -4
- package/src/{viewer.js → features/graph-enrichment.js} +8 -8
- package/src/{manifesto.js → features/manifesto.js} +15 -12
- package/src/{owners.js → features/owners.js} +6 -5
- package/src/features/sequence.js +300 -0
- package/src/features/shared/find-nodes.js +31 -0
- package/src/{snapshot.js → features/snapshot.js} +3 -3
- package/src/{structure.js → features/structure.js} +139 -108
- package/src/features/triage.js +141 -0
- package/src/graph/builders/dependency.js +33 -14
- package/src/graph/classifiers/risk.js +3 -2
- package/src/graph/classifiers/roles.js +6 -3
- package/src/index.cjs +16 -0
- package/src/index.js +40 -39
- package/src/{native.js → infrastructure/native.js} +1 -1
- package/src/mcp/middleware.js +1 -1
- package/src/mcp/server.js +68 -59
- package/src/mcp/tool-registry.js +15 -2
- package/src/mcp/tools/ast-query.js +1 -1
- package/src/mcp/tools/audit.js +1 -1
- package/src/mcp/tools/batch-query.js +1 -1
- package/src/mcp/tools/branch-compare.js +3 -1
- package/src/mcp/tools/brief.js +8 -0
- package/src/mcp/tools/cfg.js +1 -1
- package/src/mcp/tools/check.js +3 -3
- package/src/mcp/tools/co-changes.js +1 -1
- package/src/mcp/tools/code-owners.js +1 -1
- package/src/mcp/tools/communities.js +1 -1
- package/src/mcp/tools/complexity.js +1 -1
- package/src/mcp/tools/dataflow.js +2 -2
- package/src/mcp/tools/execution-flow.js +2 -2
- package/src/mcp/tools/export-graph.js +2 -2
- package/src/mcp/tools/find-cycles.js +2 -2
- package/src/mcp/tools/index.js +2 -0
- package/src/mcp/tools/list-repos.js +1 -1
- package/src/mcp/tools/sequence.js +1 -1
- package/src/mcp/tools/structure.js +1 -1
- package/src/mcp/tools/triage.js +2 -2
- package/src/{commands → presentation}/audit.js +2 -2
- package/src/{commands → presentation}/batch.js +1 -1
- package/src/{commands → presentation}/branch-compare.js +2 -2
- package/src/presentation/brief.js +51 -0
- package/src/{commands → presentation}/cfg.js +1 -1
- package/src/{commands → presentation}/check.js +2 -2
- package/src/{commands → presentation}/communities.js +1 -1
- package/src/{commands → presentation}/complexity.js +1 -1
- package/src/{commands → presentation}/dataflow.js +1 -1
- package/src/{commands → presentation}/flow.js +2 -2
- package/src/{commands → presentation}/manifesto.js +1 -1
- package/src/{commands → presentation}/owners.js +1 -1
- package/src/presentation/queries-cli/exports.js +53 -0
- package/src/presentation/queries-cli/impact.js +214 -0
- package/src/presentation/queries-cli/index.js +5 -0
- package/src/presentation/queries-cli/inspect.js +329 -0
- package/src/presentation/queries-cli/overview.js +196 -0
- package/src/presentation/queries-cli/path.js +65 -0
- package/src/presentation/queries-cli.js +27 -0
- package/src/{commands → presentation}/query.js +1 -1
- package/src/presentation/result-formatter.js +126 -3
- package/src/{commands → presentation}/sequence.js +2 -2
- package/src/{commands → presentation}/structure.js +1 -1
- package/src/presentation/table.js +0 -8
- package/src/{commands → presentation}/triage.js +1 -1
- package/src/{constants.js → shared/constants.js} +1 -1
- package/src/shared/file-utils.js +2 -2
- package/src/shared/generators.js +9 -5
- package/src/shared/hierarchy.js +1 -1
- package/src/{kinds.js → shared/kinds.js} +1 -1
- package/src/analysis/context.js +0 -408
- package/src/analysis/dependencies.js +0 -341
- package/src/analysis/impact.js +0 -463
- package/src/analysis/module-map.js +0 -322
- package/src/builder/pipeline.js +0 -130
- package/src/builder/stages/build-edges.js +0 -297
- package/src/builder/stages/insert-nodes.js +0 -195
- package/src/mcp.js +0 -2
- package/src/queries-cli.js +0 -866
- package/src/sequence.js +0 -289
- package/src/triage.js +0 -126
- /package/src/{builder → domain/graph/builder}/context.js +0 -0
- /package/src/{builder.js → domain/graph/builder.js} +0 -0
- /package/src/{embeddings → domain/search}/index.js +0 -0
- /package/src/{embeddings → domain/search}/stores/fts5.js +0 -0
- /package/src/{embeddings → domain/search}/stores/sqlite-blob.js +0 -0
- /package/src/{embeddings → domain/search}/strategies/source.js +0 -0
- /package/src/{embeddings → domain/search}/strategies/text-utils.js +0 -0
- /package/src/{config.js → infrastructure/config.js} +0 -0
- /package/src/{logger.js → infrastructure/logger.js} +0 -0
- /package/src/{registry.js → infrastructure/registry.js} +0 -0
- /package/src/{update-check.js → infrastructure/update-check.js} +0 -0
- /package/src/{commands → presentation}/cochange.js +0 -0
- /package/src/{errors.js → shared/errors.js} +0 -0
- /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