@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
|
@@ -11,19 +11,20 @@
|
|
|
11
11
|
|
|
12
12
|
import fs from 'node:fs';
|
|
13
13
|
import path from 'node:path';
|
|
14
|
-
import { DATAFLOW_RULES } from '
|
|
14
|
+
import { DATAFLOW_RULES } from '../ast-analysis/rules/index.js';
|
|
15
15
|
import {
|
|
16
16
|
makeDataflowRules as _makeDataflowRules,
|
|
17
17
|
buildExtensionSet,
|
|
18
18
|
buildExtToLangMap,
|
|
19
|
-
} from '
|
|
20
|
-
import { walkWithVisitors } from '
|
|
21
|
-
import { createDataflowVisitor } from '
|
|
22
|
-
import { hasDataflowTable, openReadonlyOrFail } from '
|
|
23
|
-
import {
|
|
24
|
-
import { info } from '
|
|
25
|
-
import {
|
|
26
|
-
import {
|
|
19
|
+
} from '../ast-analysis/shared.js';
|
|
20
|
+
import { walkWithVisitors } from '../ast-analysis/visitor.js';
|
|
21
|
+
import { createDataflowVisitor } from '../ast-analysis/visitors/dataflow-visitor.js';
|
|
22
|
+
import { hasDataflowTable, openReadonlyOrFail } from '../db/index.js';
|
|
23
|
+
import { ALL_SYMBOL_KINDS, normalizeSymbol } from '../domain/queries.js';
|
|
24
|
+
import { debug, info } from '../infrastructure/logger.js';
|
|
25
|
+
import { isTestFile } from '../infrastructure/test-filter.js';
|
|
26
|
+
import { paginateResult } from '../shared/paginate.js';
|
|
27
|
+
import { findNodes } from './shared/find-nodes.js';
|
|
27
28
|
|
|
28
29
|
// Re-export for backward compatibility
|
|
29
30
|
export { _makeDataflowRules as makeDataflowRules, DATAFLOW_RULES };
|
|
@@ -57,26 +58,11 @@ export function extractDataflow(tree, _filePath, _definitions, langId = 'javascr
|
|
|
57
58
|
return results.dataflow;
|
|
58
59
|
}
|
|
59
60
|
|
|
60
|
-
// ──
|
|
61
|
+
// ── Build-Time Helpers ──────────────────────────────────────────────────────
|
|
61
62
|
|
|
62
|
-
|
|
63
|
-
* Build dataflow edges and insert them into the database.
|
|
64
|
-
* Called during graph build when --dataflow is enabled.
|
|
65
|
-
*
|
|
66
|
-
* @param {object} db - better-sqlite3 database instance
|
|
67
|
-
* @param {Map<string, object>} fileSymbols - map of relPath → symbols
|
|
68
|
-
* @param {string} rootDir - absolute root directory
|
|
69
|
-
* @param {object} engineOpts - engine options
|
|
70
|
-
*/
|
|
71
|
-
export async function buildDataflowEdges(db, fileSymbols, rootDir, _engineOpts) {
|
|
72
|
-
// Lazily init WASM parsers if needed
|
|
73
|
-
let parsers = null;
|
|
63
|
+
async function initDataflowParsers(fileSymbols) {
|
|
74
64
|
let needsFallback = false;
|
|
75
65
|
|
|
76
|
-
// Always build ext→langId map so native-only builds (where _langId is unset)
|
|
77
|
-
// can still derive the language from the file extension.
|
|
78
|
-
const extToLang = buildExtToLangMap();
|
|
79
|
-
|
|
80
66
|
for (const [relPath, symbols] of fileSymbols) {
|
|
81
67
|
if (!symbols._tree && !symbols.dataflow) {
|
|
82
68
|
const ext = path.extname(relPath).toLowerCase();
|
|
@@ -87,25 +73,130 @@ export async function buildDataflowEdges(db, fileSymbols, rootDir, _engineOpts)
|
|
|
87
73
|
}
|
|
88
74
|
}
|
|
89
75
|
|
|
76
|
+
let parsers = null;
|
|
77
|
+
let getParserFn = null;
|
|
78
|
+
|
|
90
79
|
if (needsFallback) {
|
|
91
|
-
const { createParsers } = await import('
|
|
80
|
+
const { createParsers } = await import('../domain/parser.js');
|
|
92
81
|
parsers = await createParsers();
|
|
82
|
+
const mod = await import('../domain/parser.js');
|
|
83
|
+
getParserFn = mod.getParser;
|
|
93
84
|
}
|
|
94
85
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
86
|
+
return { parsers, getParserFn };
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function getDataflowForFile(symbols, relPath, rootDir, extToLang, parsers, getParserFn) {
|
|
90
|
+
if (symbols.dataflow) return symbols.dataflow;
|
|
91
|
+
|
|
92
|
+
let tree = symbols._tree;
|
|
93
|
+
let langId = symbols._langId;
|
|
94
|
+
|
|
95
|
+
if (!tree) {
|
|
96
|
+
if (!getParserFn) return null;
|
|
97
|
+
const ext = path.extname(relPath).toLowerCase();
|
|
98
|
+
langId = extToLang.get(ext);
|
|
99
|
+
if (!langId || !DATAFLOW_RULES.has(langId)) return null;
|
|
100
|
+
|
|
101
|
+
const absPath = path.join(rootDir, relPath);
|
|
102
|
+
let code;
|
|
103
|
+
try {
|
|
104
|
+
code = fs.readFileSync(absPath, 'utf-8');
|
|
105
|
+
} catch (e) {
|
|
106
|
+
debug(`dataflow: cannot read ${relPath}: ${e.message}`);
|
|
107
|
+
return null;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const parser = getParserFn(parsers, absPath);
|
|
111
|
+
if (!parser) return null;
|
|
112
|
+
|
|
113
|
+
try {
|
|
114
|
+
tree = parser.parse(code);
|
|
115
|
+
} catch (e) {
|
|
116
|
+
debug(`dataflow: parse failed for ${relPath}: ${e.message}`);
|
|
117
|
+
return null;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
if (!langId) {
|
|
122
|
+
const ext = path.extname(relPath).toLowerCase();
|
|
123
|
+
langId = extToLang.get(ext);
|
|
124
|
+
if (!langId) return null;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
if (!DATAFLOW_RULES.has(langId)) return null;
|
|
128
|
+
|
|
129
|
+
return extractDataflow(tree, relPath, symbols.definitions, langId);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function insertDataflowEdges(insert, data, resolveNode) {
|
|
133
|
+
let edgeCount = 0;
|
|
134
|
+
|
|
135
|
+
for (const flow of data.argFlows) {
|
|
136
|
+
const sourceNode = resolveNode(flow.callerFunc);
|
|
137
|
+
const targetNode = resolveNode(flow.calleeName);
|
|
138
|
+
if (sourceNode && targetNode) {
|
|
139
|
+
insert.run(
|
|
140
|
+
sourceNode.id,
|
|
141
|
+
targetNode.id,
|
|
142
|
+
'flows_to',
|
|
143
|
+
flow.argIndex,
|
|
144
|
+
flow.expression,
|
|
145
|
+
flow.line,
|
|
146
|
+
flow.confidence,
|
|
147
|
+
);
|
|
148
|
+
edgeCount++;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
for (const assignment of data.assignments) {
|
|
153
|
+
const producerNode = resolveNode(assignment.sourceCallName);
|
|
154
|
+
const consumerNode = resolveNode(assignment.callerFunc);
|
|
155
|
+
if (producerNode && consumerNode) {
|
|
156
|
+
insert.run(
|
|
157
|
+
producerNode.id,
|
|
158
|
+
consumerNode.id,
|
|
159
|
+
'returns',
|
|
160
|
+
null,
|
|
161
|
+
assignment.expression,
|
|
162
|
+
assignment.line,
|
|
163
|
+
1.0,
|
|
164
|
+
);
|
|
165
|
+
edgeCount++;
|
|
166
|
+
}
|
|
99
167
|
}
|
|
100
168
|
|
|
169
|
+
for (const mut of data.mutations) {
|
|
170
|
+
const mutatorNode = resolveNode(mut.funcName);
|
|
171
|
+
if (mutatorNode && mut.binding?.type === 'param') {
|
|
172
|
+
insert.run(mutatorNode.id, mutatorNode.id, 'mutates', null, mut.mutatingExpr, mut.line, 1.0);
|
|
173
|
+
edgeCount++;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
return edgeCount;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// ── buildDataflowEdges ──────────────────────────────────────────────────────
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* Build dataflow edges and insert them into the database.
|
|
184
|
+
* Called during graph build when --dataflow is enabled.
|
|
185
|
+
*
|
|
186
|
+
* @param {object} db - better-sqlite3 database instance
|
|
187
|
+
* @param {Map<string, object>} fileSymbols - map of relPath → symbols
|
|
188
|
+
* @param {string} rootDir - absolute root directory
|
|
189
|
+
* @param {object} engineOpts - engine options
|
|
190
|
+
*/
|
|
191
|
+
export async function buildDataflowEdges(db, fileSymbols, rootDir, _engineOpts) {
|
|
192
|
+
const extToLang = buildExtToLangMap();
|
|
193
|
+
const { parsers, getParserFn } = await initDataflowParsers(fileSymbols);
|
|
194
|
+
|
|
101
195
|
const insert = db.prepare(
|
|
102
196
|
`INSERT INTO dataflow (source_id, target_id, kind, param_index, expression, line, confidence)
|
|
103
197
|
VALUES (?, ?, ?, ?, ?, ?, ?)`,
|
|
104
198
|
);
|
|
105
199
|
|
|
106
|
-
// MVP scope: only resolve function/method nodes for dataflow edges.
|
|
107
|
-
// Future expansion: add 'parameter', 'property', 'constant' kinds to track
|
|
108
|
-
// data flow through property accessors or constant references.
|
|
109
200
|
const getNodeByNameAndFile = db.prepare(
|
|
110
201
|
`SELECT id, name, kind, file, line FROM nodes
|
|
111
202
|
WHERE name = ? AND file = ? AND kind IN ('function', 'method')`,
|
|
@@ -124,107 +215,17 @@ export async function buildDataflowEdges(db, fileSymbols, rootDir, _engineOpts)
|
|
|
124
215
|
const ext = path.extname(relPath).toLowerCase();
|
|
125
216
|
if (!DATAFLOW_EXTENSIONS.has(ext)) continue;
|
|
126
217
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
if (!data) {
|
|
130
|
-
let tree = symbols._tree;
|
|
131
|
-
let langId = symbols._langId;
|
|
132
|
-
|
|
133
|
-
// WASM fallback if no cached tree
|
|
134
|
-
if (!tree) {
|
|
135
|
-
if (!getParserFn) continue;
|
|
136
|
-
langId = extToLang.get(ext);
|
|
137
|
-
if (!langId || !DATAFLOW_RULES.has(langId)) continue;
|
|
138
|
-
|
|
139
|
-
const absPath = path.join(rootDir, relPath);
|
|
140
|
-
let code;
|
|
141
|
-
try {
|
|
142
|
-
code = fs.readFileSync(absPath, 'utf-8');
|
|
143
|
-
} catch {
|
|
144
|
-
continue;
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
const parser = getParserFn(parsers, absPath);
|
|
148
|
-
if (!parser) continue;
|
|
149
|
-
|
|
150
|
-
try {
|
|
151
|
-
tree = parser.parse(code);
|
|
152
|
-
} catch {
|
|
153
|
-
continue;
|
|
154
|
-
}
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
if (!langId) {
|
|
158
|
-
langId = extToLang.get(ext);
|
|
159
|
-
if (!langId) continue;
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
if (!DATAFLOW_RULES.has(langId)) continue;
|
|
218
|
+
const data = getDataflowForFile(symbols, relPath, rootDir, extToLang, parsers, getParserFn);
|
|
219
|
+
if (!data) continue;
|
|
163
220
|
|
|
164
|
-
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
// Resolve function names to node IDs in this file first, then globally
|
|
168
|
-
function resolveNode(funcName) {
|
|
221
|
+
const resolveNode = (funcName) => {
|
|
169
222
|
const local = getNodeByNameAndFile.all(funcName, relPath);
|
|
170
223
|
if (local.length > 0) return local[0];
|
|
171
224
|
const global = getNodeByName.all(funcName);
|
|
172
225
|
return global.length > 0 ? global[0] : null;
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
// flows_to: parameter/variable passed as argument to another function
|
|
176
|
-
for (const flow of data.argFlows) {
|
|
177
|
-
const sourceNode = resolveNode(flow.callerFunc);
|
|
178
|
-
const targetNode = resolveNode(flow.calleeName);
|
|
179
|
-
if (sourceNode && targetNode) {
|
|
180
|
-
insert.run(
|
|
181
|
-
sourceNode.id,
|
|
182
|
-
targetNode.id,
|
|
183
|
-
'flows_to',
|
|
184
|
-
flow.argIndex,
|
|
185
|
-
flow.expression,
|
|
186
|
-
flow.line,
|
|
187
|
-
flow.confidence,
|
|
188
|
-
);
|
|
189
|
-
totalEdges++;
|
|
190
|
-
}
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
// returns: call return value captured in caller
|
|
194
|
-
for (const assignment of data.assignments) {
|
|
195
|
-
const producerNode = resolveNode(assignment.sourceCallName);
|
|
196
|
-
const consumerNode = resolveNode(assignment.callerFunc);
|
|
197
|
-
if (producerNode && consumerNode) {
|
|
198
|
-
insert.run(
|
|
199
|
-
producerNode.id,
|
|
200
|
-
consumerNode.id,
|
|
201
|
-
'returns',
|
|
202
|
-
null,
|
|
203
|
-
assignment.expression,
|
|
204
|
-
assignment.line,
|
|
205
|
-
1.0,
|
|
206
|
-
);
|
|
207
|
-
totalEdges++;
|
|
208
|
-
}
|
|
209
|
-
}
|
|
226
|
+
};
|
|
210
227
|
|
|
211
|
-
|
|
212
|
-
for (const mut of data.mutations) {
|
|
213
|
-
const mutatorNode = resolveNode(mut.funcName);
|
|
214
|
-
if (mutatorNode && mut.binding?.type === 'param') {
|
|
215
|
-
// The mutation in this function affects the parameter source
|
|
216
|
-
insert.run(
|
|
217
|
-
mutatorNode.id,
|
|
218
|
-
mutatorNode.id,
|
|
219
|
-
'mutates',
|
|
220
|
-
null,
|
|
221
|
-
mut.mutatingExpr,
|
|
222
|
-
mut.line,
|
|
223
|
-
1.0,
|
|
224
|
-
);
|
|
225
|
-
totalEdges++;
|
|
226
|
-
}
|
|
227
|
-
}
|
|
228
|
+
totalEdges += insertDataflowEdges(insert, data, resolveNode);
|
|
228
229
|
}
|
|
229
230
|
});
|
|
230
231
|
|
|
@@ -234,31 +235,7 @@ export async function buildDataflowEdges(db, fileSymbols, rootDir, _engineOpts)
|
|
|
234
235
|
|
|
235
236
|
// ── Query functions ─────────────────────────────────────────────────────────
|
|
236
237
|
|
|
237
|
-
|
|
238
|
-
* Look up node(s) by name with optional file/kind/noTests filtering.
|
|
239
|
-
* Similar to findMatchingNodes in queries.js but operates on the dataflow table.
|
|
240
|
-
*/
|
|
241
|
-
function findNodes(db, name, opts = {}) {
|
|
242
|
-
const kinds = opts.kind ? [opts.kind] : ALL_SYMBOL_KINDS;
|
|
243
|
-
const placeholders = kinds.map(() => '?').join(', ');
|
|
244
|
-
const params = [`%${name}%`, ...kinds];
|
|
245
|
-
|
|
246
|
-
let fileCondition = '';
|
|
247
|
-
if (opts.file) {
|
|
248
|
-
fileCondition = ' AND file LIKE ?';
|
|
249
|
-
params.push(`%${opts.file}%`);
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
const rows = db
|
|
253
|
-
.prepare(
|
|
254
|
-
`SELECT * FROM nodes
|
|
255
|
-
WHERE name LIKE ? AND kind IN (${placeholders})${fileCondition}
|
|
256
|
-
ORDER BY file, line`,
|
|
257
|
-
)
|
|
258
|
-
.all(...params);
|
|
259
|
-
|
|
260
|
-
return opts.noTests ? rows.filter((n) => !isTestFile(n.file)) : rows;
|
|
261
|
-
}
|
|
238
|
+
// findNodes imported from ./shared/find-nodes.js
|
|
262
239
|
|
|
263
240
|
/**
|
|
264
241
|
* Return all dataflow edges for a symbol.
|
|
@@ -282,7 +259,12 @@ export function dataflowData(name, customDbPath, opts = {}) {
|
|
|
282
259
|
};
|
|
283
260
|
}
|
|
284
261
|
|
|
285
|
-
const nodes = findNodes(
|
|
262
|
+
const nodes = findNodes(
|
|
263
|
+
db,
|
|
264
|
+
name,
|
|
265
|
+
{ noTests, file: opts.file, kind: opts.kind },
|
|
266
|
+
ALL_SYMBOL_KINDS,
|
|
267
|
+
);
|
|
286
268
|
if (nodes.length === 0) {
|
|
287
269
|
return { name, results: [] };
|
|
288
270
|
}
|
|
@@ -426,12 +408,22 @@ export function dataflowPathData(from, to, customDbPath, opts = {}) {
|
|
|
426
408
|
};
|
|
427
409
|
}
|
|
428
410
|
|
|
429
|
-
const fromNodes = findNodes(
|
|
411
|
+
const fromNodes = findNodes(
|
|
412
|
+
db,
|
|
413
|
+
from,
|
|
414
|
+
{ noTests, file: opts.fromFile, kind: opts.kind },
|
|
415
|
+
ALL_SYMBOL_KINDS,
|
|
416
|
+
);
|
|
430
417
|
if (fromNodes.length === 0) {
|
|
431
418
|
return { from, to, found: false, error: `No symbol matching "${from}"` };
|
|
432
419
|
}
|
|
433
420
|
|
|
434
|
-
const toNodes = findNodes(
|
|
421
|
+
const toNodes = findNodes(
|
|
422
|
+
db,
|
|
423
|
+
to,
|
|
424
|
+
{ noTests, file: opts.toFile, kind: opts.kind },
|
|
425
|
+
ALL_SYMBOL_KINDS,
|
|
426
|
+
);
|
|
435
427
|
if (toNodes.length === 0) {
|
|
436
428
|
return { from, to, found: false, error: `No symbol matching "${to}"` };
|
|
437
429
|
}
|
|
@@ -554,7 +546,12 @@ export function dataflowImpactData(name, customDbPath, opts = {}) {
|
|
|
554
546
|
};
|
|
555
547
|
}
|
|
556
548
|
|
|
557
|
-
const nodes = findNodes(
|
|
549
|
+
const nodes = findNodes(
|
|
550
|
+
db,
|
|
551
|
+
name,
|
|
552
|
+
{ noTests, file: opts.file, kind: opts.kind },
|
|
553
|
+
ALL_SYMBOL_KINDS,
|
|
554
|
+
);
|
|
558
555
|
if (nodes.length === 0) {
|
|
559
556
|
return { name, results: [] };
|
|
560
557
|
}
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import path from 'node:path';
|
|
2
|
-
import { isTestFile } from '
|
|
3
|
-
import { paginateResult } from './paginate.js';
|
|
2
|
+
import { isTestFile } from '../infrastructure/test-filter.js';
|
|
4
3
|
import {
|
|
5
4
|
renderFileLevelDOT,
|
|
6
5
|
renderFileLevelGraphML,
|
|
@@ -10,7 +9,8 @@ import {
|
|
|
10
9
|
renderFunctionLevelGraphML,
|
|
11
10
|
renderFunctionLevelMermaid,
|
|
12
11
|
renderFunctionLevelNeo4jCSV,
|
|
13
|
-
} from '
|
|
12
|
+
} from '../presentation/export.js';
|
|
13
|
+
import { paginateResult } from '../shared/paginate.js';
|
|
14
14
|
|
|
15
15
|
const DEFAULT_MIN_CONFIDENCE = 0.5;
|
|
16
16
|
|
|
@@ -67,8 +67,8 @@ function loadFunctionLevelEdges(db, { noTests, minConfidence, limit }) {
|
|
|
67
67
|
FROM edges e
|
|
68
68
|
JOIN nodes n1 ON e.source_id = n1.id
|
|
69
69
|
JOIN nodes n2 ON e.target_id = n2.id
|
|
70
|
-
WHERE n1.kind IN ('function', 'method', 'class', 'interface', 'type', 'struct', 'enum', 'trait', 'record', 'module')
|
|
71
|
-
AND n2.kind IN ('function', 'method', 'class', 'interface', 'type', 'struct', 'enum', 'trait', 'record', 'module')
|
|
70
|
+
WHERE n1.kind IN ('function', 'method', 'class', 'interface', 'type', 'struct', 'enum', 'trait', 'record', 'module', 'constant')
|
|
71
|
+
AND n2.kind IN ('function', 'method', 'class', 'interface', 'type', 'struct', 'enum', 'trait', 'record', 'module', 'constant')
|
|
72
72
|
AND e.kind = 'calls'
|
|
73
73
|
AND e.confidence >= ?
|
|
74
74
|
`,
|
|
@@ -308,7 +308,7 @@ export function exportGraphSON(db, opts = {}) {
|
|
|
308
308
|
let nodes = db
|
|
309
309
|
.prepare(`
|
|
310
310
|
SELECT id, name, kind, file, line, role FROM nodes
|
|
311
|
-
WHERE kind IN ('function', 'method', 'class', 'interface', 'type', 'struct', 'enum', 'trait', 'record', 'module', 'file')
|
|
311
|
+
WHERE kind IN ('function', 'method', 'class', 'interface', 'type', 'struct', 'enum', 'trait', 'record', 'module', 'constant', 'file')
|
|
312
312
|
`)
|
|
313
313
|
.all();
|
|
314
314
|
if (noTests) nodes = nodes.filter((n) => !isTestFile(n.file));
|
|
@@ -5,10 +5,10 @@
|
|
|
5
5
|
* framework entry points (routes, commands, events) through their call chains.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
import { openReadonlyOrFail } from '
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
8
|
+
import { openReadonlyOrFail } from '../db/index.js';
|
|
9
|
+
import { CORE_SYMBOL_KINDS, findMatchingNodes } from '../domain/queries.js';
|
|
10
|
+
import { isTestFile } from '../infrastructure/test-filter.js';
|
|
11
|
+
import { paginateResult } from '../shared/paginate.js';
|
|
12
12
|
import { FRAMEWORK_ENTRY_PREFIXES } from './structure.js';
|
|
13
13
|
|
|
14
14
|
/**
|
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
import path from 'node:path';
|
|
2
|
-
import { louvainCommunities } from '
|
|
3
|
-
import { CodeGraph } from '
|
|
4
|
-
import { isTestFile } from '
|
|
2
|
+
import { louvainCommunities } from '../graph/algorithms/louvain.js';
|
|
3
|
+
import { CodeGraph } from '../graph/model.js';
|
|
4
|
+
import { isTestFile } from '../infrastructure/test-filter.js';
|
|
5
5
|
import {
|
|
6
6
|
COMMUNITY_COLORS,
|
|
7
7
|
DEFAULT_NODE_COLORS,
|
|
8
8
|
DEFAULT_ROLE_COLORS,
|
|
9
|
-
} from '
|
|
10
|
-
import { DEFAULT_CONFIG, renderPlotHTML } from '
|
|
9
|
+
} from '../presentation/colors.js';
|
|
10
|
+
import { DEFAULT_CONFIG, renderPlotHTML } from '../presentation/viewer.js';
|
|
11
11
|
|
|
12
12
|
// Re-export presentation utilities for backward compatibility
|
|
13
|
-
export { loadPlotConfig } from '
|
|
13
|
+
export { loadPlotConfig } from '../presentation/viewer.js';
|
|
14
14
|
|
|
15
15
|
const DEFAULT_MIN_CONFIDENCE = 0.5;
|
|
16
16
|
|
|
@@ -42,8 +42,8 @@ function prepareFunctionLevelData(db, noTests, minConf, cfg) {
|
|
|
42
42
|
FROM edges e
|
|
43
43
|
JOIN nodes n1 ON e.source_id = n1.id
|
|
44
44
|
JOIN nodes n2 ON e.target_id = n2.id
|
|
45
|
-
WHERE n1.kind IN ('function', 'method', 'class', 'interface', 'type', 'struct', 'enum', 'trait', 'record', 'module')
|
|
46
|
-
AND n2.kind IN ('function', 'method', 'class', 'interface', 'type', 'struct', 'enum', 'trait', 'record', 'module')
|
|
45
|
+
WHERE n1.kind IN ('function', 'method', 'class', 'interface', 'type', 'struct', 'enum', 'trait', 'record', 'module', 'constant')
|
|
46
|
+
AND n2.kind IN ('function', 'method', 'class', 'interface', 'type', 'struct', 'enum', 'trait', 'record', 'module', 'constant')
|
|
47
47
|
AND e.kind = 'calls'
|
|
48
48
|
AND e.confidence >= ?
|
|
49
49
|
`,
|
|
@@ -1,9 +1,10 @@
|
|
|
1
|
+
import { openReadonlyOrFail } from '../db/index.js';
|
|
2
|
+
import { buildFileConditionSQL } from '../db/query-builder.js';
|
|
3
|
+
import { findCycles } from '../domain/graph/cycles.js';
|
|
4
|
+
import { loadConfig } from '../infrastructure/config.js';
|
|
5
|
+
import { debug } from '../infrastructure/logger.js';
|
|
6
|
+
import { paginateResult } from '../shared/paginate.js';
|
|
1
7
|
import { evaluateBoundaries } from './boundaries.js';
|
|
2
|
-
import { loadConfig } from './config.js';
|
|
3
|
-
import { findCycles } from './cycles.js';
|
|
4
|
-
import { openReadonlyOrFail } from './db.js';
|
|
5
|
-
import { debug } from './logger.js';
|
|
6
|
-
import { paginateResult } from './paginate.js';
|
|
7
8
|
|
|
8
9
|
// ─── Rule Definitions ─────────────────────────────────────────────────
|
|
9
10
|
|
|
@@ -144,9 +145,10 @@ function evaluateFunctionRules(db, rules, opts, violations, ruleResults) {
|
|
|
144
145
|
let where = "WHERE n.kind IN ('function','method')";
|
|
145
146
|
const params = [];
|
|
146
147
|
if (opts.noTests) where += NO_TEST_SQL;
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
148
|
+
{
|
|
149
|
+
const fc = buildFileConditionSQL(opts.file, 'n.file');
|
|
150
|
+
where += fc.sql;
|
|
151
|
+
params.push(...fc.params);
|
|
150
152
|
}
|
|
151
153
|
if (opts.kind) {
|
|
152
154
|
where += ' AND n.kind = ?';
|
|
@@ -221,9 +223,10 @@ function evaluateFileRules(db, rules, opts, violations, ruleResults) {
|
|
|
221
223
|
let where = "WHERE n.kind = 'file'";
|
|
222
224
|
const params = [];
|
|
223
225
|
if (opts.noTests) where += NO_TEST_SQL;
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
226
|
+
{
|
|
227
|
+
const fc = buildFileConditionSQL(opts.file, 'n.file');
|
|
228
|
+
where += fc.sql;
|
|
229
|
+
params.push(...fc.params);
|
|
227
230
|
}
|
|
228
231
|
|
|
229
232
|
let rows;
|
|
@@ -395,7 +398,7 @@ export function manifestoData(customDbPath, opts = {}) {
|
|
|
395
398
|
const db = openReadonlyOrFail(customDbPath);
|
|
396
399
|
|
|
397
400
|
try {
|
|
398
|
-
const config = loadConfig(process.cwd());
|
|
401
|
+
const config = opts.config || loadConfig(process.cwd());
|
|
399
402
|
const rules = resolveRules(config.manifesto?.rules);
|
|
400
403
|
|
|
401
404
|
const violations = [];
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import fs from 'node:fs';
|
|
2
2
|
import path from 'node:path';
|
|
3
|
-
import { findDbPath, openReadonlyOrFail } from '
|
|
4
|
-
import {
|
|
3
|
+
import { findDbPath, openReadonlyOrFail } from '../db/index.js';
|
|
4
|
+
import { normalizeFileFilter } from '../db/query-builder.js';
|
|
5
|
+
import { isTestFile } from '../infrastructure/test-filter.js';
|
|
5
6
|
|
|
6
7
|
// ─── CODEOWNERS Parsing ──────────────────────────────────────────────
|
|
7
8
|
|
|
@@ -192,9 +193,9 @@ export function ownersData(customDbPath, opts = {}) {
|
|
|
192
193
|
.map((r) => r.file);
|
|
193
194
|
|
|
194
195
|
if (opts.noTests) allFiles = allFiles.filter((f) => !isTestFile(f));
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
allFiles = allFiles.filter((f) => f.includes(filter));
|
|
196
|
+
const fileFilters = normalizeFileFilter(opts.file);
|
|
197
|
+
if (fileFilters.length > 0) {
|
|
198
|
+
allFiles = allFiles.filter((f) => fileFilters.some((filter) => f.includes(filter)));
|
|
198
199
|
}
|
|
199
200
|
|
|
200
201
|
// Map files to owners
|