@optave/codegraph 3.5.0 → 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 +35 -14
- 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 +164 -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 +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/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/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 +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/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-kotlin.wasm +0 -0
- package/grammars/tree-sitter-scala.wasm +0 -0
- package/grammars/tree-sitter-swift.wasm +0 -0
- package/package.json +13 -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 +168 -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 +328 -281
- 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 -85
- package/src/extractors/scala.ts +285 -0
- package/src/extractors/swift.ts +293 -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 +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/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 +189 -7
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@optave/codegraph",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.6.0",
|
|
4
4
|
"description": "Local code graph CLI — parse codebases with tree-sitter, build dependency graphs, query them",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -131,12 +131,12 @@
|
|
|
131
131
|
},
|
|
132
132
|
"optionalDependencies": {
|
|
133
133
|
"@modelcontextprotocol/sdk": "^1.0.0",
|
|
134
|
-
"@optave/codegraph-darwin-arm64": "3.
|
|
135
|
-
"@optave/codegraph-darwin-x64": "3.
|
|
136
|
-
"@optave/codegraph-linux-arm64-gnu": "3.
|
|
137
|
-
"@optave/codegraph-linux-x64-gnu": "3.
|
|
138
|
-
"@optave/codegraph-linux-x64-musl": "3.
|
|
139
|
-
"@optave/codegraph-win32-x64-msvc": "3.
|
|
134
|
+
"@optave/codegraph-darwin-arm64": "3.6.0",
|
|
135
|
+
"@optave/codegraph-darwin-x64": "3.6.0",
|
|
136
|
+
"@optave/codegraph-linux-arm64-gnu": "3.6.0",
|
|
137
|
+
"@optave/codegraph-linux-x64-gnu": "3.6.0",
|
|
138
|
+
"@optave/codegraph-linux-x64-musl": "3.6.0",
|
|
139
|
+
"@optave/codegraph-win32-x64-msvc": "3.6.0"
|
|
140
140
|
},
|
|
141
141
|
"devDependencies": {
|
|
142
142
|
"@biomejs/biome": "^2.4.4",
|
|
@@ -148,15 +148,21 @@
|
|
|
148
148
|
"@vitest/coverage-v8": "^4.0.18",
|
|
149
149
|
"commit-and-tag-version": "^12.5",
|
|
150
150
|
"husky": "^9.1",
|
|
151
|
+
"tree-sitter-bash": "^0.25.1",
|
|
152
|
+
"tree-sitter-c": "^0.24.1",
|
|
151
153
|
"tree-sitter-c-sharp": "^0.23.1",
|
|
152
154
|
"tree-sitter-cli": "^0.26.5",
|
|
155
|
+
"tree-sitter-cpp": "^0.23.4",
|
|
153
156
|
"tree-sitter-go": "^0.25.0",
|
|
154
157
|
"tree-sitter-java": "^0.23.5",
|
|
155
158
|
"tree-sitter-javascript": "^0.25.0",
|
|
159
|
+
"tree-sitter-kotlin": "^0.3.8",
|
|
156
160
|
"tree-sitter-php": "^0.24.2",
|
|
157
161
|
"tree-sitter-python": "^0.25.0",
|
|
158
162
|
"tree-sitter-ruby": "^0.23.1",
|
|
159
163
|
"tree-sitter-rust": "^0.24.0",
|
|
164
|
+
"tree-sitter-scala": "^0.24.0",
|
|
165
|
+
"tree-sitter-swift": "^0.7.1",
|
|
160
166
|
"tree-sitter-typescript": "^0.23.2",
|
|
161
167
|
"typescript": "^6.0.2",
|
|
162
168
|
"vitest": "^4.0.18"
|
|
@@ -102,11 +102,12 @@ async function ensureWasmTreesIfNeeded(
|
|
|
102
102
|
opts: AnalysisOpts,
|
|
103
103
|
rootDir: string,
|
|
104
104
|
): Promise<void> {
|
|
105
|
+
const doAst = opts.ast !== false;
|
|
105
106
|
const doComplexity = opts.complexity !== false;
|
|
106
107
|
const doCfg = opts.cfg !== false;
|
|
107
108
|
const doDataflow = opts.dataflow !== false;
|
|
108
109
|
|
|
109
|
-
if (!doComplexity && !doCfg && !doDataflow) return;
|
|
110
|
+
if (!doAst && !doComplexity && !doCfg && !doDataflow) return;
|
|
110
111
|
|
|
111
112
|
let needsWasmTrees = false;
|
|
112
113
|
for (const [relPath, symbols] of fileSymbols) {
|
|
@@ -131,6 +132,8 @@ async function ensureWasmTreesIfNeeded(
|
|
|
131
132
|
d.endLine > d.line &&
|
|
132
133
|
!d.name.includes('.');
|
|
133
134
|
|
|
135
|
+
// AST: need tree when native didn't provide non-call astNodes
|
|
136
|
+
const needsAst = doAst && !Array.isArray(symbols.astNodes) && WALK_EXTENSIONS.has(ext);
|
|
134
137
|
const needsComplexity =
|
|
135
138
|
doComplexity &&
|
|
136
139
|
COMPLEXITY_EXTENSIONS.has(ext) &&
|
|
@@ -141,7 +144,7 @@ async function ensureWasmTreesIfNeeded(
|
|
|
141
144
|
defs.some((d) => hasFuncBody(d) && d.cfg !== null && !Array.isArray(d.cfg?.blocks));
|
|
142
145
|
const needsDataflow = doDataflow && !symbols.dataflow && DATAFLOW_EXTENSIONS.has(ext);
|
|
143
146
|
|
|
144
|
-
if (needsComplexity || needsCfg || needsDataflow) {
|
|
147
|
+
if (needsAst || needsComplexity || needsCfg || needsDataflow) {
|
|
145
148
|
needsWasmTrees = true;
|
|
146
149
|
break;
|
|
147
150
|
}
|
|
@@ -159,6 +162,80 @@ async function ensureWasmTreesIfNeeded(
|
|
|
159
162
|
|
|
160
163
|
// ─── Per-file visitor setup ─────────────────────────────────────────────
|
|
161
164
|
|
|
165
|
+
/** Check if a definition has a real function body (not a type signature). */
|
|
166
|
+
function hasFuncBody(d: {
|
|
167
|
+
name: string;
|
|
168
|
+
kind: string;
|
|
169
|
+
line: number;
|
|
170
|
+
endLine?: number | null;
|
|
171
|
+
}): boolean {
|
|
172
|
+
return (
|
|
173
|
+
(d.kind === 'function' || d.kind === 'method') &&
|
|
174
|
+
d.line > 0 &&
|
|
175
|
+
d.endLine != null &&
|
|
176
|
+
d.endLine > d.line &&
|
|
177
|
+
!d.name.includes('.')
|
|
178
|
+
);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/** Set up AST-store visitor if applicable. */
|
|
182
|
+
function setupAstVisitor(
|
|
183
|
+
db: BetterSqlite3Database,
|
|
184
|
+
relPath: string,
|
|
185
|
+
symbols: ExtractorOutput,
|
|
186
|
+
langId: string,
|
|
187
|
+
ext: string,
|
|
188
|
+
): Visitor | null {
|
|
189
|
+
const astTypeMap = AST_TYPE_MAPS.get(langId);
|
|
190
|
+
if (!astTypeMap || !WALK_EXTENSIONS.has(ext) || Array.isArray(symbols.astNodes)) return null;
|
|
191
|
+
const nodeIdMap = new Map<string, number>();
|
|
192
|
+
for (const row of bulkNodeIdsByFile(db, relPath)) {
|
|
193
|
+
nodeIdMap.set(`${row.name}|${row.kind}|${row.line}`, row.id);
|
|
194
|
+
}
|
|
195
|
+
return createAstStoreVisitor(astTypeMap, symbols.definitions || [], relPath, nodeIdMap);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/** Set up complexity visitor if any definitions need WASM complexity analysis. */
|
|
199
|
+
function setupComplexityVisitorForFile(
|
|
200
|
+
defs: Definition[],
|
|
201
|
+
langId: string,
|
|
202
|
+
walkerOpts: WalkOptions,
|
|
203
|
+
): Visitor | null {
|
|
204
|
+
const cRules = COMPLEXITY_RULES.get(langId);
|
|
205
|
+
if (!cRules) return null;
|
|
206
|
+
|
|
207
|
+
const hRules = HALSTEAD_RULES.get(langId);
|
|
208
|
+
const needsWasmComplexity = defs.some((d) => hasFuncBody(d) && !d.complexity);
|
|
209
|
+
if (!needsWasmComplexity) return null;
|
|
210
|
+
|
|
211
|
+
const visitor = createComplexityVisitor(cRules, hRules, { fileLevelWalk: true, langId });
|
|
212
|
+
|
|
213
|
+
for (const t of cRules.nestingNodes) walkerOpts.nestingNodeTypes?.add(t);
|
|
214
|
+
|
|
215
|
+
const dfRules = DATAFLOW_RULES.get(langId);
|
|
216
|
+
walkerOpts.getFunctionName = (node: TreeSitterNode): string | null => {
|
|
217
|
+
const nameNode = node.childForFieldName('name');
|
|
218
|
+
if (nameNode) return nameNode.text;
|
|
219
|
+
if (dfRules) return getFuncName(node, dfRules as any);
|
|
220
|
+
return null;
|
|
221
|
+
};
|
|
222
|
+
|
|
223
|
+
return visitor;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/** Set up CFG visitor if any definitions need WASM CFG analysis. */
|
|
227
|
+
function setupCfgVisitorForFile(defs: Definition[], langId: string, ext: string): Visitor | null {
|
|
228
|
+
const cfgRulesForLang = CFG_RULES.get(langId);
|
|
229
|
+
if (!cfgRulesForLang || !CFG_EXTENSIONS.has(ext)) return null;
|
|
230
|
+
|
|
231
|
+
const needsWasmCfg = defs.some(
|
|
232
|
+
(d) => hasFuncBody(d) && d.cfg !== null && !Array.isArray(d.cfg?.blocks),
|
|
233
|
+
);
|
|
234
|
+
if (!needsWasmCfg) return null;
|
|
235
|
+
|
|
236
|
+
return createCfgVisitor(cfgRulesForLang);
|
|
237
|
+
}
|
|
238
|
+
|
|
162
239
|
function setupVisitors(
|
|
163
240
|
db: BetterSqlite3Database,
|
|
164
241
|
relPath: string,
|
|
@@ -168,10 +245,6 @@ function setupVisitors(
|
|
|
168
245
|
): SetupResult {
|
|
169
246
|
const ext = path.extname(relPath).toLowerCase();
|
|
170
247
|
const defs = symbols.definitions || [];
|
|
171
|
-
const doAst = opts.ast !== false;
|
|
172
|
-
const doComplexity = opts.complexity !== false;
|
|
173
|
-
const doCfg = opts.cfg !== false;
|
|
174
|
-
const doDataflow = opts.dataflow !== false;
|
|
175
248
|
|
|
176
249
|
const visitors: Visitor[] = [];
|
|
177
250
|
const walkerOpts: WalkOptions = {
|
|
@@ -180,75 +253,19 @@ function setupVisitors(
|
|
|
180
253
|
getFunctionName: (_node: TreeSitterNode) => null,
|
|
181
254
|
};
|
|
182
255
|
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
const astTypeMap = AST_TYPE_MAPS.get(langId);
|
|
186
|
-
if (doAst && astTypeMap && WALK_EXTENSIONS.has(ext) && !Array.isArray(symbols.astNodes)) {
|
|
187
|
-
const nodeIdMap = new Map<string, number>();
|
|
188
|
-
for (const row of bulkNodeIdsByFile(db, relPath)) {
|
|
189
|
-
nodeIdMap.set(`${row.name}|${row.kind}|${row.line}`, row.id);
|
|
190
|
-
}
|
|
191
|
-
astVisitor = createAstStoreVisitor(astTypeMap, defs, relPath, nodeIdMap);
|
|
192
|
-
visitors.push(astVisitor);
|
|
193
|
-
}
|
|
256
|
+
const astVisitor = opts.ast !== false ? setupAstVisitor(db, relPath, symbols, langId, ext) : null;
|
|
257
|
+
if (astVisitor) visitors.push(astVisitor);
|
|
194
258
|
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
const hRules = HALSTEAD_RULES.get(langId);
|
|
199
|
-
if (doComplexity && cRules) {
|
|
200
|
-
// Only trigger WASM complexity for definitions with real function bodies.
|
|
201
|
-
// Interface/type property signatures (dotted names, single-line span)
|
|
202
|
-
// correctly lack native complexity data and should not trigger a fallback.
|
|
203
|
-
const needsWasmComplexity = defs.some(
|
|
204
|
-
(d) =>
|
|
205
|
-
(d.kind === 'function' || d.kind === 'method') &&
|
|
206
|
-
d.line > 0 &&
|
|
207
|
-
d.endLine != null &&
|
|
208
|
-
d.endLine > d.line &&
|
|
209
|
-
!d.name.includes('.') &&
|
|
210
|
-
!d.complexity,
|
|
211
|
-
);
|
|
212
|
-
if (needsWasmComplexity) {
|
|
213
|
-
complexityVisitor = createComplexityVisitor(cRules, hRules, { fileLevelWalk: true, langId });
|
|
214
|
-
visitors.push(complexityVisitor);
|
|
215
|
-
|
|
216
|
-
for (const t of cRules.nestingNodes) walkerOpts.nestingNodeTypes?.add(t);
|
|
217
|
-
|
|
218
|
-
const dfRules = DATAFLOW_RULES.get(langId);
|
|
219
|
-
walkerOpts.getFunctionName = (node: TreeSitterNode): string | null => {
|
|
220
|
-
const nameNode = node.childForFieldName('name');
|
|
221
|
-
if (nameNode) return nameNode.text;
|
|
222
|
-
if (dfRules) return getFuncName(node, dfRules as any);
|
|
223
|
-
return null;
|
|
224
|
-
};
|
|
225
|
-
}
|
|
226
|
-
}
|
|
259
|
+
const complexityVisitor =
|
|
260
|
+
opts.complexity !== false ? setupComplexityVisitorForFile(defs, langId, walkerOpts) : null;
|
|
261
|
+
if (complexityVisitor) visitors.push(complexityVisitor);
|
|
227
262
|
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
const cfgRulesForLang = CFG_RULES.get(langId);
|
|
231
|
-
if (doCfg && cfgRulesForLang && CFG_EXTENSIONS.has(ext)) {
|
|
232
|
-
const needsWasmCfg = defs.some(
|
|
233
|
-
(d) =>
|
|
234
|
-
(d.kind === 'function' || d.kind === 'method') &&
|
|
235
|
-
d.line > 0 &&
|
|
236
|
-
d.endLine != null &&
|
|
237
|
-
d.endLine > d.line &&
|
|
238
|
-
!d.name.includes('.') &&
|
|
239
|
-
d.cfg !== null &&
|
|
240
|
-
!Array.isArray(d.cfg?.blocks),
|
|
241
|
-
);
|
|
242
|
-
if (needsWasmCfg) {
|
|
243
|
-
cfgVisitor = createCfgVisitor(cfgRulesForLang);
|
|
244
|
-
visitors.push(cfgVisitor);
|
|
245
|
-
}
|
|
246
|
-
}
|
|
263
|
+
const cfgVisitor = opts.cfg !== false ? setupCfgVisitorForFile(defs, langId, ext) : null;
|
|
264
|
+
if (cfgVisitor) visitors.push(cfgVisitor);
|
|
247
265
|
|
|
248
|
-
// Dataflow visitor
|
|
249
266
|
let dataflowVisitor: Visitor | null = null;
|
|
250
267
|
const dfRules = DATAFLOW_RULES.get(langId);
|
|
251
|
-
if (
|
|
268
|
+
if (opts.dataflow !== false && dfRules && DATAFLOW_EXTENSIONS.has(ext) && !symbols.dataflow) {
|
|
252
269
|
dataflowVisitor = createDataflowVisitor(dfRules);
|
|
253
270
|
visitors.push(dataflowVisitor);
|
|
254
271
|
}
|
|
@@ -258,88 +275,80 @@ function setupVisitors(
|
|
|
258
275
|
|
|
259
276
|
// ─── Result storage helpers ─────────────────────────────────────────────
|
|
260
277
|
|
|
261
|
-
function
|
|
262
|
-
|
|
263
|
-
const
|
|
264
|
-
for (const r of
|
|
265
|
-
if (r.funcNode)
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
}
|
|
278
|
+
/** Index per-function results by start line for O(1) lookup. */
|
|
279
|
+
function indexByLine<T extends { funcNode: TreeSitterNode }>(results: T[]): Map<number, T[]> {
|
|
280
|
+
const byLine = new Map<number, T[]>();
|
|
281
|
+
for (const r of results) {
|
|
282
|
+
if (!r.funcNode) continue;
|
|
283
|
+
const line = r.funcNode.startPosition.row + 1;
|
|
284
|
+
if (!byLine.has(line)) byLine.set(line, []);
|
|
285
|
+
byLine.get(line)?.push(r);
|
|
270
286
|
}
|
|
287
|
+
return byLine;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
/** Find the best matching result for a definition by line + name. */
|
|
291
|
+
function matchResultToDef<T extends { funcNode: TreeSitterNode }>(
|
|
292
|
+
candidates: T[] | undefined,
|
|
293
|
+
defName: string,
|
|
294
|
+
): T | undefined {
|
|
295
|
+
if (!candidates) return undefined;
|
|
296
|
+
if (candidates.length === 1) return candidates[0];
|
|
297
|
+
return (
|
|
298
|
+
candidates.find((r) => {
|
|
299
|
+
const n = r.funcNode.childForFieldName('name');
|
|
300
|
+
return n && n.text === defName;
|
|
301
|
+
}) ?? candidates[0]
|
|
302
|
+
);
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
function storeComplexityResults(results: WalkResults, defs: Definition[], langId: string): void {
|
|
306
|
+
const byLine = indexByLine((results.complexity || []) as ComplexityFuncResult[]);
|
|
271
307
|
for (const def of defs) {
|
|
272
308
|
if ((def.kind === 'function' || def.kind === 'method') && def.line && !def.complexity) {
|
|
273
|
-
const
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
def.complexity = {
|
|
290
|
-
cognitive: metrics.cognitive,
|
|
291
|
-
cyclomatic: metrics.cyclomatic,
|
|
292
|
-
maxNesting: metrics.maxNesting,
|
|
293
|
-
halstead: metrics.halstead,
|
|
294
|
-
loc,
|
|
295
|
-
maintainabilityIndex: mi,
|
|
296
|
-
};
|
|
297
|
-
}
|
|
309
|
+
const funcResult = matchResultToDef(byLine.get(def.line), def.name);
|
|
310
|
+
if (!funcResult) continue;
|
|
311
|
+
const { metrics } = funcResult;
|
|
312
|
+
const loc = computeLOCMetrics(funcResult.funcNode, langId);
|
|
313
|
+
const volume = metrics.halstead ? metrics.halstead.volume : 0;
|
|
314
|
+
const commentRatio = loc.loc > 0 ? loc.commentLines / loc.loc : 0;
|
|
315
|
+
const mi = computeMaintainabilityIndex(volume, metrics.cyclomatic, loc.sloc, commentRatio);
|
|
316
|
+
def.complexity = {
|
|
317
|
+
cognitive: metrics.cognitive,
|
|
318
|
+
cyclomatic: metrics.cyclomatic,
|
|
319
|
+
maxNesting: metrics.maxNesting,
|
|
320
|
+
halstead: metrics.halstead,
|
|
321
|
+
loc,
|
|
322
|
+
maintainabilityIndex: mi,
|
|
323
|
+
};
|
|
298
324
|
}
|
|
299
325
|
}
|
|
300
326
|
}
|
|
301
327
|
|
|
302
328
|
function storeCfgResults(results: WalkResults, defs: Definition[]): void {
|
|
303
|
-
const
|
|
304
|
-
const cfgByLine = new Map<number, CfgFuncResult[]>();
|
|
305
|
-
for (const r of cfgResults) {
|
|
306
|
-
if (r.funcNode) {
|
|
307
|
-
const line = r.funcNode.startPosition.row + 1;
|
|
308
|
-
if (!cfgByLine.has(line)) cfgByLine.set(line, []);
|
|
309
|
-
cfgByLine.get(line)?.push(r);
|
|
310
|
-
}
|
|
311
|
-
}
|
|
329
|
+
const byLine = indexByLine((results.cfg || []) as CfgFuncResult[]);
|
|
312
330
|
for (const def of defs) {
|
|
313
331
|
if (
|
|
314
332
|
(def.kind === 'function' || def.kind === 'method') &&
|
|
315
333
|
def.line &&
|
|
316
334
|
!def.cfg?.blocks?.length
|
|
317
335
|
) {
|
|
318
|
-
const
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
def.
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
const volume = halstead ? halstead.volume : 0;
|
|
335
|
-
const commentRatio = loc && loc.loc > 0 ? loc.commentLines / loc.loc : 0;
|
|
336
|
-
def.complexity.maintainabilityIndex = computeMaintainabilityIndex(
|
|
337
|
-
volume,
|
|
338
|
-
cfgResult.cyclomatic,
|
|
339
|
-
loc?.sloc ?? 0,
|
|
340
|
-
commentRatio,
|
|
341
|
-
);
|
|
342
|
-
}
|
|
336
|
+
const cfgResult = matchResultToDef(byLine.get(def.line), def.name);
|
|
337
|
+
if (!cfgResult) continue;
|
|
338
|
+
def.cfg = { blocks: cfgResult.blocks, edges: cfgResult.edges };
|
|
339
|
+
|
|
340
|
+
// Override complexity's cyclomatic with CFG-derived value (single source of truth)
|
|
341
|
+
if (def.complexity && cfgResult.cyclomatic != null) {
|
|
342
|
+
def.complexity.cyclomatic = cfgResult.cyclomatic;
|
|
343
|
+
const { loc, halstead } = def.complexity;
|
|
344
|
+
const volume = halstead ? halstead.volume : 0;
|
|
345
|
+
const commentRatio = loc && loc.loc > 0 ? loc.commentLines / loc.loc : 0;
|
|
346
|
+
def.complexity.maintainabilityIndex = computeMaintainabilityIndex(
|
|
347
|
+
volume,
|
|
348
|
+
cfgResult.cyclomatic,
|
|
349
|
+
loc?.sloc ?? 0,
|
|
350
|
+
commentRatio,
|
|
351
|
+
);
|
|
343
352
|
}
|
|
344
353
|
}
|
|
345
354
|
}
|
|
@@ -14,7 +14,7 @@ interface AstStoreRow {
|
|
|
14
14
|
kind: string;
|
|
15
15
|
name: string | null | undefined;
|
|
16
16
|
text: string | null;
|
|
17
|
-
receiver: null;
|
|
17
|
+
receiver: string | null;
|
|
18
18
|
parentNodeId: number | null;
|
|
19
19
|
}
|
|
20
20
|
|
|
@@ -52,6 +52,14 @@ function extractCallName(node: TreeSitterNode): string {
|
|
|
52
52
|
return node.text?.split('(')[0] || '?';
|
|
53
53
|
}
|
|
54
54
|
|
|
55
|
+
/** Extract receiver for call expressions (e.g. "obj" in "obj.method()"). */
|
|
56
|
+
function extractCallReceiver(node: TreeSitterNode): string | null {
|
|
57
|
+
const fn = node.childForFieldName('function');
|
|
58
|
+
if (!fn || fn.type !== 'member_expression') return null;
|
|
59
|
+
const obj = fn.childForFieldName('object');
|
|
60
|
+
return obj ? obj.text : null;
|
|
61
|
+
}
|
|
62
|
+
|
|
55
63
|
function extractName(kind: string, node: TreeSitterNode): string | null {
|
|
56
64
|
if (kind === 'throw') {
|
|
57
65
|
for (let i = 0; i < node.childCount; i++) {
|
|
@@ -161,10 +169,12 @@ export function createAstStoreVisitor(
|
|
|
161
169
|
const line = node.startPosition.row + 1;
|
|
162
170
|
let name: string | null | undefined;
|
|
163
171
|
let text: string | null = null;
|
|
172
|
+
let receiver: string | null = null;
|
|
164
173
|
|
|
165
174
|
if (kind === 'call') {
|
|
166
175
|
name = extractCallName(node);
|
|
167
176
|
text = truncate(node.text);
|
|
177
|
+
receiver = extractCallReceiver(node);
|
|
168
178
|
} else if (kind === 'new') {
|
|
169
179
|
name = extractNewName(node);
|
|
170
180
|
text = truncate(node.text);
|
|
@@ -190,7 +200,7 @@ export function createAstStoreVisitor(
|
|
|
190
200
|
kind,
|
|
191
201
|
name,
|
|
192
202
|
text,
|
|
193
|
-
receiver
|
|
203
|
+
receiver,
|
|
194
204
|
parentNodeId: resolveParentNodeId(line),
|
|
195
205
|
});
|
|
196
206
|
|
|
@@ -201,6 +211,9 @@ export function createAstStoreVisitor(
|
|
|
201
211
|
name: 'ast-store',
|
|
202
212
|
|
|
203
213
|
enterNode(node: TreeSitterNode, _context: VisitorContext): EnterNodeResult | undefined {
|
|
214
|
+
// Guard: skip re-collection but do NOT skipChildren — node.id (memory address)
|
|
215
|
+
// can be reused by tree-sitter, so a collision would incorrectly suppress an
|
|
216
|
+
// unrelated subtree. The parent call's skipChildren handles the intended case.
|
|
204
217
|
if (matched.has(node.id)) return;
|
|
205
218
|
|
|
206
219
|
const kind = astTypeMap[node.type];
|
|
@@ -87,6 +87,16 @@ function classifyBranchNode(
|
|
|
87
87
|
}
|
|
88
88
|
}
|
|
89
89
|
|
|
90
|
+
function classifyLogicalOp(node: TreeSitterNode, cRules: AnyRules, acc: ComplexityAcc): void {
|
|
91
|
+
const op = node.child(1)?.type;
|
|
92
|
+
if (!op || !cRules.logicalOperators.has(op)) return;
|
|
93
|
+
acc.cyclomatic++;
|
|
94
|
+
const parent = node.parent;
|
|
95
|
+
const sameSequence =
|
|
96
|
+
parent != null && parent.type === cRules.logicalNodeType && parent.child(1)?.type === op;
|
|
97
|
+
if (!sameSequence) acc.cognitive++;
|
|
98
|
+
}
|
|
99
|
+
|
|
90
100
|
function classifyPlainElse(
|
|
91
101
|
node: TreeSitterNode,
|
|
92
102
|
type: string,
|
|
@@ -215,17 +225,7 @@ export function createComplexityVisitor(
|
|
|
215
225
|
if (nestingLevel > acc.maxNesting) acc.maxNesting = nestingLevel;
|
|
216
226
|
|
|
217
227
|
if (type === cRules.logicalNodeType) {
|
|
218
|
-
|
|
219
|
-
if (op && cRules.logicalOperators.has(op)) {
|
|
220
|
-
acc.cyclomatic++;
|
|
221
|
-
const parent = node.parent;
|
|
222
|
-
let sameSequence = false;
|
|
223
|
-
if (parent && parent.type === cRules.logicalNodeType) {
|
|
224
|
-
const parentOp = parent.child(1)?.type;
|
|
225
|
-
if (parentOp === op) sameSequence = true;
|
|
226
|
-
}
|
|
227
|
-
if (!sameSequence) acc.cognitive++;
|
|
228
|
-
}
|
|
228
|
+
classifyLogicalOp(node, cRules, acc);
|
|
229
229
|
}
|
|
230
230
|
|
|
231
231
|
if (type === cRules.optionalChainType) acc.cyclomatic++;
|
package/src/db/connection.ts
CHANGED
|
@@ -29,6 +29,23 @@ function getPackageVersion(): string {
|
|
|
29
29
|
/** Warn once per process when DB version mismatches the running codegraph version. */
|
|
30
30
|
let _versionWarned = false;
|
|
31
31
|
|
|
32
|
+
/** Check and warn (once) if the running codegraph version differs from the DB build version. */
|
|
33
|
+
function warnOnVersionMismatch(getBuildVersion: () => string | undefined | null): void {
|
|
34
|
+
if (_versionWarned) return;
|
|
35
|
+
_versionWarned = true;
|
|
36
|
+
try {
|
|
37
|
+
const buildVersion = getBuildVersion();
|
|
38
|
+
const currentVersion = getPackageVersion();
|
|
39
|
+
if (buildVersion && currentVersion && buildVersion !== currentVersion) {
|
|
40
|
+
warn(
|
|
41
|
+
`DB was built with codegraph v${buildVersion}, running v${currentVersion}. Consider: codegraph build --no-incremental`,
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
} catch {
|
|
45
|
+
// build_meta table may not exist in older DBs — silently ignore
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
32
49
|
/** DB instance with optional advisory lock path. */
|
|
33
50
|
export type LockedDatabase = BetterSqlite3Database & { __lockPath?: string };
|
|
34
51
|
|
|
@@ -81,11 +98,6 @@ export function _resetRepoRootCache(): void {
|
|
|
81
98
|
_cachedRepoRootCwd = undefined;
|
|
82
99
|
}
|
|
83
100
|
|
|
84
|
-
/** Reset the version warning flag (for testing). */
|
|
85
|
-
export function _resetVersionWarning(): void {
|
|
86
|
-
_versionWarned = false;
|
|
87
|
-
}
|
|
88
|
-
|
|
89
101
|
function isProcessAlive(pid: number): boolean {
|
|
90
102
|
try {
|
|
91
103
|
process.kill(pid, 0);
|
|
@@ -299,28 +311,41 @@ export function openReadonlyOrFail(customPath?: string): BetterSqlite3Database {
|
|
|
299
311
|
const Database = getDatabase();
|
|
300
312
|
const db = new Database(dbPath, { readonly: true }) as unknown as BetterSqlite3Database;
|
|
301
313
|
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
const buildVersion = row?.value;
|
|
309
|
-
const currentVersion = getPackageVersion();
|
|
310
|
-
if (buildVersion && currentVersion && buildVersion !== currentVersion) {
|
|
311
|
-
warn(
|
|
312
|
-
`DB was built with codegraph v${buildVersion}, running v${currentVersion}. Consider: codegraph build --no-incremental`,
|
|
313
|
-
);
|
|
314
|
-
}
|
|
315
|
-
} catch {
|
|
316
|
-
// build_meta table may not exist in older DBs — silently ignore
|
|
317
|
-
}
|
|
318
|
-
_versionWarned = true;
|
|
319
|
-
}
|
|
314
|
+
warnOnVersionMismatch(() => {
|
|
315
|
+
const row = db
|
|
316
|
+
.prepare<{ value: string }>('SELECT value FROM build_meta WHERE key = ?')
|
|
317
|
+
.get('codegraph_version');
|
|
318
|
+
return row?.value;
|
|
319
|
+
});
|
|
320
320
|
|
|
321
321
|
return db;
|
|
322
322
|
}
|
|
323
323
|
|
|
324
|
+
/** Open a NativeRepository via rusqlite, throwing DbError if the DB file is missing. */
|
|
325
|
+
function openRepoNative(customDbPath?: string): { repo: Repository; close(): void } {
|
|
326
|
+
const dbPath = findDbPath(customDbPath);
|
|
327
|
+
if (!fs.existsSync(dbPath)) {
|
|
328
|
+
throw new DbError(
|
|
329
|
+
`No codegraph database found at ${dbPath}.\nRun "codegraph build" first to analyze your codebase.`,
|
|
330
|
+
{ file: dbPath },
|
|
331
|
+
);
|
|
332
|
+
}
|
|
333
|
+
const native = getNative();
|
|
334
|
+
const ndb = native.NativeDatabase.openReadonly(dbPath);
|
|
335
|
+
try {
|
|
336
|
+
warnOnVersionMismatch(() => ndb.getBuildMeta('codegraph_version'));
|
|
337
|
+
return {
|
|
338
|
+
repo: new NativeRepository(ndb),
|
|
339
|
+
close() {
|
|
340
|
+
ndb.close();
|
|
341
|
+
},
|
|
342
|
+
};
|
|
343
|
+
} catch (innerErr) {
|
|
344
|
+
ndb.close();
|
|
345
|
+
throw innerErr;
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
|
|
324
349
|
/**
|
|
325
350
|
* Open a Repository from either an injected instance or a DB path.
|
|
326
351
|
*
|
|
@@ -345,42 +370,7 @@ export function openRepo(
|
|
|
345
370
|
// Try native rusqlite path first (Phase 6.14)
|
|
346
371
|
if (isNativeAvailable()) {
|
|
347
372
|
try {
|
|
348
|
-
|
|
349
|
-
if (!fs.existsSync(dbPath)) {
|
|
350
|
-
throw new DbError(
|
|
351
|
-
`No codegraph database found at ${dbPath}.\nRun "codegraph build" first to analyze your codebase.`,
|
|
352
|
-
{ file: dbPath },
|
|
353
|
-
);
|
|
354
|
-
}
|
|
355
|
-
const native = getNative();
|
|
356
|
-
const ndb = native.NativeDatabase.openReadonly(dbPath);
|
|
357
|
-
try {
|
|
358
|
-
// Version check (same logic as openReadonlyOrFail)
|
|
359
|
-
if (!_versionWarned) {
|
|
360
|
-
try {
|
|
361
|
-
const buildVersion = ndb.getBuildMeta('codegraph_version');
|
|
362
|
-
const currentVersion = getPackageVersion();
|
|
363
|
-
if (buildVersion && currentVersion && buildVersion !== currentVersion) {
|
|
364
|
-
warn(
|
|
365
|
-
`DB was built with codegraph v${buildVersion}, running v${currentVersion}. Consider: codegraph build --no-incremental`,
|
|
366
|
-
);
|
|
367
|
-
}
|
|
368
|
-
} catch {
|
|
369
|
-
// build_meta table may not exist in older DBs
|
|
370
|
-
}
|
|
371
|
-
_versionWarned = true;
|
|
372
|
-
}
|
|
373
|
-
|
|
374
|
-
return {
|
|
375
|
-
repo: new NativeRepository(ndb),
|
|
376
|
-
close() {
|
|
377
|
-
ndb.close();
|
|
378
|
-
},
|
|
379
|
-
};
|
|
380
|
-
} catch (innerErr) {
|
|
381
|
-
ndb.close();
|
|
382
|
-
throw innerErr;
|
|
383
|
-
}
|
|
373
|
+
return openRepoNative(customDbPath);
|
|
384
374
|
} catch (e) {
|
|
385
375
|
// Re-throw user-visible errors (e.g. DB not found) — only silently
|
|
386
376
|
// fall back for native-engine failures (e.g. incompatible native binary).
|
|
@@ -399,3 +389,44 @@ export function openRepo(
|
|
|
399
389
|
},
|
|
400
390
|
};
|
|
401
391
|
}
|
|
392
|
+
|
|
393
|
+
/**
|
|
394
|
+
* Open a readonly DB with an optional NativeDatabase alongside it.
|
|
395
|
+
*
|
|
396
|
+
* Returns the better-sqlite3 handle (for backwards compat) plus an optional
|
|
397
|
+
* NativeDatabase for modules that can use batched Rust query methods.
|
|
398
|
+
* Callers should use nativeDb when available and fall back to db.prepare().
|
|
399
|
+
*/
|
|
400
|
+
export function openReadonlyWithNative(customPath?: string): {
|
|
401
|
+
db: BetterSqlite3Database;
|
|
402
|
+
nativeDb: NativeDatabase | undefined;
|
|
403
|
+
close(): void;
|
|
404
|
+
} {
|
|
405
|
+
const db = openReadonlyOrFail(customPath);
|
|
406
|
+
|
|
407
|
+
let nativeDb: NativeDatabase | undefined;
|
|
408
|
+
if (isNativeAvailable()) {
|
|
409
|
+
try {
|
|
410
|
+
const dbPath = findDbPath(customPath);
|
|
411
|
+
const native = getNative();
|
|
412
|
+
nativeDb = native.NativeDatabase.openReadonly(dbPath);
|
|
413
|
+
} catch (e) {
|
|
414
|
+
debug(`openReadonlyWithNative: native path failed: ${(e as Error).message}`);
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
return {
|
|
419
|
+
db,
|
|
420
|
+
nativeDb,
|
|
421
|
+
close() {
|
|
422
|
+
db.close();
|
|
423
|
+
if (nativeDb) {
|
|
424
|
+
try {
|
|
425
|
+
nativeDb.close();
|
|
426
|
+
} catch {
|
|
427
|
+
// already closed or not closeable
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
},
|
|
431
|
+
};
|
|
432
|
+
}
|