@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,300 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sequence diagram generation – Mermaid sequenceDiagram from call graph edges.
|
|
3
|
+
*
|
|
4
|
+
* Participants are files (not individual functions). Calls within the same file
|
|
5
|
+
* become self-messages. This keeps diagrams readable and matches typical
|
|
6
|
+
* sequence-diagram conventions.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { openRepo } from '../db/index.js';
|
|
10
|
+
import { SqliteRepository } from '../db/repository/sqlite-repository.js';
|
|
11
|
+
import { findMatchingNodes } from '../domain/queries.js';
|
|
12
|
+
import { isTestFile } from '../infrastructure/test-filter.js';
|
|
13
|
+
import { paginateResult } from '../shared/paginate.js';
|
|
14
|
+
import { FRAMEWORK_ENTRY_PREFIXES } from './structure.js';
|
|
15
|
+
|
|
16
|
+
// ─── Alias generation ────────────────────────────────────────────────
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Build short participant aliases from file paths with collision handling.
|
|
20
|
+
* e.g. "src/builder.js" → "builder", but if two files share basename,
|
|
21
|
+
* progressively add parent dirs: "src/builder" vs "lib/builder".
|
|
22
|
+
*/
|
|
23
|
+
function buildAliases(files) {
|
|
24
|
+
const aliases = new Map();
|
|
25
|
+
const basenames = new Map();
|
|
26
|
+
|
|
27
|
+
// Group by basename
|
|
28
|
+
for (const file of files) {
|
|
29
|
+
const base = file
|
|
30
|
+
.split('/')
|
|
31
|
+
.pop()
|
|
32
|
+
.replace(/\.[^.]+$/, '');
|
|
33
|
+
if (!basenames.has(base)) basenames.set(base, []);
|
|
34
|
+
basenames.get(base).push(file);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
for (const [base, paths] of basenames) {
|
|
38
|
+
if (paths.length === 1) {
|
|
39
|
+
aliases.set(paths[0], base);
|
|
40
|
+
} else {
|
|
41
|
+
// Collision — progressively add parent dirs until aliases are unique
|
|
42
|
+
for (let depth = 2; depth <= 10; depth++) {
|
|
43
|
+
const trial = new Map();
|
|
44
|
+
let allUnique = true;
|
|
45
|
+
const seen = new Set();
|
|
46
|
+
|
|
47
|
+
for (const p of paths) {
|
|
48
|
+
const parts = p.replace(/\.[^.]+$/, '').split('/');
|
|
49
|
+
const alias = parts
|
|
50
|
+
.slice(-depth)
|
|
51
|
+
.join('_')
|
|
52
|
+
.replace(/[^a-zA-Z0-9_-]/g, '_');
|
|
53
|
+
trial.set(p, alias);
|
|
54
|
+
if (seen.has(alias)) allUnique = false;
|
|
55
|
+
seen.add(alias);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
if (allUnique || depth === 10) {
|
|
59
|
+
for (const [p, alias] of trial) {
|
|
60
|
+
aliases.set(p, alias);
|
|
61
|
+
}
|
|
62
|
+
break;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
return aliases;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// ─── Helpers ─────────────────────────────────────────────────────────
|
|
72
|
+
|
|
73
|
+
function findEntryNode(repo, name, opts) {
|
|
74
|
+
let matchNode = findMatchingNodes(repo, name, opts)[0] ?? null;
|
|
75
|
+
if (!matchNode) {
|
|
76
|
+
for (const prefix of FRAMEWORK_ENTRY_PREFIXES) {
|
|
77
|
+
matchNode = findMatchingNodes(repo, `${prefix}${name}`, opts)[0] ?? null;
|
|
78
|
+
if (matchNode) break;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
return matchNode;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function bfsCallees(repo, matchNode, maxDepth, noTests) {
|
|
85
|
+
const visited = new Set([matchNode.id]);
|
|
86
|
+
let frontier = [matchNode.id];
|
|
87
|
+
const messages = [];
|
|
88
|
+
const fileSet = new Set([matchNode.file]);
|
|
89
|
+
const idToNode = new Map();
|
|
90
|
+
idToNode.set(matchNode.id, matchNode);
|
|
91
|
+
let truncated = false;
|
|
92
|
+
|
|
93
|
+
for (let d = 1; d <= maxDepth; d++) {
|
|
94
|
+
const nextFrontier = [];
|
|
95
|
+
|
|
96
|
+
for (const fid of frontier) {
|
|
97
|
+
const callees = repo.findCallees(fid);
|
|
98
|
+
const caller = idToNode.get(fid);
|
|
99
|
+
|
|
100
|
+
for (const c of callees) {
|
|
101
|
+
if (noTests && isTestFile(c.file)) continue;
|
|
102
|
+
|
|
103
|
+
fileSet.add(c.file);
|
|
104
|
+
messages.push({
|
|
105
|
+
from: caller.file,
|
|
106
|
+
to: c.file,
|
|
107
|
+
label: c.name,
|
|
108
|
+
type: 'call',
|
|
109
|
+
depth: d,
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
if (visited.has(c.id)) continue;
|
|
113
|
+
|
|
114
|
+
visited.add(c.id);
|
|
115
|
+
nextFrontier.push(c.id);
|
|
116
|
+
idToNode.set(c.id, c);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
frontier = nextFrontier;
|
|
121
|
+
if (frontier.length === 0) break;
|
|
122
|
+
|
|
123
|
+
if (d === maxDepth && frontier.length > 0) {
|
|
124
|
+
const hasMoreCalls = frontier.some((fid) => repo.findCallees(fid).length > 0);
|
|
125
|
+
if (hasMoreCalls) truncated = true;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
return { messages, fileSet, idToNode, truncated };
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function annotateDataflow(repo, messages, idToNode) {
|
|
133
|
+
const hasTable = repo.hasDataflowTable();
|
|
134
|
+
|
|
135
|
+
if (!hasTable || !(repo instanceof SqliteRepository)) return;
|
|
136
|
+
|
|
137
|
+
const db = repo.db;
|
|
138
|
+
const nodeByNameFile = new Map();
|
|
139
|
+
for (const n of idToNode.values()) {
|
|
140
|
+
nodeByNameFile.set(`${n.name}|${n.file}`, n);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
const getReturns = db.prepare(
|
|
144
|
+
`SELECT d.expression FROM dataflow d
|
|
145
|
+
WHERE d.source_id = ? AND d.kind = 'returns'`,
|
|
146
|
+
);
|
|
147
|
+
const getFlowsTo = db.prepare(
|
|
148
|
+
`SELECT d.expression FROM dataflow d
|
|
149
|
+
WHERE d.target_id = ? AND d.kind = 'flows_to'
|
|
150
|
+
ORDER BY d.param_index`,
|
|
151
|
+
);
|
|
152
|
+
|
|
153
|
+
const seenReturns = new Set();
|
|
154
|
+
for (const msg of [...messages]) {
|
|
155
|
+
if (msg.type !== 'call') continue;
|
|
156
|
+
const targetNode = nodeByNameFile.get(`${msg.label}|${msg.to}`);
|
|
157
|
+
if (!targetNode) continue;
|
|
158
|
+
|
|
159
|
+
const returnKey = `${msg.to}->${msg.from}:${msg.label}`;
|
|
160
|
+
if (seenReturns.has(returnKey)) continue;
|
|
161
|
+
|
|
162
|
+
const returns = getReturns.all(targetNode.id);
|
|
163
|
+
|
|
164
|
+
if (returns.length > 0) {
|
|
165
|
+
seenReturns.add(returnKey);
|
|
166
|
+
const expr = returns[0].expression || 'result';
|
|
167
|
+
messages.push({
|
|
168
|
+
from: msg.to,
|
|
169
|
+
to: msg.from,
|
|
170
|
+
label: expr,
|
|
171
|
+
type: 'return',
|
|
172
|
+
depth: msg.depth,
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
for (const msg of messages) {
|
|
178
|
+
if (msg.type !== 'call') continue;
|
|
179
|
+
const targetNode = nodeByNameFile.get(`${msg.label}|${msg.to}`);
|
|
180
|
+
if (!targetNode) continue;
|
|
181
|
+
|
|
182
|
+
const params = getFlowsTo.all(targetNode.id);
|
|
183
|
+
|
|
184
|
+
if (params.length > 0) {
|
|
185
|
+
const paramNames = params
|
|
186
|
+
.map((p) => p.expression)
|
|
187
|
+
.filter(Boolean)
|
|
188
|
+
.slice(0, 3);
|
|
189
|
+
if (paramNames.length > 0) {
|
|
190
|
+
msg.label = `${msg.label}(${paramNames.join(', ')})`;
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
function buildParticipants(fileSet, entryFile) {
|
|
197
|
+
const aliases = buildAliases([...fileSet]);
|
|
198
|
+
const participants = [...fileSet].map((file) => ({
|
|
199
|
+
id: aliases.get(file),
|
|
200
|
+
label: file.split('/').pop(),
|
|
201
|
+
file,
|
|
202
|
+
}));
|
|
203
|
+
|
|
204
|
+
participants.sort((a, b) => {
|
|
205
|
+
if (a.file === entryFile) return -1;
|
|
206
|
+
if (b.file === entryFile) return 1;
|
|
207
|
+
return a.file.localeCompare(b.file);
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
return { participants, aliases };
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// ─── Core data function ──────────────────────────────────────────────
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* Build sequence diagram data by BFS-forward from an entry point.
|
|
217
|
+
*
|
|
218
|
+
* @param {string} name - Symbol name to trace from
|
|
219
|
+
* @param {string} [dbPath]
|
|
220
|
+
* @param {object} [opts]
|
|
221
|
+
* @param {number} [opts.depth=10]
|
|
222
|
+
* @param {boolean} [opts.noTests]
|
|
223
|
+
* @param {string} [opts.file]
|
|
224
|
+
* @param {string} [opts.kind]
|
|
225
|
+
* @param {boolean} [opts.dataflow]
|
|
226
|
+
* @param {number} [opts.limit]
|
|
227
|
+
* @param {number} [opts.offset]
|
|
228
|
+
* @returns {{ entry, participants, messages, depth, totalMessages, truncated }}
|
|
229
|
+
*/
|
|
230
|
+
export function sequenceData(name, dbPath, opts = {}) {
|
|
231
|
+
const { repo, close } = openRepo(dbPath, opts);
|
|
232
|
+
try {
|
|
233
|
+
const maxDepth = opts.depth || 10;
|
|
234
|
+
const noTests = opts.noTests || false;
|
|
235
|
+
|
|
236
|
+
const matchNode = findEntryNode(repo, name, opts);
|
|
237
|
+
if (!matchNode) {
|
|
238
|
+
return {
|
|
239
|
+
entry: null,
|
|
240
|
+
participants: [],
|
|
241
|
+
messages: [],
|
|
242
|
+
depth: maxDepth,
|
|
243
|
+
totalMessages: 0,
|
|
244
|
+
truncated: false,
|
|
245
|
+
};
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
const entry = {
|
|
249
|
+
name: matchNode.name,
|
|
250
|
+
file: matchNode.file,
|
|
251
|
+
kind: matchNode.kind,
|
|
252
|
+
line: matchNode.line,
|
|
253
|
+
};
|
|
254
|
+
|
|
255
|
+
const { messages, fileSet, idToNode, truncated } = bfsCallees(
|
|
256
|
+
repo,
|
|
257
|
+
matchNode,
|
|
258
|
+
maxDepth,
|
|
259
|
+
noTests,
|
|
260
|
+
);
|
|
261
|
+
|
|
262
|
+
if (opts.dataflow && messages.length > 0) {
|
|
263
|
+
annotateDataflow(repo, messages, idToNode);
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
messages.sort((a, b) => {
|
|
267
|
+
if (a.depth !== b.depth) return a.depth - b.depth;
|
|
268
|
+
if (a.type === 'call' && b.type === 'return') return -1;
|
|
269
|
+
if (a.type === 'return' && b.type === 'call') return 1;
|
|
270
|
+
return 0;
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
const { participants, aliases } = buildParticipants(fileSet, entry.file);
|
|
274
|
+
|
|
275
|
+
for (const msg of messages) {
|
|
276
|
+
msg.from = aliases.get(msg.from);
|
|
277
|
+
msg.to = aliases.get(msg.to);
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
const base = {
|
|
281
|
+
entry,
|
|
282
|
+
participants,
|
|
283
|
+
messages,
|
|
284
|
+
depth: maxDepth,
|
|
285
|
+
totalMessages: messages.length,
|
|
286
|
+
truncated,
|
|
287
|
+
};
|
|
288
|
+
const result = paginateResult(base, 'messages', { limit: opts.limit, offset: opts.offset });
|
|
289
|
+
if (opts.limit !== undefined || opts.offset !== undefined) {
|
|
290
|
+
const activeFiles = new Set(result.messages.flatMap((m) => [m.from, m.to]));
|
|
291
|
+
result.participants = result.participants.filter((p) => activeFiles.has(p.id));
|
|
292
|
+
}
|
|
293
|
+
return result;
|
|
294
|
+
} finally {
|
|
295
|
+
close();
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
// Re-export Mermaid renderer from presentation layer
|
|
300
|
+
export { sequenceToMermaid } from '../presentation/sequence-renderer.js';
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { buildFileConditionSQL } from '../../db/query-builder.js';
|
|
2
|
+
import { isTestFile } from '../../infrastructure/test-filter.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Look up node(s) by name with optional file/kind/noTests filtering.
|
|
6
|
+
*
|
|
7
|
+
* @param {object} db - open SQLite database handle
|
|
8
|
+
* @param {string} name - symbol name (partial LIKE match)
|
|
9
|
+
* @param {object} [opts] - { kind, file, noTests }
|
|
10
|
+
* @param {string[]} defaultKinds - fallback kinds when opts.kind is not set
|
|
11
|
+
* @returns {object[]} matching node rows
|
|
12
|
+
*/
|
|
13
|
+
export function findNodes(db, name, opts = {}, defaultKinds = []) {
|
|
14
|
+
const kinds = opts.kind ? [opts.kind] : defaultKinds;
|
|
15
|
+
if (kinds.length === 0) throw new Error('findNodes: no kinds specified');
|
|
16
|
+
const placeholders = kinds.map(() => '?').join(', ');
|
|
17
|
+
const params = [`%${name}%`, ...kinds];
|
|
18
|
+
|
|
19
|
+
const fc = buildFileConditionSQL(opts.file, 'file');
|
|
20
|
+
params.push(...fc.params);
|
|
21
|
+
|
|
22
|
+
const rows = db
|
|
23
|
+
.prepare(
|
|
24
|
+
`SELECT * FROM nodes
|
|
25
|
+
WHERE name LIKE ? AND kind IN (${placeholders})${fc.sql}
|
|
26
|
+
ORDER BY file, line`,
|
|
27
|
+
)
|
|
28
|
+
.all(...params);
|
|
29
|
+
|
|
30
|
+
return opts.noTests ? rows.filter((n) => !isTestFile(n.file)) : rows;
|
|
31
|
+
}
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import fs from 'node:fs';
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import Database from 'better-sqlite3';
|
|
4
|
-
import { findDbPath } from '
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
4
|
+
import { findDbPath } from '../db/index.js';
|
|
5
|
+
import { debug } from '../infrastructure/logger.js';
|
|
6
|
+
import { ConfigError, DbError } from '../shared/errors.js';
|
|
7
7
|
|
|
8
8
|
const NAME_RE = /^[a-zA-Z0-9_-]+$/;
|
|
9
9
|
|