@optave/codegraph 3.4.1 → 3.6.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 +50 -28
- package/dist/ast-analysis/engine.d.ts.map +1 -1
- package/dist/ast-analysis/engine.js +119 -127
- package/dist/ast-analysis/engine.js.map +1 -1
- package/dist/ast-analysis/rules/javascript.d.ts.map +1 -1
- package/dist/ast-analysis/rules/javascript.js +1 -0
- package/dist/ast-analysis/rules/javascript.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 +116 -35
- package/dist/ast-analysis/visitors/ast-store-visitor.js.map +1 -1
- package/dist/ast-analysis/visitors/complexity-visitor.d.ts.map +1 -1
- package/dist/ast-analysis/visitors/complexity-visitor.js +11 -13
- package/dist/ast-analysis/visitors/complexity-visitor.js.map +1 -1
- package/dist/db/better-sqlite3.d.ts +3 -0
- package/dist/db/better-sqlite3.d.ts.map +1 -0
- package/dist/db/better-sqlite3.js +19 -0
- package/dist/db/better-sqlite3.js.map +1 -0
- package/dist/db/connection.d.ts +25 -4
- package/dist/db/connection.d.ts.map +1 -1
- package/dist/db/connection.js +125 -23
- package/dist/db/connection.js.map +1 -1
- package/dist/db/index.d.ts +2 -2
- package/dist/db/index.d.ts.map +1 -1
- package/dist/db/index.js +1 -1
- package/dist/db/index.js.map +1 -1
- package/dist/db/migrations.d.ts.map +1 -1
- package/dist/db/migrations.js +40 -32
- package/dist/db/migrations.js.map +1 -1
- package/dist/db/query-builder.d.ts +5 -5
- package/dist/db/query-builder.d.ts.map +1 -1
- package/dist/db/query-builder.js +20 -4
- package/dist/db/query-builder.js.map +1 -1
- package/dist/db/repository/index.d.ts +1 -0
- package/dist/db/repository/index.d.ts.map +1 -1
- package/dist/db/repository/index.js +1 -0
- package/dist/db/repository/index.js.map +1 -1
- package/dist/db/repository/native-repository.d.ts +58 -0
- package/dist/db/repository/native-repository.d.ts.map +1 -0
- package/dist/db/repository/native-repository.js +261 -0
- package/dist/db/repository/native-repository.js.map +1 -0
- package/dist/db/repository/nodes.d.ts +4 -4
- package/dist/db/repository/nodes.d.ts.map +1 -1
- package/dist/db/repository/nodes.js +6 -6
- package/dist/db/repository/nodes.js.map +1 -1
- package/dist/domain/analysis/context.d.ts.map +1 -1
- package/dist/domain/analysis/context.js +51 -66
- package/dist/domain/analysis/context.js.map +1 -1
- package/dist/domain/analysis/dependencies.d.ts.map +1 -1
- package/dist/domain/analysis/dependencies.js +62 -70
- package/dist/domain/analysis/dependencies.js.map +1 -1
- package/dist/domain/analysis/diff-impact.d.ts +9 -7
- package/dist/domain/analysis/diff-impact.d.ts.map +1 -1
- package/dist/domain/analysis/exports.d.ts.map +1 -1
- package/dist/domain/analysis/exports.js +29 -33
- package/dist/domain/analysis/exports.js.map +1 -1
- package/dist/domain/analysis/fn-impact.d.ts +15 -17
- package/dist/domain/analysis/fn-impact.d.ts.map +1 -1
- package/dist/domain/analysis/fn-impact.js +35 -65
- 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 +91 -6
- package/dist/domain/analysis/module-map.js.map +1 -1
- package/dist/domain/analysis/query-helpers.d.ts +20 -0
- package/dist/domain/analysis/query-helpers.d.ts.map +1 -0
- package/dist/domain/analysis/query-helpers.js +27 -0
- package/dist/domain/analysis/query-helpers.js.map +1 -0
- package/dist/domain/graph/builder/context.d.ts +2 -1
- package/dist/domain/graph/builder/context.d.ts.map +1 -1
- package/dist/domain/graph/builder/context.js +1 -0
- package/dist/domain/graph/builder/context.js.map +1 -1
- package/dist/domain/graph/builder/helpers.d.ts.map +1 -1
- package/dist/domain/graph/builder/helpers.js +15 -9
- package/dist/domain/graph/builder/helpers.js.map +1 -1
- package/dist/domain/graph/builder/incremental.d.ts.map +1 -1
- package/dist/domain/graph/builder/incremental.js +3 -2
- package/dist/domain/graph/builder/incremental.js.map +1 -1
- package/dist/domain/graph/builder/pipeline.d.ts.map +1 -1
- package/dist/domain/graph/builder/pipeline.js +95 -7
- 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 +101 -57
- 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 +33 -3
- package/dist/domain/graph/builder/stages/build-structure.js.map +1 -1
- package/dist/domain/graph/builder/stages/collect-files.d.ts.map +1 -1
- package/dist/domain/graph/builder/stages/collect-files.js +70 -6
- package/dist/domain/graph/builder/stages/collect-files.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 +36 -14
- 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 +130 -88
- package/dist/domain/graph/builder/stages/finalize.js.map +1 -1
- package/dist/domain/graph/builder/stages/insert-nodes.d.ts.map +1 -1
- package/dist/domain/graph/builder/stages/insert-nodes.js +124 -16
- package/dist/domain/graph/builder/stages/insert-nodes.js.map +1 -1
- package/dist/domain/graph/builder/stages/resolve-imports.d.ts.map +1 -1
- package/dist/domain/graph/builder/stages/resolve-imports.js +3 -2
- package/dist/domain/graph/builder/stages/resolve-imports.js.map +1 -1
- package/dist/domain/graph/resolve.d.ts +0 -4
- package/dist/domain/graph/resolve.d.ts.map +1 -1
- package/dist/domain/graph/resolve.js +32 -48
- package/dist/domain/graph/resolve.js.map +1 -1
- package/dist/domain/graph/watcher.d.ts.map +1 -1
- package/dist/domain/graph/watcher.js +12 -12
- package/dist/domain/graph/watcher.js.map +1 -1
- package/dist/domain/parser.d.ts +1 -1
- package/dist/domain/parser.d.ts.map +1 -1
- package/dist/domain/parser.js +165 -101
- package/dist/domain/parser.js.map +1 -1
- package/dist/domain/search/search/cli-formatter.d.ts.map +1 -1
- package/dist/domain/search/search/cli-formatter.js +88 -83
- package/dist/domain/search/search/cli-formatter.js.map +1 -1
- package/dist/extractors/bash.d.ts +6 -0
- package/dist/extractors/bash.d.ts.map +1 -0
- package/dist/extractors/bash.js +91 -0
- package/dist/extractors/bash.js.map +1 -0
- package/dist/extractors/c.d.ts +6 -0
- package/dist/extractors/c.d.ts.map +1 -0
- package/dist/extractors/c.js +204 -0
- package/dist/extractors/c.js.map +1 -0
- package/dist/extractors/cpp.d.ts +6 -0
- package/dist/extractors/cpp.d.ts.map +1 -0
- package/dist/extractors/cpp.js +283 -0
- package/dist/extractors/cpp.js.map +1 -0
- package/dist/extractors/csharp.d.ts.map +1 -1
- package/dist/extractors/csharp.js +42 -54
- package/dist/extractors/csharp.js.map +1 -1
- package/dist/extractors/go.d.ts.map +1 -1
- package/dist/extractors/go.js +126 -130
- package/dist/extractors/go.js.map +1 -1
- package/dist/extractors/hcl.js +6 -6
- package/dist/extractors/hcl.js.map +1 -1
- package/dist/extractors/helpers.d.ts +32 -1
- package/dist/extractors/helpers.d.ts.map +1 -1
- package/dist/extractors/helpers.js +74 -0
- package/dist/extractors/helpers.js.map +1 -1
- package/dist/extractors/index.d.ts +6 -0
- package/dist/extractors/index.d.ts.map +1 -1
- package/dist/extractors/index.js +6 -0
- package/dist/extractors/index.js.map +1 -1
- package/dist/extractors/java.d.ts.map +1 -1
- package/dist/extractors/java.js +32 -47
- package/dist/extractors/java.js.map +1 -1
- package/dist/extractors/javascript.d.ts.map +1 -1
- package/dist/extractors/javascript.js +359 -330
- package/dist/extractors/javascript.js.map +1 -1
- package/dist/extractors/kotlin.d.ts +6 -0
- package/dist/extractors/kotlin.d.ts.map +1 -0
- package/dist/extractors/kotlin.js +275 -0
- package/dist/extractors/kotlin.js.map +1 -0
- package/dist/extractors/php.d.ts.map +1 -1
- package/dist/extractors/php.js +39 -44
- package/dist/extractors/php.js.map +1 -1
- package/dist/extractors/python.d.ts.map +1 -1
- package/dist/extractors/python.js +75 -93
- package/dist/extractors/python.js.map +1 -1
- package/dist/extractors/ruby.js +6 -13
- package/dist/extractors/ruby.js.map +1 -1
- package/dist/extractors/rust.d.ts.map +1 -1
- package/dist/extractors/rust.js +58 -82
- package/dist/extractors/rust.js.map +1 -1
- package/dist/extractors/scala.d.ts +6 -0
- package/dist/extractors/scala.d.ts.map +1 -0
- package/dist/extractors/scala.js +269 -0
- package/dist/extractors/scala.js.map +1 -0
- package/dist/extractors/swift.d.ts +6 -0
- package/dist/extractors/swift.d.ts.map +1 -0
- package/dist/extractors/swift.js +275 -0
- package/dist/extractors/swift.js.map +1 -0
- package/dist/features/ast.d.ts +16 -1
- package/dist/features/ast.d.ts.map +1 -1
- package/dist/features/ast.js +45 -23
- package/dist/features/ast.js.map +1 -1
- package/dist/features/audit.d.ts.map +1 -1
- package/dist/features/audit.js +17 -21
- package/dist/features/audit.js.map +1 -1
- package/dist/features/branch-compare.d.ts.map +1 -1
- package/dist/features/branch-compare.js +50 -4
- package/dist/features/branch-compare.js.map +1 -1
- package/dist/features/cfg.d.ts +7 -1
- package/dist/features/cfg.d.ts.map +1 -1
- package/dist/features/cfg.js +118 -62
- package/dist/features/cfg.js.map +1 -1
- package/dist/features/check.d.ts.map +1 -1
- package/dist/features/check.js +79 -62
- package/dist/features/check.js.map +1 -1
- package/dist/features/complexity-query.d.ts.map +1 -1
- package/dist/features/complexity-query.js +142 -137
- package/dist/features/complexity-query.js.map +1 -1
- package/dist/features/complexity.d.ts +7 -1
- package/dist/features/complexity.d.ts.map +1 -1
- package/dist/features/complexity.js +62 -1
- package/dist/features/complexity.js.map +1 -1
- package/dist/features/dataflow.d.ts +7 -1
- package/dist/features/dataflow.d.ts.map +1 -1
- package/dist/features/dataflow.js +356 -188
- package/dist/features/dataflow.js.map +1 -1
- package/dist/features/graph-enrichment.d.ts.map +1 -1
- package/dist/features/graph-enrichment.js +117 -104
- package/dist/features/graph-enrichment.js.map +1 -1
- package/dist/features/sequence.d.ts.map +1 -1
- package/dist/features/sequence.js +25 -4
- package/dist/features/sequence.js.map +1 -1
- package/dist/features/snapshot.d.ts.map +1 -1
- package/dist/features/snapshot.js +2 -1
- package/dist/features/snapshot.js.map +1 -1
- package/dist/features/structure-query.d.ts.map +1 -1
- package/dist/features/structure-query.js +29 -4
- package/dist/features/structure-query.js.map +1 -1
- package/dist/features/structure.d.ts.map +1 -1
- package/dist/features/structure.js +35 -15
- package/dist/features/structure.js.map +1 -1
- package/dist/graph/algorithms/leiden/adapter.d.ts.map +1 -1
- package/dist/graph/algorithms/leiden/adapter.js +88 -73
- package/dist/graph/algorithms/leiden/adapter.js.map +1 -1
- package/dist/graph/algorithms/leiden/index.js +43 -28
- package/dist/graph/algorithms/leiden/index.js.map +1 -1
- package/dist/graph/algorithms/leiden/optimiser.d.ts.map +1 -1
- package/dist/graph/algorithms/leiden/optimiser.js +90 -104
- package/dist/graph/algorithms/leiden/optimiser.js.map +1 -1
- package/dist/graph/algorithms/leiden/partition.d.ts.map +1 -1
- package/dist/graph/algorithms/leiden/partition.js +89 -106
- package/dist/graph/algorithms/leiden/partition.js.map +1 -1
- package/dist/graph/model.d.ts +2 -0
- package/dist/graph/model.d.ts.map +1 -1
- package/dist/graph/model.js +20 -8
- package/dist/graph/model.js.map +1 -1
- package/dist/infrastructure/config.d.ts +0 -8
- package/dist/infrastructure/config.d.ts.map +1 -1
- package/dist/infrastructure/config.js +73 -62
- package/dist/infrastructure/config.js.map +1 -1
- package/dist/infrastructure/registry.d.ts +0 -8
- package/dist/infrastructure/registry.d.ts.map +1 -1
- package/dist/infrastructure/registry.js +12 -14
- package/dist/infrastructure/registry.js.map +1 -1
- package/dist/mcp/server.d.ts.map +1 -1
- package/dist/mcp/server.js +47 -45
- package/dist/mcp/server.js.map +1 -1
- package/dist/presentation/audit.d.ts.map +1 -1
- package/dist/presentation/audit.js +61 -57
- package/dist/presentation/audit.js.map +1 -1
- package/dist/presentation/branch-compare.d.ts.map +1 -1
- package/dist/presentation/branch-compare.js +56 -38
- package/dist/presentation/branch-compare.js.map +1 -1
- package/dist/presentation/check.d.ts.map +1 -1
- package/dist/presentation/check.js +30 -32
- package/dist/presentation/check.js.map +1 -1
- package/dist/presentation/colors.d.ts.map +1 -1
- package/dist/presentation/colors.js +2 -0
- package/dist/presentation/colors.js.map +1 -1
- package/dist/presentation/complexity.d.ts.map +1 -1
- package/dist/presentation/complexity.js +25 -19
- package/dist/presentation/complexity.js.map +1 -1
- package/dist/presentation/queries-cli/exports.d.ts.map +1 -1
- package/dist/presentation/queries-cli/exports.js +15 -15
- package/dist/presentation/queries-cli/exports.js.map +1 -1
- package/dist/presentation/queries-cli/impact.d.ts.map +1 -1
- package/dist/presentation/queries-cli/impact.js +29 -19
- package/dist/presentation/queries-cli/impact.js.map +1 -1
- package/dist/types.d.ts +406 -3
- package/dist/types.d.ts.map +1 -1
- package/grammars/tree-sitter-bash.wasm +0 -0
- package/grammars/tree-sitter-c.wasm +0 -0
- package/grammars/tree-sitter-cpp.wasm +0 -0
- package/grammars/tree-sitter-kotlin.wasm +0 -0
- package/grammars/tree-sitter-scala.wasm +0 -0
- package/grammars/tree-sitter-swift.wasm +0 -0
- package/package.json +67 -11
- package/src/ast-analysis/engine.ts +147 -138
- package/src/ast-analysis/rules/javascript.ts +1 -0
- package/src/ast-analysis/visitors/ast-store-visitor.ts +116 -34
- package/src/ast-analysis/visitors/complexity-visitor.ts +11 -11
- package/src/db/better-sqlite3.ts +20 -0
- package/src/db/connection.ts +148 -26
- package/src/db/index.ts +4 -1
- package/src/db/migrations.ts +38 -32
- package/src/db/query-builder.ts +30 -5
- package/src/db/repository/index.ts +1 -0
- package/src/db/repository/native-repository.ts +361 -0
- package/src/db/repository/nodes.ts +7 -3
- package/src/domain/analysis/context.ts +73 -75
- package/src/domain/analysis/dependencies.ts +78 -68
- package/src/domain/analysis/exports.ts +45 -34
- package/src/domain/analysis/fn-impact.ts +67 -64
- package/src/domain/analysis/module-map.ts +103 -8
- package/src/domain/analysis/query-helpers.ts +35 -0
- package/src/domain/graph/builder/context.ts +2 -0
- package/src/domain/graph/builder/helpers.ts +12 -6
- package/src/domain/graph/builder/incremental.ts +3 -2
- package/src/domain/graph/builder/pipeline.ts +98 -6
- package/src/domain/graph/builder/stages/build-edges.ts +116 -83
- package/src/domain/graph/builder/stages/build-structure.ts +46 -8
- package/src/domain/graph/builder/stages/collect-files.ts +83 -6
- package/src/domain/graph/builder/stages/detect-changes.ts +44 -21
- package/src/domain/graph/builder/stages/finalize.ts +172 -109
- package/src/domain/graph/builder/stages/insert-nodes.ts +147 -17
- package/src/domain/graph/builder/stages/resolve-imports.ts +3 -2
- package/src/domain/graph/resolve.ts +34 -46
- package/src/domain/graph/watcher.ts +12 -14
- package/src/domain/parser.ts +169 -97
- package/src/domain/search/search/cli-formatter.ts +121 -94
- package/src/extractors/bash.ts +97 -0
- package/src/extractors/c.ts +212 -0
- package/src/extractors/cpp.ts +298 -0
- package/src/extractors/csharp.ts +53 -56
- package/src/extractors/go.ts +152 -134
- package/src/extractors/hcl.ts +6 -6
- package/src/extractors/helpers.ts +93 -1
- package/src/extractors/index.ts +6 -0
- package/src/extractors/java.ts +43 -48
- package/src/extractors/javascript.ts +382 -317
- package/src/extractors/kotlin.ts +293 -0
- package/src/extractors/php.ts +46 -40
- package/src/extractors/python.ts +81 -104
- package/src/extractors/ruby.ts +6 -13
- package/src/extractors/rust.ts +65 -84
- package/src/extractors/scala.ts +285 -0
- package/src/extractors/swift.ts +293 -0
- package/src/features/ast.ts +74 -24
- package/src/features/audit.ts +24 -20
- package/src/features/branch-compare.ts +54 -5
- package/src/features/cfg.ts +158 -65
- package/src/features/check.ts +90 -74
- package/src/features/complexity-query.ts +181 -163
- package/src/features/complexity.ts +64 -1
- package/src/features/dataflow.ts +462 -217
- package/src/features/graph-enrichment.ts +161 -117
- package/src/features/sequence.ts +27 -4
- package/src/features/snapshot.ts +2 -1
- package/src/features/structure-query.ts +43 -4
- package/src/features/structure.ts +50 -22
- package/src/graph/algorithms/leiden/adapter.ts +126 -71
- package/src/graph/algorithms/leiden/index.ts +67 -28
- package/src/graph/algorithms/leiden/optimiser.ts +114 -105
- package/src/graph/algorithms/leiden/partition.ts +131 -98
- package/src/graph/model.ts +19 -7
- package/src/infrastructure/config.ts +60 -58
- package/src/infrastructure/registry.ts +17 -14
- package/src/mcp/server.ts +48 -47
- package/src/presentation/audit.ts +72 -67
- package/src/presentation/branch-compare.ts +54 -50
- package/src/presentation/check.ts +34 -34
- package/src/presentation/colors.ts +2 -0
- package/src/presentation/complexity.ts +39 -33
- package/src/presentation/queries-cli/exports.ts +17 -17
- package/src/presentation/queries-cli/impact.ts +30 -22
- package/src/types.ts +458 -3
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { openReadonlyOrFail } from '../../db/index.js';
|
|
2
|
+
import { loadConfig } from '../../infrastructure/config.js';
|
|
3
|
+
import type { BetterSqlite3Database, CodegraphConfig } from '../../types.js';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Open a readonly DB connection, run `fn`, and close the DB on completion.
|
|
7
|
+
* Eliminates the duplicated `openReadonlyOrFail` + `try/finally/db.close()` pattern
|
|
8
|
+
* that appears in every analysis query function.
|
|
9
|
+
*/
|
|
10
|
+
export function withReadonlyDb<T>(
|
|
11
|
+
customDbPath: string | undefined,
|
|
12
|
+
fn: (db: BetterSqlite3Database) => T,
|
|
13
|
+
): T {
|
|
14
|
+
const db = openReadonlyOrFail(customDbPath);
|
|
15
|
+
try {
|
|
16
|
+
return fn(db);
|
|
17
|
+
} finally {
|
|
18
|
+
db.close();
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Resolve common analysis options into a normalized form.
|
|
24
|
+
* Shared across fn-impact, context, dependencies, and exports modules.
|
|
25
|
+
*/
|
|
26
|
+
export function resolveAnalysisOpts(opts: { noTests?: boolean; config?: CodegraphConfig }): {
|
|
27
|
+
noTests: boolean;
|
|
28
|
+
config: CodegraphConfig;
|
|
29
|
+
displayOpts: Record<string, unknown>;
|
|
30
|
+
} {
|
|
31
|
+
const noTests = opts.noTests || false;
|
|
32
|
+
const config = opts.config || loadConfig();
|
|
33
|
+
const displayOpts = config.display || {};
|
|
34
|
+
return { noTests, config, displayOpts };
|
|
35
|
+
}
|
|
@@ -12,6 +12,7 @@ import type {
|
|
|
12
12
|
ExtractorOutput,
|
|
13
13
|
FileToParse,
|
|
14
14
|
MetadataUpdate,
|
|
15
|
+
NativeDatabase,
|
|
15
16
|
NodeRow,
|
|
16
17
|
ParseChange,
|
|
17
18
|
PathAliases,
|
|
@@ -31,6 +32,7 @@ export class PipelineContext {
|
|
|
31
32
|
incremental!: boolean;
|
|
32
33
|
forceFullRebuild: boolean = false;
|
|
33
34
|
schemaVersion!: number;
|
|
35
|
+
nativeDb?: NativeDatabase;
|
|
34
36
|
|
|
35
37
|
// ── File collection (set by collectFiles stage) ────────────────────
|
|
36
38
|
allFiles!: string[];
|
|
@@ -47,6 +47,17 @@ export const BUILTIN_RECEIVERS: Set<string> = new Set([
|
|
|
47
47
|
'require',
|
|
48
48
|
]);
|
|
49
49
|
|
|
50
|
+
/** Check if a directory entry should be skipped (ignored dirs, dotfiles). */
|
|
51
|
+
function shouldSkipEntry(entry: fs.Dirent, extraIgnore: Set<string> | null): boolean {
|
|
52
|
+
if (entry.name.startsWith('.') && entry.name !== '.') {
|
|
53
|
+
if (IGNORE_DIRS.has(entry.name)) return true;
|
|
54
|
+
if (entry.isDirectory()) return true;
|
|
55
|
+
}
|
|
56
|
+
if (IGNORE_DIRS.has(entry.name)) return true;
|
|
57
|
+
if (extraIgnore?.has(entry.name)) return true;
|
|
58
|
+
return false;
|
|
59
|
+
}
|
|
60
|
+
|
|
50
61
|
/**
|
|
51
62
|
* Recursively collect all source files under `dir`.
|
|
52
63
|
* When `directories` is a Set, also tracks which directories contain files.
|
|
@@ -100,12 +111,7 @@ export function collectFiles(
|
|
|
100
111
|
}
|
|
101
112
|
|
|
102
113
|
for (const entry of entries) {
|
|
103
|
-
if (
|
|
104
|
-
if (IGNORE_DIRS.has(entry.name)) continue;
|
|
105
|
-
if (entry.isDirectory()) continue;
|
|
106
|
-
}
|
|
107
|
-
if (IGNORE_DIRS.has(entry.name)) continue;
|
|
108
|
-
if (extraIgnore?.has(entry.name)) continue;
|
|
114
|
+
if (shouldSkipEntry(entry, extraIgnore)) continue;
|
|
109
115
|
|
|
110
116
|
const full = path.join(dir, entry.name);
|
|
111
117
|
if (entry.isDirectory()) {
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
import fs from 'node:fs';
|
|
11
11
|
import path from 'node:path';
|
|
12
12
|
import { bulkNodeIdsByFile } from '../../../db/index.js';
|
|
13
|
-
import { warn } from '../../../infrastructure/logger.js';
|
|
13
|
+
import { debug, warn } from '../../../infrastructure/logger.js';
|
|
14
14
|
import { normalizePath } from '../../../shared/constants.js';
|
|
15
15
|
import type {
|
|
16
16
|
BetterSqlite3Database,
|
|
@@ -154,7 +154,8 @@ async function parseReverseDep(
|
|
|
154
154
|
let code: string;
|
|
155
155
|
try {
|
|
156
156
|
code = readFileSafe(absPath);
|
|
157
|
-
} catch {
|
|
157
|
+
} catch (e: unknown) {
|
|
158
|
+
debug(`parseReverseDep: cannot read ${absPath}: ${e instanceof Error ? e.message : String(e)}`);
|
|
158
159
|
return null;
|
|
159
160
|
}
|
|
160
161
|
|
|
@@ -6,9 +6,10 @@
|
|
|
6
6
|
*/
|
|
7
7
|
import path from 'node:path';
|
|
8
8
|
import { performance } from 'node:perf_hooks';
|
|
9
|
-
import {
|
|
9
|
+
import { closeDbPair, getBuildMeta, initSchema, MIGRATIONS, openDb } from '../../../db/index.js';
|
|
10
10
|
import { detectWorkspaces, loadConfig } from '../../../infrastructure/config.js';
|
|
11
11
|
import { info, warn } from '../../../infrastructure/logger.js';
|
|
12
|
+
import { loadNative } from '../../../infrastructure/native.js';
|
|
12
13
|
import { CODEGRAPH_VERSION } from '../../../shared/version.js';
|
|
13
14
|
import type { BuildGraphOpts, BuildResult } from '../../../types.js';
|
|
14
15
|
import { getActiveEngine } from '../../parser.js';
|
|
@@ -33,6 +34,19 @@ function initializeEngine(ctx: PipelineContext): void {
|
|
|
33
34
|
engine: ctx.opts.engine || 'auto',
|
|
34
35
|
dataflow: ctx.opts.dataflow !== false,
|
|
35
36
|
ast: ctx.opts.ast !== false,
|
|
37
|
+
nativeDb: ctx.nativeDb,
|
|
38
|
+
// WAL checkpoint callbacks for dual-connection WAL guard (#696).
|
|
39
|
+
// Feature modules (ast, cfg, complexity, dataflow) receive `db` as a
|
|
40
|
+
// parameter and cannot tolerate close/reopen (stale reference). Instead,
|
|
41
|
+
// checkpoint the WAL so native writes start with a clean slate. Features
|
|
42
|
+
// return early on native success and never read native-written WAL data
|
|
43
|
+
// through the JS connection, so a post-write checkpoint is unnecessary.
|
|
44
|
+
suspendJsDb: ctx.nativeDb
|
|
45
|
+
? () => {
|
|
46
|
+
ctx.db.pragma('wal_checkpoint(TRUNCATE)');
|
|
47
|
+
}
|
|
48
|
+
: undefined,
|
|
49
|
+
resumeJsDb: ctx.nativeDb ? () => {} : undefined,
|
|
36
50
|
};
|
|
37
51
|
const { name: engineName, version: engineVersion } = getActiveEngine(ctx.engineOpts);
|
|
38
52
|
ctx.engineName = engineName as 'native' | 'wasm';
|
|
@@ -46,19 +60,25 @@ function checkEngineSchemaMismatch(ctx: PipelineContext): void {
|
|
|
46
60
|
ctx.forceFullRebuild = false;
|
|
47
61
|
if (!ctx.incremental) return;
|
|
48
62
|
|
|
49
|
-
|
|
63
|
+
// Route metadata reads through NativeDatabase only when using the native engine,
|
|
64
|
+
// to avoid dual-SQLite WAL conflicts (rusqlite + better-sqlite3 on same file).
|
|
65
|
+
const useNativeDb = ctx.engineName === 'native' && !!ctx.nativeDb;
|
|
66
|
+
const meta = (key: string): string | null =>
|
|
67
|
+
useNativeDb ? ctx.nativeDb!.getBuildMeta(key) : getBuildMeta(ctx.db, key);
|
|
68
|
+
|
|
69
|
+
const prevEngine = meta('engine');
|
|
50
70
|
if (prevEngine && prevEngine !== ctx.engineName) {
|
|
51
71
|
info(`Engine changed (${prevEngine} → ${ctx.engineName}), promoting to full rebuild.`);
|
|
52
72
|
ctx.forceFullRebuild = true;
|
|
53
73
|
}
|
|
54
|
-
const prevSchema =
|
|
74
|
+
const prevSchema = meta('schema_version');
|
|
55
75
|
if (prevSchema && Number(prevSchema) !== ctx.schemaVersion) {
|
|
56
76
|
info(
|
|
57
77
|
`Schema version changed (${prevSchema} → ${ctx.schemaVersion}), promoting to full rebuild.`,
|
|
58
78
|
);
|
|
59
79
|
ctx.forceFullRebuild = true;
|
|
60
80
|
}
|
|
61
|
-
const prevVersion =
|
|
81
|
+
const prevVersion = meta('codegraph_version');
|
|
62
82
|
if (prevVersion && prevVersion !== CODEGRAPH_VERSION) {
|
|
63
83
|
info(
|
|
64
84
|
`Codegraph version changed (${prevVersion} → ${CODEGRAPH_VERSION}), promoting to full rebuild.`,
|
|
@@ -91,7 +111,25 @@ function setupPipeline(ctx: PipelineContext): void {
|
|
|
91
111
|
ctx.rootDir = path.resolve(ctx.rootDir);
|
|
92
112
|
ctx.dbPath = path.join(ctx.rootDir, '.codegraph', 'graph.db');
|
|
93
113
|
ctx.db = openDb(ctx.dbPath);
|
|
94
|
-
|
|
114
|
+
|
|
115
|
+
// Use NativeDatabase for schema init when native engine is available (Phase 6.13).
|
|
116
|
+
// better-sqlite3 (ctx.db) is still always opened — needed for queries and stages
|
|
117
|
+
// that haven't been migrated to rusqlite yet.
|
|
118
|
+
const native = loadNative();
|
|
119
|
+
if (native?.NativeDatabase) {
|
|
120
|
+
try {
|
|
121
|
+
ctx.nativeDb = native.NativeDatabase.openReadWrite(ctx.dbPath);
|
|
122
|
+
ctx.nativeDb.initSchema();
|
|
123
|
+
} catch (err) {
|
|
124
|
+
warn(`NativeDatabase init failed, falling back to JS: ${(err as Error).message}`);
|
|
125
|
+
ctx.nativeDb = undefined;
|
|
126
|
+
}
|
|
127
|
+
// Always run JS initSchema so better-sqlite3 sees the schema —
|
|
128
|
+
// nativeDb is closed during pipeline stages and reopened for analyses.
|
|
129
|
+
initSchema(ctx.db);
|
|
130
|
+
} else {
|
|
131
|
+
initSchema(ctx.db);
|
|
132
|
+
}
|
|
95
133
|
|
|
96
134
|
ctx.config = loadConfig(ctx.rootDir);
|
|
97
135
|
ctx.incremental =
|
|
@@ -134,6 +172,26 @@ function formatTimingResult(ctx: PipelineContext): BuildResult {
|
|
|
134
172
|
// ── Pipeline stages execution ───────────────────────────────────────────
|
|
135
173
|
|
|
136
174
|
async function runPipelineStages(ctx: PipelineContext): Promise<void> {
|
|
175
|
+
// Prevent dual-connection WAL corruption during pipeline stages: when both
|
|
176
|
+
// better-sqlite3 (ctx.db) and rusqlite (ctx.nativeDb) are open to the same
|
|
177
|
+
// WAL-mode file, native writes corrupt the DB. Close nativeDb so stages
|
|
178
|
+
// use JS fallback paths. Reopened before runAnalyses for feature modules
|
|
179
|
+
// that use suspendJsDb/resumeJsDb WAL checkpoint pattern (#696).
|
|
180
|
+
const hadNativeDb = !!ctx.nativeDb;
|
|
181
|
+
if (ctx.db && ctx.nativeDb) {
|
|
182
|
+
try {
|
|
183
|
+
ctx.nativeDb.close();
|
|
184
|
+
} catch {
|
|
185
|
+
/* ignore close errors */
|
|
186
|
+
}
|
|
187
|
+
ctx.nativeDb = undefined;
|
|
188
|
+
// Also clear stale reference in engineOpts to prevent stages from
|
|
189
|
+
// calling methods on the closed NativeDatabase.
|
|
190
|
+
if (ctx.engineOpts?.nativeDb) {
|
|
191
|
+
ctx.engineOpts.nativeDb = undefined;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
137
195
|
await collectFiles(ctx);
|
|
138
196
|
await detectChanges(ctx);
|
|
139
197
|
|
|
@@ -144,7 +202,39 @@ async function runPipelineStages(ctx: PipelineContext): Promise<void> {
|
|
|
144
202
|
await resolveImports(ctx);
|
|
145
203
|
await buildEdges(ctx);
|
|
146
204
|
await buildStructure(ctx);
|
|
205
|
+
|
|
206
|
+
// Reopen nativeDb for feature modules (ast, cfg, complexity, dataflow)
|
|
207
|
+
// which use suspendJsDb/resumeJsDb WAL checkpoint before native writes.
|
|
208
|
+
if (hadNativeDb) {
|
|
209
|
+
const native = loadNative();
|
|
210
|
+
if (native?.NativeDatabase) {
|
|
211
|
+
try {
|
|
212
|
+
ctx.nativeDb = native.NativeDatabase.openReadWrite(ctx.dbPath);
|
|
213
|
+
if (ctx.engineOpts) {
|
|
214
|
+
ctx.engineOpts.nativeDb = ctx.nativeDb;
|
|
215
|
+
}
|
|
216
|
+
} catch {
|
|
217
|
+
ctx.nativeDb = undefined;
|
|
218
|
+
if (ctx.engineOpts) {
|
|
219
|
+
ctx.engineOpts.nativeDb = undefined;
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
147
225
|
await runAnalyses(ctx);
|
|
226
|
+
|
|
227
|
+
// Close nativeDb after analyses — finalize uses JS paths for setBuildMeta
|
|
228
|
+
// and closeDbPair handles cleanup. Avoids dual-connection during finalize.
|
|
229
|
+
if (ctx.nativeDb) {
|
|
230
|
+
try {
|
|
231
|
+
ctx.nativeDb.close();
|
|
232
|
+
} catch {
|
|
233
|
+
/* ignore close errors */
|
|
234
|
+
}
|
|
235
|
+
ctx.nativeDb = undefined;
|
|
236
|
+
}
|
|
237
|
+
|
|
148
238
|
await finalize(ctx);
|
|
149
239
|
}
|
|
150
240
|
|
|
@@ -168,7 +258,9 @@ export async function buildGraph(
|
|
|
168
258
|
setupPipeline(ctx);
|
|
169
259
|
await runPipelineStages(ctx);
|
|
170
260
|
} catch (err) {
|
|
171
|
-
if (!ctx.earlyExit && ctx.db)
|
|
261
|
+
if (!ctx.earlyExit && ctx.db) {
|
|
262
|
+
closeDbPair({ db: ctx.db, nativeDb: ctx.nativeDb });
|
|
263
|
+
}
|
|
172
264
|
throw err;
|
|
173
265
|
}
|
|
174
266
|
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
import path from 'node:path';
|
|
8
8
|
import { performance } from 'node:perf_hooks';
|
|
9
9
|
import { getNodeId } from '../../../../db/index.js';
|
|
10
|
+
import { debug } from '../../../../infrastructure/logger.js';
|
|
10
11
|
import { loadNative } from '../../../../infrastructure/native.js';
|
|
11
12
|
import type {
|
|
12
13
|
BetterSqlite3Database,
|
|
@@ -21,6 +22,7 @@ import type {
|
|
|
21
22
|
import { computeConfidence } from '../../resolve.js';
|
|
22
23
|
import type { PipelineContext } from '../context.js';
|
|
23
24
|
import { BUILTIN_RECEIVERS, batchInsertEdges } from '../helpers.js';
|
|
25
|
+
|
|
24
26
|
import { getResolved, isBarrelFile, resolveBarrelExport } from './resolve-imports.js';
|
|
25
27
|
|
|
26
28
|
// ── Local types ──────────────────────────────────────────────────────────
|
|
@@ -60,12 +62,6 @@ interface NativeEdge {
|
|
|
60
62
|
dynamic: number;
|
|
61
63
|
}
|
|
62
64
|
|
|
63
|
-
/** TypeMap entry used in receiver supplement (normalized from native format). */
|
|
64
|
-
interface NormalizedTypeEntry {
|
|
65
|
-
type: string;
|
|
66
|
-
confidence: number;
|
|
67
|
-
}
|
|
68
|
-
|
|
69
65
|
// ── Node lookup setup ───────────────────────────────────────────────────
|
|
70
66
|
|
|
71
67
|
function makeGetNodeIdStmt(db: BetterSqlite3Database): NodeIdStmt {
|
|
@@ -209,14 +205,6 @@ function buildCallEdgesNative(
|
|
|
209
205
|
for (const e of nativeEdges) {
|
|
210
206
|
allEdgeRows.push([e.sourceId, e.targetId, e.kind, e.confidence, e.dynamic]);
|
|
211
207
|
}
|
|
212
|
-
|
|
213
|
-
// Older native binaries (< 3.2.0) don't emit receiver or type-resolved method-call
|
|
214
|
-
// edges. Supplement them on the JS side if the native binary missed them.
|
|
215
|
-
// TODO: Remove once all published native binaries handle receivers (>= 3.2.0)
|
|
216
|
-
const hasReceiver = nativeEdges.some((e) => e.kind === 'receiver');
|
|
217
|
-
if (!hasReceiver) {
|
|
218
|
-
supplementReceiverEdges(ctx, nativeFiles, getNodeIdStmt, allEdgeRows);
|
|
219
|
-
}
|
|
220
208
|
}
|
|
221
209
|
|
|
222
210
|
function buildImportedNamesForNative(
|
|
@@ -241,58 +229,6 @@ function buildImportedNamesForNative(
|
|
|
241
229
|
return importedNames;
|
|
242
230
|
}
|
|
243
231
|
|
|
244
|
-
// ── Receiver edge supplement for older native binaries ──────────────────
|
|
245
|
-
|
|
246
|
-
function supplementReceiverEdges(
|
|
247
|
-
ctx: PipelineContext,
|
|
248
|
-
nativeFiles: NativeFileEntry[],
|
|
249
|
-
getNodeIdStmt: NodeIdStmt,
|
|
250
|
-
allEdgeRows: EdgeRowTuple[],
|
|
251
|
-
): void {
|
|
252
|
-
const seenCallEdges = new Set<string>();
|
|
253
|
-
// Collect existing edges to avoid duplicates
|
|
254
|
-
for (const row of allEdgeRows) {
|
|
255
|
-
seenCallEdges.add(`${row[0]}|${row[1]}|${row[2]}`);
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
for (const nf of nativeFiles) {
|
|
259
|
-
const relPath = nf.file;
|
|
260
|
-
const typeMap = new Map<string, NormalizedTypeEntry>(
|
|
261
|
-
nf.typeMap.map((t) => [t.name, { type: t.typeName, confidence: t.confidence ?? 0.9 }]),
|
|
262
|
-
);
|
|
263
|
-
const fileNodeRow = { id: nf.fileNodeId };
|
|
264
|
-
|
|
265
|
-
for (const call of nf.calls) {
|
|
266
|
-
if (!call.receiver || BUILTIN_RECEIVERS.has(call.receiver)) continue;
|
|
267
|
-
if (call.receiver === 'this' || call.receiver === 'self' || call.receiver === 'super')
|
|
268
|
-
continue;
|
|
269
|
-
|
|
270
|
-
const caller = findCaller(call, nf.definitions, relPath, getNodeIdStmt, fileNodeRow);
|
|
271
|
-
|
|
272
|
-
// Receiver edge: caller → receiver type node
|
|
273
|
-
buildReceiverEdge(ctx, call, caller, relPath, seenCallEdges, allEdgeRows, typeMap);
|
|
274
|
-
|
|
275
|
-
// Type-resolved method call: caller → Type.method
|
|
276
|
-
const typeEntry = typeMap.get(call.receiver);
|
|
277
|
-
const typeName = typeEntry ? typeEntry.type : null;
|
|
278
|
-
if (typeName) {
|
|
279
|
-
const qualifiedName = `${typeName}.${call.name}`;
|
|
280
|
-
const targets = (ctx.nodesByName.get(qualifiedName) || []).filter(
|
|
281
|
-
(n) => n.kind === 'method',
|
|
282
|
-
);
|
|
283
|
-
for (const t of targets) {
|
|
284
|
-
const key = `${caller.id}|${t.id}|calls`;
|
|
285
|
-
if (t.id !== caller.id && !seenCallEdges.has(key)) {
|
|
286
|
-
seenCallEdges.add(key);
|
|
287
|
-
const confidence = computeConfidence(relPath, t.file, null);
|
|
288
|
-
allEdgeRows.push([caller.id, t.id, 'calls', confidence, call.dynamic ? 1 : 0]);
|
|
289
|
-
}
|
|
290
|
-
}
|
|
291
|
-
}
|
|
292
|
-
}
|
|
293
|
-
}
|
|
294
|
-
}
|
|
295
|
-
|
|
296
232
|
// ── Call edges (JS fallback) ────────────────────────────────────────────
|
|
297
233
|
|
|
298
234
|
function buildCallEdgesJS(
|
|
@@ -494,7 +430,7 @@ function buildReceiverEdge(
|
|
|
494
430
|
relPath: string,
|
|
495
431
|
seenCallEdges: Set<string>,
|
|
496
432
|
allEdgeRows: EdgeRowTuple[],
|
|
497
|
-
typeMap: Map<string, TypeMapEntry |
|
|
433
|
+
typeMap: Map<string, TypeMapEntry | string>,
|
|
498
434
|
): void {
|
|
499
435
|
const receiverKinds = new Set(['class', 'struct', 'interface', 'type', 'module']);
|
|
500
436
|
const typeEntry = typeMap?.get(call.receiver!);
|
|
@@ -561,22 +497,94 @@ function buildClassHierarchyEdges(
|
|
|
561
497
|
|
|
562
498
|
// ── Main entry point ────────────────────────────────────────────────────
|
|
563
499
|
|
|
500
|
+
/**
|
|
501
|
+
* For small incremental builds (≤5 changed files on a large codebase), scope
|
|
502
|
+
* the node loading query to only files that are relevant: changed files +
|
|
503
|
+
* their import targets. Falls back to loading ALL nodes for full builds or
|
|
504
|
+
* larger incremental changes.
|
|
505
|
+
*/
|
|
506
|
+
function loadNodes(ctx: PipelineContext): { rows: QueryNodeRow[]; scoped: boolean } {
|
|
507
|
+
const { db, fileSymbols, isFullBuild, batchResolved } = ctx;
|
|
508
|
+
const nodeKindFilter = `kind IN ('function','method','class','interface','struct','type','module','enum','trait','record','constant')`;
|
|
509
|
+
|
|
510
|
+
// Gate: only scope for small incremental on large codebases
|
|
511
|
+
if (!isFullBuild && fileSymbols.size <= 5) {
|
|
512
|
+
const existingFileCount = (
|
|
513
|
+
db.prepare("SELECT COUNT(*) as c FROM nodes WHERE kind = 'file'").get() as { c: number }
|
|
514
|
+
).c;
|
|
515
|
+
if (existingFileCount > 20) {
|
|
516
|
+
// Collect relevant files: changed files + their import targets
|
|
517
|
+
const relevantFiles = new Set<string>(fileSymbols.keys());
|
|
518
|
+
if (batchResolved) {
|
|
519
|
+
for (const resolvedPath of batchResolved.values()) {
|
|
520
|
+
relevantFiles.add(resolvedPath);
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
// Also add barrel-only files
|
|
524
|
+
for (const barrelPath of ctx.barrelOnlyFiles) {
|
|
525
|
+
relevantFiles.add(barrelPath);
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
const placeholders = [...relevantFiles].map(() => '?').join(',');
|
|
529
|
+
const rows = db
|
|
530
|
+
.prepare(
|
|
531
|
+
`SELECT id, name, kind, file, line FROM nodes WHERE ${nodeKindFilter} AND file IN (${placeholders})`,
|
|
532
|
+
)
|
|
533
|
+
.all(...relevantFiles) as QueryNodeRow[];
|
|
534
|
+
return { rows, scoped: true };
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
const rows = db
|
|
539
|
+
.prepare(`SELECT id, name, kind, file, line FROM nodes WHERE ${nodeKindFilter}`)
|
|
540
|
+
.all() as QueryNodeRow[];
|
|
541
|
+
return { rows, scoped: false };
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
/**
|
|
545
|
+
* For scoped node loading, patch nodesByName.get with a lazy SQL fallback
|
|
546
|
+
* so global name-only lookups (resolveByMethodOrGlobal)
|
|
547
|
+
* can still find nodes outside the scoped set.
|
|
548
|
+
*/
|
|
549
|
+
function addLazyFallback(ctx: PipelineContext, scopedLoad: boolean): void {
|
|
550
|
+
if (!scopedLoad) return;
|
|
551
|
+
const { db } = ctx;
|
|
552
|
+
const fallbackStmt = db.prepare(
|
|
553
|
+
`SELECT id, name, kind, file, line FROM nodes WHERE name = ? AND kind != 'file'`,
|
|
554
|
+
);
|
|
555
|
+
const originalGet = ctx.nodesByName.get.bind(ctx.nodesByName);
|
|
556
|
+
ctx.nodesByName.get = (name: string) => {
|
|
557
|
+
const result = originalGet(name);
|
|
558
|
+
if (result !== undefined) return result;
|
|
559
|
+
const rows = fallbackStmt.all(name) as unknown as NodeRow[];
|
|
560
|
+
if (rows.length > 0) {
|
|
561
|
+
ctx.nodesByName.set(name, rows);
|
|
562
|
+
return rows;
|
|
563
|
+
}
|
|
564
|
+
return undefined;
|
|
565
|
+
};
|
|
566
|
+
}
|
|
567
|
+
|
|
564
568
|
export async function buildEdges(ctx: PipelineContext): Promise<void> {
|
|
565
569
|
const { db, engineName } = ctx;
|
|
566
570
|
|
|
567
571
|
const getNodeIdStmt = makeGetNodeIdStmt(db);
|
|
568
572
|
|
|
569
|
-
const
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
)
|
|
573
|
-
.all() as QueryNodeRow[];
|
|
574
|
-
setupNodeLookups(ctx, allNodes);
|
|
573
|
+
const { rows: allNodesBefore, scoped: scopedLoad } = loadNodes(ctx);
|
|
574
|
+
setupNodeLookups(ctx, allNodesBefore);
|
|
575
|
+
addLazyFallback(ctx, scopedLoad);
|
|
575
576
|
|
|
576
577
|
const t0 = performance.now();
|
|
577
|
-
const
|
|
578
|
-
|
|
579
|
-
|
|
578
|
+
const native = engineName === 'native' ? loadNative() : null;
|
|
579
|
+
|
|
580
|
+
// Phase 1: Compute edges inside a better-sqlite3 transaction.
|
|
581
|
+
// Barrel-edge deletion lives here so that the JS path (which also inserts
|
|
582
|
+
// edges in this transaction) keeps deletion + insertion atomic.
|
|
583
|
+
// When using the native rusqlite path, insertion happens in Phase 2 on a
|
|
584
|
+
// separate connection — a crash between Phase 1 and Phase 2 would leave
|
|
585
|
+
// barrel edges missing until the next incremental rebuild re-creates them.
|
|
586
|
+
const allEdgeRows: EdgeRowTuple[] = [];
|
|
587
|
+
const computeEdgesTx = db.transaction(() => {
|
|
580
588
|
if (ctx.barrelOnlyFiles.size > 0) {
|
|
581
589
|
const deleteOutgoingEdges = db.prepare(
|
|
582
590
|
'DELETE FROM edges WHERE source_id IN (SELECT id FROM nodes WHERE file = ?)',
|
|
@@ -586,19 +594,44 @@ export async function buildEdges(ctx: PipelineContext): Promise<void> {
|
|
|
586
594
|
}
|
|
587
595
|
}
|
|
588
596
|
|
|
589
|
-
const allEdgeRows: EdgeRowTuple[] = [];
|
|
590
|
-
|
|
591
597
|
buildImportEdges(ctx, getNodeIdStmt, allEdgeRows);
|
|
592
598
|
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
599
|
+
// Skip native call-edge path for small incremental builds (≤3 files):
|
|
600
|
+
// napi-rs marshaling overhead for allNodes exceeds computation savings.
|
|
601
|
+
const useNativeCallEdges =
|
|
602
|
+
native?.buildCallEdges && (ctx.isFullBuild || ctx.fileSymbols.size > 3);
|
|
603
|
+
if (useNativeCallEdges) {
|
|
604
|
+
buildCallEdgesNative(ctx, getNodeIdStmt, allEdgeRows, allNodesBefore, native!);
|
|
596
605
|
} else {
|
|
597
606
|
buildCallEdgesJS(ctx, getNodeIdStmt, allEdgeRows);
|
|
598
607
|
}
|
|
599
608
|
|
|
600
|
-
|
|
609
|
+
// When using native edge insert, skip JS insert here — do it after tx commits.
|
|
610
|
+
// Otherwise insert edges within this transaction for atomicity.
|
|
611
|
+
const useNativeEdgeInsert = ctx.engineName === 'native' && !!ctx.nativeDb?.bulkInsertEdges;
|
|
612
|
+
if (!useNativeEdgeInsert) {
|
|
613
|
+
batchInsertEdges(db, allEdgeRows);
|
|
614
|
+
}
|
|
601
615
|
});
|
|
602
|
-
|
|
616
|
+
computeEdgesTx();
|
|
617
|
+
|
|
618
|
+
// Phase 2: Native rusqlite bulk insert (outside better-sqlite3 transaction
|
|
619
|
+
// to avoid SQLITE_BUSY contention). Uses NativeDatabase persistent connection.
|
|
620
|
+
// Standalone napi functions were removed in 6.17.
|
|
621
|
+
if (ctx.engineName === 'native' && ctx.nativeDb?.bulkInsertEdges && allEdgeRows.length > 0) {
|
|
622
|
+
const nativeEdges = allEdgeRows.map((r) => ({
|
|
623
|
+
sourceId: r[0],
|
|
624
|
+
targetId: r[1],
|
|
625
|
+
kind: r[2],
|
|
626
|
+
confidence: r[3],
|
|
627
|
+
dynamic: r[4],
|
|
628
|
+
}));
|
|
629
|
+
const ok = ctx.nativeDb.bulkInsertEdges(nativeEdges);
|
|
630
|
+
if (!ok) {
|
|
631
|
+
debug('Native bulkInsertEdges failed — falling back to JS batchInsertEdges');
|
|
632
|
+
batchInsertEdges(ctx.db, allEdgeRows);
|
|
633
|
+
}
|
|
634
|
+
}
|
|
635
|
+
|
|
603
636
|
ctx.timing.edgesMs = performance.now() - t0;
|
|
604
637
|
}
|
|
@@ -39,8 +39,15 @@ export async function buildStructure(ctx: PipelineContext): Promise<void> {
|
|
|
39
39
|
// loading ALL definitions from DB (~8ms) and recomputing ALL metrics (~15ms).
|
|
40
40
|
// Gate: ≤5 changed files AND significantly more existing files (>20) to
|
|
41
41
|
// avoid triggering on small test fixtures where directory metrics matter.
|
|
42
|
+
const useNativeReads = ctx.engineName === 'native' && !!ctx.nativeDb;
|
|
42
43
|
const existingFileCount = !isFullBuild
|
|
43
|
-
? (
|
|
44
|
+
? (
|
|
45
|
+
(useNativeReads
|
|
46
|
+
? ctx.nativeDb!.queryGet("SELECT COUNT(*) as c FROM nodes WHERE kind = 'file'", [])
|
|
47
|
+
: db.prepare("SELECT COUNT(*) as c FROM nodes WHERE kind = 'file'").get()) as {
|
|
48
|
+
c: number;
|
|
49
|
+
}
|
|
50
|
+
).c
|
|
44
51
|
: 0;
|
|
45
52
|
const useSmallIncrementalFastPath =
|
|
46
53
|
!isFullBuild &&
|
|
@@ -86,13 +93,44 @@ export async function buildStructure(ctx: PipelineContext): Promise<void> {
|
|
|
86
93
|
// Classify node roles (incremental: only reclassify changed files' nodes)
|
|
87
94
|
const t1 = performance.now();
|
|
88
95
|
try {
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
+
let roleSummary: Record<string, number> | null = null;
|
|
97
|
+
|
|
98
|
+
// Use NativeDatabase persistent connection (Phase 6.15+).
|
|
99
|
+
// Standalone napi functions were removed in 6.17 — falls through to JS if nativeDb unavailable.
|
|
100
|
+
// Note: classifyRoles* both read (fan-in/fan-out) and write (UPDATE nodes SET role).
|
|
101
|
+
if (useNativeReads && ctx.nativeDb?.classifyRolesFull) {
|
|
102
|
+
const nativeResult =
|
|
103
|
+
changedFileList && changedFileList.length > 0
|
|
104
|
+
? ctx.nativeDb.classifyRolesIncremental(changedFileList)
|
|
105
|
+
: 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
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// Fall back to JS path
|
|
124
|
+
if (!roleSummary) {
|
|
125
|
+
const { classifyNodeRoles } = (await import('../../../../features/structure.js')) as {
|
|
126
|
+
classifyNodeRoles: (
|
|
127
|
+
db: PipelineContext['db'],
|
|
128
|
+
changedFiles?: string[] | null,
|
|
129
|
+
) => Record<string, number>;
|
|
130
|
+
};
|
|
131
|
+
roleSummary = classifyNodeRoles(ctx.db, changedFileList);
|
|
132
|
+
}
|
|
133
|
+
|
|
96
134
|
debug(
|
|
97
135
|
`Roles${changedFileList ? ` (incremental, ${changedFileList.length} files)` : ''}: ${Object.entries(
|
|
98
136
|
roleSummary,
|