@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
|
@@ -0,0 +1,203 @@
|
|
|
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/index.js';
|
|
10
|
+
import {
|
|
11
|
+
batchInsertEdges,
|
|
12
|
+
batchInsertNodes,
|
|
13
|
+
fileHash,
|
|
14
|
+
fileStat,
|
|
15
|
+
readFileSafe,
|
|
16
|
+
} from '../helpers.js';
|
|
17
|
+
|
|
18
|
+
// ── Phase 1: Insert file nodes, definitions, exports ────────────────────
|
|
19
|
+
|
|
20
|
+
function insertDefinitionsAndExports(db, allSymbols) {
|
|
21
|
+
const phase1Rows = [];
|
|
22
|
+
for (const [relPath, symbols] of allSymbols) {
|
|
23
|
+
phase1Rows.push([relPath, 'file', relPath, 0, null, null, null, null, null]);
|
|
24
|
+
for (const def of symbols.definitions) {
|
|
25
|
+
const dotIdx = def.name.lastIndexOf('.');
|
|
26
|
+
const scope = dotIdx !== -1 ? def.name.slice(0, dotIdx) : null;
|
|
27
|
+
phase1Rows.push([
|
|
28
|
+
def.name,
|
|
29
|
+
def.kind,
|
|
30
|
+
relPath,
|
|
31
|
+
def.line,
|
|
32
|
+
def.endLine || null,
|
|
33
|
+
null,
|
|
34
|
+
def.name,
|
|
35
|
+
scope,
|
|
36
|
+
def.visibility || null,
|
|
37
|
+
]);
|
|
38
|
+
}
|
|
39
|
+
for (const exp of symbols.exports) {
|
|
40
|
+
phase1Rows.push([exp.name, exp.kind, relPath, exp.line, null, null, exp.name, null, null]);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
batchInsertNodes(db, phase1Rows);
|
|
44
|
+
|
|
45
|
+
// Mark exported symbols
|
|
46
|
+
const markExported = db.prepare(
|
|
47
|
+
'UPDATE nodes SET exported = 1 WHERE name = ? AND kind = ? AND file = ? AND line = ?',
|
|
48
|
+
);
|
|
49
|
+
for (const [relPath, symbols] of allSymbols) {
|
|
50
|
+
for (const exp of symbols.exports) {
|
|
51
|
+
markExported.run(exp.name, exp.kind, relPath, exp.line);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// ── Phase 2: Insert children (needs parent IDs) ────────────────────────
|
|
57
|
+
|
|
58
|
+
function insertChildren(db, allSymbols) {
|
|
59
|
+
const childRows = [];
|
|
60
|
+
for (const [relPath, symbols] of allSymbols) {
|
|
61
|
+
const nodeIdMap = new Map();
|
|
62
|
+
for (const row of bulkNodeIdsByFile(db, relPath)) {
|
|
63
|
+
nodeIdMap.set(`${row.name}|${row.kind}|${row.line}`, row.id);
|
|
64
|
+
}
|
|
65
|
+
for (const def of symbols.definitions) {
|
|
66
|
+
if (!def.children?.length) continue;
|
|
67
|
+
const defId = nodeIdMap.get(`${def.name}|${def.kind}|${def.line}`);
|
|
68
|
+
if (!defId) continue;
|
|
69
|
+
for (const child of def.children) {
|
|
70
|
+
const qualifiedName = `${def.name}.${child.name}`;
|
|
71
|
+
childRows.push([
|
|
72
|
+
child.name,
|
|
73
|
+
child.kind,
|
|
74
|
+
relPath,
|
|
75
|
+
child.line,
|
|
76
|
+
child.endLine || null,
|
|
77
|
+
defId,
|
|
78
|
+
qualifiedName,
|
|
79
|
+
def.name,
|
|
80
|
+
child.visibility || null,
|
|
81
|
+
]);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
batchInsertNodes(db, childRows);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// ── Phase 3: Insert containment + parameter_of edges ────────────────────
|
|
89
|
+
|
|
90
|
+
function insertContainmentEdges(db, allSymbols) {
|
|
91
|
+
const edgeRows = [];
|
|
92
|
+
for (const [relPath, symbols] of allSymbols) {
|
|
93
|
+
const nodeIdMap = new Map();
|
|
94
|
+
for (const row of bulkNodeIdsByFile(db, relPath)) {
|
|
95
|
+
nodeIdMap.set(`${row.name}|${row.kind}|${row.line}`, row.id);
|
|
96
|
+
}
|
|
97
|
+
const fileId = nodeIdMap.get(`${relPath}|file|0`);
|
|
98
|
+
for (const def of symbols.definitions) {
|
|
99
|
+
const defId = nodeIdMap.get(`${def.name}|${def.kind}|${def.line}`);
|
|
100
|
+
if (fileId && defId) {
|
|
101
|
+
edgeRows.push([fileId, defId, 'contains', 1.0, 0]);
|
|
102
|
+
}
|
|
103
|
+
if (def.children?.length && defId) {
|
|
104
|
+
for (const child of def.children) {
|
|
105
|
+
const childId = nodeIdMap.get(`${child.name}|${child.kind}|${child.line}`);
|
|
106
|
+
if (childId) {
|
|
107
|
+
edgeRows.push([defId, childId, 'contains', 1.0, 0]);
|
|
108
|
+
if (child.kind === 'parameter') {
|
|
109
|
+
edgeRows.push([childId, defId, 'parameter_of', 1.0, 0]);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
batchInsertEdges(db, edgeRows);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// ── Phase 4: Update file hashes ─────────────────────────────────────────
|
|
120
|
+
|
|
121
|
+
function updateFileHashes(_db, allSymbols, precomputedData, metadataUpdates, rootDir, upsertHash) {
|
|
122
|
+
if (!upsertHash) return;
|
|
123
|
+
|
|
124
|
+
for (const [relPath] of allSymbols) {
|
|
125
|
+
const precomputed = precomputedData.get(relPath);
|
|
126
|
+
if (precomputed?._reverseDepOnly) {
|
|
127
|
+
// no-op: file unchanged, hash already correct
|
|
128
|
+
} else if (precomputed?.hash) {
|
|
129
|
+
const stat = precomputed.stat || fileStat(path.join(rootDir, relPath));
|
|
130
|
+
const mtime = stat ? Math.floor(stat.mtimeMs) : 0;
|
|
131
|
+
const size = stat ? stat.size : 0;
|
|
132
|
+
upsertHash.run(relPath, precomputed.hash, mtime, size);
|
|
133
|
+
} else {
|
|
134
|
+
const absPath = path.join(rootDir, relPath);
|
|
135
|
+
let code;
|
|
136
|
+
try {
|
|
137
|
+
code = readFileSafe(absPath);
|
|
138
|
+
} catch {
|
|
139
|
+
code = null;
|
|
140
|
+
}
|
|
141
|
+
if (code !== null) {
|
|
142
|
+
const stat = fileStat(absPath);
|
|
143
|
+
const mtime = stat ? Math.floor(stat.mtimeMs) : 0;
|
|
144
|
+
const size = stat ? stat.size : 0;
|
|
145
|
+
upsertHash.run(relPath, fileHash(code), mtime, size);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// Also update metadata-only entries (self-heal mtime/size without re-parse)
|
|
151
|
+
for (const item of metadataUpdates) {
|
|
152
|
+
const mtime = item.stat ? Math.floor(item.stat.mtimeMs) : 0;
|
|
153
|
+
const size = item.stat ? item.stat.size : 0;
|
|
154
|
+
upsertHash.run(item.relPath, item.hash, mtime, size);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// ── Main entry point ────────────────────────────────────────────────────
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* @param {import('../context.js').PipelineContext} ctx
|
|
162
|
+
*/
|
|
163
|
+
export async function insertNodes(ctx) {
|
|
164
|
+
const { db, allSymbols, filesToParse, metadataUpdates, rootDir, removed } = ctx;
|
|
165
|
+
|
|
166
|
+
const precomputedData = new Map();
|
|
167
|
+
for (const item of filesToParse) {
|
|
168
|
+
if (item.relPath) precomputedData.set(item.relPath, item);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
let upsertHash;
|
|
172
|
+
try {
|
|
173
|
+
upsertHash = db.prepare(
|
|
174
|
+
'INSERT OR REPLACE INTO file_hashes (file, hash, mtime, size) VALUES (?, ?, ?, ?)',
|
|
175
|
+
);
|
|
176
|
+
} catch {
|
|
177
|
+
upsertHash = null;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// Populate fileSymbols before the transaction so it is a pure input
|
|
181
|
+
for (const [relPath, symbols] of allSymbols) {
|
|
182
|
+
ctx.fileSymbols.set(relPath, symbols);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
const insertAll = db.transaction(() => {
|
|
186
|
+
insertDefinitionsAndExports(db, allSymbols);
|
|
187
|
+
insertChildren(db, allSymbols);
|
|
188
|
+
insertContainmentEdges(db, allSymbols);
|
|
189
|
+
updateFileHashes(db, allSymbols, precomputedData, metadataUpdates, rootDir, upsertHash);
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
const t0 = performance.now();
|
|
193
|
+
insertAll();
|
|
194
|
+
ctx.timing.insertMs = performance.now() - t0;
|
|
195
|
+
|
|
196
|
+
// Clean up removed file hashes
|
|
197
|
+
if (upsertHash && removed.length > 0) {
|
|
198
|
+
const deleteHash = db.prepare('DELETE FROM file_hashes WHERE file = ?');
|
|
199
|
+
for (const relPath of removed) {
|
|
200
|
+
deleteHash.run(relPath);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
}
|
|
@@ -5,8 +5,8 @@
|
|
|
5
5
|
* Populates ctx.allSymbols, ctx.fileSymbols, ctx.filesToParse.
|
|
6
6
|
*/
|
|
7
7
|
import { performance } from 'node:perf_hooks';
|
|
8
|
-
import { info } from '
|
|
9
|
-
import { parseFilesAuto } from '
|
|
8
|
+
import { info } from '../../../../infrastructure/logger.js';
|
|
9
|
+
import { parseFilesAuto } from '../../../parser.js';
|
|
10
10
|
|
|
11
11
|
/**
|
|
12
12
|
* @param {import('../context.js').PipelineContext} ctx
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
*/
|
|
7
7
|
import path from 'node:path';
|
|
8
8
|
import { performance } from 'node:perf_hooks';
|
|
9
|
-
import { parseFilesAuto } from '
|
|
9
|
+
import { parseFilesAuto } from '../../../parser.js';
|
|
10
10
|
import { resolveImportPath, resolveImportsBatch } from '../../resolve.js';
|
|
11
11
|
|
|
12
12
|
/**
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* Dispatches to the unified AST analysis engine (AST nodes, complexity, CFG, dataflow).
|
|
5
5
|
* Filters out reverse-dep files for incremental builds.
|
|
6
6
|
*/
|
|
7
|
-
import { debug, warn } from '
|
|
7
|
+
import { debug, warn } from '../../../../infrastructure/logger.js';
|
|
8
8
|
|
|
9
9
|
/**
|
|
10
10
|
* @param {import('../context.js').PipelineContext} ctx
|
|
@@ -31,7 +31,7 @@ export async function runAnalyses(ctx) {
|
|
|
31
31
|
}
|
|
32
32
|
}
|
|
33
33
|
|
|
34
|
-
const { runAnalyses: runAnalysesFn } = await import('
|
|
34
|
+
const { runAnalyses: runAnalysesFn } = await import('../../../../ast-analysis/engine.js');
|
|
35
35
|
try {
|
|
36
36
|
const analysisTiming = await runAnalysesFn(db, astComplexitySymbols, rootDir, opts, engineOpts);
|
|
37
37
|
ctx.timing.astMs = analysisTiming.astMs;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import fs from 'node:fs';
|
|
2
2
|
import path from 'node:path';
|
|
3
|
-
import { debug, warn } from '
|
|
3
|
+
import { debug, warn } from '../../infrastructure/logger.js';
|
|
4
4
|
|
|
5
5
|
export const CHANGE_EVENTS_FILENAME = 'change-events.ndjson';
|
|
6
6
|
export const DEFAULT_MAX_BYTES = 1024 * 1024; // 1 MB
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { tarjan } from '
|
|
2
|
-
import { buildDependencyGraph } from '
|
|
3
|
-
import { CodeGraph } from '
|
|
4
|
-
import { loadNative } from '
|
|
1
|
+
import { tarjan } from '../../graph/algorithms/tarjan.js';
|
|
2
|
+
import { buildDependencyGraph } from '../../graph/builders/dependency.js';
|
|
3
|
+
import { CodeGraph } from '../../graph/model.js';
|
|
4
|
+
import { loadNative } from '../../infrastructure/native.js';
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
7
|
* Detect circular dependencies in the codebase using Tarjan's SCC algorithm.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import fs from 'node:fs';
|
|
2
2
|
import path from 'node:path';
|
|
3
|
-
import { debug, warn } from '
|
|
3
|
+
import { debug, warn } from '../../infrastructure/logger.js';
|
|
4
4
|
|
|
5
5
|
export const JOURNAL_FILENAME = 'changes.journal';
|
|
6
6
|
const HEADER_PREFIX = '# codegraph-journal v1 ';
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import fs from 'node:fs';
|
|
2
2
|
import path from 'node:path';
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
3
|
+
import { loadNative } from '../../infrastructure/native.js';
|
|
4
|
+
import { normalizePath } from '../../shared/constants.js';
|
|
5
5
|
|
|
6
6
|
// ── Alias format conversion ─────────────────────────────────────────
|
|
7
7
|
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import fs from 'node:fs';
|
|
2
2
|
import path from 'node:path';
|
|
3
|
+
import { closeDb, getNodeId as getNodeIdQuery, initSchema, openDb } from '../../db/index.js';
|
|
4
|
+
import { info } from '../../infrastructure/logger.js';
|
|
5
|
+
import { EXTENSIONS, IGNORE_DIRS, normalizePath } from '../../shared/constants.js';
|
|
6
|
+
import { DbError } from '../../shared/errors.js';
|
|
7
|
+
import { createParseTreeCache, getActiveEngine } from '../parser.js';
|
|
3
8
|
import { rebuildFile } from './builder/incremental.js';
|
|
4
9
|
import { appendChangeEvents, buildChangeEvent, diffSymbols } from './change-journal.js';
|
|
5
|
-
import { EXTENSIONS, IGNORE_DIRS, normalizePath } from './constants.js';
|
|
6
|
-
import { closeDb, getNodeId as getNodeIdQuery, initSchema, openDb } from './db.js';
|
|
7
|
-
import { DbError } from './errors.js';
|
|
8
10
|
import { appendJournalEntries } from './journal.js';
|
|
9
|
-
import { info } from './logger.js';
|
|
10
|
-
import { createParseTreeCache, getActiveEngine } from './parser.js';
|
|
11
11
|
|
|
12
12
|
function shouldIgnore(filePath) {
|
|
13
13
|
const parts = filePath.split(path.sep);
|
|
@@ -57,10 +57,10 @@ export async function watchProject(rootDir, opts = {}) {
|
|
|
57
57
|
countNodes: db.prepare('SELECT COUNT(*) as c FROM nodes WHERE file = ?'),
|
|
58
58
|
countEdgesForFile: null,
|
|
59
59
|
findNodeInFile: db.prepare(
|
|
60
|
-
"SELECT id, file FROM nodes WHERE name = ? AND kind IN ('function', 'method', 'class', 'interface', 'type', 'struct', 'enum', 'trait', 'record', 'module') AND file = ?",
|
|
60
|
+
"SELECT id, file FROM nodes WHERE name = ? AND kind IN ('function', 'method', 'class', 'interface', 'type', 'struct', 'enum', 'trait', 'record', 'module', 'constant') AND file = ?",
|
|
61
61
|
),
|
|
62
62
|
findNodeByName: db.prepare(
|
|
63
|
-
"SELECT id, file FROM nodes WHERE name = ? AND kind IN ('function', 'method', 'class', 'interface', 'type', 'struct', 'enum', 'trait', 'record', 'module')",
|
|
63
|
+
"SELECT id, file FROM nodes WHERE name = ? AND kind IN ('function', 'method', 'class', 'interface', 'type', 'struct', 'enum', 'trait', 'record', 'module', 'constant')",
|
|
64
64
|
),
|
|
65
65
|
listSymbols: db.prepare("SELECT name, kind, line FROM nodes WHERE file = ? AND kind != 'file'"),
|
|
66
66
|
};
|
|
@@ -2,8 +2,8 @@ import fs from 'node:fs';
|
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import { fileURLToPath } from 'node:url';
|
|
4
4
|
import { Language, Parser, Query } from 'web-tree-sitter';
|
|
5
|
-
import { warn } from '
|
|
6
|
-
import { getNative, getNativePackageVersion, loadNative } from '
|
|
5
|
+
import { debug, warn } from '../infrastructure/logger.js';
|
|
6
|
+
import { getNative, getNativePackageVersion, loadNative } from '../infrastructure/native.js';
|
|
7
7
|
|
|
8
8
|
// Re-export all extractors for backward compatibility
|
|
9
9
|
export {
|
|
@@ -16,7 +16,7 @@ export {
|
|
|
16
16
|
extractRubySymbols,
|
|
17
17
|
extractRustSymbols,
|
|
18
18
|
extractSymbols,
|
|
19
|
-
} from '
|
|
19
|
+
} from '../extractors/index.js';
|
|
20
20
|
|
|
21
21
|
import {
|
|
22
22
|
extractCSharpSymbols,
|
|
@@ -28,12 +28,12 @@ import {
|
|
|
28
28
|
extractRubySymbols,
|
|
29
29
|
extractRustSymbols,
|
|
30
30
|
extractSymbols,
|
|
31
|
-
} from '
|
|
31
|
+
} from '../extractors/index.js';
|
|
32
32
|
|
|
33
33
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
34
34
|
|
|
35
35
|
function grammarPath(name) {
|
|
36
|
-
return path.join(__dirname, '..', 'grammars', name);
|
|
36
|
+
return path.join(__dirname, '..', '..', 'grammars', name);
|
|
37
37
|
}
|
|
38
38
|
|
|
39
39
|
let _initialized = false;
|
|
@@ -116,29 +116,35 @@ export async function createParsers() {
|
|
|
116
116
|
*/
|
|
117
117
|
export function disposeParsers() {
|
|
118
118
|
if (_cachedParsers) {
|
|
119
|
-
for (const [, parser] of _cachedParsers) {
|
|
119
|
+
for (const [id, parser] of _cachedParsers) {
|
|
120
120
|
if (parser && typeof parser.delete === 'function') {
|
|
121
121
|
try {
|
|
122
122
|
parser.delete();
|
|
123
|
-
} catch {
|
|
123
|
+
} catch (e) {
|
|
124
|
+
debug(`Failed to dispose parser ${id}: ${e.message}`);
|
|
125
|
+
}
|
|
124
126
|
}
|
|
125
127
|
}
|
|
126
128
|
_cachedParsers = null;
|
|
127
129
|
}
|
|
128
|
-
for (const [, query] of _queryCache) {
|
|
130
|
+
for (const [id, query] of _queryCache) {
|
|
129
131
|
if (query && typeof query.delete === 'function') {
|
|
130
132
|
try {
|
|
131
133
|
query.delete();
|
|
132
|
-
} catch {
|
|
134
|
+
} catch (e) {
|
|
135
|
+
debug(`Failed to dispose query ${id}: ${e.message}`);
|
|
136
|
+
}
|
|
133
137
|
}
|
|
134
138
|
}
|
|
135
139
|
_queryCache.clear();
|
|
136
140
|
if (_cachedLanguages) {
|
|
137
|
-
for (const [, lang] of _cachedLanguages) {
|
|
141
|
+
for (const [id, lang] of _cachedLanguages) {
|
|
138
142
|
if (lang && typeof lang.delete === 'function') {
|
|
139
143
|
try {
|
|
140
144
|
lang.delete();
|
|
141
|
-
} catch {
|
|
145
|
+
} catch (e) {
|
|
146
|
+
debug(`Failed to dispose language ${id}: ${e.message}`);
|
|
147
|
+
}
|
|
142
148
|
}
|
|
143
149
|
}
|
|
144
150
|
_cachedLanguages = null;
|
|
@@ -189,14 +195,15 @@ export async function ensureWasmTrees(fileSymbols, rootDir) {
|
|
|
189
195
|
let code;
|
|
190
196
|
try {
|
|
191
197
|
code = fs.readFileSync(absPath, 'utf-8');
|
|
192
|
-
} catch {
|
|
198
|
+
} catch (e) {
|
|
199
|
+
debug(`ensureWasmTrees: cannot read ${relPath}: ${e.message}`);
|
|
193
200
|
continue;
|
|
194
201
|
}
|
|
195
202
|
try {
|
|
196
203
|
symbols._tree = parser.parse(code);
|
|
197
204
|
symbols._langId = entry.id;
|
|
198
|
-
} catch {
|
|
199
|
-
|
|
205
|
+
} catch (e) {
|
|
206
|
+
debug(`ensureWasmTrees: parse failed for ${relPath}: ${e.message}`);
|
|
200
207
|
}
|
|
201
208
|
}
|
|
202
209
|
}
|
|
@@ -483,7 +490,9 @@ export function getActiveEngine(opts = {}) {
|
|
|
483
490
|
if (native) {
|
|
484
491
|
try {
|
|
485
492
|
version = getNativePackageVersion() ?? version;
|
|
486
|
-
} catch {
|
|
493
|
+
} catch (e) {
|
|
494
|
+
debug(`getNativePackageVersion failed: ${e.message}`);
|
|
495
|
+
}
|
|
487
496
|
}
|
|
488
497
|
return { name, version };
|
|
489
498
|
}
|
|
@@ -6,6 +6,23 @@
|
|
|
6
6
|
* importers continue to work without changes.
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
+
// ── Re-export from dedicated module for backward compat ───────────────────
|
|
10
|
+
export { isTestFile, TEST_PATTERN } from '../infrastructure/test-filter.js';
|
|
11
|
+
export { iterListFunctions, iterRoles, iterWhere } from '../shared/generators.js';
|
|
12
|
+
// ── Kind/edge constants (canonical source: kinds.js) ─────────────────────
|
|
13
|
+
export {
|
|
14
|
+
ALL_SYMBOL_KINDS,
|
|
15
|
+
CORE_EDGE_KINDS,
|
|
16
|
+
CORE_SYMBOL_KINDS,
|
|
17
|
+
EVERY_EDGE_KIND,
|
|
18
|
+
EVERY_SYMBOL_KIND,
|
|
19
|
+
EXTENDED_SYMBOL_KINDS,
|
|
20
|
+
STRUCTURAL_EDGE_KINDS,
|
|
21
|
+
VALID_ROLES,
|
|
22
|
+
} from '../shared/kinds.js';
|
|
23
|
+
// ── Shared utilities ─────────────────────────────────────────────────────
|
|
24
|
+
export { kindIcon, normalizeSymbol } from '../shared/normalize.js';
|
|
25
|
+
export { briefData } from './analysis/brief.js';
|
|
9
26
|
export { contextData, explainData } from './analysis/context.js';
|
|
10
27
|
export { fileDepsData, fnDepsData, pathData } from './analysis/dependencies.js';
|
|
11
28
|
export { exportsData } from './analysis/exports.js';
|
|
@@ -30,19 +47,3 @@ export {
|
|
|
30
47
|
queryNameData,
|
|
31
48
|
whereData,
|
|
32
49
|
} from './analysis/symbol-lookup.js';
|
|
33
|
-
// ── Re-export from dedicated module for backward compat ───────────────────
|
|
34
|
-
export { isTestFile, TEST_PATTERN } from './infrastructure/test-filter.js';
|
|
35
|
-
// ── Kind/edge constants (canonical source: kinds.js) ─────────────────────
|
|
36
|
-
export {
|
|
37
|
-
ALL_SYMBOL_KINDS,
|
|
38
|
-
CORE_EDGE_KINDS,
|
|
39
|
-
CORE_SYMBOL_KINDS,
|
|
40
|
-
EVERY_EDGE_KIND,
|
|
41
|
-
EVERY_SYMBOL_KIND,
|
|
42
|
-
EXTENDED_SYMBOL_KINDS,
|
|
43
|
-
STRUCTURAL_EDGE_KINDS,
|
|
44
|
-
VALID_ROLES,
|
|
45
|
-
} from './kinds.js';
|
|
46
|
-
export { iterListFunctions, iterRoles, iterWhere } from './shared/generators.js';
|
|
47
|
-
// ── Shared utilities ─────────────────────────────────────────────────────
|
|
48
|
-
export { kindIcon, normalizeSymbol } from './shared/normalize.js';
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import fs from 'node:fs';
|
|
2
2
|
import path from 'node:path';
|
|
3
|
-
import { closeDb, findDbPath, openDb } from '
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
3
|
+
import { closeDb, findDbPath, openDb } from '../../db/index.js';
|
|
4
|
+
import { warn } from '../../infrastructure/logger.js';
|
|
5
|
+
import { DbError } from '../../shared/errors.js';
|
|
6
6
|
import { embed, getModelConfig } from './models.js';
|
|
7
7
|
import { buildSourceText } from './strategies/source.js';
|
|
8
8
|
import { buildStructuredText } from './strategies/structured.js';
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { execFileSync } from 'node:child_process';
|
|
2
2
|
import { createInterface } from 'node:readline';
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
3
|
+
import { info } from '../../infrastructure/logger.js';
|
|
4
|
+
import { ConfigError, EngineError } from '../../shared/errors.js';
|
|
5
5
|
|
|
6
6
|
// Lazy-load transformers (heavy, optional module)
|
|
7
7
|
let pipeline = null;
|
|
@@ -29,15 +29,19 @@ const TEST_PATTERN = /\.(test|spec)\.|__test__|__tests__|\.stories\./;
|
|
|
29
29
|
* @param {object} opts
|
|
30
30
|
* @param {string} [opts.filePattern] - Glob pattern (only applied if it contains glob chars)
|
|
31
31
|
* @param {boolean} [opts.noTests] - Exclude test/spec files
|
|
32
|
-
* @param {boolean} [opts.isGlob] - Pre-computed: does filePattern contain glob chars?
|
|
33
32
|
* @returns {Array}
|
|
34
33
|
*/
|
|
35
34
|
export function applyFilters(rows, opts = {}) {
|
|
36
35
|
let filtered = rows;
|
|
37
|
-
const
|
|
38
|
-
|
|
39
|
-
if (
|
|
40
|
-
filtered = filtered.filter((row) =>
|
|
36
|
+
const fp = opts.filePattern;
|
|
37
|
+
const fpArr = Array.isArray(fp) ? fp : fp ? [fp] : [];
|
|
38
|
+
if (fpArr.length > 0) {
|
|
39
|
+
filtered = filtered.filter((row) =>
|
|
40
|
+
fpArr.some((p) => {
|
|
41
|
+
const patternIsGlob = /[*?[\]]/.test(p);
|
|
42
|
+
return patternIsGlob ? globMatch(row.file, p) : row.file.includes(p);
|
|
43
|
+
}),
|
|
44
|
+
);
|
|
41
45
|
}
|
|
42
46
|
if (opts.noTests) {
|
|
43
47
|
filtered = filtered.filter((row) => !TEST_PATTERN.test(row.file));
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { openReadonlyOrFail } from '
|
|
1
|
+
import { openReadonlyOrFail } from '../../../db/index.js';
|
|
2
|
+
import { buildFileConditionSQL } from '../../../db/query-builder.js';
|
|
2
3
|
import { normalizeSymbol } from '../../queries.js';
|
|
3
4
|
import { hasFtsIndex, sanitizeFtsQuery } from '../stores/fts5.js';
|
|
4
5
|
import { applyFilters } from './filters.js';
|
|
@@ -36,10 +37,16 @@ export function ftsSearchData(query, customDbPath, opts = {}) {
|
|
|
36
37
|
params.push(opts.kind);
|
|
37
38
|
}
|
|
38
39
|
|
|
39
|
-
const
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
40
|
+
const fp = opts.filePattern;
|
|
41
|
+
const fpArr = Array.isArray(fp) ? fp : fp ? [fp] : [];
|
|
42
|
+
const isGlob = fpArr.length > 0 && fpArr.some((p) => /[*?[\]]/.test(p));
|
|
43
|
+
// For non-glob patterns, push filtering into SQL via buildFileConditionSQL
|
|
44
|
+
// (handles escapeLike + ESCAPE clause). Glob patterns are handled post-query
|
|
45
|
+
// by applyFilters.
|
|
46
|
+
if (fpArr.length > 0 && !isGlob) {
|
|
47
|
+
const fc = buildFileConditionSQL(fpArr, 'n.file');
|
|
48
|
+
sql += fc.sql;
|
|
49
|
+
params.push(...fc.params);
|
|
43
50
|
}
|
|
44
51
|
|
|
45
52
|
sql += ' ORDER BY rank LIMIT ?';
|
|
@@ -53,7 +60,7 @@ export function ftsSearchData(query, customDbPath, opts = {}) {
|
|
|
53
60
|
return { results: [] };
|
|
54
61
|
}
|
|
55
62
|
|
|
56
|
-
rows = applyFilters(rows,
|
|
63
|
+
rows = applyFilters(rows, opts);
|
|
57
64
|
|
|
58
65
|
const hc = new Map();
|
|
59
66
|
const results = rows.slice(0, limit).map((row) => ({
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { openReadonlyOrFail } from '../../../db/index.js';
|
|
2
|
+
import { escapeLike } from '../../../db/query-builder.js';
|
|
3
|
+
import { getEmbeddingCount, getEmbeddingMeta } from '../../../db/repository/embeddings.js';
|
|
3
4
|
import { MODELS } from '../models.js';
|
|
4
5
|
import { applyFilters } from './filters.js';
|
|
5
6
|
|
|
@@ -35,7 +36,9 @@ export function prepareSearch(customDbPath, opts = {}) {
|
|
|
35
36
|
}
|
|
36
37
|
|
|
37
38
|
// Pre-filter: allow filtering by kind or file pattern to reduce search space
|
|
38
|
-
const
|
|
39
|
+
const fp = opts.filePattern;
|
|
40
|
+
const fpArr = Array.isArray(fp) ? fp : fp ? [fp] : [];
|
|
41
|
+
const isGlob = fpArr.length > 0 && fpArr.some((p) => /[*?[\]]/.test(p));
|
|
39
42
|
let sql = `
|
|
40
43
|
SELECT e.node_id, e.vector, e.text_preview, n.name, n.kind, n.file, n.line, n.end_line, n.role
|
|
41
44
|
FROM embeddings e
|
|
@@ -47,16 +50,21 @@ export function prepareSearch(customDbPath, opts = {}) {
|
|
|
47
50
|
conditions.push('n.kind = ?');
|
|
48
51
|
params.push(opts.kind);
|
|
49
52
|
}
|
|
50
|
-
if (
|
|
51
|
-
|
|
52
|
-
|
|
53
|
+
if (fpArr.length > 0 && !isGlob) {
|
|
54
|
+
if (fpArr.length === 1) {
|
|
55
|
+
conditions.push("n.file LIKE ? ESCAPE '\\'");
|
|
56
|
+
params.push(`%${escapeLike(fpArr[0])}%`);
|
|
57
|
+
} else {
|
|
58
|
+
conditions.push(`(${fpArr.map(() => "n.file LIKE ? ESCAPE '\\'").join(' OR ')})`);
|
|
59
|
+
params.push(...fpArr.map((f) => `%${escapeLike(f)}%`));
|
|
60
|
+
}
|
|
53
61
|
}
|
|
54
62
|
if (conditions.length > 0) {
|
|
55
63
|
sql += ` WHERE ${conditions.join(' AND ')}`;
|
|
56
64
|
}
|
|
57
65
|
|
|
58
66
|
let rows = db.prepare(sql).all(...params);
|
|
59
|
-
rows = applyFilters(rows,
|
|
67
|
+
rows = applyFilters(rows, opts);
|
|
60
68
|
|
|
61
69
|
return { db, rows, modelKey, storedDim };
|
|
62
70
|
} catch (err) {
|