gitnexus 1.6.13-rc.65 → 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/swift/callable-visibility.d.ts +9 -0
- package/dist/core/ingestion/languages/swift/callable-visibility.js +57 -0
- package/dist/core/ingestion/languages/swift/scope-resolver.js +2 -0
- 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/scope-resolution/contract/scope-resolver.d.ts +3 -0
- package/dist/core/ingestion/scope-resolution/passes/free-call-fallback.d.ts +1 -6
- package/dist/core/ingestion/scope-resolution/passes/free-call-fallback.js +2 -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,9 @@
|
|
|
1
|
+
import type { ParsedFile, ScopeId, SymbolDefinition } from '../../../../_shared/index.js';
|
|
2
|
+
import type { ScopeResolutionIndexes } from '../../model/scope-resolution-indexes.js';
|
|
3
|
+
export declare function swiftIsCallableVisibleFromCaller(ctx: {
|
|
4
|
+
readonly candidate: SymbolDefinition;
|
|
5
|
+
readonly callerParsed?: ParsedFile;
|
|
6
|
+
readonly callArity?: number;
|
|
7
|
+
readonly callerScope?: ScopeId;
|
|
8
|
+
readonly scopes?: ScopeResolutionIndexes;
|
|
9
|
+
}): boolean;
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { findClassBindingInScope } from '../../scope-resolution/scope/walkers.js';
|
|
2
|
+
export function swiftIsCallableVisibleFromCaller(ctx) {
|
|
3
|
+
const indexes = ctx.scopes;
|
|
4
|
+
if (ctx.callerScope === undefined || indexes === undefined || ctx.callArity !== 0)
|
|
5
|
+
return true;
|
|
6
|
+
const name = ctx.candidate.qualifiedName?.split('.').at(-1);
|
|
7
|
+
if (name === undefined)
|
|
8
|
+
return true;
|
|
9
|
+
let scopeId = ctx.callerScope;
|
|
10
|
+
let selfType;
|
|
11
|
+
while (scopeId !== null) {
|
|
12
|
+
const scope = indexes.scopeTree.getScope(scopeId);
|
|
13
|
+
if (scope === undefined)
|
|
14
|
+
break;
|
|
15
|
+
// A local function selected inside the caller wins before member lookup.
|
|
16
|
+
if (scope.kind !== 'Class' &&
|
|
17
|
+
scope.ownedDefs.some((def) => def.nodeId === ctx.candidate.nodeId))
|
|
18
|
+
return true;
|
|
19
|
+
selfType ??= scope.typeBindings.get('self');
|
|
20
|
+
if (scope.kind === 'Class') {
|
|
21
|
+
const classDef = scope.ownedDefs.find((def) => def.type === 'Class') ??
|
|
22
|
+
(selfType === undefined
|
|
23
|
+
? undefined
|
|
24
|
+
: findClassBindingInScope(ctx.callerScope, selfType.rawName, indexes, undefined, {
|
|
25
|
+
uniqueQualifiedNameFallback: false,
|
|
26
|
+
}));
|
|
27
|
+
const propertyOnOwner = (ownerId, ownerName) => indexes.qualifiedNames.get(`${ownerName}.${name}`).some((defId) => {
|
|
28
|
+
const def = indexes.defs.get(defId);
|
|
29
|
+
return def?.type === 'Property' && def.ownerId === ownerId;
|
|
30
|
+
});
|
|
31
|
+
const ownProperty = scope.ownedDefs.some((def) => def.type === 'Property' && def.qualifiedName?.split('.').at(-1) === name) ||
|
|
32
|
+
(classDef !== undefined &&
|
|
33
|
+
classDef.qualifiedName !== undefined &&
|
|
34
|
+
propertyOnOwner(classDef.nodeId, classDef.qualifiedName));
|
|
35
|
+
const inheritedProperty = classDef !== undefined &&
|
|
36
|
+
indexes.methodDispatch.mroFor(classDef.nodeId).some((ownerId) => {
|
|
37
|
+
const ownerName = indexes.defs.get(ownerId)?.qualifiedName;
|
|
38
|
+
return ownerName !== undefined && propertyOnOwner(ownerId, ownerName);
|
|
39
|
+
});
|
|
40
|
+
if (!ownProperty && !inheritedProperty)
|
|
41
|
+
return true;
|
|
42
|
+
// Swift's nested function is owned by its own Function scope. A
|
|
43
|
+
// same-file sibling must not count as a nearer lexical binding.
|
|
44
|
+
const declarationScope = ctx.callerParsed?.scopes.find((candidateScope) => candidateScope.ownedDefs.some((def) => def.nodeId === ctx.candidate.nodeId));
|
|
45
|
+
if (declarationScope !== undefined &&
|
|
46
|
+
declarationScope.kind === 'Function' &&
|
|
47
|
+
(declarationScope.id === ctx.callerScope ||
|
|
48
|
+
indexes.scopeTree.getAncestors(declarationScope.id).includes(ctx.callerScope)))
|
|
49
|
+
return true;
|
|
50
|
+
// Scope defs do not carry Swift access modifiers. A method positively
|
|
51
|
+
// selected on this type must not be vetoed by an uncertain ancestor.
|
|
52
|
+
return classDef !== undefined && ctx.candidate.ownerId === classDef.nodeId;
|
|
53
|
+
}
|
|
54
|
+
scopeId = scope.parent;
|
|
55
|
+
}
|
|
56
|
+
return true;
|
|
57
|
+
}
|
|
@@ -56,6 +56,7 @@ import { swiftArityCompatibility, swiftMergeBindings, interpretSwiftImport, reso
|
|
|
56
56
|
import { stripSwiftTypePreservingDecoration } from './interpret.js';
|
|
57
57
|
import { groupSwiftFilesByModule } from './target-grouping.js';
|
|
58
58
|
import { swiftIsGlobalNameFallbackPlausible } from './name-fallback-visibility.js';
|
|
59
|
+
import { swiftIsCallableVisibleFromCaller } from './callable-visibility.js';
|
|
59
60
|
const ZERO_RANGE = { startLine: 0, startCol: 0, endLine: 0, endCol: 0 };
|
|
60
61
|
const swiftScopeResolver = {
|
|
61
62
|
language: SupportedLanguages.Swift,
|
|
@@ -123,6 +124,7 @@ const swiftScopeResolver = {
|
|
|
123
124
|
// no-`new` constructor + cross-file free-call shape).
|
|
124
125
|
allowGlobalFreeCallFallback: true,
|
|
125
126
|
isGlobalNameFallbackPlausible: swiftIsGlobalNameFallbackPlausible,
|
|
127
|
+
isCallableVisibleFromCaller: swiftIsCallableVisibleFromCaller,
|
|
126
128
|
// Swift's call graph models `Type(...)` as a reference to the type
|
|
127
129
|
// itself, not its `init` — both the legacy DAG and this test suite link
|
|
128
130
|
// `Foo()` to the Class node even when an explicit `init` exists.
|
|
@@ -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
|
|
@@ -1112,6 +1112,9 @@ export interface ScopeResolver {
|
|
|
1112
1112
|
readonly isCallableVisibleFromCaller?: (ctx: {
|
|
1113
1113
|
readonly callerParsed: ParsedFile;
|
|
1114
1114
|
readonly candidate: SymbolDefinition;
|
|
1115
|
+
/** Arity of the actual call, when known. A visibility veto must not
|
|
1116
|
+
* infer applicability from a name match alone. */
|
|
1117
|
+
readonly callArity?: number;
|
|
1115
1118
|
/** Caller's enclosing scope id. Languages that gate visibility on
|
|
1116
1119
|
* caller scope (e.g. C++ two-phase template lookup) consult it;
|
|
1117
1120
|
* others ignore. Optional so existing implementations stay valid. */
|
|
@@ -47,12 +47,7 @@ export declare function emitFreeCallFallback(graph: KnowledgeGraph, scopes: Scop
|
|
|
47
47
|
* appended to its reason. See `ScopeResolver.markConstructionSites`. */
|
|
48
48
|
readonly markConstructionSites?: boolean;
|
|
49
49
|
readonly isFileLocalDef?: (def: SymbolDefinition) => boolean;
|
|
50
|
-
readonly isCallableVisibleFromCaller?:
|
|
51
|
-
readonly callerParsed: ParsedFile;
|
|
52
|
-
readonly candidate: SymbolDefinition;
|
|
53
|
-
readonly callerScope?: ScopeId;
|
|
54
|
-
readonly scopes?: ScopeResolutionIndexes;
|
|
55
|
-
}) => boolean;
|
|
50
|
+
readonly isCallableVisibleFromCaller?: ScopeResolver['isCallableVisibleFromCaller'];
|
|
56
51
|
readonly resolveAdlCandidates?: (site: {
|
|
57
52
|
readonly name: string;
|
|
58
53
|
readonly arity?: number;
|
|
@@ -447,6 +447,7 @@ export function emitFreeCallFallback(graph, scopes, parsedFiles, nodeLookup, _re
|
|
|
447
447
|
? (candidate) => options.isCallableVisibleFromCaller({
|
|
448
448
|
callerParsed: parsed,
|
|
449
449
|
candidate,
|
|
450
|
+
callArity: site.arity,
|
|
450
451
|
callerScope: site.inScope,
|
|
451
452
|
scopes,
|
|
452
453
|
})
|
|
@@ -514,6 +515,7 @@ export function emitFreeCallFallback(graph, scopes, parsedFiles, nodeLookup, _re
|
|
|
514
515
|
!options.isCallableVisibleFromCaller({
|
|
515
516
|
callerParsed: parsed,
|
|
516
517
|
candidate: fnDef,
|
|
518
|
+
callArity: site.arity,
|
|
517
519
|
callerScope: site.inScope,
|
|
518
520
|
scopes,
|
|
519
521
|
})) {
|
|
@@ -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