@optave/codegraph 3.8.0 → 3.9.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 +13 -8
- package/dist/ast-analysis/engine.d.ts.map +1 -1
- package/dist/ast-analysis/engine.js +137 -86
- package/dist/ast-analysis/engine.js.map +1 -1
- package/dist/ast-analysis/metrics.d.ts +0 -3
- package/dist/ast-analysis/metrics.d.ts.map +1 -1
- package/dist/ast-analysis/metrics.js +30 -13
- package/dist/ast-analysis/metrics.js.map +1 -1
- package/dist/ast-analysis/shared.d.ts.map +1 -1
- package/dist/ast-analysis/shared.js +24 -19
- package/dist/ast-analysis/shared.js.map +1 -1
- package/dist/ast-analysis/visitor-utils.d.ts.map +1 -1
- package/dist/ast-analysis/visitor-utils.js +55 -39
- package/dist/ast-analysis/visitor-utils.js.map +1 -1
- package/dist/ast-analysis/visitor.d.ts.map +1 -1
- package/dist/ast-analysis/visitor.js +91 -70
- package/dist/ast-analysis/visitor.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 +54 -58
- 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 +81 -39
- package/dist/ast-analysis/visitors/complexity-visitor.js.map +1 -1
- package/dist/ast-analysis/visitors/dataflow-visitor.d.ts.map +1 -1
- package/dist/ast-analysis/visitors/dataflow-visitor.js +57 -38
- package/dist/ast-analysis/visitors/dataflow-visitor.js.map +1 -1
- package/dist/cli/commands/branch-compare.d.ts.map +1 -1
- package/dist/cli/commands/branch-compare.js +4 -0
- package/dist/cli/commands/branch-compare.js.map +1 -1
- package/dist/cli/commands/diff-impact.d.ts.map +1 -1
- package/dist/cli/commands/diff-impact.js +2 -1
- package/dist/cli/commands/diff-impact.js.map +1 -1
- package/dist/cli/commands/info.d.ts.map +1 -1
- package/dist/cli/commands/info.js +3 -2
- package/dist/cli/commands/info.js.map +1 -1
- package/dist/cli/commands/watch.d.ts.map +1 -1
- package/dist/cli/commands/watch.js +16 -2
- package/dist/cli/commands/watch.js.map +1 -1
- package/dist/db/connection.d.ts.map +1 -1
- package/dist/db/connection.js +29 -26
- package/dist/db/connection.js.map +1 -1
- package/dist/db/query-builder.d.ts.map +1 -1
- package/dist/db/query-builder.js +16 -5
- package/dist/db/query-builder.js.map +1 -1
- package/dist/db/repository/base.d.ts +16 -0
- package/dist/db/repository/base.d.ts.map +1 -1
- package/dist/db/repository/base.js +31 -0
- package/dist/db/repository/base.js.map +1 -1
- package/dist/db/repository/native-repository.d.ts +7 -1
- package/dist/db/repository/native-repository.d.ts.map +1 -1
- package/dist/db/repository/native-repository.js +100 -1
- package/dist/db/repository/native-repository.js.map +1 -1
- package/dist/db/repository/nodes.d.ts.map +1 -1
- package/dist/db/repository/nodes.js +8 -4
- package/dist/db/repository/nodes.js.map +1 -1
- package/dist/db/repository/sqlite-repository.d.ts +4 -0
- package/dist/db/repository/sqlite-repository.d.ts.map +1 -1
- package/dist/db/repository/sqlite-repository.js +51 -0
- package/dist/db/repository/sqlite-repository.js.map +1 -1
- package/dist/domain/analysis/brief.d.ts.map +1 -1
- package/dist/domain/analysis/brief.js +13 -17
- package/dist/domain/analysis/brief.js.map +1 -1
- package/dist/domain/analysis/context.d.ts.map +1 -1
- package/dist/domain/analysis/context.js +14 -11
- 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 +64 -59
- package/dist/domain/analysis/dependencies.js.map +1 -1
- package/dist/domain/analysis/fn-impact.d.ts +2 -7
- package/dist/domain/analysis/fn-impact.d.ts.map +1 -1
- package/dist/domain/analysis/fn-impact.js +33 -31
- package/dist/domain/analysis/fn-impact.js.map +1 -1
- package/dist/domain/analysis/implementations.d.ts.map +1 -1
- package/dist/domain/analysis/implementations.js +11 -19
- package/dist/domain/analysis/implementations.js.map +1 -1
- package/dist/domain/analysis/module-map.d.ts.map +1 -1
- package/dist/domain/analysis/module-map.js +55 -76
- package/dist/domain/analysis/module-map.js.map +1 -1
- package/dist/domain/analysis/query-helpers.d.ts +7 -0
- package/dist/domain/analysis/query-helpers.d.ts.map +1 -1
- package/dist/domain/analysis/query-helpers.js +15 -1
- package/dist/domain/analysis/query-helpers.js.map +1 -1
- package/dist/domain/graph/builder/pipeline.d.ts.map +1 -1
- package/dist/domain/graph/builder/pipeline.js +352 -107
- 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 +49 -18
- package/dist/domain/graph/builder/stages/build-edges.js.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.js +2 -2
- 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 +32 -21
- 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 +95 -84
- package/dist/domain/graph/builder/stages/resolve-imports.js.map +1 -1
- package/dist/domain/graph/cycles.d.ts +6 -0
- package/dist/domain/graph/cycles.d.ts.map +1 -1
- package/dist/domain/graph/cycles.js +114 -22
- package/dist/domain/graph/cycles.js.map +1 -1
- package/dist/domain/graph/resolve.js +1 -1
- package/dist/domain/graph/resolve.js.map +1 -1
- package/dist/domain/graph/watcher.d.ts +2 -0
- package/dist/domain/graph/watcher.d.ts.map +1 -1
- package/dist/domain/graph/watcher.js +170 -75
- package/dist/domain/graph/watcher.js.map +1 -1
- package/dist/domain/parser.d.ts +3 -4
- package/dist/domain/parser.d.ts.map +1 -1
- package/dist/domain/parser.js +141 -89
- package/dist/domain/parser.js.map +1 -1
- package/dist/domain/search/generator.js +1 -1
- package/dist/domain/search/generator.js.map +1 -1
- package/dist/domain/search/models.d.ts +4 -3
- package/dist/domain/search/models.d.ts.map +1 -1
- package/dist/domain/search/models.js +23 -8
- package/dist/domain/search/models.js.map +1 -1
- package/dist/domain/search/search/hybrid.d.ts.map +1 -1
- package/dist/domain/search/search/hybrid.js +29 -18
- package/dist/domain/search/search/hybrid.js.map +1 -1
- package/dist/extractors/go.js +36 -33
- package/dist/extractors/go.js.map +1 -1
- package/dist/extractors/helpers.d.ts.map +1 -1
- package/dist/extractors/helpers.js +40 -29
- package/dist/extractors/helpers.js.map +1 -1
- package/dist/extractors/java.js +58 -46
- package/dist/extractors/java.js.map +1 -1
- package/dist/extractors/javascript.js +65 -54
- package/dist/extractors/javascript.js.map +1 -1
- package/dist/extractors/kotlin.js +84 -78
- package/dist/extractors/kotlin.js.map +1 -1
- package/dist/extractors/python.js +29 -24
- package/dist/extractors/python.js.map +1 -1
- package/dist/extractors/rust.js +41 -32
- package/dist/extractors/rust.js.map +1 -1
- package/dist/extractors/solidity.js +58 -67
- package/dist/extractors/solidity.js.map +1 -1
- package/dist/extractors/swift.js +83 -81
- package/dist/extractors/swift.js.map +1 -1
- package/dist/extractors/zig.js +58 -60
- package/dist/extractors/zig.js.map +1 -1
- package/dist/features/ast.d.ts +16 -14
- package/dist/features/ast.d.ts.map +1 -1
- package/dist/features/ast.js +83 -81
- package/dist/features/ast.js.map +1 -1
- package/dist/features/audit.d.ts.map +1 -1
- package/dist/features/audit.js +8 -6
- package/dist/features/audit.js.map +1 -1
- package/dist/features/branch-compare.d.ts.map +1 -1
- package/dist/features/branch-compare.js +69 -72
- package/dist/features/branch-compare.js.map +1 -1
- package/dist/features/communities.d.ts.map +1 -1
- package/dist/features/communities.js +19 -7
- package/dist/features/communities.js.map +1 -1
- package/dist/features/complexity.d.ts.map +1 -1
- package/dist/features/complexity.js +120 -125
- package/dist/features/complexity.js.map +1 -1
- package/dist/features/dataflow.d.ts.map +1 -1
- package/dist/features/dataflow.js +136 -137
- package/dist/features/dataflow.js.map +1 -1
- package/dist/features/flow.d.ts.map +1 -1
- package/dist/features/flow.js +84 -79
- package/dist/features/flow.js.map +1 -1
- package/dist/features/structure-query.d.ts.map +1 -1
- package/dist/features/structure-query.js +69 -65
- package/dist/features/structure-query.js.map +1 -1
- package/dist/graph/algorithms/leiden/optimiser.d.ts.map +1 -1
- package/dist/graph/algorithms/leiden/optimiser.js +70 -55
- 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 +288 -266
- package/dist/graph/algorithms/leiden/partition.js.map +1 -1
- package/dist/graph/model.d.ts.map +1 -1
- package/dist/graph/model.js +5 -1
- package/dist/graph/model.js.map +1 -1
- package/dist/infrastructure/config.d.ts.map +1 -1
- package/dist/infrastructure/config.js +6 -4
- package/dist/infrastructure/config.js.map +1 -1
- package/dist/infrastructure/suppress.d.ts +25 -0
- package/dist/infrastructure/suppress.d.ts.map +1 -0
- package/dist/infrastructure/suppress.js +43 -0
- package/dist/infrastructure/suppress.js.map +1 -0
- package/dist/mcp/server.d.ts.map +1 -1
- package/dist/mcp/server.js +29 -24
- package/dist/mcp/server.js.map +1 -1
- package/dist/presentation/dataflow.d.ts.map +1 -1
- package/dist/presentation/dataflow.js +47 -38
- package/dist/presentation/dataflow.js.map +1 -1
- package/dist/presentation/diff-impact-mermaid.d.ts.map +1 -1
- package/dist/presentation/diff-impact-mermaid.js +60 -51
- package/dist/presentation/diff-impact-mermaid.js.map +1 -1
- package/dist/presentation/queries-cli/exports.d.ts.map +1 -1
- package/dist/presentation/queries-cli/exports.js +20 -14
- 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 +15 -13
- package/dist/presentation/queries-cli/impact.js.map +1 -1
- package/dist/presentation/queries-cli/inspect.d.ts.map +1 -1
- package/dist/presentation/queries-cli/inspect.js +101 -79
- package/dist/presentation/queries-cli/inspect.js.map +1 -1
- package/dist/presentation/queries-cli/overview.d.ts.map +1 -1
- package/dist/presentation/queries-cli/overview.js +25 -16
- package/dist/presentation/queries-cli/overview.js.map +1 -1
- package/dist/presentation/queries-cli/path.js +26 -20
- package/dist/presentation/queries-cli/path.js.map +1 -1
- package/dist/presentation/result-formatter.d.ts +10 -0
- package/dist/presentation/result-formatter.d.ts.map +1 -1
- package/dist/presentation/result-formatter.js +16 -1
- package/dist/presentation/result-formatter.js.map +1 -1
- package/dist/presentation/viewer.d.ts.map +1 -1
- package/dist/presentation/viewer.js +18 -12
- package/dist/presentation/viewer.js.map +1 -1
- package/dist/shared/errors.d.ts +5 -0
- package/dist/shared/errors.d.ts.map +1 -1
- package/dist/shared/errors.js +5 -0
- package/dist/shared/errors.js.map +1 -1
- package/dist/shared/hierarchy.d.ts +8 -2
- package/dist/shared/hierarchy.d.ts.map +1 -1
- package/dist/shared/hierarchy.js +42 -1
- package/dist/shared/hierarchy.js.map +1 -1
- package/dist/shared/normalize.d.ts +6 -1
- package/dist/shared/normalize.d.ts.map +1 -1
- package/dist/shared/normalize.js +20 -12
- package/dist/shared/normalize.js.map +1 -1
- package/dist/shared/paginate.d.ts +0 -9
- package/dist/shared/paginate.d.ts.map +1 -1
- package/dist/shared/paginate.js +0 -15
- package/dist/shared/paginate.js.map +1 -1
- package/dist/types.d.ts +12 -5
- package/dist/types.d.ts.map +1 -1
- package/grammars/tree-sitter-erlang.wasm +0 -0
- package/grammars/tree-sitter-gleam.wasm +0 -0
- package/package.json +9 -9
- package/src/ast-analysis/engine.ts +176 -104
- package/src/ast-analysis/metrics.ts +33 -11
- package/src/ast-analysis/shared.ts +33 -24
- package/src/ast-analysis/visitor-utils.ts +52 -32
- package/src/ast-analysis/visitor.ts +132 -71
- package/src/ast-analysis/visitors/ast-store-visitor.ts +53 -50
- package/src/ast-analysis/visitors/complexity-visitor.ts +89 -40
- package/src/ast-analysis/visitors/dataflow-visitor.ts +87 -43
- package/src/cli/commands/branch-compare.ts +4 -0
- package/src/cli/commands/diff-impact.ts +2 -1
- package/src/cli/commands/info.ts +3 -2
- package/src/cli/commands/watch.ts +16 -2
- package/src/db/connection.ts +29 -28
- package/src/db/query-builder.ts +15 -3
- package/src/db/repository/base.ts +34 -0
- package/src/db/repository/native-repository.ts +104 -1
- package/src/db/repository/nodes.ts +13 -8
- package/src/db/repository/sqlite-repository.ts +55 -0
- package/src/domain/analysis/brief.ts +15 -25
- package/src/domain/analysis/context.ts +17 -10
- package/src/domain/analysis/dependencies.ts +77 -81
- package/src/domain/analysis/fn-impact.ts +36 -43
- package/src/domain/analysis/implementations.ts +11 -17
- package/src/domain/analysis/module-map.ts +58 -92
- package/src/domain/analysis/query-helpers.ts +18 -1
- package/src/domain/graph/builder/pipeline.ts +409 -99
- package/src/domain/graph/builder/stages/build-edges.ts +45 -19
- package/src/domain/graph/builder/stages/detect-changes.ts +2 -2
- package/src/domain/graph/builder/stages/finalize.ts +2 -2
- package/src/domain/graph/builder/stages/insert-nodes.ts +59 -34
- package/src/domain/graph/builder/stages/resolve-imports.ts +122 -100
- package/src/domain/graph/cycles.ts +110 -23
- package/src/domain/graph/resolve.ts +1 -1
- package/src/domain/graph/watcher.ts +202 -96
- package/src/domain/parser.ts +143 -89
- package/src/domain/search/generator.ts +1 -1
- package/src/domain/search/models.ts +26 -7
- package/src/domain/search/search/hybrid.ts +69 -51
- package/src/extractors/go.ts +43 -33
- package/src/extractors/helpers.ts +37 -23
- package/src/extractors/java.ts +66 -47
- package/src/extractors/javascript.ts +66 -54
- package/src/extractors/kotlin.ts +84 -77
- package/src/extractors/python.ts +31 -25
- package/src/extractors/rust.ts +37 -29
- package/src/extractors/solidity.ts +57 -61
- package/src/extractors/swift.ts +81 -80
- package/src/extractors/zig.ts +58 -61
- package/src/features/ast.ts +130 -110
- package/src/features/audit.ts +8 -6
- package/src/features/branch-compare.ts +105 -79
- package/src/features/communities.ts +25 -10
- package/src/features/complexity.ts +171 -134
- package/src/features/dataflow.ts +165 -175
- package/src/features/flow.ts +129 -92
- package/src/features/structure-query.ts +79 -64
- package/src/graph/algorithms/leiden/optimiser.ts +99 -55
- package/src/graph/algorithms/leiden/partition.ts +359 -294
- package/src/graph/model.ts +6 -1
- package/src/infrastructure/config.ts +6 -4
- package/src/infrastructure/suppress.ts +47 -0
- package/src/mcp/server.ts +53 -37
- package/src/presentation/dataflow.ts +50 -44
- package/src/presentation/diff-impact-mermaid.ts +104 -62
- package/src/presentation/queries-cli/exports.ts +21 -13
- package/src/presentation/queries-cli/impact.ts +15 -13
- package/src/presentation/queries-cli/inspect.ts +100 -81
- package/src/presentation/queries-cli/overview.ts +26 -16
- package/src/presentation/queries-cli/path.ts +33 -25
- package/src/presentation/result-formatter.ts +19 -1
- package/src/presentation/viewer.ts +42 -14
- package/src/shared/errors.ts +6 -0
- package/src/shared/hierarchy.ts +50 -2
- package/src/shared/normalize.ts +31 -12
- package/src/shared/paginate.ts +0 -17
- package/src/types.ts +26 -5
|
@@ -116,14 +116,13 @@ function isCall(node: TreeSitterNode | null, isCallNode: (t: string) => boolean)
|
|
|
116
116
|
return node != null && isCallNode(node.type);
|
|
117
117
|
}
|
|
118
118
|
|
|
119
|
-
|
|
119
|
+
/** Resolve the value node from a variable declarator, trying multiple strategies. */
|
|
120
|
+
function resolveValueNode(
|
|
120
121
|
node: TreeSitterNode,
|
|
122
|
+
nameNode: TreeSitterNode | null,
|
|
121
123
|
rules: AnyRules,
|
|
122
|
-
scopeStack: ScopeEntry[],
|
|
123
|
-
assignments: DataflowAssignment[],
|
|
124
124
|
isCallNode: (t: string) => boolean,
|
|
125
|
-
):
|
|
126
|
-
let nameNode = node.childForFieldName(rules.varNameField);
|
|
125
|
+
): TreeSitterNode | null {
|
|
127
126
|
let valueNode: TreeSitterNode | null = rules.varValueField
|
|
128
127
|
? node.childForFieldName(rules.varValueField)
|
|
129
128
|
: null;
|
|
@@ -146,52 +145,97 @@ function handleVarDeclarator(
|
|
|
146
145
|
}
|
|
147
146
|
}
|
|
148
147
|
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
148
|
+
return valueNode;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/** Unwrap expression-list wrappers from name/value nodes. */
|
|
152
|
+
function unwrapExpressionList(
|
|
153
|
+
nameNode: TreeSitterNode | null,
|
|
154
|
+
valueNode: TreeSitterNode | null,
|
|
155
|
+
rules: AnyRules,
|
|
156
|
+
): { name: TreeSitterNode | null; value: TreeSitterNode | null } {
|
|
157
|
+
if (!rules.expressionListType) return { name: nameNode, value: valueNode };
|
|
158
|
+
const name =
|
|
159
|
+
nameNode && nameNode.type === rules.expressionListType
|
|
160
|
+
? (nameNode.namedChildren[0] ?? null)
|
|
161
|
+
: nameNode;
|
|
162
|
+
const value =
|
|
163
|
+
valueNode && valueNode.type === rules.expressionListType
|
|
164
|
+
? (valueNode.namedChildren[0] ?? null)
|
|
165
|
+
: valueNode;
|
|
166
|
+
return { name, value };
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/** Record a destructured call assignment (object or array destructuring). */
|
|
170
|
+
function recordDestructuredAssignment(
|
|
171
|
+
nameNode: TreeSitterNode,
|
|
172
|
+
node: TreeSitterNode,
|
|
173
|
+
callee: string,
|
|
174
|
+
scope: ScopeEntry,
|
|
175
|
+
assignments: DataflowAssignment[],
|
|
176
|
+
rules: AnyRules,
|
|
177
|
+
): void {
|
|
178
|
+
const names = extractParamNames(nameNode, rules);
|
|
179
|
+
for (const n of names) {
|
|
180
|
+
assignments.push({
|
|
181
|
+
varName: n,
|
|
182
|
+
callerFunc: scope.funcName!,
|
|
183
|
+
sourceCallName: callee,
|
|
184
|
+
expression: truncate(node.text),
|
|
185
|
+
line: node.startPosition.row + 1,
|
|
186
|
+
});
|
|
187
|
+
scope.locals.set(n, { type: 'destructured', callee });
|
|
154
188
|
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/** Record a simple (non-destructured) call assignment. */
|
|
192
|
+
function recordSimpleAssignment(
|
|
193
|
+
nameNode: TreeSitterNode,
|
|
194
|
+
node: TreeSitterNode,
|
|
195
|
+
callee: string,
|
|
196
|
+
scope: ScopeEntry,
|
|
197
|
+
assignments: DataflowAssignment[],
|
|
198
|
+
): void {
|
|
199
|
+
const varName = nameNode.text;
|
|
200
|
+
assignments.push({
|
|
201
|
+
varName,
|
|
202
|
+
callerFunc: scope.funcName!,
|
|
203
|
+
sourceCallName: callee,
|
|
204
|
+
expression: truncate(node.text),
|
|
205
|
+
line: node.startPosition.row + 1,
|
|
206
|
+
});
|
|
207
|
+
scope.locals.set(varName, { type: 'call_return', callee });
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
function handleVarDeclarator(
|
|
211
|
+
node: TreeSitterNode,
|
|
212
|
+
rules: AnyRules,
|
|
213
|
+
scopeStack: ScopeEntry[],
|
|
214
|
+
assignments: DataflowAssignment[],
|
|
215
|
+
isCallNode: (t: string) => boolean,
|
|
216
|
+
): void {
|
|
217
|
+
const rawName = node.childForFieldName(rules.varNameField);
|
|
218
|
+
const rawValue = resolveValueNode(node, rawName, rules, isCallNode);
|
|
219
|
+
const { name: nameNode, value: valueNode } = unwrapExpressionList(rawName, rawValue, rules);
|
|
155
220
|
|
|
156
221
|
const scope = currentScope(scopeStack);
|
|
157
222
|
if (!nameNode || !valueNode || !scope) return;
|
|
158
223
|
|
|
159
224
|
const unwrapped = unwrapAwait(valueNode, rules);
|
|
160
225
|
const callExpr = isCall(unwrapped, isCallNode) ? unwrapped : null;
|
|
226
|
+
if (!callExpr) return;
|
|
161
227
|
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
callerFunc: scope.funcName,
|
|
174
|
-
sourceCallName: callee,
|
|
175
|
-
expression: truncate(node.text),
|
|
176
|
-
line: node.startPosition.row + 1,
|
|
177
|
-
});
|
|
178
|
-
scope.locals.set(n, { type: 'destructured', callee });
|
|
179
|
-
}
|
|
180
|
-
} else {
|
|
181
|
-
const varName =
|
|
182
|
-
nameNode.type === 'identifier' || nameNode.type === rules.paramIdentifier
|
|
183
|
-
? nameNode.text
|
|
184
|
-
: nameNode.text;
|
|
185
|
-
assignments.push({
|
|
186
|
-
varName,
|
|
187
|
-
callerFunc: scope.funcName,
|
|
188
|
-
sourceCallName: callee,
|
|
189
|
-
expression: truncate(node.text),
|
|
190
|
-
line: node.startPosition.row + 1,
|
|
191
|
-
});
|
|
192
|
-
scope.locals.set(varName, { type: 'call_return', callee });
|
|
193
|
-
}
|
|
194
|
-
}
|
|
228
|
+
const callee = resolveCalleeName(callExpr, rules);
|
|
229
|
+
if (!callee || !scope.funcName) return;
|
|
230
|
+
|
|
231
|
+
const isDestructured =
|
|
232
|
+
(rules.objectDestructType && nameNode.type === rules.objectDestructType) ||
|
|
233
|
+
(rules.arrayDestructType && nameNode.type === rules.arrayDestructType);
|
|
234
|
+
|
|
235
|
+
if (isDestructured) {
|
|
236
|
+
recordDestructuredAssignment(nameNode, node, callee, scope, assignments, rules);
|
|
237
|
+
} else {
|
|
238
|
+
recordSimpleAssignment(nameNode, node, callee, scope, assignments);
|
|
195
239
|
}
|
|
196
240
|
}
|
|
197
241
|
|
|
@@ -5,6 +5,7 @@ export const command: CommandDefinition = {
|
|
|
5
5
|
description: 'Compare code structure between two branches/refs',
|
|
6
6
|
options: [
|
|
7
7
|
['--depth <n>', 'Max transitive caller depth', '3'],
|
|
8
|
+
['-d, --db <path>', 'Path to graph.db'],
|
|
8
9
|
['-T, --no-tests', 'Exclude test/spec files'],
|
|
9
10
|
['--include-tests', 'Include test/spec files (overrides excludeTests config)'],
|
|
10
11
|
['-j, --json', 'Output as JSON'],
|
|
@@ -12,12 +13,15 @@ export const command: CommandDefinition = {
|
|
|
12
13
|
],
|
|
13
14
|
async execute([base, target], opts, ctx) {
|
|
14
15
|
const { branchCompare } = await import('../../presentation/branch-compare.js');
|
|
16
|
+
const path = await import('node:path');
|
|
17
|
+
const repoRoot = opts.db ? path.resolve(path.dirname(opts.db as string), '..') : undefined;
|
|
15
18
|
await branchCompare(base!, target!, {
|
|
16
19
|
engine: ctx.program.opts().engine,
|
|
17
20
|
depth: parseInt(opts.depth as string, 10),
|
|
18
21
|
noTests: ctx.resolveNoTests(opts),
|
|
19
22
|
json: opts.json,
|
|
20
23
|
format: opts.format,
|
|
24
|
+
repoRoot,
|
|
21
25
|
});
|
|
22
26
|
},
|
|
23
27
|
};
|
|
@@ -13,6 +13,7 @@ export const command: CommandDefinition = {
|
|
|
13
13
|
['--ndjson', 'Newline-delimited JSON output'],
|
|
14
14
|
['--staged', 'Analyze staged changes instead of unstaged'],
|
|
15
15
|
['--depth <n>', 'Max transitive caller depth', '3'],
|
|
16
|
+
['-j, --json', 'Output as JSON (shorthand for -f json)'],
|
|
16
17
|
['-f, --format <format>', 'Output format: text, mermaid, json', 'text'],
|
|
17
18
|
['--no-implementations', 'Exclude interface/trait implementors from blast radius'],
|
|
18
19
|
],
|
|
@@ -21,7 +22,7 @@ export const command: CommandDefinition = {
|
|
|
21
22
|
ref,
|
|
22
23
|
staged: opts.staged,
|
|
23
24
|
depth: parseInt(opts.depth as string, 10),
|
|
24
|
-
format: opts.format,
|
|
25
|
+
format: opts.json ? 'json' : opts.format,
|
|
25
26
|
includeImplementors: opts.implementations !== false,
|
|
26
27
|
...ctx.resolveQueryOpts(opts),
|
|
27
28
|
});
|
package/src/cli/commands/info.ts
CHANGED
|
@@ -3,7 +3,8 @@ import type { CommandDefinition } from '../types.js';
|
|
|
3
3
|
export const command: CommandDefinition = {
|
|
4
4
|
name: 'info',
|
|
5
5
|
description: 'Show codegraph engine info and diagnostics',
|
|
6
|
-
|
|
6
|
+
options: [['-d, --db <path>', 'Path to graph.db']],
|
|
7
|
+
async execute(_args, opts, ctx) {
|
|
7
8
|
const { getNativePackageVersion, isNativeAvailable, loadNative } = await import(
|
|
8
9
|
'../../infrastructure/native.js'
|
|
9
10
|
);
|
|
@@ -40,7 +41,7 @@ export const command: CommandDefinition = {
|
|
|
40
41
|
try {
|
|
41
42
|
const { findDbPath, getBuildMeta } = await import('../../db/index.js');
|
|
42
43
|
const Database = (await import('better-sqlite3')).default;
|
|
43
|
-
const dbPath = findDbPath();
|
|
44
|
+
const dbPath = findDbPath(opts.db as string | undefined);
|
|
44
45
|
const fs = await import('node:fs');
|
|
45
46
|
if (fs.existsSync(dbPath)) {
|
|
46
47
|
const db = new Database(dbPath, { readonly: true });
|
|
@@ -5,9 +5,23 @@ import type { CommandDefinition } from '../types.js';
|
|
|
5
5
|
export const command: CommandDefinition = {
|
|
6
6
|
name: 'watch [dir]',
|
|
7
7
|
description: 'Watch project for file changes and incrementally update the graph',
|
|
8
|
-
|
|
8
|
+
options: [
|
|
9
|
+
['--poll', 'Use stat-based polling (default on Windows to avoid ReFS/Dev Drive crashes)'],
|
|
10
|
+
['--native', 'Force native OS file watchers instead of polling'],
|
|
11
|
+
['--poll-interval <ms>', 'Polling interval in milliseconds (default: 2000)'],
|
|
12
|
+
],
|
|
13
|
+
async execute([dir], opts, ctx) {
|
|
9
14
|
const root = path.resolve(dir || '.');
|
|
10
15
|
const engine = ctx.program.opts().engine;
|
|
11
|
-
|
|
16
|
+
if (opts.poll && opts.native) {
|
|
17
|
+
ctx.program.error('--poll and --native are mutually exclusive');
|
|
18
|
+
}
|
|
19
|
+
// Explicit --poll or --native wins; otherwise let watcher auto-detect by platform
|
|
20
|
+
const poll = opts.poll ? true : opts.native ? false : undefined;
|
|
21
|
+
await watchProject(root, {
|
|
22
|
+
engine,
|
|
23
|
+
poll,
|
|
24
|
+
pollInterval: opts.pollInterval ? Number(opts.pollInterval) : undefined,
|
|
25
|
+
});
|
|
12
26
|
},
|
|
13
27
|
};
|
package/src/db/connection.ts
CHANGED
|
@@ -4,7 +4,7 @@ import path from 'node:path';
|
|
|
4
4
|
import { fileURLToPath } from 'node:url';
|
|
5
5
|
import { debug, warn } from '../infrastructure/logger.js';
|
|
6
6
|
import { getNative, isNativeAvailable } from '../infrastructure/native.js';
|
|
7
|
-
import { DbError } from '../shared/errors.js';
|
|
7
|
+
import { DbError, toErrorMessage } from '../shared/errors.js';
|
|
8
8
|
import type { BetterSqlite3Database, NativeDatabase } from '../types.js';
|
|
9
9
|
import { getDatabase } from './better-sqlite3.js';
|
|
10
10
|
import { Repository } from './repository/base.js';
|
|
@@ -20,7 +20,8 @@ function getPackageVersion(): string {
|
|
|
20
20
|
const pkgPath = path.join(connDir, '..', '..', 'package.json');
|
|
21
21
|
_packageVersion = (JSON.parse(fs.readFileSync(pkgPath, 'utf-8')) as { version: string })
|
|
22
22
|
.version;
|
|
23
|
-
} catch {
|
|
23
|
+
} catch (e) {
|
|
24
|
+
debug(`Failed to read package version: ${toErrorMessage(e)}`);
|
|
24
25
|
_packageVersion = '';
|
|
25
26
|
}
|
|
26
27
|
return _packageVersion;
|
|
@@ -41,8 +42,8 @@ function warnOnVersionMismatch(getBuildVersion: () => string | undefined | null)
|
|
|
41
42
|
`DB was built with codegraph v${buildVersion}, running v${currentVersion}. Consider: codegraph build --no-incremental`,
|
|
42
43
|
);
|
|
43
44
|
}
|
|
44
|
-
} catch {
|
|
45
|
-
|
|
45
|
+
} catch (e) {
|
|
46
|
+
debug(`Version mismatch check skipped (build_meta may not exist): ${toErrorMessage(e)}`);
|
|
46
47
|
}
|
|
47
48
|
}
|
|
48
49
|
|
|
@@ -78,11 +79,11 @@ export function findRepoRoot(fromDir?: string): string | null {
|
|
|
78
79
|
try {
|
|
79
80
|
root = fs.realpathSync(raw);
|
|
80
81
|
} catch (e) {
|
|
81
|
-
debug(`realpathSync failed for git root "${raw}", using resolve: ${(e
|
|
82
|
+
debug(`realpathSync failed for git root "${raw}", using resolve: ${toErrorMessage(e)}`);
|
|
82
83
|
root = path.resolve(raw);
|
|
83
84
|
}
|
|
84
85
|
} catch (e) {
|
|
85
|
-
debug(`git rev-parse failed for "${dir}": ${(e
|
|
86
|
+
debug(`git rev-parse failed for "${dir}": ${toErrorMessage(e)}`);
|
|
86
87
|
root = null;
|
|
87
88
|
}
|
|
88
89
|
if (!fromDir) {
|
|
@@ -103,7 +104,7 @@ function isProcessAlive(pid: number): boolean {
|
|
|
103
104
|
process.kill(pid, 0);
|
|
104
105
|
return true;
|
|
105
106
|
} catch (e) {
|
|
106
|
-
debug(`PID ${pid} not alive: ${(e as NodeJS.ErrnoException).code || (e
|
|
107
|
+
debug(`PID ${pid} not alive: ${(e as NodeJS.ErrnoException).code || toErrorMessage(e)}`);
|
|
107
108
|
return false;
|
|
108
109
|
}
|
|
109
110
|
}
|
|
@@ -119,12 +120,12 @@ function acquireAdvisoryLock(dbPath: string): void {
|
|
|
119
120
|
}
|
|
120
121
|
}
|
|
121
122
|
} catch (e) {
|
|
122
|
-
debug(`Advisory lock read failed: ${(e
|
|
123
|
+
debug(`Advisory lock read failed: ${toErrorMessage(e)}`);
|
|
123
124
|
}
|
|
124
125
|
try {
|
|
125
126
|
fs.writeFileSync(lockPath, String(process.pid), 'utf-8');
|
|
126
127
|
} catch (e) {
|
|
127
|
-
debug(`Advisory lock write failed: ${(e
|
|
128
|
+
debug(`Advisory lock write failed: ${toErrorMessage(e)}`);
|
|
128
129
|
}
|
|
129
130
|
}
|
|
130
131
|
|
|
@@ -135,7 +136,7 @@ function releaseAdvisoryLock(lockPath: string): void {
|
|
|
135
136
|
fs.unlinkSync(lockPath);
|
|
136
137
|
}
|
|
137
138
|
} catch (e) {
|
|
138
|
-
debug(`Advisory lock release failed for ${lockPath}: ${(e
|
|
139
|
+
debug(`Advisory lock release failed for ${lockPath}: ${toErrorMessage(e)}`);
|
|
139
140
|
}
|
|
140
141
|
}
|
|
141
142
|
|
|
@@ -151,7 +152,7 @@ function isSameDirectory(a: string, b: string): boolean {
|
|
|
151
152
|
const sb = fs.statSync(b);
|
|
152
153
|
return sa.dev === sb.dev && sa.ino === sb.ino;
|
|
153
154
|
} catch (e) {
|
|
154
|
-
debug(`isSameDirectory stat failed: ${(e
|
|
155
|
+
debug(`isSameDirectory stat failed: ${toErrorMessage(e)}`);
|
|
155
156
|
return false;
|
|
156
157
|
}
|
|
157
158
|
}
|
|
@@ -187,8 +188,8 @@ export function flushDeferredClose(): void {
|
|
|
187
188
|
const db = _deferredDbs.pop()!;
|
|
188
189
|
try {
|
|
189
190
|
db.close();
|
|
190
|
-
} catch {
|
|
191
|
-
|
|
191
|
+
} catch (e) {
|
|
192
|
+
debug(`Deferred DB close failed (handle may already be closed): ${toErrorMessage(e)}`);
|
|
192
193
|
}
|
|
193
194
|
}
|
|
194
195
|
}
|
|
@@ -216,8 +217,8 @@ export function closeDbDeferred(db: LockedDatabase): void {
|
|
|
216
217
|
_deferredDbs.splice(idx, 1);
|
|
217
218
|
try {
|
|
218
219
|
db.close();
|
|
219
|
-
} catch {
|
|
220
|
-
|
|
220
|
+
} catch (e) {
|
|
221
|
+
debug(`Deferred DB close failed (may already be closed by flush): ${toErrorMessage(e)}`);
|
|
221
222
|
}
|
|
222
223
|
}
|
|
223
224
|
});
|
|
@@ -239,8 +240,8 @@ export function closeDbPair(pair: LockedDatabasePair): void {
|
|
|
239
240
|
if (pair.nativeDb) {
|
|
240
241
|
try {
|
|
241
242
|
pair.nativeDb.close();
|
|
242
|
-
} catch {
|
|
243
|
-
|
|
243
|
+
} catch (e) {
|
|
244
|
+
debug(`closeDbPair: native close failed: ${toErrorMessage(e)}`);
|
|
244
245
|
}
|
|
245
246
|
}
|
|
246
247
|
closeDb(pair.db);
|
|
@@ -251,8 +252,8 @@ export function closeDbPairDeferred(pair: LockedDatabasePair): void {
|
|
|
251
252
|
if (pair.nativeDb) {
|
|
252
253
|
try {
|
|
253
254
|
pair.nativeDb.close();
|
|
254
|
-
} catch {
|
|
255
|
-
|
|
255
|
+
} catch (e) {
|
|
256
|
+
debug(`closeDbPairDeferred: native close failed: ${toErrorMessage(e)}`);
|
|
256
257
|
}
|
|
257
258
|
}
|
|
258
259
|
closeDbDeferred(pair.db);
|
|
@@ -270,7 +271,7 @@ export function findDbPath(customPath?: string): string {
|
|
|
270
271
|
try {
|
|
271
272
|
ceiling = fs.realpathSync(rawCeiling);
|
|
272
273
|
} catch (e) {
|
|
273
|
-
debug(`realpathSync failed for ceiling "${rawCeiling}": ${(e
|
|
274
|
+
debug(`realpathSync failed for ceiling "${rawCeiling}": ${toErrorMessage(e)}`);
|
|
274
275
|
ceiling = rawCeiling;
|
|
275
276
|
}
|
|
276
277
|
} else {
|
|
@@ -281,7 +282,7 @@ export function findDbPath(customPath?: string): string {
|
|
|
281
282
|
try {
|
|
282
283
|
dir = fs.realpathSync(process.cwd());
|
|
283
284
|
} catch (e) {
|
|
284
|
-
debug(`realpathSync failed for cwd: ${(e
|
|
285
|
+
debug(`realpathSync failed for cwd: ${toErrorMessage(e)}`);
|
|
285
286
|
dir = process.cwd();
|
|
286
287
|
}
|
|
287
288
|
while (true) {
|
|
@@ -334,9 +335,11 @@ function openRepoNative(customDbPath?: string): { repo: Repository; close(): voi
|
|
|
334
335
|
const ndb = native.NativeDatabase.openReadonly(dbPath);
|
|
335
336
|
try {
|
|
336
337
|
warnOnVersionMismatch(() => ndb.getBuildMeta('codegraph_version'));
|
|
338
|
+
const repo = new NativeRepository(ndb, dbPath);
|
|
337
339
|
return {
|
|
338
|
-
repo
|
|
340
|
+
repo,
|
|
339
341
|
close() {
|
|
342
|
+
repo.closeFallback();
|
|
340
343
|
ndb.close();
|
|
341
344
|
},
|
|
342
345
|
};
|
|
@@ -375,9 +378,7 @@ export function openRepo(
|
|
|
375
378
|
// Re-throw user-visible errors (e.g. DB not found) — only silently
|
|
376
379
|
// fall back for native-engine failures (e.g. incompatible native binary).
|
|
377
380
|
if (e instanceof DbError) throw e;
|
|
378
|
-
debug(
|
|
379
|
-
`openRepo: native path failed, falling back to better-sqlite3: ${(e as Error).message}`,
|
|
380
|
-
);
|
|
381
|
+
debug(`openRepo: native path failed, falling back to better-sqlite3: ${toErrorMessage(e)}`);
|
|
381
382
|
}
|
|
382
383
|
}
|
|
383
384
|
|
|
@@ -411,7 +412,7 @@ export function openReadonlyWithNative(customPath?: string): {
|
|
|
411
412
|
const native = getNative();
|
|
412
413
|
nativeDb = native.NativeDatabase.openReadonly(dbPath);
|
|
413
414
|
} catch (e) {
|
|
414
|
-
debug(`openReadonlyWithNative: native path failed: ${(e
|
|
415
|
+
debug(`openReadonlyWithNative: native path failed: ${toErrorMessage(e)}`);
|
|
415
416
|
}
|
|
416
417
|
}
|
|
417
418
|
|
|
@@ -423,8 +424,8 @@ export function openReadonlyWithNative(customPath?: string): {
|
|
|
423
424
|
if (nativeDb) {
|
|
424
425
|
try {
|
|
425
426
|
nativeDb.close();
|
|
426
|
-
} catch {
|
|
427
|
-
|
|
427
|
+
} catch (e) {
|
|
428
|
+
debug(`openReadonlyWithNative: native close failed: ${toErrorMessage(e)}`);
|
|
428
429
|
}
|
|
429
430
|
}
|
|
430
431
|
},
|
package/src/db/query-builder.ts
CHANGED
|
@@ -33,14 +33,26 @@ function validateOrderBy(clause: string): void {
|
|
|
33
33
|
}
|
|
34
34
|
}
|
|
35
35
|
|
|
36
|
+
/**
|
|
37
|
+
* Track parenthesis depth change for a character.
|
|
38
|
+
* Returns non-zero only for '(' / ')'; a character that is '(' or ')'
|
|
39
|
+
* can never simultaneously be ',' so the comma check in the caller
|
|
40
|
+
* remains mutually exclusive with the depth update.
|
|
41
|
+
*/
|
|
42
|
+
function parenDepthDelta(ch: string): number {
|
|
43
|
+
if (ch === '(') return 1;
|
|
44
|
+
if (ch === ')') return -1;
|
|
45
|
+
return 0;
|
|
46
|
+
}
|
|
47
|
+
|
|
36
48
|
function splitTopLevelCommas(str: string): string[] {
|
|
37
49
|
const parts: string[] = [];
|
|
38
50
|
let depth = 0;
|
|
39
51
|
let start = 0;
|
|
40
52
|
for (let i = 0; i < str.length; i++) {
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
53
|
+
const ch = str[i]!;
|
|
54
|
+
depth += parenDepthDelta(ch);
|
|
55
|
+
if (ch === ',' && depth === 0) {
|
|
44
56
|
parts.push(str.slice(start, i).trim());
|
|
45
57
|
start = i + 1;
|
|
46
58
|
}
|
|
@@ -100,6 +100,20 @@ export class Repository implements IRepository {
|
|
|
100
100
|
throw new Error('not implemented');
|
|
101
101
|
}
|
|
102
102
|
|
|
103
|
+
/**
|
|
104
|
+
* Batch version of findCallers — returns callers for multiple node IDs in a
|
|
105
|
+
* single query. Default implementation loops; subclasses override with SQL
|
|
106
|
+
* `IN (...)` for efficiency.
|
|
107
|
+
*/
|
|
108
|
+
findCallersBatch(nodeIds: number[]): Map<number, RelatedNodeRow[]> {
|
|
109
|
+
const result = new Map<number, RelatedNodeRow[]>();
|
|
110
|
+
for (const id of nodeIds) {
|
|
111
|
+
const callers = this.findCallers(id);
|
|
112
|
+
if (callers.length > 0) result.set(id, callers);
|
|
113
|
+
}
|
|
114
|
+
return result;
|
|
115
|
+
}
|
|
116
|
+
|
|
103
117
|
findDistinctCallers(_nodeId: number): RelatedNodeRow[] {
|
|
104
118
|
throw new Error('not implemented');
|
|
105
119
|
}
|
|
@@ -189,4 +203,24 @@ export class Repository implements IRepository {
|
|
|
189
203
|
getComplexityForNode(_nodeId: number): ComplexityMetrics | undefined {
|
|
190
204
|
throw new Error('not implemented');
|
|
191
205
|
}
|
|
206
|
+
|
|
207
|
+
// ── Convenience queries ──────────────────────────────────────────────
|
|
208
|
+
/**
|
|
209
|
+
* Look up the stored content hash for a file.
|
|
210
|
+
* Returns null when the file is not in file_hashes or the method is
|
|
211
|
+
* not yet implemented on the concrete repository.
|
|
212
|
+
*/
|
|
213
|
+
getFileHash(_file: string): string | null {
|
|
214
|
+
return null;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/** Check whether the graph contains any 'implements' edges. */
|
|
218
|
+
hasImplementsEdges(): boolean {
|
|
219
|
+
return false;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/** Check whether the co_changes table exists and has data. */
|
|
223
|
+
hasCoChangesTable(): boolean {
|
|
224
|
+
return false;
|
|
225
|
+
}
|
|
192
226
|
}
|
|
@@ -8,9 +8,11 @@
|
|
|
8
8
|
* back to the snake_case field names that the Repository interface expects.
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
|
+
import Database from 'better-sqlite3';
|
|
11
12
|
import { ConfigError } from '../../shared/errors.js';
|
|
12
13
|
import type {
|
|
13
14
|
AdjacentEdgeRow,
|
|
15
|
+
BetterSqlite3Database,
|
|
14
16
|
CallableNodeRow,
|
|
15
17
|
CallEdgeRow,
|
|
16
18
|
ChildNodeRow,
|
|
@@ -159,10 +161,33 @@ function toComplexityMetrics(r: NativeComplexityMetrics): ComplexityMetrics {
|
|
|
159
161
|
|
|
160
162
|
export class NativeRepository extends Repository {
|
|
161
163
|
#ndb: NativeDatabase;
|
|
164
|
+
#dbPath?: string;
|
|
165
|
+
#fallbackDb?: BetterSqlite3Database;
|
|
162
166
|
|
|
163
|
-
constructor(ndb: NativeDatabase) {
|
|
167
|
+
constructor(ndb: NativeDatabase, dbPath?: string) {
|
|
164
168
|
super();
|
|
165
169
|
this.#ndb = ndb;
|
|
170
|
+
this.#dbPath = dbPath;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/** Lazy better-sqlite3 handle for methods not yet ported to Rust. */
|
|
174
|
+
#getFallbackDb(): BetterSqlite3Database | undefined {
|
|
175
|
+
if (this.#fallbackDb) return this.#fallbackDb;
|
|
176
|
+
if (!this.#dbPath) return undefined;
|
|
177
|
+
try {
|
|
178
|
+
this.#fallbackDb = new Database(this.#dbPath, { readonly: true });
|
|
179
|
+
return this.#fallbackDb;
|
|
180
|
+
} catch {
|
|
181
|
+
return undefined;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/** Close the lazy fallback connection if it was opened. */
|
|
186
|
+
closeFallback(): void {
|
|
187
|
+
if (this.#fallbackDb) {
|
|
188
|
+
this.#fallbackDb.close();
|
|
189
|
+
this.#fallbackDb = undefined;
|
|
190
|
+
}
|
|
166
191
|
}
|
|
167
192
|
|
|
168
193
|
// ── Node lookups ──────────────────────────────────────────────────
|
|
@@ -266,6 +291,31 @@ export class NativeRepository extends Repository {
|
|
|
266
291
|
return this.#ndb.findCallers(nodeId).map(toRelatedNodeRow);
|
|
267
292
|
}
|
|
268
293
|
|
|
294
|
+
findCallersBatch(nodeIds: number[]): Map<number, RelatedNodeRow[]> {
|
|
295
|
+
if (nodeIds.length === 0) return new Map();
|
|
296
|
+
const placeholders = nodeIds.map(() => '?').join(',');
|
|
297
|
+
const rows = this.#ndb.queryAll(
|
|
298
|
+
`SELECT e.target_id AS queried_id, n.id, n.name, n.kind, n.file, n.line, n.end_line
|
|
299
|
+
FROM edges e JOIN nodes n ON e.source_id = n.id
|
|
300
|
+
WHERE e.target_id IN (${placeholders}) AND e.kind = 'calls'`,
|
|
301
|
+
nodeIds,
|
|
302
|
+
) as Array<Record<string, unknown>>;
|
|
303
|
+
const result = new Map<number, RelatedNodeRow[]>();
|
|
304
|
+
for (const row of rows) {
|
|
305
|
+
const qid = row.queried_id as number;
|
|
306
|
+
if (!result.has(qid)) result.set(qid, []);
|
|
307
|
+
result.get(qid)!.push({
|
|
308
|
+
id: row.id as number,
|
|
309
|
+
name: row.name as string,
|
|
310
|
+
kind: row.kind as string,
|
|
311
|
+
file: row.file as string,
|
|
312
|
+
line: row.line as number,
|
|
313
|
+
end_line: (row.end_line as number | null) ?? null,
|
|
314
|
+
});
|
|
315
|
+
}
|
|
316
|
+
return result;
|
|
317
|
+
}
|
|
318
|
+
|
|
269
319
|
findDistinctCallers(nodeId: number): RelatedNodeRow[] {
|
|
270
320
|
return this.#ndb.findDistinctCallers(nodeId).map(toRelatedNodeRow);
|
|
271
321
|
}
|
|
@@ -358,4 +408,57 @@ export class NativeRepository extends Repository {
|
|
|
358
408
|
const r = this.#ndb.getComplexityForNode(nodeId);
|
|
359
409
|
return r ? toComplexityMetrics(r) : undefined;
|
|
360
410
|
}
|
|
411
|
+
|
|
412
|
+
// ── Convenience queries ────────────────────────────────────────────
|
|
413
|
+
|
|
414
|
+
getFileHash(file: string): string | null {
|
|
415
|
+
if (typeof this.#ndb.getFileHash === 'function') return this.#ndb.getFileHash(file);
|
|
416
|
+
// Fallback to better-sqlite3 until Rust implements getFileHash
|
|
417
|
+
const db = this.#getFallbackDb();
|
|
418
|
+
if (db) {
|
|
419
|
+
const row = db.prepare('SELECT hash FROM file_hashes WHERE file = ?').get(file) as
|
|
420
|
+
| { hash: string }
|
|
421
|
+
| undefined;
|
|
422
|
+
return row?.hash ?? null;
|
|
423
|
+
}
|
|
424
|
+
return null;
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
#implementsEdgesCache?: boolean;
|
|
428
|
+
hasImplementsEdges(): boolean {
|
|
429
|
+
if (this.#implementsEdgesCache !== undefined) return this.#implementsEdgesCache;
|
|
430
|
+
if (typeof this.#ndb.hasImplementsEdges === 'function') {
|
|
431
|
+
this.#implementsEdgesCache = this.#ndb.hasImplementsEdges();
|
|
432
|
+
return this.#implementsEdgesCache;
|
|
433
|
+
}
|
|
434
|
+
// Fallback to better-sqlite3
|
|
435
|
+
const db = this.#getFallbackDb();
|
|
436
|
+
if (db) {
|
|
437
|
+
this.#implementsEdgesCache = !!db
|
|
438
|
+
.prepare("SELECT 1 FROM edges WHERE kind = 'implements' LIMIT 1")
|
|
439
|
+
.get();
|
|
440
|
+
return this.#implementsEdgesCache;
|
|
441
|
+
}
|
|
442
|
+
return true; // conservative: assume yes when no fallback available
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
#coChangesTableCache?: boolean;
|
|
446
|
+
hasCoChangesTable(): boolean {
|
|
447
|
+
if (this.#coChangesTableCache !== undefined) return this.#coChangesTableCache;
|
|
448
|
+
if (typeof this.#ndb.hasCoChangesTable === 'function') {
|
|
449
|
+
this.#coChangesTableCache = this.#ndb.hasCoChangesTable();
|
|
450
|
+
return this.#coChangesTableCache;
|
|
451
|
+
}
|
|
452
|
+
// Fallback to better-sqlite3
|
|
453
|
+
const db = this.#getFallbackDb();
|
|
454
|
+
if (db) {
|
|
455
|
+
try {
|
|
456
|
+
this.#coChangesTableCache = !!db.prepare('SELECT 1 FROM co_changes LIMIT 1').get();
|
|
457
|
+
} catch {
|
|
458
|
+
this.#coChangesTableCache = false;
|
|
459
|
+
}
|
|
460
|
+
return this.#coChangesTableCache;
|
|
461
|
+
}
|
|
462
|
+
return false;
|
|
463
|
+
}
|
|
361
464
|
}
|
|
@@ -42,14 +42,8 @@ export function findNodesWithFanIn(
|
|
|
42
42
|
return q.all(db, nativeDb);
|
|
43
43
|
}
|
|
44
44
|
|
|
45
|
-
/**
|
|
46
|
-
|
|
47
|
-
*/
|
|
48
|
-
export function findNodesForTriage(
|
|
49
|
-
db: BetterSqlite3Database,
|
|
50
|
-
opts: TriageQueryOpts = {},
|
|
51
|
-
nativeDb?: NativeDatabase,
|
|
52
|
-
): TriageNodeRow[] {
|
|
45
|
+
/** Validate kind and role options, throwing on invalid values. */
|
|
46
|
+
function validateTriageOpts(opts: TriageQueryOpts): void {
|
|
53
47
|
if (opts.kind && !(EVERY_SYMBOL_KIND as readonly string[]).includes(opts.kind)) {
|
|
54
48
|
throw new ConfigError(
|
|
55
49
|
`Invalid kind: ${opts.kind} (expected one of ${EVERY_SYMBOL_KIND.join(', ')})`,
|
|
@@ -58,6 +52,17 @@ export function findNodesForTriage(
|
|
|
58
52
|
if (opts.role && !VALID_ROLES.includes(opts.role)) {
|
|
59
53
|
throw new ConfigError(`Invalid role: ${opts.role} (expected one of ${VALID_ROLES.join(', ')})`);
|
|
60
54
|
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Fetch nodes for triage scoring: fan-in + complexity + churn.
|
|
59
|
+
*/
|
|
60
|
+
export function findNodesForTriage(
|
|
61
|
+
db: BetterSqlite3Database,
|
|
62
|
+
opts: TriageQueryOpts = {},
|
|
63
|
+
nativeDb?: NativeDatabase,
|
|
64
|
+
): TriageNodeRow[] {
|
|
65
|
+
validateTriageOpts(opts);
|
|
61
66
|
|
|
62
67
|
const kindsToUse = opts.kind ? [opts.kind] : ['function', 'method', 'class'];
|
|
63
68
|
const q = new NodeQuery()
|