@optave/codegraph 3.1.5 → 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 +3 -2
- package/package.json +7 -7
- package/src/ast-analysis/engine.js +252 -258
- package/src/ast-analysis/shared.js +0 -12
- 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 +2 -1
- package/src/cli/commands/audit.js +2 -1
- package/src/cli/commands/batch.js +2 -1
- package/src/cli/commands/brief.js +12 -0
- package/src/cli/commands/cfg.js +2 -1
- package/src/cli/commands/check.js +20 -23
- package/src/cli/commands/children.js +6 -1
- package/src/cli/commands/complexity.js +2 -1
- package/src/cli/commands/context.js +6 -1
- package/src/cli/commands/dataflow.js +2 -1
- package/src/cli/commands/deps.js +8 -3
- package/src/cli/commands/flow.js +2 -1
- package/src/cli/commands/fn-impact.js +6 -1
- package/src/cli/commands/owners.js +4 -2
- package/src/cli/commands/query.js +6 -1
- package/src/cli/commands/roles.js +2 -1
- package/src/cli/commands/search.js +8 -2
- package/src/cli/commands/sequence.js +2 -1
- package/src/cli/commands/triage.js +38 -27
- package/src/db/connection.js +18 -12
- package/src/db/migrations.js +41 -64
- package/src/db/query-builder.js +60 -4
- package/src/db/repository/in-memory-repository.js +27 -16
- package/src/db/repository/nodes.js +8 -10
- package/src/domain/analysis/brief.js +155 -0
- package/src/domain/analysis/context.js +174 -190
- package/src/domain/analysis/dependencies.js +200 -146
- package/src/domain/analysis/exports.js +3 -2
- package/src/domain/analysis/impact.js +267 -152
- package/src/domain/analysis/module-map.js +247 -221
- package/src/domain/analysis/roles.js +8 -5
- package/src/domain/analysis/symbol-lookup.js +7 -5
- package/src/domain/graph/builder/helpers.js +1 -1
- package/src/domain/graph/builder/incremental.js +116 -90
- package/src/domain/graph/builder/pipeline.js +106 -80
- package/src/domain/graph/builder/stages/build-edges.js +318 -239
- package/src/domain/graph/builder/stages/detect-changes.js +198 -177
- package/src/domain/graph/builder/stages/insert-nodes.js +147 -139
- package/src/domain/graph/watcher.js +2 -2
- package/src/domain/parser.js +20 -11
- package/src/domain/queries.js +1 -0
- package/src/domain/search/search/filters.js +9 -5
- package/src/domain/search/search/keyword.js +12 -5
- package/src/domain/search/search/prepare.js +13 -5
- 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 +274 -304
- 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/features/ast.js +5 -3
- package/src/features/audit.js +4 -2
- package/src/features/boundaries.js +98 -83
- package/src/features/cfg.js +134 -143
- package/src/features/communities.js +68 -53
- package/src/features/complexity.js +143 -132
- package/src/features/dataflow.js +146 -149
- package/src/features/export.js +3 -3
- package/src/features/graph-enrichment.js +2 -2
- package/src/features/manifesto.js +9 -6
- package/src/features/owners.js +4 -3
- package/src/features/sequence.js +152 -141
- package/src/features/shared/find-nodes.js +31 -0
- package/src/features/structure.js +130 -99
- package/src/features/triage.js +83 -68
- package/src/graph/classifiers/risk.js +3 -2
- package/src/graph/classifiers/roles.js +6 -3
- package/src/index.js +1 -0
- package/src/mcp/server.js +65 -56
- package/src/mcp/tool-registry.js +13 -0
- package/src/mcp/tools/brief.js +8 -0
- package/src/mcp/tools/index.js +2 -0
- package/src/presentation/brief.js +51 -0
- package/src/presentation/queries-cli/exports.js +21 -14
- package/src/presentation/queries-cli/impact.js +55 -39
- package/src/presentation/queries-cli/inspect.js +184 -189
- package/src/presentation/queries-cli/overview.js +57 -58
- package/src/presentation/queries-cli/path.js +36 -29
- package/src/presentation/table.js +0 -8
- package/src/shared/generators.js +7 -3
- package/src/shared/kinds.js +1 -1
|
@@ -12,10 +12,121 @@ 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}
|
|
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
|
-
|
|
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
|
-
|
|
86
|
-
|
|
87
|
-
|
|
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';
|
|
@@ -23,94 +23,73 @@ import { parseFiles } from './stages/parse-files.js';
|
|
|
23
23
|
import { resolveImports } from './stages/resolve-imports.js';
|
|
24
24
|
import { runAnalyses } from './stages/run-analyses.js';
|
|
25
25
|
|
|
26
|
-
|
|
27
|
-
* Build the dependency graph for a codebase.
|
|
28
|
-
*
|
|
29
|
-
* Signature and return value are identical to the original monolithic buildGraph().
|
|
30
|
-
*
|
|
31
|
-
* @param {string} rootDir - Root directory to scan
|
|
32
|
-
* @param {object} [opts] - Build options
|
|
33
|
-
* @returns {Promise<{ phases: object } | undefined>}
|
|
34
|
-
*/
|
|
35
|
-
export async function buildGraph(rootDir, opts = {}) {
|
|
36
|
-
const ctx = new PipelineContext();
|
|
37
|
-
ctx.buildStart = performance.now();
|
|
38
|
-
ctx.opts = opts;
|
|
26
|
+
// ── Setup helpers ───────────────────────────────────────────────────────
|
|
39
27
|
|
|
40
|
-
|
|
41
|
-
ctx.
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
ctx.engineOpts = {
|
|
52
|
-
engine: opts.engine || 'auto',
|
|
53
|
-
dataflow: opts.dataflow !== false,
|
|
54
|
-
ast: opts.ast !== false,
|
|
55
|
-
};
|
|
56
|
-
const { name: engineName, version: engineVersion } = getActiveEngine(ctx.engineOpts);
|
|
57
|
-
ctx.engineName = engineName;
|
|
58
|
-
ctx.engineVersion = engineVersion;
|
|
59
|
-
info(`Using ${engineName} engine${engineVersion ? ` (v${engineVersion})` : ''}`);
|
|
60
|
-
|
|
61
|
-
// Engine/schema mismatch detection
|
|
62
|
-
ctx.schemaVersion = MIGRATIONS[MIGRATIONS.length - 1].version;
|
|
63
|
-
ctx.forceFullRebuild = false;
|
|
64
|
-
if (ctx.incremental) {
|
|
65
|
-
const prevEngine = getBuildMeta(ctx.db, 'engine');
|
|
66
|
-
if (prevEngine && prevEngine !== engineName) {
|
|
67
|
-
info(`Engine changed (${prevEngine} → ${engineName}), promoting to full rebuild.`);
|
|
68
|
-
ctx.forceFullRebuild = true;
|
|
69
|
-
}
|
|
70
|
-
const prevSchema = getBuildMeta(ctx.db, 'schema_version');
|
|
71
|
-
if (prevSchema && Number(prevSchema) !== ctx.schemaVersion) {
|
|
72
|
-
info(
|
|
73
|
-
`Schema version changed (${prevSchema} → ${ctx.schemaVersion}), promoting to full rebuild.`,
|
|
74
|
-
);
|
|
75
|
-
ctx.forceFullRebuild = true;
|
|
76
|
-
}
|
|
77
|
-
}
|
|
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
|
+
}
|
|
78
39
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
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}/*`];
|
|
92
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
|
+
}
|
|
93
74
|
|
|
94
|
-
|
|
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);
|
|
95
80
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
81
|
+
ctx.config = loadConfig(ctx.rootDir);
|
|
82
|
+
ctx.incremental =
|
|
83
|
+
ctx.opts.incremental !== false && ctx.config.build && ctx.config.build.incremental !== false;
|
|
99
84
|
|
|
100
|
-
|
|
85
|
+
initializeEngine(ctx);
|
|
86
|
+
checkEngineSchemaMismatch(ctx);
|
|
87
|
+
loadAliases(ctx);
|
|
101
88
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
await resolveImports(ctx);
|
|
105
|
-
await buildEdges(ctx);
|
|
106
|
-
await buildStructure(ctx);
|
|
107
|
-
await runAnalyses(ctx);
|
|
108
|
-
await finalize(ctx);
|
|
109
|
-
} catch (err) {
|
|
110
|
-
if (!ctx.earlyExit) closeDb(ctx.db);
|
|
111
|
-
throw err;
|
|
112
|
-
}
|
|
89
|
+
ctx.timing.setupMs = performance.now() - ctx.buildStart;
|
|
90
|
+
}
|
|
113
91
|
|
|
92
|
+
function formatTimingResult(ctx) {
|
|
114
93
|
return {
|
|
115
94
|
phases: {
|
|
116
95
|
setupMs: +ctx.timing.setupMs.toFixed(1),
|
|
@@ -128,3 +107,50 @@ export async function buildGraph(rootDir, opts = {}) {
|
|
|
128
107
|
},
|
|
129
108
|
};
|
|
130
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
|
+
}
|