@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
|
@@ -89,12 +89,74 @@ function setupNodeLookups(ctx: PipelineContext, allNodes: QueryNodeRow[]): void
|
|
|
89
89
|
|
|
90
90
|
// ── Import edges ────────────────────────────────────────────────────────
|
|
91
91
|
|
|
92
|
+
/** Pick the edge kind for an import statement based on its modifiers. */
|
|
93
|
+
function importEdgeKind(imp: Import): string {
|
|
94
|
+
if (imp.reexport) return 'reexports';
|
|
95
|
+
if (imp.typeOnly) return 'imports-type';
|
|
96
|
+
if (imp.dynamicImport) return 'dynamic-imports';
|
|
97
|
+
return 'imports';
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* For a `import type` statement, emit symbol-level `imports-type` edges so
|
|
102
|
+
* the target symbols get fan-in credit and aren't classified as dead code.
|
|
103
|
+
*/
|
|
104
|
+
function emitTypeOnlySymbolEdges(
|
|
105
|
+
ctx: PipelineContext,
|
|
106
|
+
imp: Import,
|
|
107
|
+
resolvedPath: string,
|
|
108
|
+
fileNodeId: number,
|
|
109
|
+
allEdgeRows: EdgeRowTuple[],
|
|
110
|
+
): void {
|
|
111
|
+
if (!ctx.nodesByNameAndFile) return;
|
|
112
|
+
for (const name of imp.names) {
|
|
113
|
+
const cleanName = name.replace(/^\*\s+as\s+/, '');
|
|
114
|
+
let targetFile = resolvedPath;
|
|
115
|
+
if (isBarrelFile(ctx, resolvedPath)) {
|
|
116
|
+
const actual = resolveBarrelExport(ctx, resolvedPath, cleanName);
|
|
117
|
+
if (actual) targetFile = actual;
|
|
118
|
+
}
|
|
119
|
+
const candidates = ctx.nodesByNameAndFile.get(`${cleanName}|${targetFile}`);
|
|
120
|
+
if (candidates && candidates.length > 0) {
|
|
121
|
+
allEdgeRows.push([fileNodeId, candidates[0]!.id, 'imports-type', 1.0, 0]);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Process a single import statement and emit all resulting edges (file→file,
|
|
128
|
+
* type-only symbol-level, and barrel re-export targets).
|
|
129
|
+
*/
|
|
130
|
+
function emitEdgesForImport(
|
|
131
|
+
ctx: PipelineContext,
|
|
132
|
+
imp: Import,
|
|
133
|
+
fileNodeId: number,
|
|
134
|
+
relPath: string,
|
|
135
|
+
getNodeIdStmt: NodeIdStmt,
|
|
136
|
+
allEdgeRows: EdgeRowTuple[],
|
|
137
|
+
): void {
|
|
138
|
+
const resolvedPath = getResolved(ctx, path.join(ctx.rootDir, relPath), imp.source);
|
|
139
|
+
const targetRow = getNodeIdStmt.get(resolvedPath, 'file', resolvedPath, 0);
|
|
140
|
+
if (!targetRow) return;
|
|
141
|
+
|
|
142
|
+
const edgeKind = importEdgeKind(imp);
|
|
143
|
+
allEdgeRows.push([fileNodeId, targetRow.id, edgeKind, 1.0, 0]);
|
|
144
|
+
|
|
145
|
+
if (imp.typeOnly) {
|
|
146
|
+
emitTypeOnlySymbolEdges(ctx, imp, resolvedPath, fileNodeId, allEdgeRows);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
if (!imp.reexport && isBarrelFile(ctx, resolvedPath)) {
|
|
150
|
+
buildBarrelEdges(ctx, imp, resolvedPath, fileNodeId, edgeKind, getNodeIdStmt, allEdgeRows);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
92
154
|
function buildImportEdges(
|
|
93
155
|
ctx: PipelineContext,
|
|
94
156
|
getNodeIdStmt: NodeIdStmt,
|
|
95
157
|
allEdgeRows: EdgeRowTuple[],
|
|
96
158
|
): void {
|
|
97
|
-
const { fileSymbols, barrelOnlyFiles
|
|
159
|
+
const { fileSymbols, barrelOnlyFiles } = ctx;
|
|
98
160
|
|
|
99
161
|
for (const [relPath, symbols] of fileSymbols) {
|
|
100
162
|
const isBarrelOnly = barrelOnlyFiles.has(relPath);
|
|
@@ -105,40 +167,7 @@ function buildImportEdges(
|
|
|
105
167
|
for (const imp of symbols.imports) {
|
|
106
168
|
// Barrel-only files: only emit reexport edges, skip regular imports
|
|
107
169
|
if (isBarrelOnly && !imp.reexport) continue;
|
|
108
|
-
|
|
109
|
-
const resolvedPath = getResolved(ctx, path.join(rootDir, relPath), imp.source);
|
|
110
|
-
const targetRow = getNodeIdStmt.get(resolvedPath, 'file', resolvedPath, 0);
|
|
111
|
-
if (!targetRow) continue;
|
|
112
|
-
|
|
113
|
-
const edgeKind = imp.reexport
|
|
114
|
-
? 'reexports'
|
|
115
|
-
: imp.typeOnly
|
|
116
|
-
? 'imports-type'
|
|
117
|
-
: imp.dynamicImport
|
|
118
|
-
? 'dynamic-imports'
|
|
119
|
-
: 'imports';
|
|
120
|
-
allEdgeRows.push([fileNodeId, targetRow.id, edgeKind, 1.0, 0]);
|
|
121
|
-
|
|
122
|
-
// Type-only imports: create symbol-level edges so the target symbols
|
|
123
|
-
// get fan-in credit and aren't falsely classified as dead code.
|
|
124
|
-
if (imp.typeOnly && ctx.nodesByNameAndFile) {
|
|
125
|
-
for (const name of imp.names) {
|
|
126
|
-
const cleanName = name.replace(/^\*\s+as\s+/, '');
|
|
127
|
-
let targetFile = resolvedPath;
|
|
128
|
-
if (isBarrelFile(ctx, resolvedPath)) {
|
|
129
|
-
const actual = resolveBarrelExport(ctx, resolvedPath, cleanName);
|
|
130
|
-
if (actual) targetFile = actual;
|
|
131
|
-
}
|
|
132
|
-
const candidates = ctx.nodesByNameAndFile.get(`${cleanName}|${targetFile}`);
|
|
133
|
-
if (candidates && candidates.length > 0) {
|
|
134
|
-
allEdgeRows.push([fileNodeId, candidates[0]!.id, 'imports-type', 1.0, 0]);
|
|
135
|
-
}
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
if (!imp.reexport && isBarrelFile(ctx, resolvedPath)) {
|
|
140
|
-
buildBarrelEdges(ctx, imp, resolvedPath, fileNodeId, edgeKind, getNodeIdStmt, allEdgeRows);
|
|
141
|
-
}
|
|
170
|
+
emitEdgesForImport(ctx, imp, fileNodeId, relPath, getNodeIdStmt, allEdgeRows);
|
|
142
171
|
}
|
|
143
172
|
}
|
|
144
173
|
}
|
|
@@ -174,83 +203,98 @@ function buildBarrelEdges(
|
|
|
174
203
|
|
|
175
204
|
// ── Import edges (native engine) ────────────────────────────────────────
|
|
176
205
|
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
206
|
+
/** Native FFI input shape for a single import statement. */
|
|
207
|
+
interface NativeImportInfo {
|
|
208
|
+
source: string;
|
|
209
|
+
names: string[];
|
|
210
|
+
reexport: boolean;
|
|
211
|
+
typeOnly: boolean;
|
|
212
|
+
dynamicImport: boolean;
|
|
213
|
+
wildcardReexport: boolean;
|
|
214
|
+
}
|
|
184
215
|
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
216
|
+
/** Native FFI input shape for a single file. */
|
|
217
|
+
interface NativeFileInput {
|
|
218
|
+
file: string;
|
|
219
|
+
fileNodeId: number;
|
|
220
|
+
isBarrelOnly: boolean;
|
|
221
|
+
imports: NativeImportInfo[];
|
|
222
|
+
definitionNames: string[];
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/** Native FFI input shape for re-exports of a single file. */
|
|
226
|
+
interface NativeReexportInput {
|
|
227
|
+
file: string;
|
|
228
|
+
reexports: Array<{ source: string; names: string[]; wildcardReexport: boolean }>;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/** Lazily-resolving cache of file-node rows for the native input arrays. */
|
|
232
|
+
interface FileNodeIdRegistry {
|
|
233
|
+
ids: Array<{ file: string; nodeId: number }>;
|
|
234
|
+
add(relPath: string): { id: number } | undefined;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
function createFileNodeIdRegistry(getNodeIdStmt: NodeIdStmt): FileNodeIdRegistry {
|
|
238
|
+
const ids: Array<{ file: string; nodeId: number }> = [];
|
|
239
|
+
const seen = new Set<string>();
|
|
240
|
+
const cache = new Map<string, { id: number }>();
|
|
241
|
+
return {
|
|
242
|
+
ids,
|
|
243
|
+
add(relPath: string) {
|
|
244
|
+
if (seen.has(relPath)) return cache.get(relPath);
|
|
245
|
+
const row = getNodeIdStmt.get(relPath, 'file', relPath, 0);
|
|
246
|
+
if (row) {
|
|
247
|
+
seen.add(relPath);
|
|
248
|
+
ids.push({ file: relPath, nodeId: row.id });
|
|
249
|
+
cache.set(relPath, row);
|
|
250
|
+
}
|
|
251
|
+
return row;
|
|
252
|
+
},
|
|
214
253
|
};
|
|
215
|
-
|
|
254
|
+
}
|
|
216
255
|
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
256
|
+
function toNativeImportInfo(imp: Import): NativeImportInfo {
|
|
257
|
+
return {
|
|
258
|
+
source: imp.source,
|
|
259
|
+
names: imp.names,
|
|
260
|
+
reexport: !!imp.reexport,
|
|
261
|
+
typeOnly: !!imp.typeOnly,
|
|
262
|
+
dynamicImport: !!imp.dynamicImport,
|
|
263
|
+
wildcardReexport: !!imp.wildcardReexport,
|
|
264
|
+
};
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
/**
|
|
268
|
+
* Pre-resolve every import for the given files, registering each resolved
|
|
269
|
+
* target with the registry so the native side has full node-id coverage.
|
|
270
|
+
*
|
|
271
|
+
* Resolved-import keys use forward-slash-normalized rootDir + "/" + relPath to
|
|
272
|
+
* match the Rust lookup format. On Windows, rootDir has backslashes but Rust
|
|
273
|
+
* normalizes them — the JS side must do the same or every key lookup misses
|
|
274
|
+
* (#750).
|
|
275
|
+
*/
|
|
276
|
+
function buildNativeFileInputs(
|
|
277
|
+
ctx: PipelineContext,
|
|
278
|
+
registry: FileNodeIdRegistry,
|
|
279
|
+
): {
|
|
280
|
+
files: NativeFileInput[];
|
|
281
|
+
resolvedImports: Array<{ key: string; resolvedPath: string }>;
|
|
282
|
+
} {
|
|
283
|
+
const { fileSymbols, barrelOnlyFiles, rootDir } = ctx;
|
|
223
284
|
const fwdRootDir = rootDir.replace(/\\/g, '/');
|
|
285
|
+
const files: NativeFileInput[] = [];
|
|
286
|
+
const resolvedImports: Array<{ key: string; resolvedPath: string }> = [];
|
|
224
287
|
|
|
225
288
|
for (const [relPath, symbols] of fileSymbols) {
|
|
226
|
-
const fileNodeRow =
|
|
289
|
+
const fileNodeRow = registry.add(relPath);
|
|
227
290
|
if (!fileNodeRow) continue;
|
|
228
291
|
|
|
229
|
-
const importInfos:
|
|
230
|
-
source: string;
|
|
231
|
-
names: string[];
|
|
232
|
-
reexport: boolean;
|
|
233
|
-
typeOnly: boolean;
|
|
234
|
-
dynamicImport: boolean;
|
|
235
|
-
wildcardReexport: boolean;
|
|
236
|
-
}> = [];
|
|
237
|
-
|
|
292
|
+
const importInfos: NativeImportInfo[] = [];
|
|
238
293
|
for (const imp of symbols.imports) {
|
|
239
|
-
// Pre-resolve and register target file node
|
|
240
294
|
const resolvedPath = getResolved(ctx, path.join(rootDir, relPath), imp.source);
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
// Key matches Rust's format!("{}/{}", root_dir.replace('\\', "/"), file_input.file)
|
|
295
|
+
registry.add(resolvedPath);
|
|
244
296
|
resolvedImports.push({ key: `${fwdRootDir}/${relPath}|${imp.source}`, resolvedPath });
|
|
245
|
-
|
|
246
|
-
importInfos.push({
|
|
247
|
-
source: imp.source,
|
|
248
|
-
names: imp.names,
|
|
249
|
-
reexport: !!imp.reexport,
|
|
250
|
-
typeOnly: !!imp.typeOnly,
|
|
251
|
-
dynamicImport: !!imp.dynamicImport,
|
|
252
|
-
wildcardReexport: !!imp.wildcardReexport,
|
|
253
|
-
});
|
|
297
|
+
importInfos.push(toNativeImportInfo(imp));
|
|
254
298
|
}
|
|
255
299
|
|
|
256
300
|
files.push({
|
|
@@ -261,61 +305,75 @@ function buildImportEdgesNative(
|
|
|
261
305
|
definitionNames: symbols.definitions.map((d) => d.name),
|
|
262
306
|
});
|
|
263
307
|
}
|
|
308
|
+
return { files, resolvedImports };
|
|
309
|
+
}
|
|
264
310
|
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
}));
|
|
283
|
-
fileReexports.push({ file, reexports });
|
|
311
|
+
/** Flatten `ctx.reexportMap` into the array shape the native side expects. */
|
|
312
|
+
function buildNativeReexports(
|
|
313
|
+
ctx: PipelineContext,
|
|
314
|
+
registry: FileNodeIdRegistry,
|
|
315
|
+
): NativeReexportInput[] {
|
|
316
|
+
const fileReexports: NativeReexportInput[] = [];
|
|
317
|
+
if (!ctx.reexportMap) return fileReexports;
|
|
318
|
+
|
|
319
|
+
for (const [file, entries] of ctx.reexportMap) {
|
|
320
|
+
const reexports = (
|
|
321
|
+
entries as Array<{ source: string; names: string[]; wildcardReexport: boolean }>
|
|
322
|
+
).map((re) => ({
|
|
323
|
+
source: re.source,
|
|
324
|
+
names: re.names,
|
|
325
|
+
wildcardReexport: !!re.wildcardReexport,
|
|
326
|
+
}));
|
|
327
|
+
fileReexports.push({ file, reexports });
|
|
284
328
|
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
addFileNodeId(re.source);
|
|
288
|
-
}
|
|
329
|
+
for (const re of reexports) {
|
|
330
|
+
registry.add(re.source);
|
|
289
331
|
}
|
|
290
332
|
}
|
|
333
|
+
return fileReexports;
|
|
334
|
+
}
|
|
291
335
|
|
|
292
|
-
|
|
336
|
+
function collectBarrelFiles(ctx: PipelineContext): string[] {
|
|
293
337
|
const barrelFiles: string[] = [];
|
|
294
|
-
for (const [relPath] of fileSymbols) {
|
|
295
|
-
if (isBarrelFile(ctx, relPath))
|
|
296
|
-
barrelFiles.push(relPath);
|
|
297
|
-
}
|
|
338
|
+
for (const [relPath] of ctx.fileSymbols) {
|
|
339
|
+
if (isBarrelFile(ctx, relPath)) barrelFiles.push(relPath);
|
|
298
340
|
}
|
|
341
|
+
return barrelFiles;
|
|
342
|
+
}
|
|
299
343
|
|
|
300
|
-
|
|
344
|
+
function collectSymbolNodes(
|
|
345
|
+
ctx: PipelineContext,
|
|
346
|
+
): Array<{ name: string; file: string; nodeId: number }> {
|
|
301
347
|
const symbolNodes: Array<{ name: string; file: string; nodeId: number }> = [];
|
|
302
|
-
if (ctx.nodesByNameAndFile)
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
}
|
|
308
|
-
}
|
|
348
|
+
if (!ctx.nodesByNameAndFile) return symbolNodes;
|
|
349
|
+
for (const [key, nodes] of ctx.nodesByNameAndFile) {
|
|
350
|
+
if (nodes.length === 0) continue;
|
|
351
|
+
const [name, file] = key.split('|');
|
|
352
|
+
symbolNodes.push({ name: name!, file: file!, nodeId: nodes[0]!.id });
|
|
309
353
|
}
|
|
354
|
+
return symbolNodes;
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
function buildImportEdgesNative(
|
|
358
|
+
ctx: PipelineContext,
|
|
359
|
+
getNodeIdStmt: NodeIdStmt,
|
|
360
|
+
allEdgeRows: EdgeRowTuple[],
|
|
361
|
+
native: NativeAddon,
|
|
362
|
+
): void {
|
|
363
|
+
const registry = createFileNodeIdRegistry(getNodeIdStmt);
|
|
364
|
+
|
|
365
|
+
const { files, resolvedImports } = buildNativeFileInputs(ctx, registry);
|
|
366
|
+
const fileReexports = buildNativeReexports(ctx, registry);
|
|
367
|
+
const barrelFiles = collectBarrelFiles(ctx);
|
|
368
|
+
const symbolNodes = collectSymbolNodes(ctx);
|
|
310
369
|
|
|
311
|
-
// 7. Call native
|
|
312
370
|
const nativeEdges = native.buildImportEdges!(
|
|
313
371
|
files,
|
|
314
372
|
resolvedImports,
|
|
315
373
|
fileReexports,
|
|
316
|
-
|
|
374
|
+
registry.ids,
|
|
317
375
|
barrelFiles,
|
|
318
|
-
rootDir,
|
|
376
|
+
ctx.rootDir,
|
|
319
377
|
symbolNodes,
|
|
320
378
|
) as NativeEdge[];
|
|
321
379
|
|
|
@@ -770,9 +828,11 @@ function reconnectReverseDepEdges(ctx: PipelineContext): void {
|
|
|
770
828
|
* their import targets. Falls back to loading ALL nodes for full builds or
|
|
771
829
|
* larger incremental changes.
|
|
772
830
|
*/
|
|
831
|
+
const NODE_KIND_FILTER_SQL = `kind IN ('function','method','class','interface','struct','type','module','enum','trait','record','constant')`;
|
|
832
|
+
|
|
773
833
|
function loadNodes(ctx: PipelineContext): { rows: QueryNodeRow[]; scoped: boolean } {
|
|
774
834
|
const { db, fileSymbols, isFullBuild, batchResolved } = ctx;
|
|
775
|
-
const nodeKindFilter =
|
|
835
|
+
const nodeKindFilter = NODE_KIND_FILTER_SQL;
|
|
776
836
|
|
|
777
837
|
// Gate: only scope for small incremental on large codebases
|
|
778
838
|
if (!isFullBuild && fileSymbols.size <= ctx.config.build.smallFilesThreshold) {
|
|
@@ -816,8 +876,13 @@ function loadNodes(ctx: PipelineContext): { rows: QueryNodeRow[]; scoped: boolea
|
|
|
816
876
|
function addLazyFallback(ctx: PipelineContext, scopedLoad: boolean): void {
|
|
817
877
|
if (!scopedLoad) return;
|
|
818
878
|
const { db } = ctx;
|
|
879
|
+
// Match the upfront kind filter exactly. Using `kind != 'file'` here lets
|
|
880
|
+
// parameters, properties, and other non-definition kinds leak into call
|
|
881
|
+
// resolution, producing bogus call edges like `parser.ts → <a parameter
|
|
882
|
+
// with the same name>` (#1174 follow-up). Calls only ever target the
|
|
883
|
+
// definition kinds, so the fallback's filter must agree with `loadNodes`.
|
|
819
884
|
const fallbackStmt = db.prepare(
|
|
820
|
-
`SELECT id, name, kind, file, line FROM nodes WHERE name = ? AND
|
|
885
|
+
`SELECT id, name, kind, file, line FROM nodes WHERE name = ? AND ${NODE_KIND_FILTER_SQL}`,
|
|
821
886
|
);
|
|
822
887
|
const originalGet = ctx.nodesByName.get.bind(ctx.nodesByName);
|
|
823
888
|
ctx.nodesByName.get = (name: string) => {
|
|
@@ -11,87 +11,104 @@ import type { ExtractorOutput } from '../../../../types.js';
|
|
|
11
11
|
import type { PipelineContext } from '../context.js';
|
|
12
12
|
import { readFileSafe } from '../helpers.js';
|
|
13
13
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
// Build line count map (prefer cached _lineCount from parser)
|
|
14
|
+
/** Populate `ctx.lineCountMap` from cached parser results, falling back to disk. */
|
|
15
|
+
function populateLineCountMap(ctx: PipelineContext): void {
|
|
16
|
+
const { fileSymbols, rootDir } = ctx;
|
|
18
17
|
ctx.lineCountMap = new Map();
|
|
19
18
|
for (const [relPath, symbols] of fileSymbols) {
|
|
20
19
|
const lineCount =
|
|
21
20
|
(symbols as ExtractorOutput & { lineCount?: number }).lineCount ?? symbols._lineCount;
|
|
22
21
|
if (lineCount) {
|
|
23
22
|
ctx.lineCountMap.set(relPath, lineCount);
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
23
|
+
continue;
|
|
24
|
+
}
|
|
25
|
+
const absPath = path.join(rootDir, relPath);
|
|
26
|
+
try {
|
|
27
|
+
const content = readFileSafe(absPath);
|
|
28
|
+
ctx.lineCountMap.set(relPath, content.split('\n').length);
|
|
29
|
+
} catch {
|
|
30
|
+
ctx.lineCountMap.set(relPath, 0);
|
|
32
31
|
}
|
|
33
32
|
}
|
|
33
|
+
}
|
|
34
34
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
// For small incremental builds on large codebases, use a fast path that
|
|
38
|
-
// updates only the changed files' metrics via targeted SQL instead of
|
|
39
|
-
// loading ALL definitions from DB (~8ms) and recomputing ALL metrics (~15ms).
|
|
40
|
-
// Gate: ≤smallFilesThreshold changed files AND significantly more existing files (>20) to
|
|
41
|
-
// avoid triggering on small test fixtures where directory metrics matter.
|
|
35
|
+
/** Count file-kind nodes already in the DB, preferring the native connection. */
|
|
36
|
+
function countExistingFiles(ctx: PipelineContext): number {
|
|
42
37
|
const useNativeReads = ctx.engineName === 'native' && !!ctx.nativeDb;
|
|
43
|
-
const
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
).c
|
|
51
|
-
: 0;
|
|
52
|
-
const useSmallIncrementalFastPath =
|
|
53
|
-
!isFullBuild &&
|
|
54
|
-
changedFileList != null &&
|
|
55
|
-
changedFileList.length <= ctx.config.build.smallFilesThreshold &&
|
|
56
|
-
existingFileCount > 20;
|
|
57
|
-
|
|
58
|
-
if (!isFullBuild && !useSmallIncrementalFastPath) {
|
|
59
|
-
// Medium/large incremental: load unchanged files from DB for complete structure
|
|
60
|
-
loadUnchangedFilesFromDb(ctx);
|
|
61
|
-
}
|
|
38
|
+
const row = (
|
|
39
|
+
useNativeReads
|
|
40
|
+
? ctx.nativeDb!.queryGet("SELECT COUNT(*) as c FROM nodes WHERE kind = 'file'", [])
|
|
41
|
+
: ctx.db.prepare("SELECT COUNT(*) as c FROM nodes WHERE kind = 'file'").get()
|
|
42
|
+
) as { c: number };
|
|
43
|
+
return row.c;
|
|
44
|
+
}
|
|
62
45
|
|
|
63
|
-
|
|
64
|
-
|
|
46
|
+
/**
|
|
47
|
+
* Build directory structure + metrics. Chooses between the fast incremental
|
|
48
|
+
* path (a handful of files changed on a large codebase) and the full path
|
|
49
|
+
* (delegated to `features/structure`).
|
|
50
|
+
*/
|
|
51
|
+
async function buildDirectoryStructure(
|
|
52
|
+
ctx: PipelineContext,
|
|
53
|
+
changedFileList: string[] | null,
|
|
54
|
+
useSmallIncrementalFastPath: boolean,
|
|
55
|
+
): Promise<void> {
|
|
65
56
|
if (useSmallIncrementalFastPath) {
|
|
66
57
|
updateChangedFileMetrics(ctx, changedFileList!);
|
|
67
|
-
|
|
68
|
-
const relDirs = new Set<string>();
|
|
69
|
-
for (const absDir of discoveredDirs) {
|
|
70
|
-
relDirs.add(normalizePath(path.relative(rootDir, absDir)));
|
|
71
|
-
}
|
|
72
|
-
try {
|
|
73
|
-
const { buildStructure: buildStructureFn } = (await import(
|
|
74
|
-
'../../../../features/structure.js'
|
|
75
|
-
)) as {
|
|
76
|
-
buildStructure: (
|
|
77
|
-
db: PipelineContext['db'],
|
|
78
|
-
fileSymbols: Map<string, ExtractorOutput>,
|
|
79
|
-
rootDir: string,
|
|
80
|
-
lineCountMap: Map<string, number>,
|
|
81
|
-
directories: Set<string>,
|
|
82
|
-
changedFiles: string[] | null,
|
|
83
|
-
) => void;
|
|
84
|
-
};
|
|
85
|
-
const changedFilePaths = isFullBuild ? null : [...allSymbols.keys()];
|
|
86
|
-
buildStructureFn(db, fileSymbols, rootDir, ctx.lineCountMap, relDirs, changedFilePaths);
|
|
87
|
-
} catch (err) {
|
|
88
|
-
debug(`Structure analysis failed: ${(err as Error).message}`);
|
|
89
|
-
}
|
|
58
|
+
return;
|
|
90
59
|
}
|
|
91
|
-
ctx.timing.structureMs = performance.now() - t0;
|
|
92
60
|
|
|
93
|
-
|
|
94
|
-
const
|
|
61
|
+
const { db, fileSymbols, rootDir, discoveredDirs, allSymbols, isFullBuild } = ctx;
|
|
62
|
+
const relDirs = new Set<string>();
|
|
63
|
+
for (const absDir of discoveredDirs) {
|
|
64
|
+
relDirs.add(normalizePath(path.relative(rootDir, absDir)));
|
|
65
|
+
}
|
|
66
|
+
try {
|
|
67
|
+
const { buildStructure: buildStructureFn } = (await import(
|
|
68
|
+
'../../../../features/structure.js'
|
|
69
|
+
)) as {
|
|
70
|
+
buildStructure: (
|
|
71
|
+
db: PipelineContext['db'],
|
|
72
|
+
fileSymbols: Map<string, ExtractorOutput>,
|
|
73
|
+
rootDir: string,
|
|
74
|
+
lineCountMap: Map<string, number>,
|
|
75
|
+
directories: Set<string>,
|
|
76
|
+
changedFiles: string[] | null,
|
|
77
|
+
) => void;
|
|
78
|
+
};
|
|
79
|
+
const changedFilePaths = isFullBuild ? null : [...allSymbols.keys()];
|
|
80
|
+
buildStructureFn(db, fileSymbols, rootDir, ctx.lineCountMap, relDirs, changedFilePaths);
|
|
81
|
+
} catch (err) {
|
|
82
|
+
debug(`Structure analysis failed: ${(err as Error).message}`);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/** Convert a `NativeDatabase.classifyRoles*` result into the JS summary shape. */
|
|
87
|
+
function nativeRoleSummaryToRecord(
|
|
88
|
+
nativeResult: NonNullable<
|
|
89
|
+
ReturnType<NonNullable<PipelineContext['nativeDb']>['classifyRolesFull']>
|
|
90
|
+
>,
|
|
91
|
+
): Record<string, number> {
|
|
92
|
+
return {
|
|
93
|
+
entry: nativeResult.entry,
|
|
94
|
+
core: nativeResult.core,
|
|
95
|
+
utility: nativeResult.utility,
|
|
96
|
+
adapter: nativeResult.adapter,
|
|
97
|
+
dead: nativeResult.dead,
|
|
98
|
+
'dead-leaf': nativeResult.deadLeaf,
|
|
99
|
+
'dead-entry': nativeResult.deadEntry,
|
|
100
|
+
'dead-ffi': nativeResult.deadFfi,
|
|
101
|
+
'dead-unresolved': nativeResult.deadUnresolved,
|
|
102
|
+
'test-only': nativeResult.testOnly,
|
|
103
|
+
leaf: nativeResult.leaf,
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
async function classifyRoles(
|
|
108
|
+
ctx: PipelineContext,
|
|
109
|
+
changedFileList: string[] | null,
|
|
110
|
+
): Promise<void> {
|
|
111
|
+
const useNativeReads = ctx.engineName === 'native' && !!ctx.nativeDb;
|
|
95
112
|
try {
|
|
96
113
|
let roleSummary: Record<string, number> | null = null;
|
|
97
114
|
|
|
@@ -103,24 +120,9 @@ export async function buildStructure(ctx: PipelineContext): Promise<void> {
|
|
|
103
120
|
changedFileList && changedFileList.length > 0
|
|
104
121
|
? ctx.nativeDb.classifyRolesIncremental(changedFileList)
|
|
105
122
|
: ctx.nativeDb.classifyRolesFull();
|
|
106
|
-
if (nativeResult)
|
|
107
|
-
roleSummary = {
|
|
108
|
-
entry: nativeResult.entry,
|
|
109
|
-
core: nativeResult.core,
|
|
110
|
-
utility: nativeResult.utility,
|
|
111
|
-
adapter: nativeResult.adapter,
|
|
112
|
-
dead: nativeResult.dead,
|
|
113
|
-
'dead-leaf': nativeResult.deadLeaf,
|
|
114
|
-
'dead-entry': nativeResult.deadEntry,
|
|
115
|
-
'dead-ffi': nativeResult.deadFfi,
|
|
116
|
-
'dead-unresolved': nativeResult.deadUnresolved,
|
|
117
|
-
'test-only': nativeResult.testOnly,
|
|
118
|
-
leaf: nativeResult.leaf,
|
|
119
|
-
};
|
|
120
|
-
}
|
|
123
|
+
if (nativeResult) roleSummary = nativeRoleSummaryToRecord(nativeResult);
|
|
121
124
|
}
|
|
122
125
|
|
|
123
|
-
// Fall back to JS path
|
|
124
126
|
if (!roleSummary) {
|
|
125
127
|
const { classifyNodeRoles } = (await import('../../../../features/structure.js')) as {
|
|
126
128
|
classifyNodeRoles: (
|
|
@@ -141,6 +143,37 @@ export async function buildStructure(ctx: PipelineContext): Promise<void> {
|
|
|
141
143
|
} catch (err) {
|
|
142
144
|
debug(`Role classification failed: ${(err as Error).message}`);
|
|
143
145
|
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
export async function buildStructure(ctx: PipelineContext): Promise<void> {
|
|
149
|
+
const { allSymbols, isFullBuild } = ctx;
|
|
150
|
+
|
|
151
|
+
populateLineCountMap(ctx);
|
|
152
|
+
|
|
153
|
+
const changedFileList = isFullBuild ? null : [...allSymbols.keys()];
|
|
154
|
+
|
|
155
|
+
// For small incremental builds on large codebases, use a fast path that
|
|
156
|
+
// updates only the changed files' metrics via targeted SQL instead of
|
|
157
|
+
// loading ALL definitions from DB (~8ms) and recomputing ALL metrics (~15ms).
|
|
158
|
+
// Gate: ≤smallFilesThreshold changed files AND significantly more existing files (>20) to
|
|
159
|
+
// avoid triggering on small test fixtures where directory metrics matter.
|
|
160
|
+
const existingFileCount = !isFullBuild ? countExistingFiles(ctx) : 0;
|
|
161
|
+
const useSmallIncrementalFastPath =
|
|
162
|
+
!isFullBuild &&
|
|
163
|
+
changedFileList != null &&
|
|
164
|
+
changedFileList.length <= ctx.config.build.smallFilesThreshold &&
|
|
165
|
+
existingFileCount > 20;
|
|
166
|
+
|
|
167
|
+
if (!isFullBuild && !useSmallIncrementalFastPath) {
|
|
168
|
+
loadUnchangedFilesFromDb(ctx);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
const t0 = performance.now();
|
|
172
|
+
await buildDirectoryStructure(ctx, changedFileList, useSmallIncrementalFastPath);
|
|
173
|
+
ctx.timing.structureMs = performance.now() - t0;
|
|
174
|
+
|
|
175
|
+
const t1 = performance.now();
|
|
176
|
+
await classifyRoles(ctx, changedFileList);
|
|
144
177
|
ctx.timing.rolesMs = performance.now() - t1;
|
|
145
178
|
}
|
|
146
179
|
|