@optave/codegraph 3.10.0 → 3.11.1
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 +40 -33
- package/dist/ast-analysis/engine.d.ts.map +1 -1
- package/dist/ast-analysis/engine.js +91 -60
- package/dist/ast-analysis/engine.js.map +1 -1
- package/dist/ast-analysis/rules/index.d.ts.map +1 -1
- package/dist/ast-analysis/rules/index.js +77 -0
- package/dist/ast-analysis/rules/index.js.map +1 -1
- package/dist/ast-analysis/visitor-utils.d.ts +3 -0
- package/dist/ast-analysis/visitor-utils.d.ts.map +1 -1
- package/dist/ast-analysis/visitor-utils.js +83 -49
- package/dist/ast-analysis/visitor-utils.js.map +1 -1
- package/dist/ast-analysis/visitors/ast-store-visitor.d.ts.map +1 -1
- package/dist/ast-analysis/visitors/ast-store-visitor.js +78 -62
- package/dist/ast-analysis/visitors/ast-store-visitor.js.map +1 -1
- package/dist/ast-analysis/visitors/dataflow-visitor.d.ts.map +1 -1
- package/dist/ast-analysis/visitors/dataflow-visitor.js +61 -42
- package/dist/ast-analysis/visitors/dataflow-visitor.js.map +1 -1
- package/dist/cli/commands/audit.js +1 -1
- package/dist/cli/commands/audit.js.map +1 -1
- package/dist/cli/commands/build.d.ts.map +1 -1
- package/dist/cli/commands/build.js +2 -0
- package/dist/cli/commands/build.js.map +1 -1
- package/dist/cli/commands/check.js +1 -1
- package/dist/cli/commands/check.js.map +1 -1
- package/dist/cli/commands/children.js +1 -1
- package/dist/cli/commands/children.js.map +1 -1
- package/dist/cli/commands/diff-impact.js +1 -1
- package/dist/cli/commands/diff-impact.js.map +1 -1
- package/dist/cli/commands/embed.d.ts.map +1 -1
- package/dist/cli/commands/embed.js +49 -4
- package/dist/cli/commands/embed.js.map +1 -1
- package/dist/cli/commands/roles.js +1 -1
- package/dist/cli/commands/roles.js.map +1 -1
- package/dist/cli/commands/structure.js +1 -1
- package/dist/cli/commands/structure.js.map +1 -1
- package/dist/cli/shared/options.js +1 -1
- package/dist/cli/shared/options.js.map +1 -1
- package/dist/db/connection.d.ts.map +1 -1
- package/dist/db/connection.js +8 -0
- package/dist/db/connection.js.map +1 -1
- package/dist/domain/analysis/dependencies.d.ts.map +1 -1
- package/dist/domain/analysis/dependencies.js +106 -80
- package/dist/domain/analysis/dependencies.js.map +1 -1
- package/dist/domain/analysis/fn-impact.d.ts.map +1 -1
- package/dist/domain/analysis/fn-impact.js +77 -52
- package/dist/domain/analysis/fn-impact.js.map +1 -1
- package/dist/domain/analysis/module-map.d.ts.map +1 -1
- package/dist/domain/analysis/module-map.js +132 -121
- package/dist/domain/analysis/module-map.js.map +1 -1
- package/dist/domain/graph/builder/helpers.d.ts +4 -4
- package/dist/domain/graph/builder/helpers.d.ts.map +1 -1
- package/dist/domain/graph/builder/helpers.js +47 -33
- package/dist/domain/graph/builder/helpers.js.map +1 -1
- package/dist/domain/graph/builder/incremental.d.ts +6 -6
- package/dist/domain/graph/builder/incremental.d.ts.map +1 -1
- package/dist/domain/graph/builder/incremental.js +148 -99
- package/dist/domain/graph/builder/incremental.js.map +1 -1
- package/dist/domain/graph/builder/pipeline.d.ts +1 -0
- package/dist/domain/graph/builder/pipeline.d.ts.map +1 -1
- package/dist/domain/graph/builder/pipeline.js +23 -637
- package/dist/domain/graph/builder/pipeline.js.map +1 -1
- package/dist/domain/graph/builder/stages/build-edges.d.ts.map +1 -1
- package/dist/domain/graph/builder/stages/build-edges.js +141 -98
- package/dist/domain/graph/builder/stages/build-edges.js.map +1 -1
- package/dist/domain/graph/builder/stages/build-structure.d.ts.map +1 -1
- package/dist/domain/graph/builder/stages/build-structure.js +82 -65
- package/dist/domain/graph/builder/stages/build-structure.js.map +1 -1
- package/dist/domain/graph/builder/stages/detect-changes.d.ts.map +1 -1
- package/dist/domain/graph/builder/stages/detect-changes.js +84 -56
- package/dist/domain/graph/builder/stages/detect-changes.js.map +1 -1
- package/dist/domain/graph/builder/stages/finalize.d.ts.map +1 -1
- package/dist/domain/graph/builder/stages/finalize.js +60 -51
- package/dist/domain/graph/builder/stages/finalize.js.map +1 -1
- package/dist/domain/graph/builder/stages/insert-nodes.d.ts +8 -6
- package/dist/domain/graph/builder/stages/insert-nodes.d.ts.map +1 -1
- package/dist/domain/graph/builder/stages/insert-nodes.js +107 -122
- package/dist/domain/graph/builder/stages/insert-nodes.js.map +1 -1
- package/dist/domain/graph/builder/stages/native-db-lifecycle.d.ts +14 -0
- package/dist/domain/graph/builder/stages/native-db-lifecycle.d.ts.map +1 -0
- package/dist/domain/graph/builder/stages/native-db-lifecycle.js +77 -0
- package/dist/domain/graph/builder/stages/native-db-lifecycle.js.map +1 -0
- package/dist/domain/graph/builder/stages/native-orchestrator.d.ts +62 -0
- package/dist/domain/graph/builder/stages/native-orchestrator.d.ts.map +1 -0
- package/dist/domain/graph/builder/stages/native-orchestrator.js +747 -0
- package/dist/domain/graph/builder/stages/native-orchestrator.js.map +1 -0
- package/dist/domain/graph/builder/stages/resolve-imports.d.ts.map +1 -1
- package/dist/domain/graph/builder/stages/resolve-imports.js +73 -22
- package/dist/domain/graph/builder/stages/resolve-imports.js.map +1 -1
- package/dist/domain/graph/cycles.d.ts +6 -4
- package/dist/domain/graph/cycles.d.ts.map +1 -1
- package/dist/domain/graph/cycles.js +50 -55
- package/dist/domain/graph/cycles.js.map +1 -1
- package/dist/domain/graph/journal.d.ts.map +1 -1
- package/dist/domain/graph/journal.js +89 -70
- package/dist/domain/graph/journal.js.map +1 -1
- package/dist/domain/graph/watcher.d.ts.map +1 -1
- package/dist/domain/graph/watcher.js +28 -20
- package/dist/domain/graph/watcher.js.map +1 -1
- package/dist/domain/parser.d.ts +12 -23
- package/dist/domain/parser.d.ts.map +1 -1
- package/dist/domain/parser.js +153 -80
- package/dist/domain/parser.js.map +1 -1
- package/dist/domain/search/generator.d.ts +3 -1
- package/dist/domain/search/generator.d.ts.map +1 -1
- package/dist/domain/search/generator.js +68 -45
- package/dist/domain/search/generator.js.map +1 -1
- package/dist/domain/search/models.d.ts +18 -0
- package/dist/domain/search/models.d.ts.map +1 -1
- package/dist/domain/search/models.js +72 -4
- package/dist/domain/search/models.js.map +1 -1
- package/dist/domain/search/search/hybrid.d.ts.map +1 -1
- package/dist/domain/search/search/hybrid.js +49 -40
- package/dist/domain/search/search/hybrid.js.map +1 -1
- package/dist/domain/search/search/semantic.d.ts.map +1 -1
- package/dist/domain/search/search/semantic.js +69 -49
- package/dist/domain/search/search/semantic.js.map +1 -1
- package/dist/domain/wasm-worker-entry.js +209 -137
- package/dist/domain/wasm-worker-entry.js.map +1 -1
- package/dist/extractors/c.js +25 -6
- package/dist/extractors/c.js.map +1 -1
- package/dist/extractors/cpp.js +47 -6
- package/dist/extractors/cpp.js.map +1 -1
- package/dist/extractors/cuda.js +90 -14
- package/dist/extractors/cuda.js.map +1 -1
- package/dist/extractors/elixir.js +108 -4
- package/dist/extractors/elixir.js.map +1 -1
- package/dist/extractors/erlang.js +56 -20
- package/dist/extractors/erlang.js.map +1 -1
- package/dist/extractors/fsharp.d.ts +7 -0
- package/dist/extractors/fsharp.d.ts.map +1 -1
- package/dist/extractors/fsharp.js +94 -0
- package/dist/extractors/fsharp.js.map +1 -1
- package/dist/extractors/gleam.d.ts.map +1 -1
- package/dist/extractors/gleam.js +29 -33
- package/dist/extractors/gleam.js.map +1 -1
- package/dist/extractors/groovy.js +41 -1
- package/dist/extractors/groovy.js.map +1 -1
- package/dist/extractors/haskell.js +48 -4
- package/dist/extractors/haskell.js.map +1 -1
- package/dist/extractors/helpers.d.ts +79 -1
- package/dist/extractors/helpers.d.ts.map +1 -1
- package/dist/extractors/helpers.js +137 -0
- package/dist/extractors/helpers.js.map +1 -1
- package/dist/extractors/java.d.ts.map +1 -1
- package/dist/extractors/java.js +37 -49
- package/dist/extractors/java.js.map +1 -1
- package/dist/extractors/javascript.d.ts.map +1 -1
- package/dist/extractors/javascript.js +44 -44
- package/dist/extractors/javascript.js.map +1 -1
- package/dist/extractors/julia.js +198 -74
- package/dist/extractors/julia.js.map +1 -1
- package/dist/extractors/kotlin.js +4 -0
- package/dist/extractors/kotlin.js.map +1 -1
- package/dist/extractors/objc.js +184 -47
- package/dist/extractors/objc.js.map +1 -1
- package/dist/extractors/python.js +7 -4
- package/dist/extractors/python.js.map +1 -1
- package/dist/extractors/r.d.ts.map +1 -1
- package/dist/extractors/r.js +103 -87
- package/dist/extractors/r.js.map +1 -1
- package/dist/extractors/scala.d.ts.map +1 -1
- package/dist/extractors/scala.js +18 -32
- package/dist/extractors/scala.js.map +1 -1
- package/dist/extractors/solidity.d.ts.map +1 -1
- package/dist/extractors/solidity.js +55 -69
- package/dist/extractors/solidity.js.map +1 -1
- package/dist/extractors/verilog.js +80 -15
- package/dist/extractors/verilog.js.map +1 -1
- package/dist/features/boundaries.d.ts.map +1 -1
- package/dist/features/boundaries.js +49 -39
- package/dist/features/boundaries.js.map +1 -1
- package/dist/features/cfg.d.ts.map +1 -1
- package/dist/features/cfg.js +90 -63
- package/dist/features/cfg.js.map +1 -1
- package/dist/features/check.d.ts.map +1 -1
- package/dist/features/check.js +43 -34
- package/dist/features/check.js.map +1 -1
- package/dist/features/cochange.d.ts.map +1 -1
- package/dist/features/cochange.js +68 -56
- package/dist/features/cochange.js.map +1 -1
- package/dist/features/complexity.d.ts.map +1 -1
- package/dist/features/complexity.js +105 -75
- package/dist/features/complexity.js.map +1 -1
- package/dist/features/dataflow.d.ts.map +1 -1
- package/dist/features/dataflow.js +37 -29
- package/dist/features/dataflow.js.map +1 -1
- package/dist/features/flow.d.ts.map +1 -1
- package/dist/features/flow.js +31 -22
- package/dist/features/flow.js.map +1 -1
- package/dist/features/graph-enrichment.d.ts.map +1 -1
- package/dist/features/graph-enrichment.js +77 -70
- package/dist/features/graph-enrichment.js.map +1 -1
- package/dist/features/owners.d.ts +17 -26
- package/dist/features/owners.d.ts.map +1 -1
- package/dist/features/owners.js +120 -109
- package/dist/features/owners.js.map +1 -1
- package/dist/features/sequence.d.ts.map +1 -1
- package/dist/features/sequence.js +59 -54
- package/dist/features/sequence.js.map +1 -1
- package/dist/features/structure-query.d.ts.map +1 -1
- package/dist/features/structure-query.js +60 -60
- package/dist/features/structure-query.js.map +1 -1
- package/dist/features/structure.js +28 -36
- package/dist/features/structure.js.map +1 -1
- package/dist/graph/algorithms/leiden/optimiser.d.ts.map +1 -1
- package/dist/graph/algorithms/leiden/optimiser.js +100 -69
- package/dist/graph/algorithms/leiden/optimiser.js.map +1 -1
- package/dist/graph/classifiers/roles.d.ts.map +1 -1
- package/dist/graph/classifiers/roles.js +63 -59
- package/dist/graph/classifiers/roles.js.map +1 -1
- package/dist/infrastructure/config.d.ts +1 -1
- package/dist/infrastructure/config.d.ts.map +1 -1
- package/dist/infrastructure/config.js +1 -1
- package/dist/infrastructure/config.js.map +1 -1
- package/dist/mcp/tool-registry.d.ts.map +1 -1
- package/dist/mcp/tool-registry.js +4 -0
- package/dist/mcp/tool-registry.js.map +1 -1
- package/dist/mcp/tools/semantic-search.d.ts +1 -0
- package/dist/mcp/tools/semantic-search.d.ts.map +1 -1
- package/dist/mcp/tools/semantic-search.js +1 -0
- package/dist/mcp/tools/semantic-search.js.map +1 -1
- package/dist/presentation/cfg.d.ts.map +1 -1
- package/dist/presentation/cfg.js +44 -29
- package/dist/presentation/cfg.js.map +1 -1
- package/dist/presentation/flow.d.ts.map +1 -1
- package/dist/presentation/flow.js +58 -38
- package/dist/presentation/flow.js.map +1 -1
- package/dist/types.d.ts +16 -2
- package/dist/types.d.ts.map +1 -1
- package/grammars/tree-sitter-erlang.wasm +0 -0
- package/grammars/tree-sitter-fsharp.wasm +0 -0
- package/grammars/tree-sitter-fsharp_signature.wasm +0 -0
- package/grammars/tree-sitter-gleam.wasm +0 -0
- package/package.json +10 -10
- package/src/ast-analysis/engine.ts +145 -61
- package/src/ast-analysis/rules/index.ts +87 -0
- package/src/ast-analysis/visitor-utils.ts +86 -46
- package/src/ast-analysis/visitors/ast-store-visitor.ts +104 -69
- package/src/ast-analysis/visitors/dataflow-visitor.ts +86 -47
- package/src/cli/commands/audit.ts +1 -1
- package/src/cli/commands/build.ts +2 -0
- package/src/cli/commands/check.ts +1 -1
- package/src/cli/commands/children.ts +1 -1
- package/src/cli/commands/diff-impact.ts +1 -1
- package/src/cli/commands/embed.ts +54 -4
- package/src/cli/commands/roles.ts +1 -1
- package/src/cli/commands/structure.ts +1 -1
- package/src/cli/shared/options.ts +1 -1
- package/src/db/connection.ts +8 -0
- package/src/domain/analysis/dependencies.ts +166 -85
- package/src/domain/analysis/fn-impact.ts +120 -50
- package/src/domain/analysis/module-map.ts +175 -140
- package/src/domain/graph/builder/helpers.ts +85 -76
- package/src/domain/graph/builder/incremental.ts +223 -131
- package/src/domain/graph/builder/pipeline.ts +32 -785
- package/src/domain/graph/builder/stages/build-edges.ts +207 -142
- package/src/domain/graph/builder/stages/build-structure.ts +115 -82
- package/src/domain/graph/builder/stages/detect-changes.ts +107 -64
- package/src/domain/graph/builder/stages/finalize.ts +72 -70
- package/src/domain/graph/builder/stages/insert-nodes.ts +154 -120
- package/src/domain/graph/builder/stages/native-db-lifecycle.ts +74 -0
- package/src/domain/graph/builder/stages/native-orchestrator.ts +942 -0
- package/src/domain/graph/builder/stages/resolve-imports.ts +79 -25
- package/src/domain/graph/cycles.ts +51 -49
- package/src/domain/graph/journal.ts +84 -69
- package/src/domain/graph/watcher.ts +29 -25
- package/src/domain/parser.ts +170 -67
- package/src/domain/search/generator.ts +132 -74
- package/src/domain/search/models.ts +75 -4
- package/src/domain/search/search/hybrid.ts +53 -42
- package/src/domain/search/search/semantic.ts +105 -65
- package/src/domain/wasm-worker-entry.ts +243 -153
- package/src/extractors/c.ts +27 -8
- package/src/extractors/cpp.ts +50 -8
- package/src/extractors/cuda.ts +90 -16
- package/src/extractors/elixir.ts +103 -4
- package/src/extractors/erlang.ts +63 -20
- package/src/extractors/fsharp.ts +104 -0
- package/src/extractors/gleam.ts +40 -39
- package/src/extractors/groovy.ts +45 -1
- package/src/extractors/haskell.ts +45 -4
- package/src/extractors/helpers.ts +205 -1
- package/src/extractors/java.ts +42 -45
- package/src/extractors/javascript.ts +44 -43
- package/src/extractors/julia.ts +191 -77
- package/src/extractors/kotlin.ts +4 -0
- package/src/extractors/objc.ts +171 -47
- package/src/extractors/python.ts +5 -3
- package/src/extractors/r.ts +104 -82
- package/src/extractors/scala.ts +24 -36
- package/src/extractors/solidity.ts +59 -78
- package/src/extractors/verilog.ts +83 -15
- package/src/features/boundaries.ts +64 -46
- package/src/features/cfg.ts +145 -74
- package/src/features/check.ts +60 -43
- package/src/features/cochange.ts +95 -72
- package/src/features/complexity.ts +134 -79
- package/src/features/dataflow.ts +57 -34
- package/src/features/flow.ts +48 -24
- package/src/features/graph-enrichment.ts +105 -70
- package/src/features/owners.ts +186 -146
- package/src/features/sequence.ts +99 -69
- package/src/features/structure-query.ts +94 -79
- package/src/features/structure.ts +56 -56
- package/src/graph/algorithms/leiden/optimiser.ts +142 -87
- package/src/graph/classifiers/roles.ts +64 -54
- package/src/infrastructure/config.ts +1 -1
- package/src/mcp/tool-registry.ts +5 -0
- package/src/mcp/tools/semantic-search.ts +2 -0
- package/src/presentation/cfg.ts +48 -32
- package/src/presentation/flow.ts +100 -52
- package/src/types.ts +16 -1
package/src/features/sequence.ts
CHANGED
|
@@ -91,6 +91,40 @@ interface BfsResult {
|
|
|
91
91
|
truncated: boolean;
|
|
92
92
|
}
|
|
93
93
|
|
|
94
|
+
type CalleeNode = { id: number; name: string; file: string; kind: string; line: number };
|
|
95
|
+
|
|
96
|
+
interface BfsFrame {
|
|
97
|
+
visited: Set<number>;
|
|
98
|
+
messages: SequenceMessage[];
|
|
99
|
+
fileSet: Set<string>;
|
|
100
|
+
idToNode: Map<number, CalleeNode>;
|
|
101
|
+
nextFrontier: number[];
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function processCallee(
|
|
105
|
+
c: CalleeNode,
|
|
106
|
+
caller: CalleeNode,
|
|
107
|
+
depth: number,
|
|
108
|
+
noTests: boolean,
|
|
109
|
+
frame: BfsFrame,
|
|
110
|
+
): void {
|
|
111
|
+
if (noTests && isTestFile(c.file)) return;
|
|
112
|
+
|
|
113
|
+
frame.fileSet.add(c.file);
|
|
114
|
+
frame.messages.push({
|
|
115
|
+
from: caller.file,
|
|
116
|
+
to: c.file,
|
|
117
|
+
label: c.name,
|
|
118
|
+
type: 'call',
|
|
119
|
+
depth,
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
if (frame.visited.has(c.id)) return;
|
|
123
|
+
frame.visited.add(c.id);
|
|
124
|
+
frame.nextFrontier.push(c.id);
|
|
125
|
+
frame.idToNode.set(c.id, c);
|
|
126
|
+
}
|
|
127
|
+
|
|
94
128
|
function bfsCallees(
|
|
95
129
|
repo: Repository,
|
|
96
130
|
matchNode: MatchNode,
|
|
@@ -101,46 +135,25 @@ function bfsCallees(
|
|
|
101
135
|
let frontier = [matchNode.id];
|
|
102
136
|
const messages: SequenceMessage[] = [];
|
|
103
137
|
const fileSet = new Set<string>([matchNode.file]);
|
|
104
|
-
const idToNode = new Map<
|
|
105
|
-
number,
|
|
106
|
-
{ id: number; name: string; file: string; kind: string; line: number }
|
|
107
|
-
>();
|
|
138
|
+
const idToNode = new Map<number, CalleeNode>();
|
|
108
139
|
idToNode.set(matchNode.id, matchNode);
|
|
109
140
|
let truncated = false;
|
|
110
141
|
|
|
111
142
|
for (let d = 1; d <= maxDepth; d++) {
|
|
112
|
-
const
|
|
143
|
+
const frame: BfsFrame = { visited, messages, fileSet, idToNode, nextFrontier: [] };
|
|
113
144
|
|
|
114
145
|
for (const fid of frontier) {
|
|
115
|
-
const callees = repo.findCallees(fid);
|
|
116
146
|
const caller = idToNode.get(fid)!;
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
if (noTests && isTestFile(c.file)) continue;
|
|
120
|
-
|
|
121
|
-
fileSet.add(c.file);
|
|
122
|
-
messages.push({
|
|
123
|
-
from: caller.file,
|
|
124
|
-
to: c.file,
|
|
125
|
-
label: c.name,
|
|
126
|
-
type: 'call',
|
|
127
|
-
depth: d,
|
|
128
|
-
});
|
|
129
|
-
|
|
130
|
-
if (visited.has(c.id)) continue;
|
|
131
|
-
|
|
132
|
-
visited.add(c.id);
|
|
133
|
-
nextFrontier.push(c.id);
|
|
134
|
-
idToNode.set(c.id, c);
|
|
147
|
+
for (const c of repo.findCallees(fid)) {
|
|
148
|
+
processCallee(c, caller, d, noTests, frame);
|
|
135
149
|
}
|
|
136
150
|
}
|
|
137
151
|
|
|
138
|
-
frontier = nextFrontier;
|
|
152
|
+
frontier = frame.nextFrontier;
|
|
139
153
|
if (frontier.length === 0) break;
|
|
140
154
|
|
|
141
|
-
if (d === maxDepth && frontier.length > 0) {
|
|
142
|
-
|
|
143
|
-
if (hasMoreCalls) truncated = true;
|
|
155
|
+
if (d === maxDepth && frontier.some((fid) => repo.findCallees(fid).length > 0)) {
|
|
156
|
+
truncated = true;
|
|
144
157
|
}
|
|
145
158
|
}
|
|
146
159
|
|
|
@@ -174,26 +187,16 @@ function annotateDataflow(
|
|
|
174
187
|
}
|
|
175
188
|
}
|
|
176
189
|
|
|
177
|
-
|
|
178
|
-
|
|
190
|
+
type DataflowStmts = {
|
|
191
|
+
getReturns: ReturnType<BetterSqlite3Database['prepare']>;
|
|
192
|
+
getFlowsTo: ReturnType<BetterSqlite3Database['prepare']>;
|
|
193
|
+
};
|
|
194
|
+
|
|
195
|
+
function appendReturnMessages(
|
|
179
196
|
messages: SequenceMessage[],
|
|
180
|
-
|
|
197
|
+
nodeByNameFile: Map<string, { id: number; name: string; file: string }>,
|
|
198
|
+
stmts: DataflowStmts,
|
|
181
199
|
): void {
|
|
182
|
-
const nodeByNameFile = new Map<string, { id: number; name: string; file: string }>();
|
|
183
|
-
for (const n of idToNode.values()) {
|
|
184
|
-
nodeByNameFile.set(`${n.name}|${n.file}`, n);
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
const getReturns = db.prepare(
|
|
188
|
-
`SELECT d.expression FROM dataflow d
|
|
189
|
-
WHERE d.source_id = ? AND d.kind = 'returns'`,
|
|
190
|
-
);
|
|
191
|
-
const getFlowsTo = db.prepare(
|
|
192
|
-
`SELECT d.expression FROM dataflow d
|
|
193
|
-
WHERE d.target_id = ? AND d.kind = 'flows_to'
|
|
194
|
-
ORDER BY d.param_index`,
|
|
195
|
-
);
|
|
196
|
-
|
|
197
200
|
const seenReturns = new Set<string>();
|
|
198
201
|
for (const msg of [...messages]) {
|
|
199
202
|
if (msg.type !== 'call') continue;
|
|
@@ -203,40 +206,67 @@ function _annotateDataflowImpl(
|
|
|
203
206
|
const returnKey = `${msg.to}->${msg.from}:${msg.label}`;
|
|
204
207
|
if (seenReturns.has(returnKey)) continue;
|
|
205
208
|
|
|
206
|
-
const returns = getReturns.all(targetNode.id) as { expression: string }[];
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
});
|
|
218
|
-
}
|
|
209
|
+
const returns = stmts.getReturns.all(targetNode.id) as { expression: string }[];
|
|
210
|
+
if (returns.length === 0) continue;
|
|
211
|
+
|
|
212
|
+
seenReturns.add(returnKey);
|
|
213
|
+
messages.push({
|
|
214
|
+
from: msg.to,
|
|
215
|
+
to: msg.from,
|
|
216
|
+
label: returns[0]!.expression || 'result',
|
|
217
|
+
type: 'return',
|
|
218
|
+
depth: msg.depth,
|
|
219
|
+
});
|
|
219
220
|
}
|
|
221
|
+
}
|
|
220
222
|
|
|
223
|
+
function annotateCallParams(
|
|
224
|
+
messages: SequenceMessage[],
|
|
225
|
+
nodeByNameFile: Map<string, { id: number; name: string; file: string }>,
|
|
226
|
+
stmts: DataflowStmts,
|
|
227
|
+
): void {
|
|
221
228
|
for (const msg of messages) {
|
|
222
229
|
if (msg.type !== 'call') continue;
|
|
223
230
|
const targetNode = nodeByNameFile.get(`${msg.label}|${msg.to}`);
|
|
224
231
|
if (!targetNode) continue;
|
|
225
232
|
|
|
226
|
-
const params = getFlowsTo.all(targetNode.id) as { expression: string }[];
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
if (paramNames.length > 0) {
|
|
234
|
-
msg.label = `${msg.label}(${paramNames.join(', ')})`;
|
|
235
|
-
}
|
|
233
|
+
const params = stmts.getFlowsTo.all(targetNode.id) as { expression: string }[];
|
|
234
|
+
const paramNames = params
|
|
235
|
+
.map((p) => p.expression)
|
|
236
|
+
.filter(Boolean)
|
|
237
|
+
.slice(0, 3);
|
|
238
|
+
if (paramNames.length > 0) {
|
|
239
|
+
msg.label = `${msg.label}(${paramNames.join(', ')})`;
|
|
236
240
|
}
|
|
237
241
|
}
|
|
238
242
|
}
|
|
239
243
|
|
|
244
|
+
function _annotateDataflowImpl(
|
|
245
|
+
db: BetterSqlite3Database,
|
|
246
|
+
messages: SequenceMessage[],
|
|
247
|
+
idToNode: Map<number, { id: number; name: string; file: string; kind: string; line: number }>,
|
|
248
|
+
): void {
|
|
249
|
+
const nodeByNameFile = new Map<string, { id: number; name: string; file: string }>();
|
|
250
|
+
for (const n of idToNode.values()) {
|
|
251
|
+
nodeByNameFile.set(`${n.name}|${n.file}`, n);
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
const stmts: DataflowStmts = {
|
|
255
|
+
getReturns: db.prepare(
|
|
256
|
+
`SELECT d.expression FROM dataflow d
|
|
257
|
+
WHERE d.source_id = ? AND d.kind = 'returns'`,
|
|
258
|
+
),
|
|
259
|
+
getFlowsTo: db.prepare(
|
|
260
|
+
`SELECT d.expression FROM dataflow d
|
|
261
|
+
WHERE d.target_id = ? AND d.kind = 'flows_to'
|
|
262
|
+
ORDER BY d.param_index`,
|
|
263
|
+
),
|
|
264
|
+
};
|
|
265
|
+
|
|
266
|
+
appendReturnMessages(messages, nodeByNameFile, stmts);
|
|
267
|
+
annotateCallParams(messages, nodeByNameFile, stmts);
|
|
268
|
+
}
|
|
269
|
+
|
|
240
270
|
interface Participant {
|
|
241
271
|
id: string;
|
|
242
272
|
label: string;
|
|
@@ -227,6 +227,96 @@ interface HotspotsDataOpts {
|
|
|
227
227
|
noTests?: boolean;
|
|
228
228
|
}
|
|
229
229
|
|
|
230
|
+
type HotspotEntry = {
|
|
231
|
+
name: string;
|
|
232
|
+
kind: string;
|
|
233
|
+
lineCount: number | null;
|
|
234
|
+
symbolCount: number | null;
|
|
235
|
+
importCount: number | null;
|
|
236
|
+
exportCount: number | null;
|
|
237
|
+
fanIn: number | null;
|
|
238
|
+
fanOut: number | null;
|
|
239
|
+
cohesion: number | null;
|
|
240
|
+
fileCount: number | null;
|
|
241
|
+
density: number;
|
|
242
|
+
coupling: number;
|
|
243
|
+
};
|
|
244
|
+
|
|
245
|
+
/** Compute density from either fileCount/symbolCount or lineCount/symbolCount. */
|
|
246
|
+
function computeHotspotDensity(
|
|
247
|
+
symbolCount: number | null,
|
|
248
|
+
fileCount: number | null,
|
|
249
|
+
lineCount: number | null,
|
|
250
|
+
): number {
|
|
251
|
+
if ((fileCount ?? 0) > 0) return (symbolCount || 0) / (fileCount ?? 1);
|
|
252
|
+
if ((lineCount ?? 0) > 0) return (symbolCount || 0) / (lineCount ?? 1);
|
|
253
|
+
return 0;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
/** Map a native-engine hotspot row (camelCase keys) to the public HotspotEntry shape. */
|
|
257
|
+
function mapNativeHotspotRow(r: {
|
|
258
|
+
name: string;
|
|
259
|
+
kind: string;
|
|
260
|
+
lineCount: number | null;
|
|
261
|
+
symbolCount: number | null;
|
|
262
|
+
importCount: number | null;
|
|
263
|
+
exportCount: number | null;
|
|
264
|
+
fanIn: number | null;
|
|
265
|
+
fanOut: number | null;
|
|
266
|
+
cohesion: number | null;
|
|
267
|
+
fileCount: number | null;
|
|
268
|
+
}): HotspotEntry {
|
|
269
|
+
return {
|
|
270
|
+
name: r.name,
|
|
271
|
+
kind: r.kind,
|
|
272
|
+
lineCount: r.lineCount,
|
|
273
|
+
symbolCount: r.symbolCount,
|
|
274
|
+
importCount: r.importCount,
|
|
275
|
+
exportCount: r.exportCount,
|
|
276
|
+
fanIn: r.fanIn,
|
|
277
|
+
fanOut: r.fanOut,
|
|
278
|
+
cohesion: r.cohesion,
|
|
279
|
+
fileCount: r.fileCount,
|
|
280
|
+
density: computeHotspotDensity(r.symbolCount, r.fileCount, r.lineCount),
|
|
281
|
+
coupling: (r.fanIn || 0) + (r.fanOut || 0),
|
|
282
|
+
};
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
/** Map a JS-path hotspot row (snake_case keys from SQLite) to the public HotspotEntry shape. */
|
|
286
|
+
function mapJsHotspotRow(r: HotspotRow): HotspotEntry {
|
|
287
|
+
return {
|
|
288
|
+
name: r.name,
|
|
289
|
+
kind: r.kind,
|
|
290
|
+
lineCount: r.line_count,
|
|
291
|
+
symbolCount: r.symbol_count,
|
|
292
|
+
importCount: r.import_count,
|
|
293
|
+
exportCount: r.export_count,
|
|
294
|
+
fanIn: r.fan_in,
|
|
295
|
+
fanOut: r.fan_out,
|
|
296
|
+
cohesion: r.cohesion,
|
|
297
|
+
fileCount: r.file_count,
|
|
298
|
+
density: computeHotspotDensity(r.symbol_count, r.file_count, r.line_count),
|
|
299
|
+
coupling: (r.fan_in || 0) + (r.fan_out || 0),
|
|
300
|
+
};
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
/** ORDER BY clause for each ranking dimension (strategy pattern). */
|
|
304
|
+
const HOTSPOT_ORDER_BY: Record<string, string> = {
|
|
305
|
+
'fan-in': 'nm.fan_in DESC NULLS LAST',
|
|
306
|
+
'fan-out': 'nm.fan_out DESC NULLS LAST',
|
|
307
|
+
density: 'nm.symbol_count DESC NULLS LAST',
|
|
308
|
+
coupling: '(COALESCE(nm.fan_in, 0) + COALESCE(nm.fan_out, 0)) DESC NULLS LAST',
|
|
309
|
+
};
|
|
310
|
+
|
|
311
|
+
/** Build the JS-path SQL query for a given metric and test filter. */
|
|
312
|
+
function buildHotspotQuery(metric: string, testFilter: string): string {
|
|
313
|
+
const orderBy = HOTSPOT_ORDER_BY[metric] ?? HOTSPOT_ORDER_BY['fan-in'];
|
|
314
|
+
return `SELECT n.name, n.kind, nm.line_count, nm.symbol_count, nm.import_count, nm.export_count,
|
|
315
|
+
nm.fan_in, nm.fan_out, nm.cohesion, nm.file_count
|
|
316
|
+
FROM nodes n JOIN node_metrics nm ON n.id = nm.node_id
|
|
317
|
+
WHERE n.kind = ? ${testFilter} ORDER BY ${orderBy} LIMIT ?`;
|
|
318
|
+
}
|
|
319
|
+
|
|
230
320
|
export function hotspotsData(
|
|
231
321
|
customDbPath?: string,
|
|
232
322
|
opts: HotspotsDataOpts = {},
|
|
@@ -242,96 +332,21 @@ export function hotspotsData(
|
|
|
242
332
|
const level = opts.level || 'file';
|
|
243
333
|
const limit = opts.limit || 10;
|
|
244
334
|
const noTests = opts.noTests || false;
|
|
245
|
-
|
|
246
335
|
const kind = level === 'directory' ? 'directory' : 'file';
|
|
247
336
|
|
|
248
|
-
const mapRow = (r: {
|
|
249
|
-
name: string;
|
|
250
|
-
kind: string;
|
|
251
|
-
lineCount: number | null;
|
|
252
|
-
symbolCount: number | null;
|
|
253
|
-
importCount: number | null;
|
|
254
|
-
exportCount: number | null;
|
|
255
|
-
fanIn: number | null;
|
|
256
|
-
fanOut: number | null;
|
|
257
|
-
cohesion: number | null;
|
|
258
|
-
fileCount: number | null;
|
|
259
|
-
}) => ({
|
|
260
|
-
name: r.name,
|
|
261
|
-
kind: r.kind,
|
|
262
|
-
lineCount: r.lineCount,
|
|
263
|
-
symbolCount: r.symbolCount,
|
|
264
|
-
importCount: r.importCount,
|
|
265
|
-
exportCount: r.exportCount,
|
|
266
|
-
fanIn: r.fanIn,
|
|
267
|
-
fanOut: r.fanOut,
|
|
268
|
-
cohesion: r.cohesion,
|
|
269
|
-
fileCount: r.fileCount,
|
|
270
|
-
density:
|
|
271
|
-
(r.fileCount ?? 0) > 0
|
|
272
|
-
? (r.symbolCount || 0) / (r.fileCount ?? 1)
|
|
273
|
-
: (r.lineCount ?? 0) > 0
|
|
274
|
-
? (r.symbolCount || 0) / (r.lineCount ?? 1)
|
|
275
|
-
: 0,
|
|
276
|
-
coupling: (r.fanIn || 0) + (r.fanOut || 0),
|
|
277
|
-
});
|
|
278
|
-
|
|
279
337
|
// ── Native fast path: single query instead of 4 eagerly prepared ──
|
|
280
338
|
if (nativeDb?.getHotspots) {
|
|
281
339
|
const rows = nativeDb.getHotspots(kind, metric, noTests, limit);
|
|
282
|
-
const hotspots = rows.map(
|
|
340
|
+
const hotspots = rows.map(mapNativeHotspotRow);
|
|
283
341
|
const base = { metric, level, limit, hotspots };
|
|
284
342
|
return paginateResult(base, 'hotspots', { limit: opts.limit, offset: opts.offset });
|
|
285
343
|
}
|
|
286
344
|
|
|
287
345
|
// ── JS fallback ───────────────────────────────────────────────────
|
|
288
346
|
const testFilter = testFilterSQL('n.name', noTests && kind === 'file');
|
|
289
|
-
|
|
290
|
-
const
|
|
291
|
-
|
|
292
|
-
SELECT n.name, n.kind, nm.line_count, nm.symbol_count, nm.import_count, nm.export_count,
|
|
293
|
-
nm.fan_in, nm.fan_out, nm.cohesion, nm.file_count
|
|
294
|
-
FROM nodes n JOIN node_metrics nm ON n.id = nm.node_id
|
|
295
|
-
WHERE n.kind = ? ${testFilter} ORDER BY nm.fan_in DESC NULLS LAST LIMIT ?`),
|
|
296
|
-
'fan-out': db.prepare(`
|
|
297
|
-
SELECT n.name, n.kind, nm.line_count, nm.symbol_count, nm.import_count, nm.export_count,
|
|
298
|
-
nm.fan_in, nm.fan_out, nm.cohesion, nm.file_count
|
|
299
|
-
FROM nodes n JOIN node_metrics nm ON n.id = nm.node_id
|
|
300
|
-
WHERE n.kind = ? ${testFilter} ORDER BY nm.fan_out DESC NULLS LAST LIMIT ?`),
|
|
301
|
-
density: db.prepare(`
|
|
302
|
-
SELECT n.name, n.kind, nm.line_count, nm.symbol_count, nm.import_count, nm.export_count,
|
|
303
|
-
nm.fan_in, nm.fan_out, nm.cohesion, nm.file_count
|
|
304
|
-
FROM nodes n JOIN node_metrics nm ON n.id = nm.node_id
|
|
305
|
-
WHERE n.kind = ? ${testFilter} ORDER BY nm.symbol_count DESC NULLS LAST LIMIT ?`),
|
|
306
|
-
coupling: db.prepare(`
|
|
307
|
-
SELECT n.name, n.kind, nm.line_count, nm.symbol_count, nm.import_count, nm.export_count,
|
|
308
|
-
nm.fan_in, nm.fan_out, nm.cohesion, nm.file_count
|
|
309
|
-
FROM nodes n JOIN node_metrics nm ON n.id = nm.node_id
|
|
310
|
-
WHERE n.kind = ? ${testFilter} ORDER BY (COALESCE(nm.fan_in, 0) + COALESCE(nm.fan_out, 0)) DESC NULLS LAST LIMIT ?`),
|
|
311
|
-
};
|
|
312
|
-
|
|
313
|
-
const stmt = HOTSPOT_QUERIES[metric] ?? HOTSPOT_QUERIES['fan-in'];
|
|
314
|
-
const rows = stmt!.all(kind, limit);
|
|
315
|
-
|
|
316
|
-
const hotspots = rows.map((r) => ({
|
|
317
|
-
name: r.name,
|
|
318
|
-
kind: r.kind,
|
|
319
|
-
lineCount: r.line_count,
|
|
320
|
-
symbolCount: r.symbol_count,
|
|
321
|
-
importCount: r.import_count,
|
|
322
|
-
exportCount: r.export_count,
|
|
323
|
-
fanIn: r.fan_in,
|
|
324
|
-
fanOut: r.fan_out,
|
|
325
|
-
cohesion: r.cohesion,
|
|
326
|
-
fileCount: r.file_count,
|
|
327
|
-
density:
|
|
328
|
-
(r.file_count ?? 0) > 0
|
|
329
|
-
? (r.symbol_count || 0) / (r.file_count ?? 1)
|
|
330
|
-
: (r.line_count ?? 0) > 0
|
|
331
|
-
? (r.symbol_count || 0) / (r.line_count ?? 1)
|
|
332
|
-
: 0,
|
|
333
|
-
coupling: (r.fan_in || 0) + (r.fan_out || 0),
|
|
334
|
-
}));
|
|
347
|
+
const stmt = db.prepare(buildHotspotQuery(metric, testFilter));
|
|
348
|
+
const rows = stmt.all(kind, limit) as HotspotRow[];
|
|
349
|
+
const hotspots = rows.map(mapJsHotspotRow);
|
|
335
350
|
|
|
336
351
|
const base = { metric, level, limit, hotspots };
|
|
337
352
|
return paginateResult(base, 'hotspots', { limit: opts.limit, offset: opts.offset });
|
|
@@ -532,6 +532,56 @@ function batchUpdateRoles(
|
|
|
532
532
|
})();
|
|
533
533
|
}
|
|
534
534
|
|
|
535
|
+
interface CallableNodeRow {
|
|
536
|
+
id: number;
|
|
537
|
+
name: string;
|
|
538
|
+
kind: string;
|
|
539
|
+
file: string;
|
|
540
|
+
fan_in: number;
|
|
541
|
+
fan_out: number;
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
/** Build the activeFiles set: files with at least one callable connected to the graph. */
|
|
545
|
+
function buildActiveFilesSet(rows: CallableNodeRow[]): Set<string> {
|
|
546
|
+
const activeFiles = new Set<string>();
|
|
547
|
+
for (const r of rows) {
|
|
548
|
+
if ((r.fan_in > 0 || r.fan_out > 0) && r.kind !== 'constant') {
|
|
549
|
+
activeFiles.add(r.file);
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
return activeFiles;
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
/** Map callable rows to classifier input objects, attaching exported/prod-fan-in/active-file metadata. */
|
|
556
|
+
function buildClassifierInput(
|
|
557
|
+
rows: CallableNodeRow[],
|
|
558
|
+
exportedIds: Set<number>,
|
|
559
|
+
prodFanInMap: Map<number, number>,
|
|
560
|
+
activeFiles: Set<string>,
|
|
561
|
+
): Array<{
|
|
562
|
+
id: string;
|
|
563
|
+
name: string;
|
|
564
|
+
kind: string;
|
|
565
|
+
file: string;
|
|
566
|
+
fanIn: number;
|
|
567
|
+
fanOut: number;
|
|
568
|
+
isExported: boolean;
|
|
569
|
+
productionFanIn: number;
|
|
570
|
+
hasActiveFileSiblings: boolean | undefined;
|
|
571
|
+
}> {
|
|
572
|
+
return rows.map((r) => ({
|
|
573
|
+
id: String(r.id),
|
|
574
|
+
name: r.name,
|
|
575
|
+
kind: r.kind,
|
|
576
|
+
file: r.file,
|
|
577
|
+
fanIn: r.fan_in,
|
|
578
|
+
fanOut: r.fan_out,
|
|
579
|
+
isExported: exportedIds.has(r.id),
|
|
580
|
+
productionFanIn: prodFanInMap.get(r.id) || 0,
|
|
581
|
+
hasActiveFileSiblings: r.kind === 'constant' ? activeFiles.has(r.file) : undefined,
|
|
582
|
+
}));
|
|
583
|
+
}
|
|
584
|
+
|
|
535
585
|
function classifyNodeRolesFull(db: BetterSqlite3Database, emptySummary: RoleSummary): RoleSummary {
|
|
536
586
|
// Leaf kinds (parameter, property) can never have callers/callees.
|
|
537
587
|
// Classify them directly as dead-leaf without the expensive fan-in/fan-out JOINs.
|
|
@@ -558,14 +608,7 @@ function classifyNodeRolesFull(db: BetterSqlite3Database, emptySummary: RoleSumm
|
|
|
558
608
|
) fo ON n.id = fo.source_id
|
|
559
609
|
WHERE n.kind NOT IN ('file', 'directory', 'parameter', 'property')`,
|
|
560
610
|
)
|
|
561
|
-
.all() as
|
|
562
|
-
id: number;
|
|
563
|
-
name: string;
|
|
564
|
-
kind: string;
|
|
565
|
-
file: string;
|
|
566
|
-
fan_in: number;
|
|
567
|
-
fan_out: number;
|
|
568
|
-
}[];
|
|
611
|
+
.all() as CallableNodeRow[];
|
|
569
612
|
|
|
570
613
|
if (rows.length === 0 && leafRows.length === 0) return emptySummary;
|
|
571
614
|
|
|
@@ -629,28 +672,9 @@ function classifyNodeRolesFull(db: BetterSqlite3Database, emptySummary: RoleSumm
|
|
|
629
672
|
prodFanInMap.set(r.target_id, r.cnt);
|
|
630
673
|
}
|
|
631
674
|
|
|
632
|
-
// Files with at least one callable (non-constant) connected to the graph.
|
|
633
|
-
// Constants in these files are likely consumed locally via identifier reference.
|
|
634
|
-
const activeFiles = new Set<string>();
|
|
635
|
-
for (const r of rows) {
|
|
636
|
-
if ((r.fan_in > 0 || r.fan_out > 0) && r.kind !== 'constant') {
|
|
637
|
-
activeFiles.add(r.file);
|
|
638
|
-
}
|
|
639
|
-
}
|
|
640
|
-
|
|
641
675
|
// Delegate classification to the pure-logic classifier
|
|
642
|
-
const
|
|
643
|
-
|
|
644
|
-
name: r.name,
|
|
645
|
-
kind: r.kind,
|
|
646
|
-
file: r.file,
|
|
647
|
-
fanIn: r.fan_in,
|
|
648
|
-
fanOut: r.fan_out,
|
|
649
|
-
isExported: exportedIds.has(r.id),
|
|
650
|
-
productionFanIn: prodFanInMap.get(r.id) || 0,
|
|
651
|
-
hasActiveFileSiblings: r.kind === 'constant' ? activeFiles.has(r.file) : undefined,
|
|
652
|
-
}));
|
|
653
|
-
|
|
676
|
+
const activeFiles = buildActiveFilesSet(rows);
|
|
677
|
+
const classifierInput = buildClassifierInput(rows, exportedIds, prodFanInMap, activeFiles);
|
|
654
678
|
const roleMap = classifyRoles(classifierInput);
|
|
655
679
|
|
|
656
680
|
const { summary, idsByRole } = buildRoleSummary(rows, leafRows, roleMap, emptySummary);
|
|
@@ -733,14 +757,7 @@ function classifyNodeRolesIncremental(
|
|
|
733
757
|
WHERE n.kind NOT IN ('file', 'directory', 'parameter', 'property')
|
|
734
758
|
AND n.file IN (${placeholders})`,
|
|
735
759
|
)
|
|
736
|
-
.all(...allAffectedFiles) as
|
|
737
|
-
id: number;
|
|
738
|
-
name: string;
|
|
739
|
-
kind: string;
|
|
740
|
-
file: string;
|
|
741
|
-
fan_in: number;
|
|
742
|
-
fan_out: number;
|
|
743
|
-
}[];
|
|
760
|
+
.all(...allAffectedFiles) as CallableNodeRow[];
|
|
744
761
|
|
|
745
762
|
if (rows.length === 0 && leafRows.length === 0) return emptySummary;
|
|
746
763
|
|
|
@@ -810,25 +827,8 @@ function classifyNodeRolesIncremental(
|
|
|
810
827
|
}
|
|
811
828
|
|
|
812
829
|
// 5. Classify affected nodes using global medians
|
|
813
|
-
const activeFiles =
|
|
814
|
-
|
|
815
|
-
if ((r.fan_in > 0 || r.fan_out > 0) && r.kind !== 'constant') {
|
|
816
|
-
activeFiles.add(r.file);
|
|
817
|
-
}
|
|
818
|
-
}
|
|
819
|
-
|
|
820
|
-
const classifierInput = rows.map((r) => ({
|
|
821
|
-
id: String(r.id),
|
|
822
|
-
name: r.name,
|
|
823
|
-
kind: r.kind,
|
|
824
|
-
file: r.file,
|
|
825
|
-
fanIn: r.fan_in,
|
|
826
|
-
fanOut: r.fan_out,
|
|
827
|
-
isExported: exportedIds.has(r.id),
|
|
828
|
-
productionFanIn: prodFanInMap.get(r.id) || 0,
|
|
829
|
-
hasActiveFileSiblings: r.kind === 'constant' ? activeFiles.has(r.file) : undefined,
|
|
830
|
-
}));
|
|
831
|
-
|
|
830
|
+
const activeFiles = buildActiveFilesSet(rows);
|
|
831
|
+
const classifierInput = buildClassifierInput(rows, exportedIds, prodFanInMap, activeFiles);
|
|
832
832
|
const roleMap = classifyRoles(classifierInput, globalMedians);
|
|
833
833
|
|
|
834
834
|
// 6. Build summary (only for affected nodes) and update only those nodes
|