@optave/codegraph 3.10.0 → 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 +13 -13
- 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/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/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 +181 -39
- 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/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.map +1 -1
- package/dist/domain/parser.js +27 -1
- 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 +35 -1
- package/dist/domain/search/models.js.map +1 -1
- package/dist/domain/wasm-worker-entry.js +8 -1
- 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/mcp/tool-registry.d.ts.map +1 -1
- package/dist/mcp/tool-registry.js +4 -0
- 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 +15 -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 +10 -10
- package/src/ast-analysis/rules/index.ts +87 -0
- 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/incremental.ts +6 -41
- package/src/domain/graph/builder/pipeline.ts +222 -37
- package/src/domain/graph/builder/stages/build-edges.ts +9 -2
- package/src/domain/graph/builder/stages/resolve-imports.ts +79 -25
- package/src/domain/graph/watcher.ts +21 -23
- package/src/domain/parser.ts +27 -1
- package/src/domain/search/models.ts +36 -1
- package/src/domain/wasm-worker-entry.ts +8 -1
- 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/mcp/tool-registry.ts +5 -0
- package/src/mcp/tools/semantic-search.ts +2 -0
- package/src/types.ts +15 -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/tool-registry.ts
CHANGED
|
@@ -322,6 +322,11 @@ const BASE_TOOLS: ToolSchema[] = [
|
|
|
322
322
|
description:
|
|
323
323
|
'Search mode: hybrid (BM25 + semantic, default), semantic (embeddings only), keyword (BM25 only)',
|
|
324
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
|
+
},
|
|
325
330
|
...PAGINATION_PROPS,
|
|
326
331
|
},
|
|
327
332
|
required: ['query'],
|
|
@@ -9,6 +9,7 @@ interface SemanticSearchArgs {
|
|
|
9
9
|
limit?: number;
|
|
10
10
|
offset?: number;
|
|
11
11
|
min_score?: number;
|
|
12
|
+
file_pattern?: string | string[];
|
|
12
13
|
}
|
|
13
14
|
|
|
14
15
|
export async function handler(args: SemanticSearchArgs, ctx: McpToolContext): Promise<unknown> {
|
|
@@ -17,6 +18,7 @@ export async function handler(args: SemanticSearchArgs, ctx: McpToolContext): Pr
|
|
|
17
18
|
limit: Math.min(args.limit ?? MCP_DEFAULTS.semantic_search ?? 100, ctx.MCP_MAX_LIMIT),
|
|
18
19
|
offset: effectiveOffset(args),
|
|
19
20
|
minScore: args.min_score,
|
|
21
|
+
filePattern: args.file_pattern,
|
|
20
22
|
};
|
|
21
23
|
|
|
22
24
|
if (mode === 'keyword') {
|
package/src/types.ts
CHANGED
|
@@ -99,6 +99,7 @@ export type LanguageId =
|
|
|
99
99
|
| 'ocaml'
|
|
100
100
|
| 'ocaml-interface'
|
|
101
101
|
| 'fsharp'
|
|
102
|
+
| 'fsharp-signature'
|
|
102
103
|
| 'gleam'
|
|
103
104
|
| 'clojure'
|
|
104
105
|
| 'julia'
|
|
@@ -576,6 +577,7 @@ export interface TreeSitterNode {
|
|
|
576
577
|
child(index: number): TreeSitterNode | null;
|
|
577
578
|
namedChild(index: number): TreeSitterNode | null;
|
|
578
579
|
childForFieldName(name: string): TreeSitterNode | null;
|
|
580
|
+
fieldNameForChild(index: number): string | null;
|
|
579
581
|
parent: TreeSitterNode | null;
|
|
580
582
|
previousSibling: TreeSitterNode | null;
|
|
581
583
|
nextSibling: TreeSitterNode | null;
|
|
@@ -1062,7 +1064,20 @@ export interface BuildGraphOpts {
|
|
|
1062
1064
|
complexity?: boolean;
|
|
1063
1065
|
cfg?: boolean;
|
|
1064
1066
|
scope?: string[];
|
|
1067
|
+
/**
|
|
1068
|
+
* Glob patterns merged on top of `config.exclude` for this build only.
|
|
1069
|
+
* Lets callers extend exclusion programmatically without writing a config
|
|
1070
|
+
* file — used by the benchmark scripts to skip resolution-benchmark
|
|
1071
|
+
* fixtures that aren't representative of real code.
|
|
1072
|
+
*/
|
|
1073
|
+
exclude?: string[];
|
|
1065
1074
|
skipRegistry?: boolean;
|
|
1075
|
+
/**
|
|
1076
|
+
* Override the graph.db location. Resolved absolute. When omitted, the
|
|
1077
|
+
* pipeline writes to `<rootDir>/.codegraph/graph.db` — same default as
|
|
1078
|
+
* `findDbPath` for every other DB-scoped command.
|
|
1079
|
+
*/
|
|
1080
|
+
dbPath?: string;
|
|
1066
1081
|
}
|
|
1067
1082
|
|
|
1068
1083
|
/** Build timing result from buildGraph. */
|