gitnexus 1.6.13-rc.66 → 1.6.13-rc.67
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/dist/core/ingestion/language-provider.d.ts +4 -1
- package/dist/core/ingestion/languages/typescript/tool-definitions.d.ts +4 -0
- package/dist/core/ingestion/languages/typescript/tool-definitions.js +355 -0
- package/dist/core/ingestion/languages/typescript.js +3 -0
- package/dist/core/ingestion/pipeline-phases/processes.js +2 -0
- package/dist/core/ingestion/pipeline-phases/tools.d.ts +1 -0
- package/dist/core/ingestion/pipeline-phases/tools.js +1 -0
- package/dist/core/ingestion/workers/parse-worker.d.ts +2 -0
- package/dist/core/ingestion/workers/parse-worker.js +21 -0
- package/dist/storage/parse-cache.js +8 -1
- package/package.json +1 -1
|
@@ -26,7 +26,7 @@ import type { ExtractedRoute } from './route-extractors/laravel.js';
|
|
|
26
26
|
import type { SharedSpringType } from './route-extractors/spring-shared.js';
|
|
27
27
|
import type { ModuleConstants, Operand, RepoConstants } from './route-extractors/constant-resolver.js';
|
|
28
28
|
import type Parser from 'tree-sitter';
|
|
29
|
-
import type { ExtractedDecoratorRoute } from './workers/parse-worker.js';
|
|
29
|
+
import type { ExtractedDecoratorRoute, ExtractedToolDef } from './workers/parse-worker.js';
|
|
30
30
|
import type { SemanticModel } from './model/semantic-model.js';
|
|
31
31
|
/** What a provider's {@link LanguageProviderConfig.resolveRouteHandler} can see. */
|
|
32
32
|
export interface RouteHandlerResolutionHookContext {
|
|
@@ -430,6 +430,9 @@ interface LanguageProviderConfig {
|
|
|
430
430
|
* Default: undefined (no text-route extraction).
|
|
431
431
|
*/
|
|
432
432
|
readonly extractTextRoutes?: (filePath: string, content: string) => ExtractedRoute[];
|
|
433
|
+
/** Extract tool registrations after captures, using only emitted callable identities.
|
|
434
|
+
* The map keys are declaration-name AST node IDs, local to this parsed tree. */
|
|
435
|
+
readonly extractToolDefinitions?: (tree: Parser.Tree, filePath: string, lineOffset: number, callableBindings: ReadonlyMap<number, string>) => ExtractedToolDef[];
|
|
433
436
|
/**
|
|
434
437
|
* Extract routes that a parsed file declares in its own AST.
|
|
435
438
|
*
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type Parser from 'tree-sitter';
|
|
2
|
+
import type { ExtractedToolDef } from '../../workers/parse-worker.js';
|
|
3
|
+
/** Direct SDK registrations only; no wrapper, alias-chain or runtime-value inference. */
|
|
4
|
+
export declare function extractToolDefinitions(tree: Parser.Tree, filePath: string, lineOffset?: number, callableBindings?: ReadonlyMap<number, string>): ExtractedToolDef[];
|
|
@@ -0,0 +1,355 @@
|
|
|
1
|
+
import { plainString, propertyName } from '../../route-extractors/data-route-table.js';
|
|
2
|
+
const FUNCTIONS = new Set([
|
|
3
|
+
'function_declaration',
|
|
4
|
+
'generator_function_declaration',
|
|
5
|
+
'function_expression',
|
|
6
|
+
'generator_function',
|
|
7
|
+
'arrow_function',
|
|
8
|
+
'method_definition',
|
|
9
|
+
]);
|
|
10
|
+
const BLOCKS = new Set([
|
|
11
|
+
'statement_block',
|
|
12
|
+
'for_statement',
|
|
13
|
+
'for_in_statement',
|
|
14
|
+
'switch_body',
|
|
15
|
+
'catch_clause',
|
|
16
|
+
'class_body',
|
|
17
|
+
]);
|
|
18
|
+
const SDK_MODULES = new Set([
|
|
19
|
+
'@modelcontextprotocol/sdk/server/mcp.js',
|
|
20
|
+
'@modelcontextprotocol/sdk/server/mcp',
|
|
21
|
+
]);
|
|
22
|
+
function lookup(scope, name) {
|
|
23
|
+
for (let current = scope; current; current = current.parent) {
|
|
24
|
+
const binding = current.bindings.get(name);
|
|
25
|
+
if (binding)
|
|
26
|
+
return binding;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
/** Only binding/assignment patterns: never descend into keys, types or defaults.
|
|
30
|
+
* Member assignment targets are reported separately from binding names. */
|
|
31
|
+
function patternNames(pattern, onMember) {
|
|
32
|
+
const names = [];
|
|
33
|
+
const pending = [pattern];
|
|
34
|
+
while (pending.length) {
|
|
35
|
+
const node = pending.pop();
|
|
36
|
+
if (node.type === 'identifier' || node.type === 'shorthand_property_identifier_pattern') {
|
|
37
|
+
names.push(node);
|
|
38
|
+
}
|
|
39
|
+
else if (node.type === 'member_expression' || node.type === 'subscript_expression') {
|
|
40
|
+
onMember?.(node);
|
|
41
|
+
}
|
|
42
|
+
else if (node.type === 'pair_pattern') {
|
|
43
|
+
const value = node.childForFieldName('value');
|
|
44
|
+
if (value)
|
|
45
|
+
pending.push(value);
|
|
46
|
+
}
|
|
47
|
+
else if (node.type === 'assignment_pattern' || node.type === 'object_assignment_pattern') {
|
|
48
|
+
const left = node.childForFieldName('left');
|
|
49
|
+
if (left)
|
|
50
|
+
pending.push(left);
|
|
51
|
+
}
|
|
52
|
+
else if (node.type === 'array_pattern' ||
|
|
53
|
+
node.type === 'object_pattern' ||
|
|
54
|
+
node.type === 'rest_pattern') {
|
|
55
|
+
pending.push(...node.namedChildren);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
return names;
|
|
59
|
+
}
|
|
60
|
+
function declare(scope, name, details = {}) {
|
|
61
|
+
const previous = scope.bindings.get(name.text);
|
|
62
|
+
if (previous) {
|
|
63
|
+
previous.invalid = true;
|
|
64
|
+
}
|
|
65
|
+
else {
|
|
66
|
+
scope.bindings.set(name.text, { name, scope, kind: 'unknown', ...details });
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
function variableScope(scope) {
|
|
70
|
+
while (!scope.functionScope && scope.parent)
|
|
71
|
+
scope = scope.parent;
|
|
72
|
+
return scope;
|
|
73
|
+
}
|
|
74
|
+
function collectBindings(root) {
|
|
75
|
+
const moduleScope = { functionScope: true, bindings: new Map() };
|
|
76
|
+
const scopes = new Map();
|
|
77
|
+
const calls = [];
|
|
78
|
+
const writes = [];
|
|
79
|
+
const pending = [{ node: root, scope: moduleScope }];
|
|
80
|
+
while (pending.length) {
|
|
81
|
+
const entry = pending.pop();
|
|
82
|
+
const node = entry.node;
|
|
83
|
+
let scope = entry.scope;
|
|
84
|
+
const isFunction = FUNCTIONS.has(node.type);
|
|
85
|
+
const name = node.childForFieldName('name');
|
|
86
|
+
if (node.type === 'function_declaration' || node.type === 'generator_function_declaration') {
|
|
87
|
+
if (name)
|
|
88
|
+
declare(scope, name, { kind: 'function', value: node, immutable: true });
|
|
89
|
+
}
|
|
90
|
+
else if ([
|
|
91
|
+
'class_declaration',
|
|
92
|
+
'interface_declaration',
|
|
93
|
+
'type_alias_declaration',
|
|
94
|
+
'enum_declaration',
|
|
95
|
+
].includes(node.type)) {
|
|
96
|
+
if (name)
|
|
97
|
+
declare(scope, name);
|
|
98
|
+
}
|
|
99
|
+
if (isFunction ||
|
|
100
|
+
BLOCKS.has(node.type) ||
|
|
101
|
+
node.type === 'class' ||
|
|
102
|
+
node.type === 'class_declaration') {
|
|
103
|
+
scope = { parent: scope, functionScope: isFunction, bindings: new Map() };
|
|
104
|
+
}
|
|
105
|
+
scopes.set(node.id, scope);
|
|
106
|
+
if (node.type === 'class' && name)
|
|
107
|
+
declare(scope, name);
|
|
108
|
+
if (isFunction) {
|
|
109
|
+
if (name && (node.type === 'function_expression' || node.type === 'generator_function')) {
|
|
110
|
+
declare(scope, name, { kind: 'function', value: node, immutable: true });
|
|
111
|
+
}
|
|
112
|
+
const parameters = node.childForFieldName('parameters');
|
|
113
|
+
const single = node.childForFieldName('parameter');
|
|
114
|
+
for (const parameter of parameters?.namedChildren ?? (single ? [single] : [])) {
|
|
115
|
+
const pattern = parameter.childForFieldName('pattern') ?? parameter;
|
|
116
|
+
const type = parameter.childForFieldName('type')?.namedChildren[0];
|
|
117
|
+
for (const bindingName of patternNames(pattern)) {
|
|
118
|
+
declare(scope, bindingName, {
|
|
119
|
+
kind: 'parameter',
|
|
120
|
+
...(pattern.type === 'identifier' && type ? { type } : {}),
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
else if (node.type === 'import_statement') {
|
|
126
|
+
const source = node.childForFieldName('source');
|
|
127
|
+
const sdk = source !== null && SDK_MODULES.has(plainString(source) ?? '');
|
|
128
|
+
const typeOnly = node.children.some((child) => child.type === 'type');
|
|
129
|
+
const clause = node.namedChildren.find((child) => child.type === 'import_clause');
|
|
130
|
+
for (const child of clause?.namedChildren ?? []) {
|
|
131
|
+
if (child.type === 'identifier')
|
|
132
|
+
declare(scope, child);
|
|
133
|
+
else if (child.type === 'namespace_import') {
|
|
134
|
+
const local = child.namedChildren[0];
|
|
135
|
+
if (local)
|
|
136
|
+
declare(scope, local, { kind: sdk ? 'sdk-namespace' : 'unknown', typeOnly });
|
|
137
|
+
}
|
|
138
|
+
else if (child.type === 'named_imports') {
|
|
139
|
+
for (const specifier of child.namedChildren) {
|
|
140
|
+
const imported = specifier.childForFieldName('name');
|
|
141
|
+
const local = specifier.childForFieldName('alias') ?? imported;
|
|
142
|
+
if (local)
|
|
143
|
+
declare(scope, local, {
|
|
144
|
+
kind: sdk && imported?.text === 'McpServer' ? 'sdk' : 'unknown',
|
|
145
|
+
typeOnly: typeOnly || specifier.children.some((part) => part.type === 'type'),
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
else if (node.type === 'variable_declarator') {
|
|
152
|
+
if (name) {
|
|
153
|
+
const target = node.parent?.type === 'variable_declaration' ? variableScope(scope) : scope;
|
|
154
|
+
for (const bindingName of patternNames(name)) {
|
|
155
|
+
declare(target, bindingName, {
|
|
156
|
+
kind: 'variable',
|
|
157
|
+
immutable: node.parent?.childForFieldName('kind')?.type === 'const',
|
|
158
|
+
...(name.type === 'identifier'
|
|
159
|
+
? { value: node.childForFieldName('value') ?? undefined }
|
|
160
|
+
: {}),
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
else if (node.type === 'catch_clause') {
|
|
166
|
+
const parameter = node.childForFieldName('parameter');
|
|
167
|
+
if (parameter)
|
|
168
|
+
for (const bindingName of patternNames(parameter))
|
|
169
|
+
declare(scope, bindingName);
|
|
170
|
+
}
|
|
171
|
+
else if (node.type === 'for_in_statement') {
|
|
172
|
+
const left = node.childForFieldName('left');
|
|
173
|
+
const kind = node.childForFieldName('kind');
|
|
174
|
+
if (left && kind) {
|
|
175
|
+
const target = kind.type === 'var' ? variableScope(scope) : scope;
|
|
176
|
+
for (const bindingName of patternNames(left))
|
|
177
|
+
declare(target, bindingName);
|
|
178
|
+
}
|
|
179
|
+
else if (left)
|
|
180
|
+
writes.push(left);
|
|
181
|
+
}
|
|
182
|
+
else if (node.type === 'type_parameter') {
|
|
183
|
+
if (name)
|
|
184
|
+
declare(scope, name);
|
|
185
|
+
}
|
|
186
|
+
if (node.type === 'call_expression')
|
|
187
|
+
calls.push(node);
|
|
188
|
+
if (node.type === 'assignment_expression' || node.type === 'augmented_assignment_expression') {
|
|
189
|
+
const left = node.childForFieldName('left');
|
|
190
|
+
if (left)
|
|
191
|
+
writes.push(left);
|
|
192
|
+
}
|
|
193
|
+
else if (node.type === 'update_expression' ||
|
|
194
|
+
(node.type === 'unary_expression' && node.children.some((child) => child.type === 'delete'))) {
|
|
195
|
+
const argument = node.childForFieldName('argument');
|
|
196
|
+
if (argument)
|
|
197
|
+
writes.push(argument);
|
|
198
|
+
}
|
|
199
|
+
for (let index = node.namedChildCount - 1; index >= 0; index--) {
|
|
200
|
+
pending.push({ node: node.namedChild(index), scope });
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
// Resolve writes after declarations so later declarations also shadow outer names.
|
|
204
|
+
while (writes.length) {
|
|
205
|
+
let target = writes.pop();
|
|
206
|
+
const scope = scopes.get(target.id);
|
|
207
|
+
const members = [];
|
|
208
|
+
while (target.type === 'member_expression' || target.type === 'subscript_expression') {
|
|
209
|
+
const object = target.childForFieldName('object');
|
|
210
|
+
if (!object)
|
|
211
|
+
break;
|
|
212
|
+
const property = target.childForFieldName('property');
|
|
213
|
+
const index = target.childForFieldName('index');
|
|
214
|
+
members.push(property ? propertyName(property) : index ? plainString(index) : null);
|
|
215
|
+
target = object;
|
|
216
|
+
}
|
|
217
|
+
// Nested member targets must pass the same guard as direct property writes.
|
|
218
|
+
for (const name of patternNames(target, (member) => writes.push(member))) {
|
|
219
|
+
const binding = lookup(scope, name.text);
|
|
220
|
+
// Lifecycle callbacks and other known properties do not replace the receiver
|
|
221
|
+
// or its registration methods. Unknown keys and constructor mutations remain unsafe.
|
|
222
|
+
if (binding?.kind !== 'sdk' &&
|
|
223
|
+
binding?.kind !== 'sdk-namespace' &&
|
|
224
|
+
members.length > 0 &&
|
|
225
|
+
members.every((member) => member !== null) &&
|
|
226
|
+
!['tool', 'registerTool', '__proto__'].includes(members[members.length - 1]))
|
|
227
|
+
continue;
|
|
228
|
+
if (binding)
|
|
229
|
+
binding.invalid = true;
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
return { scopes, calls };
|
|
233
|
+
}
|
|
234
|
+
function sdkBinding(node, scope, forType = false) {
|
|
235
|
+
let kind = 'sdk';
|
|
236
|
+
if (node.type === (forType ? 'nested_type_identifier' : 'member_expression')) {
|
|
237
|
+
const namespace = node.childForFieldName(forType ? 'module' : 'object');
|
|
238
|
+
const member = node.childForFieldName(forType ? 'name' : 'property');
|
|
239
|
+
if (namespace?.type !== 'identifier' || member?.text !== 'McpServer')
|
|
240
|
+
return false;
|
|
241
|
+
node = namespace;
|
|
242
|
+
kind = 'sdk-namespace';
|
|
243
|
+
}
|
|
244
|
+
if (node.type !== 'identifier' && node.type !== 'type_identifier')
|
|
245
|
+
return false;
|
|
246
|
+
const binding = lookup(scope, node.text);
|
|
247
|
+
return binding?.kind === kind && !binding.invalid && (forType || !binding.typeOnly);
|
|
248
|
+
}
|
|
249
|
+
function sdkReceiver(node, scope, scopes) {
|
|
250
|
+
if (node.type !== 'identifier')
|
|
251
|
+
return false;
|
|
252
|
+
const binding = lookup(scope, node.text);
|
|
253
|
+
if (!binding || binding.invalid)
|
|
254
|
+
return false;
|
|
255
|
+
if (binding.kind === 'parameter' && binding.type) {
|
|
256
|
+
return sdkBinding(binding.type, binding.scope, true);
|
|
257
|
+
}
|
|
258
|
+
const value = binding.value;
|
|
259
|
+
if (binding.kind !== 'variable' || value?.type !== 'new_expression')
|
|
260
|
+
return false;
|
|
261
|
+
if (value.endIndex > node.startIndex && variableScope(binding.scope) === variableScope(scope))
|
|
262
|
+
return false;
|
|
263
|
+
const constructor = value.childForFieldName('constructor');
|
|
264
|
+
return constructor !== null && sdkBinding(constructor, scopes.get(value.id));
|
|
265
|
+
}
|
|
266
|
+
/** An unknown later property can replace description; a later explicit property restores proof. */
|
|
267
|
+
function descriptionFromConfig(config) {
|
|
268
|
+
let description = '';
|
|
269
|
+
if (config.type !== 'object')
|
|
270
|
+
return description;
|
|
271
|
+
for (const child of config.namedChildren) {
|
|
272
|
+
if (child.type === 'comment')
|
|
273
|
+
continue;
|
|
274
|
+
const key = child.childForFieldName('key') ?? child.childForFieldName('name');
|
|
275
|
+
const name = child.type === 'shorthand_property_identifier' ? child.text : key && propertyName(key);
|
|
276
|
+
if (child.type === 'pair' && name === 'description') {
|
|
277
|
+
const value = child.childForFieldName('value');
|
|
278
|
+
description = value ? (plainString(value) ?? '') : '';
|
|
279
|
+
}
|
|
280
|
+
else if (!name || name === 'description') {
|
|
281
|
+
description = '';
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
return description;
|
|
285
|
+
}
|
|
286
|
+
function handlerNodeId(node, scope, callableBindings) {
|
|
287
|
+
if (node.type !== 'identifier')
|
|
288
|
+
return undefined;
|
|
289
|
+
const binding = lookup(scope, node.text);
|
|
290
|
+
if (!binding || binding.invalid)
|
|
291
|
+
return undefined;
|
|
292
|
+
if (binding.kind === 'variable') {
|
|
293
|
+
const value = binding.value;
|
|
294
|
+
if (!binding.immutable ||
|
|
295
|
+
!value ||
|
|
296
|
+
(value.type !== 'arrow_function' && value.type !== 'function_expression'))
|
|
297
|
+
return undefined;
|
|
298
|
+
if (value.endIndex > node.startIndex && variableScope(binding.scope) === variableScope(scope))
|
|
299
|
+
return undefined;
|
|
300
|
+
}
|
|
301
|
+
else if (binding.kind !== 'function')
|
|
302
|
+
return undefined;
|
|
303
|
+
return callableBindings?.get(binding.name.id);
|
|
304
|
+
}
|
|
305
|
+
/** Direct SDK registrations only; no wrapper, alias-chain or runtime-value inference. */
|
|
306
|
+
export function extractToolDefinitions(tree, filePath, lineOffset = 0, callableBindings) {
|
|
307
|
+
// Ordinary files need no lexical walk; every supported receiver originates here.
|
|
308
|
+
const importsSdk = tree.rootNode.namedChildren.some((node) => {
|
|
309
|
+
if (node.type !== 'import_statement')
|
|
310
|
+
return false;
|
|
311
|
+
const source = node.childForFieldName('source');
|
|
312
|
+
return source !== null && SDK_MODULES.has(plainString(source) ?? '');
|
|
313
|
+
});
|
|
314
|
+
if (!importsSdk)
|
|
315
|
+
return [];
|
|
316
|
+
const { scopes, calls } = collectBindings(tree.rootNode);
|
|
317
|
+
const definitions = [];
|
|
318
|
+
for (const call of calls) {
|
|
319
|
+
const callee = call.childForFieldName('function');
|
|
320
|
+
if (call.hasError || callee?.type !== 'member_expression')
|
|
321
|
+
continue;
|
|
322
|
+
const receiver = callee.childForFieldName('object');
|
|
323
|
+
const method = callee.childForFieldName('property');
|
|
324
|
+
if (!receiver ||
|
|
325
|
+
method?.type !== 'property_identifier' ||
|
|
326
|
+
(method.text !== 'registerTool' && method.text !== 'tool') ||
|
|
327
|
+
!sdkReceiver(receiver, scopes.get(call.id), scopes))
|
|
328
|
+
continue;
|
|
329
|
+
const args = call
|
|
330
|
+
.childForFieldName('arguments')
|
|
331
|
+
?.namedChildren.filter((child) => child.type !== 'comment') ?? [];
|
|
332
|
+
if (args.some((arg) => arg.type === 'spread_element'))
|
|
333
|
+
continue;
|
|
334
|
+
if (method.text === 'registerTool' ? args.length !== 3 : args.length < 2 || args.length > 5)
|
|
335
|
+
continue;
|
|
336
|
+
const toolName = plainString(args[0]);
|
|
337
|
+
if (toolName === null)
|
|
338
|
+
continue;
|
|
339
|
+
const description = method.text === 'registerTool'
|
|
340
|
+
? descriptionFromConfig(args[1])
|
|
341
|
+
: args.length > 2
|
|
342
|
+
? (plainString(args[1]) ?? '')
|
|
343
|
+
: '';
|
|
344
|
+
const handler = handlerNodeId(args[args.length - 1], scopes.get(call.id), callableBindings);
|
|
345
|
+
definitions.push({
|
|
346
|
+
filePath,
|
|
347
|
+
toolName,
|
|
348
|
+
description,
|
|
349
|
+
lineNumber: call.startPosition.row + 1 + lineOffset,
|
|
350
|
+
...(handler !== undefined ? { handlerNodeId: handler } : {}),
|
|
351
|
+
allowFileFallback: false,
|
|
352
|
+
});
|
|
353
|
+
}
|
|
354
|
+
return definitions;
|
|
355
|
+
}
|
|
@@ -77,6 +77,7 @@ import { extractDataRouteTableRoutes } from '../route-extractors/data-route-tabl
|
|
|
77
77
|
import { extractNestRoutes } from '../route-extractors/nest.js';
|
|
78
78
|
import { extractTrpcRoutes, shouldScanForTrpcRoutes } from '../route-extractors/trpc.js';
|
|
79
79
|
import { extractConvexEndpointProperties } from './typescript/convex-endpoint-metadata.js';
|
|
80
|
+
import { extractToolDefinitions } from './typescript/tool-definitions.js';
|
|
80
81
|
const extractJsTsRoutes = (...args) => [
|
|
81
82
|
...extractDispatchGuardRoutes(...args),
|
|
82
83
|
...extractDataRouteTableRoutes(...args),
|
|
@@ -415,6 +416,7 @@ export const typescriptProvider = defineLanguage({
|
|
|
415
416
|
// Content-based (not AST): tRPC procedure routers are scanned from source text.
|
|
416
417
|
// Path-gate lives here (language provider), not in the shared parse worker.
|
|
417
418
|
extractTextRoutes: extractJsTsTextRoutes,
|
|
419
|
+
extractToolDefinitions,
|
|
418
420
|
});
|
|
419
421
|
export const javascriptProvider = defineLanguage({
|
|
420
422
|
id: SupportedLanguages.JavaScript,
|
|
@@ -494,4 +496,5 @@ export const javascriptProvider = defineLanguage({
|
|
|
494
496
|
extractDecoratorRoutes: extractJsTsRoutes,
|
|
495
497
|
// Content-based (not AST): tRPC procedure routers are scanned from source text.
|
|
496
498
|
extractTextRoutes: extractJsTsTextRoutes,
|
|
499
|
+
extractToolDefinitions,
|
|
497
500
|
});
|
|
@@ -261,6 +261,8 @@ export const processesPhase = {
|
|
|
261
261
|
const toolsByHandlerId = new Map();
|
|
262
262
|
const toolsWithoutHandlerByFile = new Map();
|
|
263
263
|
for (const td of toolDefs) {
|
|
264
|
+
if (!td.handlerNodeId && td.allowFileFallback === false)
|
|
265
|
+
continue;
|
|
264
266
|
const key = td.handlerNodeId ?? td.filePath;
|
|
265
267
|
const targetMap = td.handlerNodeId ? toolsByHandlerId : toolsWithoutHandlerByFile;
|
|
266
268
|
let list = targetMap.get(key);
|
|
@@ -30,6 +30,7 @@ export const toolsPhase = {
|
|
|
30
30
|
filePath: td.filePath,
|
|
31
31
|
description: td.description,
|
|
32
32
|
...(handlerNodeId !== undefined ? { handlerNodeId } : {}),
|
|
33
|
+
...(td.allowFileFallback === false ? { allowFileFallback: false } : {}),
|
|
33
34
|
});
|
|
34
35
|
}
|
|
35
36
|
// TS tool definition arrays — require inputSchema nearby
|
|
@@ -196,6 +196,8 @@ export interface ExtractedToolDef {
|
|
|
196
196
|
description: string;
|
|
197
197
|
lineNumber: number;
|
|
198
198
|
handlerNodeId?: string;
|
|
199
|
+
/** Unresolved registrations must not inherit unrelated same-file flows. */
|
|
200
|
+
allowFileFallback?: false;
|
|
199
201
|
}
|
|
200
202
|
export interface ExtractedORMQuery {
|
|
201
203
|
filePath: string;
|
|
@@ -1087,6 +1087,7 @@ const processFileGroup = (files, language, queryString, result, onFileProcessed)
|
|
|
1087
1087
|
// node id → graph node id for classes THIS file's capture loop materialized.
|
|
1088
1088
|
// Keyed by in-memory AST identity (never persisted); filled below.
|
|
1089
1089
|
const classOwnersByNodeId = new Map();
|
|
1090
|
+
const callableBindings = new Map();
|
|
1090
1091
|
// #2687: ONE pass over `matches` yields both suppression sets — the
|
|
1091
1092
|
// definition-name claims by rank (callable > Property > value), so the dedup
|
|
1092
1093
|
// below cannot depend on tree-sitter's match order, and the concrete-typedef
|
|
@@ -2328,6 +2329,10 @@ const processFileGroup = (files, language, queryString, result, onFileProcessed)
|
|
|
2328
2329
|
...(returnShapeProperty ? { fromReturnShape: true, isDetail: true } : {}),
|
|
2329
2330
|
}),
|
|
2330
2331
|
});
|
|
2332
|
+
// Keep actual emitted identities; providers must not reconstruct graph IDs.
|
|
2333
|
+
if (nameNode && (nodeLabel === 'Function' || nodeLabel === 'Method')) {
|
|
2334
|
+
callableBindings.set(nameNode.id, nodeId);
|
|
2335
|
+
}
|
|
2331
2336
|
// enclosingClassId already computed above (before nodeId generation)
|
|
2332
2337
|
const ownerId = enclosingClassId ?? objectLiteralOwnerInfo?.ownerId;
|
|
2333
2338
|
result.symbols.push({
|
|
@@ -2419,6 +2424,22 @@ const processFileGroup = (files, language, queryString, result, onFileProcessed)
|
|
|
2419
2424
|
}
|
|
2420
2425
|
}
|
|
2421
2426
|
}
|
|
2427
|
+
if (provider.extractToolDefinitions) {
|
|
2428
|
+
// Distinct lexical declarations can share a graph ID (for example, sibling
|
|
2429
|
+
// block-scoped functions). Such IDs cannot prove which handler owns a tool.
|
|
2430
|
+
const seenCallableIds = new Set();
|
|
2431
|
+
const ambiguousCallableIds = new Set();
|
|
2432
|
+
for (const nodeId of callableBindings.values()) {
|
|
2433
|
+
if (seenCallableIds.has(nodeId))
|
|
2434
|
+
ambiguousCallableIds.add(nodeId);
|
|
2435
|
+
seenCallableIds.add(nodeId);
|
|
2436
|
+
}
|
|
2437
|
+
for (const [bindingId, nodeId] of callableBindings) {
|
|
2438
|
+
if (ambiguousCallableIds.has(nodeId))
|
|
2439
|
+
callableBindings.delete(bindingId);
|
|
2440
|
+
}
|
|
2441
|
+
result.toolDefs.push(...provider.extractToolDefinitions(tree, file.path, lineOffset, callableBindings));
|
|
2442
|
+
}
|
|
2422
2443
|
// Extract framework routes via provider detection (e.g., Laravel routes.php)
|
|
2423
2444
|
if (provider.isRouteFile?.(file.path)) {
|
|
2424
2445
|
const extractedRoutes = extractLaravelRoutes(tree, file.path);
|
|
@@ -817,7 +817,14 @@ import { copyV8CacheIfPresent, tryLoadV8Cache, writeV8CacheFile } from './v8-sid
|
|
|
817
817
|
// `handlerReceiver` hint. Warm v123 Go worker results carry no routes.
|
|
818
818
|
// v125 (#3402): Go route hints now honor lexical declarations and captured writes;
|
|
819
819
|
// namespace imports retain whether their local name comes from the package clause.
|
|
820
|
-
|
|
820
|
+
// v126 (#3446): SDK positional tool registrations now emit tool definitions,
|
|
821
|
+
// exact handler identities, and an opt-out from unrelated file-level flows.
|
|
822
|
+
// Warm v125 worker results omit these definitions and must be re-extracted.
|
|
823
|
+
// v127 (#3450): Destructured member writes invalidate SDK registration evidence.
|
|
824
|
+
// Warm v126 worker results can retain false tools after a method replacement.
|
|
825
|
+
// v128 (#3450): SDK namespace imports now prove positional tool receivers.
|
|
826
|
+
// Warm v127 worker results omit these definitions and must be re-extracted.
|
|
827
|
+
const SCHEMA_BUMP = 128;
|
|
821
828
|
const GITNEXUS_PKG_VERSION = (() => {
|
|
822
829
|
try {
|
|
823
830
|
// package.json sits at gitnexus/package.json — two levels up from
|
package/package.json
CHANGED