@optave/codegraph 3.9.6 → 3.11.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 +26 -12
- package/dist/ast-analysis/engine.d.ts.map +1 -1
- package/dist/ast-analysis/engine.js +1 -1
- package/dist/ast-analysis/engine.js.map +1 -1
- package/dist/ast-analysis/rules/index.d.ts.map +1 -1
- package/dist/ast-analysis/rules/index.js +77 -0
- package/dist/ast-analysis/rules/index.js.map +1 -1
- package/dist/ast-analysis/visitors/ast-store-visitor.d.ts.map +1 -1
- package/dist/ast-analysis/visitors/ast-store-visitor.js +50 -8
- package/dist/ast-analysis/visitors/ast-store-visitor.js.map +1 -1
- package/dist/cli/commands/audit.js +1 -1
- package/dist/cli/commands/audit.js.map +1 -1
- package/dist/cli/commands/build.d.ts.map +1 -1
- package/dist/cli/commands/build.js +2 -0
- package/dist/cli/commands/build.js.map +1 -1
- package/dist/cli/commands/check.js +1 -1
- package/dist/cli/commands/check.js.map +1 -1
- package/dist/cli/commands/children.js +1 -1
- package/dist/cli/commands/children.js.map +1 -1
- package/dist/cli/commands/diff-impact.js +1 -1
- package/dist/cli/commands/diff-impact.js.map +1 -1
- package/dist/cli/commands/roles.js +1 -1
- package/dist/cli/commands/roles.js.map +1 -1
- package/dist/cli/commands/structure.js +1 -1
- package/dist/cli/commands/structure.js.map +1 -1
- package/dist/cli/shared/options.js +1 -1
- package/dist/cli/shared/options.js.map +1 -1
- package/dist/db/connection.d.ts.map +1 -1
- package/dist/db/connection.js +8 -0
- package/dist/db/connection.js.map +1 -1
- package/dist/domain/graph/builder/context.d.ts +10 -0
- package/dist/domain/graph/builder/context.d.ts.map +1 -1
- package/dist/domain/graph/builder/context.js +10 -0
- package/dist/domain/graph/builder/context.js.map +1 -1
- package/dist/domain/graph/builder/helpers.d.ts +7 -2
- package/dist/domain/graph/builder/helpers.d.ts.map +1 -1
- package/dist/domain/graph/builder/helpers.js +7 -2
- package/dist/domain/graph/builder/helpers.js.map +1 -1
- package/dist/domain/graph/builder/incremental.d.ts +0 -6
- package/dist/domain/graph/builder/incremental.d.ts.map +1 -1
- package/dist/domain/graph/builder/incremental.js +6 -23
- package/dist/domain/graph/builder/incremental.js.map +1 -1
- package/dist/domain/graph/builder/pipeline.d.ts +44 -0
- package/dist/domain/graph/builder/pipeline.d.ts.map +1 -1
- package/dist/domain/graph/builder/pipeline.js +348 -42
- 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 +8 -2
- package/dist/domain/graph/builder/stages/build-edges.js.map +1 -1
- package/dist/domain/graph/builder/stages/collect-files.d.ts.map +1 -1
- package/dist/domain/graph/builder/stages/collect-files.js +8 -0
- package/dist/domain/graph/builder/stages/collect-files.js.map +1 -1
- package/dist/domain/graph/builder/stages/detect-changes.d.ts +24 -0
- package/dist/domain/graph/builder/stages/detect-changes.d.ts.map +1 -1
- package/dist/domain/graph/builder/stages/detect-changes.js +117 -3
- 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 +9 -6
- package/dist/domain/graph/builder/stages/finalize.js.map +1 -1
- package/dist/domain/graph/builder/stages/insert-nodes.d.ts +30 -0
- package/dist/domain/graph/builder/stages/insert-nodes.d.ts.map +1 -1
- package/dist/domain/graph/builder/stages/insert-nodes.js +36 -13
- 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 +73 -22
- package/dist/domain/graph/builder/stages/resolve-imports.js.map +1 -1
- package/dist/domain/graph/watcher.d.ts.map +1 -1
- package/dist/domain/graph/watcher.js +23 -18
- package/dist/domain/graph/watcher.js.map +1 -1
- package/dist/domain/parser.d.ts +14 -1
- package/dist/domain/parser.d.ts.map +1 -1
- package/dist/domain/parser.js +104 -11
- package/dist/domain/parser.js.map +1 -1
- package/dist/domain/search/models.d.ts +16 -0
- package/dist/domain/search/models.d.ts.map +1 -1
- package/dist/domain/search/models.js +36 -2
- package/dist/domain/search/models.js.map +1 -1
- package/dist/domain/wasm-worker-entry.js +20 -13
- package/dist/domain/wasm-worker-entry.js.map +1 -1
- package/dist/extractors/c.js +25 -6
- package/dist/extractors/c.js.map +1 -1
- package/dist/extractors/cpp.js +47 -6
- package/dist/extractors/cpp.js.map +1 -1
- package/dist/extractors/cuda.js +90 -14
- package/dist/extractors/cuda.js.map +1 -1
- package/dist/extractors/elixir.js +83 -3
- package/dist/extractors/elixir.js.map +1 -1
- package/dist/extractors/erlang.js +56 -20
- package/dist/extractors/erlang.js.map +1 -1
- package/dist/extractors/fsharp.d.ts +7 -0
- package/dist/extractors/fsharp.d.ts.map +1 -1
- package/dist/extractors/fsharp.js +94 -0
- package/dist/extractors/fsharp.js.map +1 -1
- package/dist/extractors/gleam.js +6 -2
- package/dist/extractors/gleam.js.map +1 -1
- package/dist/extractors/groovy.js +41 -1
- package/dist/extractors/groovy.js.map +1 -1
- package/dist/extractors/haskell.js +48 -4
- package/dist/extractors/haskell.js.map +1 -1
- package/dist/extractors/julia.js +172 -41
- package/dist/extractors/julia.js.map +1 -1
- package/dist/extractors/kotlin.js +4 -0
- package/dist/extractors/kotlin.js.map +1 -1
- package/dist/extractors/objc.js +184 -47
- package/dist/extractors/objc.js.map +1 -1
- package/dist/extractors/python.js +7 -4
- package/dist/extractors/python.js.map +1 -1
- package/dist/extractors/r.js +93 -52
- package/dist/extractors/r.js.map +1 -1
- package/dist/extractors/scala.d.ts.map +1 -1
- package/dist/extractors/scala.js +18 -32
- package/dist/extractors/scala.js.map +1 -1
- package/dist/extractors/solidity.js +18 -9
- package/dist/extractors/solidity.js.map +1 -1
- package/dist/extractors/verilog.js +80 -15
- package/dist/extractors/verilog.js.map +1 -1
- package/dist/infrastructure/config.d.ts +1 -0
- package/dist/infrastructure/config.d.ts.map +1 -1
- package/dist/infrastructure/config.js +1 -0
- package/dist/infrastructure/config.js.map +1 -1
- package/dist/mcp/server.d.ts.map +1 -1
- package/dist/mcp/server.js +14 -8
- package/dist/mcp/server.js.map +1 -1
- package/dist/mcp/tool-registry.d.ts +1 -1
- package/dist/mcp/tool-registry.d.ts.map +1 -1
- package/dist/mcp/tool-registry.js +23 -5
- package/dist/mcp/tool-registry.js.map +1 -1
- package/dist/mcp/tools/semantic-search.d.ts +1 -0
- package/dist/mcp/tools/semantic-search.d.ts.map +1 -1
- package/dist/mcp/tools/semantic-search.js +1 -0
- package/dist/mcp/tools/semantic-search.js.map +1 -1
- package/dist/types.d.ts +16 -1
- package/dist/types.d.ts.map +1 -1
- package/grammars/tree-sitter-erlang.wasm +0 -0
- package/grammars/tree-sitter-fsharp.wasm +0 -0
- package/grammars/tree-sitter-fsharp_signature.wasm +0 -0
- package/grammars/tree-sitter-gleam.wasm +0 -0
- package/package.json +11 -10
- package/src/ast-analysis/engine.ts +3 -1
- package/src/ast-analysis/rules/index.ts +87 -0
- package/src/ast-analysis/visitors/ast-store-visitor.ts +45 -9
- package/src/cli/commands/audit.ts +1 -1
- package/src/cli/commands/build.ts +2 -0
- package/src/cli/commands/check.ts +1 -1
- package/src/cli/commands/children.ts +1 -1
- package/src/cli/commands/diff-impact.ts +1 -1
- package/src/cli/commands/roles.ts +1 -1
- package/src/cli/commands/structure.ts +1 -1
- package/src/cli/shared/options.ts +1 -1
- package/src/db/connection.ts +8 -0
- package/src/domain/graph/builder/context.ts +10 -0
- package/src/domain/graph/builder/helpers.ts +8 -3
- package/src/domain/graph/builder/incremental.ts +6 -41
- package/src/domain/graph/builder/pipeline.ts +404 -41
- package/src/domain/graph/builder/stages/build-edges.ts +9 -2
- package/src/domain/graph/builder/stages/collect-files.ts +9 -0
- package/src/domain/graph/builder/stages/detect-changes.ts +130 -4
- package/src/domain/graph/builder/stages/finalize.ts +9 -6
- package/src/domain/graph/builder/stages/insert-nodes.ts +38 -14
- package/src/domain/graph/builder/stages/resolve-imports.ts +79 -25
- package/src/domain/graph/watcher.ts +21 -23
- package/src/domain/parser.ts +110 -10
- package/src/domain/search/models.ts +37 -2
- package/src/domain/wasm-worker-entry.ts +20 -13
- package/src/extractors/c.ts +27 -8
- package/src/extractors/cpp.ts +50 -8
- package/src/extractors/cuda.ts +90 -16
- package/src/extractors/elixir.ts +75 -3
- package/src/extractors/erlang.ts +63 -20
- package/src/extractors/fsharp.ts +104 -0
- package/src/extractors/gleam.ts +7 -2
- package/src/extractors/groovy.ts +45 -1
- package/src/extractors/haskell.ts +45 -4
- package/src/extractors/julia.ts +164 -43
- package/src/extractors/kotlin.ts +4 -0
- package/src/extractors/objc.ts +171 -47
- package/src/extractors/python.ts +5 -3
- package/src/extractors/r.ts +88 -48
- package/src/extractors/scala.ts +24 -36
- package/src/extractors/solidity.ts +17 -8
- package/src/extractors/verilog.ts +83 -15
- package/src/infrastructure/config.ts +1 -0
- package/src/mcp/server.ts +16 -9
- package/src/mcp/tool-registry.ts +28 -5
- package/src/mcp/tools/semantic-search.ts +2 -0
- package/src/types.ts +16 -0
package/src/extractors/python.ts
CHANGED
|
@@ -122,7 +122,7 @@ function handlePyFunctionDef(node: TreeSitterNode, ctx: ExtractorOutput): void {
|
|
|
122
122
|
const parentClass = findPythonParentClass(node);
|
|
123
123
|
const fullName = parentClass ? `${parentClass}.${nameNode.text}` : nameNode.text;
|
|
124
124
|
const kind = parentClass ? 'method' : 'function';
|
|
125
|
-
const fnChildren = extractPythonParameters(node);
|
|
125
|
+
const fnChildren = extractPythonParameters(node, parentClass !== null);
|
|
126
126
|
ctx.definitions.push({
|
|
127
127
|
name: fullName,
|
|
128
128
|
kind,
|
|
@@ -238,7 +238,7 @@ function handlePyImportFrom(node: TreeSitterNode, ctx: ExtractorOutput): void {
|
|
|
238
238
|
|
|
239
239
|
// ── Python-specific helpers ─────────────────────────────────────────────────
|
|
240
240
|
|
|
241
|
-
function extractPythonParameters(fnNode: TreeSitterNode): SubDeclaration[] {
|
|
241
|
+
function extractPythonParameters(fnNode: TreeSitterNode, isMethod: boolean): SubDeclaration[] {
|
|
242
242
|
const params: SubDeclaration[] = [];
|
|
243
243
|
const paramsNode = fnNode.childForFieldName('parameters') || findChild(fnNode, 'parameters');
|
|
244
244
|
if (!paramsNode) return params;
|
|
@@ -246,7 +246,9 @@ function extractPythonParameters(fnNode: TreeSitterNode): SubDeclaration[] {
|
|
|
246
246
|
const child = paramsNode.child(i);
|
|
247
247
|
if (!child) continue;
|
|
248
248
|
const param = extractSinglePyParam(child);
|
|
249
|
-
if (param)
|
|
249
|
+
if (!param) continue;
|
|
250
|
+
if (isMethod && (param.name === 'self' || param.name === 'cls')) continue;
|
|
251
|
+
params.push(param);
|
|
250
252
|
}
|
|
251
253
|
return params;
|
|
252
254
|
}
|
package/src/extractors/r.ts
CHANGED
|
@@ -125,11 +125,16 @@ function handleCall(node: TreeSitterNode, ctx: ExtractorOutput): void {
|
|
|
125
125
|
return;
|
|
126
126
|
}
|
|
127
127
|
|
|
128
|
-
if (funcName === 'setGeneric'
|
|
128
|
+
if (funcName === 'setGeneric') {
|
|
129
129
|
handleSetGeneric(node, ctx);
|
|
130
130
|
return;
|
|
131
131
|
}
|
|
132
132
|
|
|
133
|
+
if (funcName === 'setMethod') {
|
|
134
|
+
handleSetMethod(node, ctx);
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
|
|
133
138
|
// Regular call
|
|
134
139
|
if (funcNode.type === 'identifier') {
|
|
135
140
|
ctx.calls.push({ name: funcName, line: node.startPosition.row + 1 });
|
|
@@ -147,7 +152,10 @@ function handleCall(node: TreeSitterNode, ctx: ExtractorOutput): void {
|
|
|
147
152
|
}
|
|
148
153
|
|
|
149
154
|
function handleLibraryCall(node: TreeSitterNode, ctx: ExtractorOutput): void {
|
|
150
|
-
// Find the package name in arguments
|
|
155
|
+
// Find the package name in arguments. For named arguments like
|
|
156
|
+
// `library(package = dplyr)`, prefer the field-named `value` child of the
|
|
157
|
+
// `argument` node so we extract `dplyr` (the value), not `package` (the
|
|
158
|
+
// parameter name). Keeps native (Rust) and WASM extractors in parity.
|
|
151
159
|
for (let i = 0; i < node.childCount; i++) {
|
|
152
160
|
const child = node.child(i);
|
|
153
161
|
if (!child) continue;
|
|
@@ -174,9 +182,27 @@ function handleLibraryCall(node: TreeSitterNode, ctx: ExtractorOutput): void {
|
|
|
174
182
|
}
|
|
175
183
|
// Argument might be wrapped
|
|
176
184
|
if (arg.type === 'argument') {
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
185
|
+
// Prefer the `value` field (correct for named arguments).
|
|
186
|
+
const valueNode = arg.childForFieldName('value');
|
|
187
|
+
let pick: TreeSitterNode | null = null;
|
|
188
|
+
if (valueNode && (valueNode.type === 'string' || valueNode.type === 'identifier')) {
|
|
189
|
+
pick = valueNode;
|
|
190
|
+
} else {
|
|
191
|
+
// Fallback: skip the parameter-name child if the grammar exposes
|
|
192
|
+
// it via the `name` field, then pick the first string/identifier.
|
|
193
|
+
const nameNode = arg.childForFieldName('name');
|
|
194
|
+
for (let k = 0; k < arg.childCount; k++) {
|
|
195
|
+
const inner = arg.child(k);
|
|
196
|
+
if (!inner) continue;
|
|
197
|
+
if (nameNode && inner.id === nameNode.id) continue;
|
|
198
|
+
if (inner.type === 'string' || inner.type === 'identifier') {
|
|
199
|
+
pick = inner;
|
|
200
|
+
break;
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
if (pick) {
|
|
205
|
+
const text = pick.text.replace(/^["']|["']$/g, '');
|
|
180
206
|
ctx.imports.push({
|
|
181
207
|
source: text,
|
|
182
208
|
names: [text],
|
|
@@ -191,47 +217,55 @@ function handleLibraryCall(node: TreeSitterNode, ctx: ExtractorOutput): void {
|
|
|
191
217
|
}
|
|
192
218
|
|
|
193
219
|
function handleSourceCall(node: TreeSitterNode, ctx: ExtractorOutput): void {
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
ctx.imports.push({
|
|
203
|
-
source: text,
|
|
204
|
-
names: ['source'],
|
|
205
|
-
line: node.startPosition.row + 1,
|
|
206
|
-
});
|
|
207
|
-
return;
|
|
208
|
-
}
|
|
209
|
-
}
|
|
210
|
-
}
|
|
220
|
+
// source() only accepts string literals — `source(varname)` is not an import.
|
|
221
|
+
const path = firstStringArgument(node);
|
|
222
|
+
if (path === null) return;
|
|
223
|
+
ctx.imports.push({
|
|
224
|
+
source: path,
|
|
225
|
+
names: ['source'],
|
|
226
|
+
line: node.startPosition.row + 1,
|
|
227
|
+
});
|
|
211
228
|
}
|
|
212
229
|
|
|
213
230
|
function handleSetClass(node: TreeSitterNode, ctx: ExtractorOutput): void {
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
ctx.definitions.push({
|
|
223
|
-
name,
|
|
224
|
-
kind: 'class',
|
|
225
|
-
line: node.startPosition.row + 1,
|
|
226
|
-
endLine: nodeEndLine(node),
|
|
227
|
-
});
|
|
228
|
-
return;
|
|
229
|
-
}
|
|
230
|
-
}
|
|
231
|
-
}
|
|
231
|
+
const name = firstStringArgument(node);
|
|
232
|
+
if (name === null) return;
|
|
233
|
+
ctx.definitions.push({
|
|
234
|
+
name,
|
|
235
|
+
kind: 'class',
|
|
236
|
+
line: node.startPosition.row + 1,
|
|
237
|
+
endLine: nodeEndLine(node),
|
|
238
|
+
});
|
|
232
239
|
}
|
|
233
240
|
|
|
234
241
|
function handleSetGeneric(node: TreeSitterNode, ctx: ExtractorOutput): void {
|
|
242
|
+
const name = firstStringArgument(node);
|
|
243
|
+
if (name === null) return;
|
|
244
|
+
ctx.definitions.push({
|
|
245
|
+
name,
|
|
246
|
+
kind: 'function',
|
|
247
|
+
line: node.startPosition.row + 1,
|
|
248
|
+
endLine: nodeEndLine(node),
|
|
249
|
+
});
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
// setMethod("greet", "Person", function(x) ...) registers an implementation of
|
|
253
|
+
// the generic `greet` — it is not a new top-level definition. Emitting a
|
|
254
|
+
// definition here produced two `function` nodes with the same name (one from
|
|
255
|
+
// setGeneric, one from setMethod) and broke resolution. Emit a call edge to
|
|
256
|
+
// the generic instead; the method body's calls are still picked up by the
|
|
257
|
+
// recursive walk of the anonymous function argument.
|
|
258
|
+
function handleSetMethod(node: TreeSitterNode, ctx: ExtractorOutput): void {
|
|
259
|
+
const name = firstStringArgument(node);
|
|
260
|
+
if (name === null) return;
|
|
261
|
+
ctx.calls.push({ name, line: node.startPosition.row + 1 });
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
// tree-sitter-r wraps each positional argument in an `argument` node that
|
|
265
|
+
// contains the actual `string` (or `identifier`) child, so the inner string
|
|
266
|
+
// must be unwrapped — checking `child.type === 'string'` directly misses it.
|
|
267
|
+
// Mirrors `first_argument_value` in the Rust extractor for parity.
|
|
268
|
+
function firstStringArgument(node: TreeSitterNode): string | null {
|
|
235
269
|
for (let i = 0; i < node.childCount; i++) {
|
|
236
270
|
const child = node.child(i);
|
|
237
271
|
if (!child || child.type !== 'arguments') continue;
|
|
@@ -239,15 +273,21 @@ function handleSetGeneric(node: TreeSitterNode, ctx: ExtractorOutput): void {
|
|
|
239
273
|
const arg = child.child(j);
|
|
240
274
|
if (!arg) continue;
|
|
241
275
|
if (arg.type === 'string') {
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
276
|
+
return stripQuotes(arg.text);
|
|
277
|
+
}
|
|
278
|
+
if (arg.type === 'argument') {
|
|
279
|
+
const valueNode = arg.childForFieldName('value');
|
|
280
|
+
if (valueNode && valueNode.type === 'string') return stripQuotes(valueNode.text);
|
|
281
|
+
for (let k = 0; k < arg.childCount; k++) {
|
|
282
|
+
const inner = arg.child(k);
|
|
283
|
+
if (inner && inner.type === 'string') return stripQuotes(inner.text);
|
|
284
|
+
}
|
|
250
285
|
}
|
|
251
286
|
}
|
|
252
287
|
}
|
|
288
|
+
return null;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
function stripQuotes(text: string): string {
|
|
292
|
+
return text.replace(/^["']|["']$/g, '');
|
|
253
293
|
}
|
package/src/extractors/scala.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type {
|
|
2
2
|
Call,
|
|
3
|
+
Definition,
|
|
3
4
|
ExtractorOutput,
|
|
4
5
|
SubDeclaration,
|
|
5
6
|
TreeSitterNode,
|
|
@@ -59,52 +60,37 @@ function walkScalaNode(node: TreeSitterNode, ctx: ExtractorOutput): void {
|
|
|
59
60
|
// ── Walk-path per-node-type handlers ────────────────────────────────────────
|
|
60
61
|
|
|
61
62
|
function handleScalaClassDef(node: TreeSitterNode, ctx: ExtractorOutput): void {
|
|
62
|
-
|
|
63
|
-
if (!nameNode) return;
|
|
64
|
-
const name = nameNode.text;
|
|
65
|
-
const children = extractScalaBodyMembers(node, name, ctx);
|
|
66
|
-
|
|
67
|
-
ctx.definitions.push({
|
|
68
|
-
name,
|
|
69
|
-
kind: 'class',
|
|
70
|
-
line: node.startPosition.row + 1,
|
|
71
|
-
endLine: nodeEndLine(node),
|
|
72
|
-
children: children.length > 0 ? children : undefined,
|
|
73
|
-
});
|
|
74
|
-
|
|
75
|
-
extractScalaInheritance(node, name, ctx);
|
|
63
|
+
emitScalaTypeDef(node, ctx, 'class');
|
|
76
64
|
}
|
|
77
65
|
|
|
78
66
|
function handleScalaTraitDef(node: TreeSitterNode, ctx: ExtractorOutput): void {
|
|
79
|
-
|
|
80
|
-
if (!nameNode) return;
|
|
81
|
-
const name = nameNode.text;
|
|
82
|
-
const children = extractScalaBodyMembers(node, name, ctx);
|
|
83
|
-
|
|
84
|
-
ctx.definitions.push({
|
|
85
|
-
name,
|
|
86
|
-
kind: 'interface',
|
|
87
|
-
line: node.startPosition.row + 1,
|
|
88
|
-
endLine: nodeEndLine(node),
|
|
89
|
-
children: children.length > 0 ? children : undefined,
|
|
90
|
-
});
|
|
91
|
-
|
|
92
|
-
extractScalaInheritance(node, name, ctx);
|
|
67
|
+
emitScalaTypeDef(node, ctx, 'interface');
|
|
93
68
|
}
|
|
94
69
|
|
|
95
70
|
function handleScalaObjectDef(node: TreeSitterNode, ctx: ExtractorOutput): void {
|
|
71
|
+
emitScalaTypeDef(node, ctx, 'class');
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function emitScalaTypeDef(
|
|
75
|
+
node: TreeSitterNode,
|
|
76
|
+
ctx: ExtractorOutput,
|
|
77
|
+
kind: 'class' | 'interface',
|
|
78
|
+
): void {
|
|
96
79
|
const nameNode = node.childForFieldName('name');
|
|
97
80
|
if (!nameNode) return;
|
|
98
81
|
const name = nameNode.text;
|
|
99
|
-
const children =
|
|
82
|
+
const { children, methods } = collectScalaBodyMembers(node, name);
|
|
100
83
|
|
|
84
|
+
// Push the type def before its methods so the definition order follows
|
|
85
|
+
// source-line order (matches native, which walks depth-first).
|
|
101
86
|
ctx.definitions.push({
|
|
102
87
|
name,
|
|
103
|
-
kind
|
|
88
|
+
kind,
|
|
104
89
|
line: node.startPosition.row + 1,
|
|
105
90
|
endLine: nodeEndLine(node),
|
|
106
91
|
children: children.length > 0 ? children : undefined,
|
|
107
92
|
});
|
|
93
|
+
for (const m of methods) ctx.definitions.push(m);
|
|
108
94
|
|
|
109
95
|
extractScalaInheritance(node, name, ctx);
|
|
110
96
|
}
|
|
@@ -224,14 +210,14 @@ function extractScalaInheritance(node: TreeSitterNode, name: string, ctx: Extrac
|
|
|
224
210
|
|
|
225
211
|
// ── Body member extraction ──────────────────────────────────────────────────
|
|
226
212
|
|
|
227
|
-
function
|
|
213
|
+
function collectScalaBodyMembers(
|
|
228
214
|
parentNode: TreeSitterNode,
|
|
229
215
|
parentName: string,
|
|
230
|
-
|
|
231
|
-
): SubDeclaration[] {
|
|
216
|
+
): { children: SubDeclaration[]; methods: Definition[] } {
|
|
232
217
|
const children: SubDeclaration[] = [];
|
|
218
|
+
const methods: Definition[] = [];
|
|
233
219
|
const body = findChild(parentNode, 'template_body');
|
|
234
|
-
if (!body) return children;
|
|
220
|
+
if (!body) return { children, methods };
|
|
235
221
|
|
|
236
222
|
for (let i = 0; i < body.childCount; i++) {
|
|
237
223
|
const member = body.child(i);
|
|
@@ -240,12 +226,14 @@ function extractScalaBodyMembers(
|
|
|
240
226
|
if (member.type === 'function_definition') {
|
|
241
227
|
const methName = member.childForFieldName('name');
|
|
242
228
|
if (methName) {
|
|
243
|
-
|
|
229
|
+
const params = extractScalaParameters(member);
|
|
230
|
+
methods.push({
|
|
244
231
|
name: `${parentName}.${methName.text}`,
|
|
245
232
|
kind: 'method',
|
|
246
233
|
line: member.startPosition.row + 1,
|
|
247
234
|
endLine: member.endPosition.row + 1,
|
|
248
235
|
visibility: extractModifierVisibility(member),
|
|
236
|
+
children: params.length > 0 ? params : undefined,
|
|
249
237
|
});
|
|
250
238
|
}
|
|
251
239
|
} else if (member.type === 'val_definition' || member.type === 'var_definition') {
|
|
@@ -264,7 +252,7 @@ function extractScalaBodyMembers(
|
|
|
264
252
|
}
|
|
265
253
|
}
|
|
266
254
|
|
|
267
|
-
return children;
|
|
255
|
+
return { children, methods };
|
|
268
256
|
}
|
|
269
257
|
|
|
270
258
|
// ── Parameter extraction ────────────────────────────────────────────────────
|
|
@@ -156,15 +156,24 @@ function extractContractMember(child: TreeSitterNode): SubDeclaration | null {
|
|
|
156
156
|
}
|
|
157
157
|
}
|
|
158
158
|
|
|
159
|
-
/**
|
|
159
|
+
/**
|
|
160
|
+
* Extract inheritance (extends) relationships from a contract node.
|
|
161
|
+
*
|
|
162
|
+
* Each parent in `contract A is B, C, D { }` is its own `inheritance_specifier`
|
|
163
|
+
* sibling under the contract node (see tree-sitter-solidity grammar:
|
|
164
|
+
* `_class_heritage: "is" commaSep1($.inheritance_specifier)`), so we must walk
|
|
165
|
+
* all direct children rather than stopping at the first match.
|
|
166
|
+
*/
|
|
160
167
|
function extractInheritance(node: TreeSitterNode, name: string, ctx: ExtractorOutput): void {
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
+
for (let i = 0; i < node.childCount; i++) {
|
|
169
|
+
const inheritance = node.child(i);
|
|
170
|
+
if (!inheritance || inheritance.type !== 'inheritance_specifier') continue;
|
|
171
|
+
for (let j = 0; j < inheritance.childCount; j++) {
|
|
172
|
+
const child = inheritance.child(j);
|
|
173
|
+
if (!child) continue;
|
|
174
|
+
if (child.type === 'user_defined_type' || child.type === 'identifier') {
|
|
175
|
+
ctx.classes.push({ name, extends: child.text, line: node.startPosition.row + 1 });
|
|
176
|
+
}
|
|
168
177
|
}
|
|
169
178
|
}
|
|
170
179
|
}
|
|
@@ -99,27 +99,60 @@ function handlePackageDecl(node: TreeSitterNode, ctx: ExtractorOutput): void {
|
|
|
99
99
|
}
|
|
100
100
|
|
|
101
101
|
function handleClassDecl(node: TreeSitterNode, ctx: ExtractorOutput): void {
|
|
102
|
-
|
|
103
|
-
|
|
102
|
+
// tree-sitter-verilog exposes no field names on `class_declaration`. The
|
|
103
|
+
// class name lives under a `class_identifier > simple_identifier` chain, and
|
|
104
|
+
// the superclass appears as a `class_type` child (no `superclass` field).
|
|
105
|
+
// The Rust extractor in `crates/codegraph-core/src/extractors/verilog.rs`
|
|
106
|
+
// uses the same structural lookups so both engines emit identical class
|
|
107
|
+
// definitions and `extends` relations.
|
|
108
|
+
const name = findClassName(node);
|
|
109
|
+
if (!name) return;
|
|
104
110
|
|
|
105
111
|
ctx.definitions.push({
|
|
106
|
-
name
|
|
112
|
+
name,
|
|
107
113
|
kind: 'class',
|
|
108
114
|
line: node.startPosition.row + 1,
|
|
109
115
|
endLine: nodeEndLine(node),
|
|
110
116
|
});
|
|
111
117
|
|
|
112
|
-
|
|
113
|
-
const superclass = node.childForFieldName('superclass');
|
|
118
|
+
const superclass = findClassSuperclass(node);
|
|
114
119
|
if (superclass) {
|
|
115
120
|
ctx.classes.push({
|
|
116
|
-
name
|
|
117
|
-
extends: superclass
|
|
121
|
+
name,
|
|
122
|
+
extends: superclass,
|
|
118
123
|
line: node.startPosition.row + 1,
|
|
119
124
|
});
|
|
120
125
|
}
|
|
121
126
|
}
|
|
122
127
|
|
|
128
|
+
function findClassName(node: TreeSitterNode): string | null {
|
|
129
|
+
const fieldName = node.childForFieldName('name');
|
|
130
|
+
if (fieldName) return fieldName.text;
|
|
131
|
+
for (let i = 0; i < node.childCount; i++) {
|
|
132
|
+
const child = node.child(i);
|
|
133
|
+
if (child && child.type === 'class_identifier') {
|
|
134
|
+
const simple = findChild(child, 'simple_identifier');
|
|
135
|
+
return (simple ?? child).text.trim();
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
return null;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function findClassSuperclass(node: TreeSitterNode): string | null {
|
|
142
|
+
for (let i = 0; i < node.childCount; i++) {
|
|
143
|
+
const child = node.child(i);
|
|
144
|
+
if (child && child.type === 'class_type') {
|
|
145
|
+
const id = findChild(child, 'class_identifier');
|
|
146
|
+
if (id) {
|
|
147
|
+
const simple = findChild(id, 'simple_identifier');
|
|
148
|
+
return (simple ?? id).text.trim();
|
|
149
|
+
}
|
|
150
|
+
return child.text.trim();
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
return null;
|
|
154
|
+
}
|
|
155
|
+
|
|
123
156
|
function handleFunctionDecl(node: TreeSitterNode, ctx: ExtractorOutput): void {
|
|
124
157
|
const nameNode = findFunctionOrTaskName(node, 'function_identifier');
|
|
125
158
|
if (!nameNode) return;
|
|
@@ -151,8 +184,12 @@ function handleTaskDecl(node: TreeSitterNode, ctx: ExtractorOutput): void {
|
|
|
151
184
|
}
|
|
152
185
|
|
|
153
186
|
function handleModuleInstantiation(node: TreeSitterNode, ctx: ExtractorOutput): void {
|
|
154
|
-
// Module instantiations are like function calls: `ModuleName instance_name(...)
|
|
155
|
-
|
|
187
|
+
// Module instantiations are like function calls: `ModuleName instance_name(...);`.
|
|
188
|
+
// The module type identifier is the first *named* child; using
|
|
189
|
+
// `namedChild(0)` (instead of `child(0)`) skips anonymous tokens like a
|
|
190
|
+
// leading `#` parameter-override punctuation so we never capture that as a
|
|
191
|
+
// call name. The Rust extractor uses the same lookup for parity.
|
|
192
|
+
const moduleType = node.childForFieldName('type') ?? node.namedChild(0);
|
|
156
193
|
if (!moduleType) return;
|
|
157
194
|
|
|
158
195
|
ctx.calls.push({
|
|
@@ -169,7 +206,10 @@ function handlePackageImport(node: TreeSitterNode, ctx: ExtractorOutput): void {
|
|
|
169
206
|
if (child.type === 'package_import_item') {
|
|
170
207
|
const text = child.text;
|
|
171
208
|
const parts = text.split('::');
|
|
172
|
-
|
|
209
|
+
// `String.split('::')` always yields at least one element — when the
|
|
210
|
+
// delimiter is absent the whole string is the sole item, so the
|
|
211
|
+
// empty-string fallback is unreachable in practice.
|
|
212
|
+
const pkg = parts[0] ?? '';
|
|
173
213
|
const item = parts[1] ?? '*';
|
|
174
214
|
ctx.imports.push({
|
|
175
215
|
source: pkg,
|
|
@@ -182,9 +222,18 @@ function handlePackageImport(node: TreeSitterNode, ctx: ExtractorOutput): void {
|
|
|
182
222
|
|
|
183
223
|
function handleIncludeDirective(node: TreeSitterNode, ctx: ExtractorOutput): void {
|
|
184
224
|
// `include "file.vh"
|
|
225
|
+
// Mirrors the Rust `handle_include_directive` which checks all three node
|
|
226
|
+
// kinds — tree-sitter-verilog has emitted `double_quoted_string` in some
|
|
227
|
+
// grammar revisions, and missing it would silently drop the import in WASM
|
|
228
|
+
// while the native engine still records it.
|
|
185
229
|
for (let i = 0; i < node.childCount; i++) {
|
|
186
230
|
const child = node.child(i);
|
|
187
|
-
if (
|
|
231
|
+
if (
|
|
232
|
+
child &&
|
|
233
|
+
(child.type === 'string_literal' ||
|
|
234
|
+
child.type === 'quoted_string' ||
|
|
235
|
+
child.type === 'double_quoted_string')
|
|
236
|
+
) {
|
|
188
237
|
const source = child.text.replace(/^["']|["']$/g, '');
|
|
189
238
|
ctx.imports.push({
|
|
190
239
|
source,
|
|
@@ -266,8 +315,14 @@ function findVerilogParent(node: TreeSitterNode): string | null {
|
|
|
266
315
|
current.type === 'package_declaration' ||
|
|
267
316
|
current.type === 'class_declaration'
|
|
268
317
|
) {
|
|
269
|
-
|
|
270
|
-
|
|
318
|
+
// `class_declaration` wraps its name in `class_identifier >
|
|
319
|
+
// simple_identifier`; `findDeclName` / `findModuleName` only look at
|
|
320
|
+
// bare `simple_identifier`/`identifier` children, so they miss it.
|
|
321
|
+
// `findClassName` already handles the wrapper, so consult it last to
|
|
322
|
+
// qualify tasks/functions nested inside a SystemVerilog class.
|
|
323
|
+
const nameNode = findDeclName(current) || findModuleName(current);
|
|
324
|
+
if (nameNode) return nameNode.text;
|
|
325
|
+
return findClassName(current);
|
|
271
326
|
}
|
|
272
327
|
current = current.parent;
|
|
273
328
|
}
|
|
@@ -292,17 +347,30 @@ function extractPorts(moduleNode: TreeSitterNode): SubDeclaration[] {
|
|
|
292
347
|
) {
|
|
293
348
|
const nameNode =
|
|
294
349
|
child.childForFieldName('name') ||
|
|
350
|
+
findChild(child, 'port_identifier') ||
|
|
295
351
|
findChild(child, 'simple_identifier') ||
|
|
296
352
|
findChild(child, 'identifier');
|
|
297
353
|
if (nameNode) {
|
|
298
|
-
|
|
354
|
+
// `port_identifier` wraps a `simple_identifier`; descend to the
|
|
355
|
+
// innermost identifier for a clean, whitespace-free name.
|
|
356
|
+
const inner =
|
|
357
|
+
findChild(nameNode, 'simple_identifier') ||
|
|
358
|
+
findChild(nameNode, 'identifier') ||
|
|
359
|
+
nameNode;
|
|
360
|
+
ports.push({ name: inner.text, kind: 'property', line: child.startPosition.row + 1 });
|
|
299
361
|
}
|
|
300
362
|
}
|
|
301
363
|
|
|
302
|
-
// Recurse into port list containers
|
|
364
|
+
// Recurse into port list containers. `module_ansi_header` wraps the
|
|
365
|
+
// ANSI-style declarations emitted by tree-sitter-verilog (e.g.
|
|
366
|
+
// `module top(input clk, output reg q);`) — without this branch the
|
|
367
|
+
// WASM engine returns an empty children array while the native engine
|
|
368
|
+
// (which includes the same kind in its CONTAINER_KINDS list) returns
|
|
369
|
+
// the correct ports, breaking engine parity.
|
|
303
370
|
if (
|
|
304
371
|
child.type === 'list_of_port_declarations' ||
|
|
305
372
|
child.type === 'module_header' ||
|
|
373
|
+
child.type === 'module_ansi_header' ||
|
|
306
374
|
child.type === 'port_declaration_list'
|
|
307
375
|
) {
|
|
308
376
|
collectFromNode(child);
|
package/src/mcp/server.ts
CHANGED
|
@@ -98,17 +98,15 @@ async function resolveDbPath(
|
|
|
98
98
|
return dbPath;
|
|
99
99
|
}
|
|
100
100
|
|
|
101
|
-
function validateMultiRepoAccess(multiRepo: boolean,
|
|
101
|
+
function validateMultiRepoAccess(multiRepo: boolean, args: { repo?: string }): void {
|
|
102
102
|
if (!multiRepo && args.repo) {
|
|
103
103
|
throw new ConfigError(
|
|
104
104
|
'Multi-repo access is disabled. Restart with `codegraph mcp --multi-repo` to access other repositories.',
|
|
105
105
|
);
|
|
106
106
|
}
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
);
|
|
111
|
-
}
|
|
107
|
+
// Note: the `list_repos` tool is excluded from `enabledToolNames` when
|
|
108
|
+
// `multiRepo` is false (see `buildToolList`), so any call to it is rejected
|
|
109
|
+
// earlier in `createCallToolHandler` with an "Unknown tool" error.
|
|
112
110
|
}
|
|
113
111
|
|
|
114
112
|
/**
|
|
@@ -163,11 +161,17 @@ function createCallToolHandler(
|
|
|
163
161
|
customDbPath: string | undefined,
|
|
164
162
|
allowedRepos: string[] | undefined,
|
|
165
163
|
getQueries: () => Promise<unknown>,
|
|
164
|
+
enabledToolNames: Set<string>,
|
|
166
165
|
) {
|
|
167
166
|
return async (request: any) => {
|
|
168
167
|
const { name, arguments: args } = request.params;
|
|
169
168
|
try {
|
|
170
|
-
|
|
169
|
+
if (!enabledToolNames.has(name)) {
|
|
170
|
+
return { content: [{ type: 'text', text: `Unknown tool: ${name}` }], isError: true };
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
validateMultiRepoAccess(multiRepo, args);
|
|
174
|
+
|
|
171
175
|
const dbPath = await resolveDbPath(customDbPath, args, allowedRepos);
|
|
172
176
|
|
|
173
177
|
const toolEntry = TOOL_HANDLERS.get(name);
|
|
@@ -209,6 +213,9 @@ export async function startMCPServer(
|
|
|
209
213
|
// Apply config-based MCP page-size overrides
|
|
210
214
|
const config = options.config || loadConfig();
|
|
211
215
|
initMcpDefaults(config.mcp?.defaults ? { ...config.mcp.defaults } : undefined);
|
|
216
|
+
const disabledTools = [...(config.mcp?.disabledTools ?? [])];
|
|
217
|
+
const enabledTools = buildToolList(multiRepo, disabledTools);
|
|
218
|
+
const enabledToolNames = new Set(enabledTools.map((tool) => tool.name));
|
|
212
219
|
|
|
213
220
|
const { Server, StdioServerTransport, ListToolsRequestSchema, CallToolRequestSchema } =
|
|
214
221
|
await loadMCPSdk();
|
|
@@ -225,12 +232,12 @@ export async function startMCPServer(
|
|
|
225
232
|
);
|
|
226
233
|
|
|
227
234
|
server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
228
|
-
tools:
|
|
235
|
+
tools: enabledTools,
|
|
229
236
|
}));
|
|
230
237
|
|
|
231
238
|
server.setRequestHandler(
|
|
232
239
|
CallToolRequestSchema,
|
|
233
|
-
createCallToolHandler(multiRepo, customDbPath, allowedRepos, getQueries),
|
|
240
|
+
createCallToolHandler(multiRepo, customDbPath, allowedRepos, getQueries, enabledToolNames),
|
|
234
241
|
);
|
|
235
242
|
|
|
236
243
|
const transport = new (StdioServerTransport as any)();
|
package/src/mcp/tool-registry.ts
CHANGED
|
@@ -29,6 +29,17 @@ const PAGINATION_PROPS: Record<string, unknown> = {
|
|
|
29
29
|
offset: { type: 'number', description: 'Skip this many results (pagination, default: 0)' },
|
|
30
30
|
};
|
|
31
31
|
|
|
32
|
+
function normalizeToolName(name: string): string {
|
|
33
|
+
return name
|
|
34
|
+
.trim()
|
|
35
|
+
.toLowerCase()
|
|
36
|
+
.replace(/^codegraph\d+_/, '');
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function buildDisabledToolSet(disabledTools?: string[]): Set<string> {
|
|
40
|
+
return new Set((disabledTools || []).map((name) => normalizeToolName(name)).filter(Boolean));
|
|
41
|
+
}
|
|
42
|
+
|
|
32
43
|
const BASE_TOOLS: ToolSchema[] = [
|
|
33
44
|
{
|
|
34
45
|
name: 'query',
|
|
@@ -311,6 +322,11 @@ const BASE_TOOLS: ToolSchema[] = [
|
|
|
311
322
|
description:
|
|
312
323
|
'Search mode: hybrid (BM25 + semantic, default), semantic (embeddings only), keyword (BM25 only)',
|
|
313
324
|
},
|
|
325
|
+
file_pattern: {
|
|
326
|
+
oneOf: [{ type: 'string' }, { type: 'array', items: { type: 'string' } }],
|
|
327
|
+
description:
|
|
328
|
+
'Restrict results to files matching one or more glob or substring patterns (e.g. "db/", "src/**/*.ts", or ["db/", "src/"])',
|
|
329
|
+
},
|
|
314
330
|
...PAGINATION_PROPS,
|
|
315
331
|
},
|
|
316
332
|
required: ['query'],
|
|
@@ -849,18 +865,25 @@ const LIST_REPOS_TOOL: ToolSchema = {
|
|
|
849
865
|
/**
|
|
850
866
|
* Build the tool list based on multi-repo mode.
|
|
851
867
|
*/
|
|
852
|
-
export function buildToolList(multiRepo: boolean): ToolSchema[] {
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
868
|
+
export function buildToolList(multiRepo: boolean, disabledTools?: string[]): ToolSchema[] {
|
|
869
|
+
const disabled = buildDisabledToolSet(disabledTools);
|
|
870
|
+
const includeTool = (tool: ToolSchema): boolean => !disabled.has(normalizeToolName(tool.name));
|
|
871
|
+
const baseTools = BASE_TOOLS.filter(includeTool);
|
|
872
|
+
|
|
873
|
+
if (!multiRepo) return baseTools;
|
|
874
|
+
|
|
875
|
+
const tools: ToolSchema[] = [
|
|
876
|
+
...baseTools.map((tool) => ({
|
|
856
877
|
...tool,
|
|
857
878
|
inputSchema: {
|
|
858
879
|
...tool.inputSchema,
|
|
859
880
|
properties: { ...tool.inputSchema.properties, ...REPO_PROP },
|
|
860
881
|
},
|
|
861
882
|
})),
|
|
862
|
-
LIST_REPOS_TOOL,
|
|
863
883
|
];
|
|
884
|
+
|
|
885
|
+
if (includeTool(LIST_REPOS_TOOL)) tools.push(LIST_REPOS_TOOL);
|
|
886
|
+
return tools;
|
|
864
887
|
}
|
|
865
888
|
|
|
866
889
|
// Backward-compatible export: full multi-repo tool list
|