@optave/codegraph 3.5.0 → 3.7.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 +47 -21
- 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/visitors/ast-store-visitor.d.ts.map +1 -1
- package/dist/ast-analysis/visitors/ast-store-visitor.js +14 -1
- 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/connection.d.ts +12 -2
- package/dist/db/connection.d.ts.map +1 -1
- package/dist/db/connection.js +81 -53
- package/dist/db/connection.js.map +1 -1
- package/dist/db/index.d.ts +1 -1
- 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 +38 -32
- package/dist/db/migrations.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/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 +69 -3
- 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 +7 -51
- 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 +7 -5
- package/dist/domain/graph/builder/stages/build-structure.js.map +1 -1
- package/dist/domain/graph/builder/stages/collect-files.js +2 -2
- 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 +2 -2
- 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 +124 -105
- 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 +28 -15
- 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 +206 -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/dart.d.ts +6 -0
- package/dist/extractors/dart.d.ts.map +1 -0
- package/dist/extractors/dart.js +277 -0
- package/dist/extractors/dart.js.map +1 -0
- package/dist/extractors/elixir.d.ts +9 -0
- package/dist/extractors/elixir.d.ts.map +1 -0
- package/dist/extractors/elixir.js +223 -0
- package/dist/extractors/elixir.js.map +1 -0
- 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/haskell.d.ts +8 -0
- package/dist/extractors/haskell.d.ts.map +1 -0
- package/dist/extractors/haskell.js +217 -0
- package/dist/extractors/haskell.js.map +1 -0
- 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 +12 -0
- package/dist/extractors/index.d.ts.map +1 -1
- package/dist/extractors/index.js +12 -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 +306 -292
- 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/lua.d.ts +6 -0
- package/dist/extractors/lua.d.ts.map +1 -0
- package/dist/extractors/lua.js +162 -0
- package/dist/extractors/lua.js.map +1 -0
- package/dist/extractors/ocaml.d.ts +6 -0
- package/dist/extractors/ocaml.d.ts.map +1 -0
- package/dist/extractors/ocaml.js +236 -0
- package/dist/extractors/ocaml.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 -83
- 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/extractors/zig.d.ts +9 -0
- package/dist/extractors/zig.d.ts.map +1 -0
- package/dist/extractors/zig.js +276 -0
- package/dist/extractors/zig.js.map +1 -0
- package/dist/features/ast.d.ts +2 -0
- package/dist/features/ast.d.ts.map +1 -1
- package/dist/features/ast.js +9 -24
- 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 +47 -3
- 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 +72 -61
- 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/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 +45 -36
- 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 +182 -7
- 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-dart.wasm +0 -0
- package/grammars/tree-sitter-elixir.wasm +0 -0
- package/grammars/tree-sitter-haskell.wasm +0 -0
- package/grammars/tree-sitter-kotlin.wasm +0 -0
- package/grammars/tree-sitter-lua.wasm +0 -0
- package/grammars/tree-sitter-ocaml.wasm +0 -0
- package/grammars/tree-sitter-scala.wasm +0 -0
- package/grammars/tree-sitter-swift.wasm +0 -0
- package/grammars/tree-sitter-zig.wasm +0 -0
- package/package.json +19 -7
- package/src/ast-analysis/engine.ts +147 -138
- package/src/ast-analysis/visitors/ast-store-visitor.ts +15 -2
- package/src/ast-analysis/visitors/complexity-visitor.ts +11 -11
- package/src/db/connection.ts +90 -59
- package/src/db/index.ts +1 -0
- package/src/db/migrations.ts +36 -32
- 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/helpers.ts +12 -6
- package/src/domain/graph/builder/incremental.ts +3 -2
- package/src/domain/graph/builder/pipeline.ts +71 -3
- package/src/domain/graph/builder/stages/build-edges.ts +10 -75
- package/src/domain/graph/builder/stages/build-structure.ts +9 -7
- package/src/domain/graph/builder/stages/collect-files.ts +2 -2
- package/src/domain/graph/builder/stages/detect-changes.ts +7 -2
- package/src/domain/graph/builder/stages/finalize.ts +159 -125
- package/src/domain/graph/builder/stages/insert-nodes.ts +32 -21
- 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 +222 -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/dart.ts +304 -0
- package/src/extractors/elixir.ts +251 -0
- package/src/extractors/go.ts +152 -134
- package/src/extractors/haskell.ts +235 -0
- package/src/extractors/hcl.ts +6 -6
- package/src/extractors/helpers.ts +93 -1
- package/src/extractors/index.ts +12 -0
- package/src/extractors/java.ts +43 -48
- package/src/extractors/javascript.ts +328 -281
- package/src/extractors/kotlin.ts +293 -0
- package/src/extractors/lua.ts +169 -0
- package/src/extractors/ocaml.ts +259 -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 -85
- package/src/extractors/scala.ts +285 -0
- package/src/extractors/swift.ts +293 -0
- package/src/extractors/zig.ts +294 -0
- package/src/features/ast.ts +10 -25
- package/src/features/audit.ts +24 -20
- package/src/features/branch-compare.ts +51 -4
- package/src/features/cfg.ts +113 -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/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 +46 -37
- 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 +195 -7
|
@@ -5,8 +5,10 @@ import path from 'node:path';
|
|
|
5
5
|
import { getDatabase } from '../db/better-sqlite3.js';
|
|
6
6
|
import { buildGraph } from '../domain/graph/builder.js';
|
|
7
7
|
import { kindIcon } from '../domain/queries.js';
|
|
8
|
+
import { debug } from '../infrastructure/logger.js';
|
|
9
|
+
import { getNative, isNativeAvailable } from '../infrastructure/native.js';
|
|
8
10
|
import { isTestFile } from '../infrastructure/test-filter.js';
|
|
9
|
-
import type { EngineMode } from '../types.js';
|
|
11
|
+
import type { EngineMode, NativeDatabase } from '../types.js';
|
|
10
12
|
|
|
11
13
|
// ─── Git Helpers ────────────────────────────────────────────────────────
|
|
12
14
|
|
|
@@ -107,6 +109,18 @@ function loadSymbolsFromDb(
|
|
|
107
109
|
): Map<string, SymbolInfo> {
|
|
108
110
|
const Database = getDatabase();
|
|
109
111
|
const db = new Database(dbPath, { readonly: true });
|
|
112
|
+
|
|
113
|
+
// Try opening a NativeDatabase for batched fan metrics
|
|
114
|
+
let nativeDb: NativeDatabase | undefined;
|
|
115
|
+
if (isNativeAvailable()) {
|
|
116
|
+
try {
|
|
117
|
+
const native = getNative();
|
|
118
|
+
nativeDb = native.NativeDatabase.openReadonly(dbPath);
|
|
119
|
+
} catch (e) {
|
|
120
|
+
debug(`loadSymbolsFromDb: native path failed: ${(e as Error).message}`);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
110
124
|
try {
|
|
111
125
|
const symbols = new Map<string, SymbolInfo>();
|
|
112
126
|
|
|
@@ -132,6 +146,34 @@ function loadSymbolsFromDb(
|
|
|
132
146
|
end_line: number | null;
|
|
133
147
|
}>;
|
|
134
148
|
|
|
149
|
+
// Filter first, then batch fan metrics for all surviving rows
|
|
150
|
+
const filtered = noTests ? rows.filter((r) => !isTestFile(r.file)) : rows;
|
|
151
|
+
|
|
152
|
+
// ── Native fast path: batch all fan-in/fan-out in one napi call ──
|
|
153
|
+
if (nativeDb?.batchFanMetrics && filtered.length > 0) {
|
|
154
|
+
const nodeIds = filtered.map((r) => r.id);
|
|
155
|
+
const metrics = nativeDb.batchFanMetrics(nodeIds);
|
|
156
|
+
const metricsMap = new Map(metrics.map((m) => [m.nodeId, m]));
|
|
157
|
+
|
|
158
|
+
for (const row of filtered) {
|
|
159
|
+
const lineCount = row.end_line ? row.end_line - row.line + 1 : 0;
|
|
160
|
+
const m = metricsMap.get(row.id);
|
|
161
|
+
const key = makeSymbolKey(row.kind, row.file, row.name);
|
|
162
|
+
symbols.set(key, {
|
|
163
|
+
id: row.id,
|
|
164
|
+
name: row.name,
|
|
165
|
+
kind: row.kind,
|
|
166
|
+
file: row.file,
|
|
167
|
+
line: row.line,
|
|
168
|
+
lineCount,
|
|
169
|
+
fanIn: m?.fanIn ?? 0,
|
|
170
|
+
fanOut: m?.fanOut ?? 0,
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
return symbols;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// ── JS fallback ───────────────────────────────────────────────────
|
|
135
177
|
const fanInStmt = db.prepare(
|
|
136
178
|
`SELECT COUNT(*) AS cnt FROM edges WHERE target_id = ? AND kind = 'calls'`,
|
|
137
179
|
);
|
|
@@ -139,9 +181,7 @@ function loadSymbolsFromDb(
|
|
|
139
181
|
`SELECT COUNT(*) AS cnt FROM edges WHERE source_id = ? AND kind = 'calls'`,
|
|
140
182
|
);
|
|
141
183
|
|
|
142
|
-
for (const row of
|
|
143
|
-
if (noTests && isTestFile(row.file)) continue;
|
|
144
|
-
|
|
184
|
+
for (const row of filtered) {
|
|
145
185
|
const lineCount = row.end_line ? row.end_line - row.line + 1 : 0;
|
|
146
186
|
const fanIn = (fanInStmt.get(row.id) as { cnt: number }).cnt;
|
|
147
187
|
const fanOut = (fanOutStmt.get(row.id) as { cnt: number }).cnt;
|
|
@@ -162,6 +202,13 @@ function loadSymbolsFromDb(
|
|
|
162
202
|
return symbols;
|
|
163
203
|
} finally {
|
|
164
204
|
db.close();
|
|
205
|
+
if (nativeDb) {
|
|
206
|
+
try {
|
|
207
|
+
nativeDb.close();
|
|
208
|
+
} catch {
|
|
209
|
+
/* already closed */
|
|
210
|
+
}
|
|
211
|
+
}
|
|
165
212
|
}
|
|
166
213
|
}
|
|
167
214
|
|
package/src/features/cfg.ts
CHANGED
|
@@ -275,16 +275,116 @@ function allCfgNative(fileSymbols: Map<string, FileSymbols>): boolean {
|
|
|
275
275
|
return hasCfgFile;
|
|
276
276
|
}
|
|
277
277
|
|
|
278
|
+
/** Persist native CFG data for a single file (fast path — no tree/visitor needed). */
|
|
279
|
+
function persistNativeFileCfg(
|
|
280
|
+
db: BetterSqlite3Database,
|
|
281
|
+
symbols: FileSymbols,
|
|
282
|
+
relPath: string,
|
|
283
|
+
insertBlock: ReturnType<BetterSqlite3Database['prepare']>,
|
|
284
|
+
insertEdge: ReturnType<BetterSqlite3Database['prepare']>,
|
|
285
|
+
): number {
|
|
286
|
+
let count = 0;
|
|
287
|
+
for (const def of symbols.definitions) {
|
|
288
|
+
if (def.kind !== 'function' && def.kind !== 'method') continue;
|
|
289
|
+
if (!def.line) continue;
|
|
290
|
+
|
|
291
|
+
const nodeId = getFunctionNodeId(db, def.name, relPath, def.line);
|
|
292
|
+
if (!nodeId) continue;
|
|
293
|
+
|
|
294
|
+
deleteCfgForNode(db, nodeId);
|
|
295
|
+
if (!def.cfg?.blocks?.length) continue;
|
|
296
|
+
|
|
297
|
+
persistCfg(
|
|
298
|
+
def.cfg as unknown as { blocks: CfgBuildBlock[]; edges: CfgBuildEdge[] },
|
|
299
|
+
nodeId,
|
|
300
|
+
insertBlock,
|
|
301
|
+
insertEdge,
|
|
302
|
+
);
|
|
303
|
+
count++;
|
|
304
|
+
}
|
|
305
|
+
return count;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
/** Resolve CFG for a definition from native data or visitor results. */
|
|
309
|
+
function resolveCfgForDef(
|
|
310
|
+
def: Definition,
|
|
311
|
+
visitorCfgByLine: Map<number, VisitorCfgResult[]> | null,
|
|
312
|
+
): { blocks: CfgBuildBlock[]; edges: CfgBuildEdge[] } | null {
|
|
313
|
+
if (def.cfg?.blocks?.length) {
|
|
314
|
+
return def.cfg as unknown as { blocks: CfgBuildBlock[]; edges: CfgBuildEdge[] };
|
|
315
|
+
}
|
|
316
|
+
if (!visitorCfgByLine) return null;
|
|
317
|
+
const candidates = visitorCfgByLine.get(def.line);
|
|
318
|
+
if (!candidates) return null;
|
|
319
|
+
const r =
|
|
320
|
+
candidates.length === 1
|
|
321
|
+
? candidates[0]
|
|
322
|
+
: (candidates.find((c) => {
|
|
323
|
+
const n = c.funcNode.childForFieldName?.('name');
|
|
324
|
+
return n && n.text === def.name;
|
|
325
|
+
}) ?? candidates[0]);
|
|
326
|
+
return r ? { blocks: r.blocks, edges: r.edges } : null;
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
/** Persist CFG data for a single file using visitor/native hybrid path. */
|
|
330
|
+
function persistVisitorFileCfg(
|
|
331
|
+
db: BetterSqlite3Database,
|
|
332
|
+
symbols: FileSymbols,
|
|
333
|
+
relPath: string,
|
|
334
|
+
rootDir: string,
|
|
335
|
+
extToLang: Map<string, string>,
|
|
336
|
+
parsers: unknown,
|
|
337
|
+
getParserFn: unknown,
|
|
338
|
+
insertBlock: ReturnType<BetterSqlite3Database['prepare']>,
|
|
339
|
+
insertEdge: ReturnType<BetterSqlite3Database['prepare']>,
|
|
340
|
+
): number {
|
|
341
|
+
const treeLang = getTreeAndLang(symbols, relPath, rootDir, extToLang, parsers, getParserFn);
|
|
342
|
+
if (!treeLang) return 0;
|
|
343
|
+
const { tree, langId } = treeLang;
|
|
344
|
+
|
|
345
|
+
const cfgRules = CFG_RULES.get(langId);
|
|
346
|
+
if (!cfgRules) return 0;
|
|
347
|
+
|
|
348
|
+
const visitorCfgByLine = buildVisitorCfgMap(tree, cfgRules, symbols, langId);
|
|
349
|
+
let count = 0;
|
|
350
|
+
|
|
351
|
+
for (const def of symbols.definitions) {
|
|
352
|
+
if (def.kind !== 'function' && def.kind !== 'method') continue;
|
|
353
|
+
if (!def.line) continue;
|
|
354
|
+
|
|
355
|
+
const nodeId = getFunctionNodeId(db, def.name, relPath, def.line);
|
|
356
|
+
if (!nodeId) continue;
|
|
357
|
+
|
|
358
|
+
const cfg = resolveCfgForDef(def, visitorCfgByLine);
|
|
359
|
+
deleteCfgForNode(db, nodeId);
|
|
360
|
+
if (!cfg || cfg.blocks.length === 0) continue;
|
|
361
|
+
|
|
362
|
+
persistCfg(cfg, nodeId, insertBlock, insertEdge);
|
|
363
|
+
count++;
|
|
364
|
+
}
|
|
365
|
+
return count;
|
|
366
|
+
}
|
|
367
|
+
|
|
278
368
|
export async function buildCFGData(
|
|
279
369
|
db: BetterSqlite3Database,
|
|
280
370
|
fileSymbols: Map<string, FileSymbols>,
|
|
281
371
|
rootDir: string,
|
|
282
|
-
_engineOpts?:
|
|
372
|
+
_engineOpts?: {
|
|
373
|
+
nativeDb?: { bulkInsertCfg?(entries: Array<Record<string, unknown>>): number };
|
|
374
|
+
suspendJsDb?: () => void;
|
|
375
|
+
resumeJsDb?: () => void;
|
|
376
|
+
},
|
|
283
377
|
): Promise<void> {
|
|
284
378
|
// Fast path: when all function/method defs already have native CFG data,
|
|
285
379
|
// skip WASM parser init, tree parsing, and JS visitor entirely — just persist.
|
|
286
380
|
const allNative = allCfgNative(fileSymbols);
|
|
287
381
|
|
|
382
|
+
// NOTE: nativeDb.bulkInsertCfg is intentionally NOT used here.
|
|
383
|
+
// The CFG path requires delete-before-insert (deleteCfgForNode) which creates
|
|
384
|
+
// a dual-connection WAL conflict when deletes go through JS (better-sqlite3)
|
|
385
|
+
// and inserts go through native (rusqlite). The JS-only persistNativeFileCfg
|
|
386
|
+
// path below handles both on a single connection safely.
|
|
387
|
+
|
|
288
388
|
const extToLang = buildExtToLangMap();
|
|
289
389
|
let parsers: unknown = null;
|
|
290
390
|
let getParserFn: unknown = null;
|
|
@@ -308,74 +408,22 @@ export async function buildCFGData(
|
|
|
308
408
|
const ext = path.extname(relPath).toLowerCase();
|
|
309
409
|
if (!CFG_EXTENSIONS.has(ext)) continue;
|
|
310
410
|
|
|
311
|
-
// Native fast path: skip tree/visitor setup when all CFG is pre-computed.
|
|
312
|
-
// Only apply to files without _tree — files with _tree were WASM-parsed
|
|
313
|
-
// and need the slow path (visitor) to compute CFG.
|
|
314
411
|
if (allNative && !symbols._tree) {
|
|
315
|
-
|
|
316
|
-
if (def.kind !== 'function' && def.kind !== 'method') continue;
|
|
317
|
-
if (!def.line) continue;
|
|
318
|
-
|
|
319
|
-
const nodeId = getFunctionNodeId(db, def.name, relPath, def.line);
|
|
320
|
-
if (!nodeId) continue;
|
|
321
|
-
|
|
322
|
-
// Always delete stale CFG rows (handles body-removed case)
|
|
323
|
-
deleteCfgForNode(db, nodeId);
|
|
324
|
-
if (!def.cfg?.blocks?.length) continue;
|
|
325
|
-
|
|
326
|
-
persistCfg(
|
|
327
|
-
def.cfg as unknown as { blocks: CfgBuildBlock[]; edges: CfgBuildEdge[] },
|
|
328
|
-
nodeId,
|
|
329
|
-
insertBlock,
|
|
330
|
-
insertEdge,
|
|
331
|
-
);
|
|
332
|
-
analyzed++;
|
|
333
|
-
}
|
|
412
|
+
analyzed += persistNativeFileCfg(db, symbols, relPath, insertBlock, insertEdge);
|
|
334
413
|
continue;
|
|
335
414
|
}
|
|
336
415
|
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
for (const def of symbols.definitions) {
|
|
350
|
-
if (def.kind !== 'function' && def.kind !== 'method') continue;
|
|
351
|
-
if (!def.line) continue;
|
|
352
|
-
|
|
353
|
-
const nodeId = getFunctionNodeId(db, def.name, relPath, def.line);
|
|
354
|
-
if (!nodeId) continue;
|
|
355
|
-
|
|
356
|
-
let cfg: { blocks: CfgBuildBlock[]; edges: CfgBuildEdge[] } | null = null;
|
|
357
|
-
if (def.cfg?.blocks?.length) {
|
|
358
|
-
cfg = def.cfg as unknown as { blocks: CfgBuildBlock[]; edges: CfgBuildEdge[] };
|
|
359
|
-
} else if (visitorCfgByLine) {
|
|
360
|
-
const candidates = visitorCfgByLine.get(def.line);
|
|
361
|
-
const r = !candidates
|
|
362
|
-
? undefined
|
|
363
|
-
: candidates.length === 1
|
|
364
|
-
? candidates[0]
|
|
365
|
-
: (candidates.find((c) => {
|
|
366
|
-
const n = c.funcNode.childForFieldName?.('name');
|
|
367
|
-
return n && n.text === def.name;
|
|
368
|
-
}) ?? candidates[0]);
|
|
369
|
-
if (r) cfg = { blocks: r.blocks, edges: r.edges };
|
|
370
|
-
}
|
|
371
|
-
|
|
372
|
-
// Always purge stale rows (handles body-removed case)
|
|
373
|
-
deleteCfgForNode(db, nodeId);
|
|
374
|
-
if (!cfg || cfg.blocks.length === 0) continue;
|
|
375
|
-
|
|
376
|
-
persistCfg(cfg, nodeId, insertBlock, insertEdge);
|
|
377
|
-
analyzed++;
|
|
378
|
-
}
|
|
416
|
+
analyzed += persistVisitorFileCfg(
|
|
417
|
+
db,
|
|
418
|
+
symbols,
|
|
419
|
+
relPath,
|
|
420
|
+
rootDir,
|
|
421
|
+
extToLang,
|
|
422
|
+
parsers,
|
|
423
|
+
getParserFn,
|
|
424
|
+
insertBlock,
|
|
425
|
+
insertEdge,
|
|
426
|
+
);
|
|
379
427
|
}
|
|
380
428
|
});
|
|
381
429
|
|
package/src/features/check.ts
CHANGED
|
@@ -291,6 +291,85 @@ interface CheckOpts {
|
|
|
291
291
|
config?: CodegraphConfig;
|
|
292
292
|
}
|
|
293
293
|
|
|
294
|
+
/** Walk up from repoRoot to find the nearest .git directory. */
|
|
295
|
+
function findGitRoot(repoRoot: string): string | null {
|
|
296
|
+
let dir = repoRoot;
|
|
297
|
+
while (dir) {
|
|
298
|
+
if (fs.existsSync(path.join(dir, '.git'))) return dir;
|
|
299
|
+
const parent = path.dirname(dir);
|
|
300
|
+
if (parent === dir) break;
|
|
301
|
+
dir = parent;
|
|
302
|
+
}
|
|
303
|
+
return null;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
/** Run git diff and return the raw output string. */
|
|
307
|
+
function getGitDiff(repoRoot: string, opts: { staged?: boolean; ref?: string }): string {
|
|
308
|
+
const args = opts.staged
|
|
309
|
+
? ['diff', '--cached', '--unified=0', '--no-color']
|
|
310
|
+
: ['diff', opts.ref || 'HEAD', '--unified=0', '--no-color'];
|
|
311
|
+
return execFileSync('git', args, {
|
|
312
|
+
cwd: repoRoot,
|
|
313
|
+
encoding: 'utf-8',
|
|
314
|
+
maxBuffer: 10 * 1024 * 1024,
|
|
315
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
316
|
+
});
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
/** Resolve which check predicates are enabled from opts + config. */
|
|
320
|
+
function resolveCheckFlags(opts: CheckOpts, config: CodegraphConfig) {
|
|
321
|
+
const checkConfig = config.check || ({} as CodegraphConfig['check']);
|
|
322
|
+
return {
|
|
323
|
+
enableCycles: opts.cycles ?? checkConfig.cycles ?? true,
|
|
324
|
+
enableSignatures: opts.signatures ?? checkConfig.signatures ?? true,
|
|
325
|
+
enableBoundaries: opts.boundaries ?? checkConfig.boundaries ?? true,
|
|
326
|
+
blastRadiusThreshold: opts.blastRadius ?? checkConfig.blastRadius ?? null,
|
|
327
|
+
};
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
/** Run all enabled check predicates and return the results. */
|
|
331
|
+
function runPredicates(
|
|
332
|
+
db: BetterSqlite3Database,
|
|
333
|
+
diff: ParsedDiff,
|
|
334
|
+
flags: ReturnType<typeof resolveCheckFlags>,
|
|
335
|
+
repoRoot: string,
|
|
336
|
+
noTests: boolean,
|
|
337
|
+
maxDepth: number,
|
|
338
|
+
): PredicateResult[] {
|
|
339
|
+
const changedFiles = new Set(diff.changedRanges.keys());
|
|
340
|
+
const predicates: PredicateResult[] = [];
|
|
341
|
+
|
|
342
|
+
if (flags.enableCycles) {
|
|
343
|
+
predicates.push({ name: 'cycles', ...checkNoNewCycles(db, changedFiles, noTests) });
|
|
344
|
+
}
|
|
345
|
+
if (flags.blastRadiusThreshold != null) {
|
|
346
|
+
predicates.push({
|
|
347
|
+
name: 'blast-radius',
|
|
348
|
+
...checkMaxBlastRadius(db, diff.changedRanges, flags.blastRadiusThreshold, noTests, maxDepth),
|
|
349
|
+
});
|
|
350
|
+
}
|
|
351
|
+
if (flags.enableSignatures) {
|
|
352
|
+
predicates.push({
|
|
353
|
+
name: 'signatures',
|
|
354
|
+
...checkNoSignatureChanges(db, diff.oldRanges, noTests),
|
|
355
|
+
});
|
|
356
|
+
}
|
|
357
|
+
if (flags.enableBoundaries) {
|
|
358
|
+
predicates.push({
|
|
359
|
+
name: 'boundaries',
|
|
360
|
+
...checkNoBoundaryViolations(db, changedFiles, repoRoot, noTests),
|
|
361
|
+
});
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
return predicates;
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
const EMPTY_CHECK: CheckResult = {
|
|
368
|
+
predicates: [],
|
|
369
|
+
summary: { total: 0, passed: 0, failed: 0, changedFiles: 0, newFiles: 0 },
|
|
370
|
+
passed: true,
|
|
371
|
+
};
|
|
372
|
+
|
|
294
373
|
export function checkData(customDbPath: string | undefined, opts: CheckOpts = {}): CheckResult {
|
|
295
374
|
const db = openReadonlyOrFail(customDbPath);
|
|
296
375
|
|
|
@@ -301,89 +380,26 @@ export function checkData(customDbPath: string | undefined, opts: CheckOpts = {}
|
|
|
301
380
|
const maxDepth = opts.depth || 3;
|
|
302
381
|
|
|
303
382
|
const config = opts.config || loadConfig(repoRoot);
|
|
304
|
-
const
|
|
305
|
-
|
|
306
|
-
const
|
|
307
|
-
|
|
308
|
-
const enableBoundaries = opts.boundaries ?? checkConfig.boundaries ?? true;
|
|
309
|
-
const blastRadiusThreshold = opts.blastRadius ?? checkConfig.blastRadius ?? null;
|
|
310
|
-
|
|
311
|
-
let checkDir = repoRoot;
|
|
312
|
-
let isGitRepo = false;
|
|
313
|
-
while (checkDir) {
|
|
314
|
-
if (fs.existsSync(path.join(checkDir, '.git'))) {
|
|
315
|
-
isGitRepo = true;
|
|
316
|
-
break;
|
|
317
|
-
}
|
|
318
|
-
const parent = path.dirname(checkDir);
|
|
319
|
-
if (parent === checkDir) break;
|
|
320
|
-
checkDir = parent;
|
|
321
|
-
}
|
|
322
|
-
if (!isGitRepo) {
|
|
383
|
+
const flags = resolveCheckFlags(opts, config);
|
|
384
|
+
|
|
385
|
+
const gitRoot = findGitRoot(repoRoot);
|
|
386
|
+
if (!gitRoot) {
|
|
323
387
|
return { error: `Not a git repository: ${repoRoot}` };
|
|
324
388
|
}
|
|
325
389
|
|
|
326
390
|
let diffOutput: string;
|
|
327
391
|
try {
|
|
328
|
-
|
|
329
|
-
? ['diff', '--cached', '--unified=0', '--no-color']
|
|
330
|
-
: ['diff', opts.ref || 'HEAD', '--unified=0', '--no-color'];
|
|
331
|
-
diffOutput = execFileSync('git', args, {
|
|
332
|
-
cwd: repoRoot,
|
|
333
|
-
encoding: 'utf-8',
|
|
334
|
-
maxBuffer: 10 * 1024 * 1024,
|
|
335
|
-
stdio: ['pipe', 'pipe', 'pipe'],
|
|
336
|
-
});
|
|
392
|
+
diffOutput = getGitDiff(repoRoot, opts);
|
|
337
393
|
} catch (e) {
|
|
338
394
|
return { error: `Failed to run git diff: ${(e as Error).message}` };
|
|
339
395
|
}
|
|
340
396
|
|
|
341
|
-
if (!diffOutput.trim())
|
|
342
|
-
return {
|
|
343
|
-
predicates: [],
|
|
344
|
-
summary: { total: 0, passed: 0, failed: 0, changedFiles: 0, newFiles: 0 },
|
|
345
|
-
passed: true,
|
|
346
|
-
};
|
|
347
|
-
}
|
|
348
|
-
|
|
349
|
-
const { changedRanges, oldRanges, newFiles } = parseDiffOutput(diffOutput);
|
|
350
|
-
if (changedRanges.size === 0) {
|
|
351
|
-
return {
|
|
352
|
-
predicates: [],
|
|
353
|
-
summary: { total: 0, passed: 0, failed: 0, changedFiles: 0, newFiles: 0 },
|
|
354
|
-
passed: true,
|
|
355
|
-
};
|
|
356
|
-
}
|
|
357
|
-
|
|
358
|
-
const changedFiles = new Set(changedRanges.keys());
|
|
397
|
+
if (!diffOutput.trim()) return EMPTY_CHECK;
|
|
359
398
|
|
|
360
|
-
const
|
|
399
|
+
const diff = parseDiffOutput(diffOutput);
|
|
400
|
+
if (diff.changedRanges.size === 0) return EMPTY_CHECK;
|
|
361
401
|
|
|
362
|
-
|
|
363
|
-
const result = checkNoNewCycles(db, changedFiles, noTests);
|
|
364
|
-
predicates.push({ name: 'cycles', ...result });
|
|
365
|
-
}
|
|
366
|
-
|
|
367
|
-
if (blastRadiusThreshold != null) {
|
|
368
|
-
const result = checkMaxBlastRadius(
|
|
369
|
-
db,
|
|
370
|
-
changedRanges,
|
|
371
|
-
blastRadiusThreshold,
|
|
372
|
-
noTests,
|
|
373
|
-
maxDepth,
|
|
374
|
-
);
|
|
375
|
-
predicates.push({ name: 'blast-radius', ...result });
|
|
376
|
-
}
|
|
377
|
-
|
|
378
|
-
if (enableSignatures) {
|
|
379
|
-
const result = checkNoSignatureChanges(db, oldRanges, noTests);
|
|
380
|
-
predicates.push({ name: 'signatures', ...result });
|
|
381
|
-
}
|
|
382
|
-
|
|
383
|
-
if (enableBoundaries) {
|
|
384
|
-
const result = checkNoBoundaryViolations(db, changedFiles, repoRoot, noTests);
|
|
385
|
-
predicates.push({ name: 'boundaries', ...result });
|
|
386
|
-
}
|
|
402
|
+
const predicates = runPredicates(db, diff, flags, repoRoot, noTests, maxDepth);
|
|
387
403
|
|
|
388
404
|
const passedCount = predicates.filter((p) => p.passed).length;
|
|
389
405
|
const failedCount = predicates.length - passedCount;
|
|
@@ -394,8 +410,8 @@ export function checkData(customDbPath: string | undefined, opts: CheckOpts = {}
|
|
|
394
410
|
total: predicates.length,
|
|
395
411
|
passed: passedCount,
|
|
396
412
|
failed: failedCount,
|
|
397
|
-
changedFiles:
|
|
398
|
-
newFiles: newFiles.size,
|
|
413
|
+
changedFiles: diff.changedRanges.size,
|
|
414
|
+
newFiles: diff.newFiles.size,
|
|
399
415
|
},
|
|
400
416
|
passed: failedCount === 0,
|
|
401
417
|
};
|