@compilr-dev/agents-coding-python 0.1.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/LICENSE +21 -0
- package/dist/index.d.ts +40 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +27 -0
- package/dist/parser/index.d.ts +6 -0
- package/dist/parser/index.d.ts.map +1 -0
- package/dist/parser/index.js +5 -0
- package/dist/parser/node-types.d.ts +119 -0
- package/dist/parser/node-types.d.ts.map +1 -0
- package/dist/parser/node-types.js +4 -0
- package/dist/parser/python-parser.d.ts +85 -0
- package/dist/parser/python-parser.d.ts.map +1 -0
- package/dist/parser/python-parser.js +477 -0
- package/dist/skills/index.d.ts +26 -0
- package/dist/skills/index.d.ts.map +1 -0
- package/dist/skills/index.js +36 -0
- package/dist/skills/python-best-practices.d.ts +7 -0
- package/dist/skills/python-best-practices.d.ts.map +1 -0
- package/dist/skills/python-best-practices.js +78 -0
- package/dist/skills/python-code-health.d.ts +7 -0
- package/dist/skills/python-code-health.d.ts.map +1 -0
- package/dist/skills/python-code-health.js +209 -0
- package/dist/skills/python-code-structure.d.ts +7 -0
- package/dist/skills/python-code-structure.d.ts.map +1 -0
- package/dist/skills/python-code-structure.js +155 -0
- package/dist/skills/python-dependency-audit.d.ts +7 -0
- package/dist/skills/python-dependency-audit.d.ts.map +1 -0
- package/dist/skills/python-dependency-audit.js +246 -0
- package/dist/skills/python-refactor-impact.d.ts +7 -0
- package/dist/skills/python-refactor-impact.d.ts.map +1 -0
- package/dist/skills/python-refactor-impact.js +232 -0
- package/dist/tools/extract-docstrings.d.ts +70 -0
- package/dist/tools/extract-docstrings.d.ts.map +1 -0
- package/dist/tools/extract-docstrings.js +575 -0
- package/dist/tools/find-dead-code.d.ts +62 -0
- package/dist/tools/find-dead-code.d.ts.map +1 -0
- package/dist/tools/find-dead-code.js +422 -0
- package/dist/tools/find-duplicates.d.ts +65 -0
- package/dist/tools/find-duplicates.d.ts.map +1 -0
- package/dist/tools/find-duplicates.js +289 -0
- package/dist/tools/find-implementations.d.ts +71 -0
- package/dist/tools/find-implementations.d.ts.map +1 -0
- package/dist/tools/find-implementations.js +342 -0
- package/dist/tools/find-patterns.d.ts +71 -0
- package/dist/tools/find-patterns.d.ts.map +1 -0
- package/dist/tools/find-patterns.js +477 -0
- package/dist/tools/find-references.d.ts +66 -0
- package/dist/tools/find-references.d.ts.map +1 -0
- package/dist/tools/find-references.js +306 -0
- package/dist/tools/find-symbol.d.ts +86 -0
- package/dist/tools/find-symbol.d.ts.map +1 -0
- package/dist/tools/find-symbol.js +414 -0
- package/dist/tools/get-call-graph.d.ts +89 -0
- package/dist/tools/get-call-graph.d.ts.map +1 -0
- package/dist/tools/get-call-graph.js +431 -0
- package/dist/tools/get-class-hierarchy.d.ts +38 -0
- package/dist/tools/get-class-hierarchy.d.ts.map +1 -0
- package/dist/tools/get-class-hierarchy.js +289 -0
- package/dist/tools/get-complexity.d.ts +61 -0
- package/dist/tools/get-complexity.d.ts.map +1 -0
- package/dist/tools/get-complexity.js +384 -0
- package/dist/tools/get-dependency-graph.d.ts +85 -0
- package/dist/tools/get-dependency-graph.d.ts.map +1 -0
- package/dist/tools/get-dependency-graph.js +387 -0
- package/dist/tools/get-exports.d.ts +78 -0
- package/dist/tools/get-exports.d.ts.map +1 -0
- package/dist/tools/get-exports.js +437 -0
- package/dist/tools/get-file-structure.d.ts +28 -0
- package/dist/tools/get-file-structure.d.ts.map +1 -0
- package/dist/tools/get-file-structure.js +186 -0
- package/dist/tools/get-imports.d.ts +34 -0
- package/dist/tools/get-imports.d.ts.map +1 -0
- package/dist/tools/get-imports.js +455 -0
- package/dist/tools/get-signature.d.ts +100 -0
- package/dist/tools/get-signature.d.ts.map +1 -0
- package/dist/tools/get-signature.js +800 -0
- package/dist/tools/index.d.ts +55 -0
- package/dist/tools/index.d.ts.map +1 -0
- package/dist/tools/index.js +75 -0
- package/dist/tools/types.d.ts +378 -0
- package/dist/tools/types.d.ts.map +1 -0
- package/dist/tools/types.js +4 -0
- package/package.json +85 -0
|
@@ -0,0 +1,800 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* getSignature Tool
|
|
3
|
+
*
|
|
4
|
+
* Get detailed signature information for a function, method, or class in Python code.
|
|
5
|
+
* Includes parameters, type hints, decorators, and docstrings.
|
|
6
|
+
*/
|
|
7
|
+
import * as fs from "node:fs/promises";
|
|
8
|
+
import * as path from "node:path";
|
|
9
|
+
import { defineTool, createSuccessResult, createErrorResult, } from "@compilr-dev/agents";
|
|
10
|
+
import { parseFile, parseFunction, parseClass, } from "../parser/python-parser.js";
|
|
11
|
+
// Tool description
|
|
12
|
+
const TOOL_DESCRIPTION = `Get detailed signature information for a function, method, or class in Python.
|
|
13
|
+
Returns parameters with type hints, return type, decorators, and extracted docstring documentation.
|
|
14
|
+
Useful for understanding API contracts without reading full source code.`;
|
|
15
|
+
// Tool input schema
|
|
16
|
+
const TOOL_INPUT_SCHEMA = {
|
|
17
|
+
type: "object",
|
|
18
|
+
properties: {
|
|
19
|
+
path: {
|
|
20
|
+
type: "string",
|
|
21
|
+
description: "File containing the symbol",
|
|
22
|
+
},
|
|
23
|
+
name: {
|
|
24
|
+
type: "string",
|
|
25
|
+
description: "Symbol name to get signature for",
|
|
26
|
+
},
|
|
27
|
+
line: {
|
|
28
|
+
type: "number",
|
|
29
|
+
description: "Line number for disambiguation (optional)",
|
|
30
|
+
},
|
|
31
|
+
includeDoc: {
|
|
32
|
+
type: "boolean",
|
|
33
|
+
description: "Include full documentation (default: true)",
|
|
34
|
+
default: true,
|
|
35
|
+
},
|
|
36
|
+
},
|
|
37
|
+
required: ["path", "name"],
|
|
38
|
+
};
|
|
39
|
+
/**
|
|
40
|
+
* getSignature tool
|
|
41
|
+
*/
|
|
42
|
+
export const getSignatureTool = defineTool({
|
|
43
|
+
name: "get_signature_python",
|
|
44
|
+
description: TOOL_DESCRIPTION,
|
|
45
|
+
inputSchema: TOOL_INPUT_SCHEMA,
|
|
46
|
+
execute: executeGetSignature,
|
|
47
|
+
});
|
|
48
|
+
/**
|
|
49
|
+
* Execute the getSignature tool
|
|
50
|
+
*/
|
|
51
|
+
async function executeGetSignature(input) {
|
|
52
|
+
const { path: inputPath, name, line, includeDoc = true } = input;
|
|
53
|
+
// Validate input
|
|
54
|
+
if (!inputPath || inputPath.trim().length === 0) {
|
|
55
|
+
return createErrorResult("Path is required");
|
|
56
|
+
}
|
|
57
|
+
if (!name || name.trim().length === 0) {
|
|
58
|
+
return createErrorResult("Symbol name is required");
|
|
59
|
+
}
|
|
60
|
+
try {
|
|
61
|
+
const resolvedPath = path.resolve(inputPath);
|
|
62
|
+
// Check if file exists
|
|
63
|
+
try {
|
|
64
|
+
await fs.access(resolvedPath);
|
|
65
|
+
}
|
|
66
|
+
catch {
|
|
67
|
+
return createErrorResult(`File not found: ${resolvedPath}`);
|
|
68
|
+
}
|
|
69
|
+
// Check if Python file
|
|
70
|
+
if (!resolvedPath.endsWith(".py")) {
|
|
71
|
+
return createErrorResult("File must be a Python file (.py)");
|
|
72
|
+
}
|
|
73
|
+
// Parse the file
|
|
74
|
+
const parseResult = await parseFile(resolvedPath);
|
|
75
|
+
const { tree, source } = parseResult;
|
|
76
|
+
// Find the symbol
|
|
77
|
+
const result = findSymbolSignature(tree.rootNode, source, name, line, includeDoc);
|
|
78
|
+
if (!result) {
|
|
79
|
+
return createErrorResult(`Symbol "${name}" not found in ${resolvedPath}`);
|
|
80
|
+
}
|
|
81
|
+
return createSuccessResult({
|
|
82
|
+
...result,
|
|
83
|
+
path: resolvedPath,
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
catch (error) {
|
|
87
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
88
|
+
return createErrorResult(`Failed to get signature: ${message}`);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Find a symbol and extract its signature
|
|
93
|
+
*/
|
|
94
|
+
function findSymbolSignature(rootNode, source, symbolName, targetLine, includeDoc) {
|
|
95
|
+
let foundNode = null;
|
|
96
|
+
let foundKind = null;
|
|
97
|
+
function visit(node, className) {
|
|
98
|
+
if (foundNode && !targetLine)
|
|
99
|
+
return; // Already found, stop if no line disambiguation
|
|
100
|
+
const nodeLine = node.startPosition.row + 1;
|
|
101
|
+
// Skip if we have a target line and this isn't it
|
|
102
|
+
if (targetLine && nodeLine !== targetLine) {
|
|
103
|
+
for (const child of node.children) {
|
|
104
|
+
visit(child, className);
|
|
105
|
+
}
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
// Function definitions
|
|
109
|
+
if (node.type === "function_definition") {
|
|
110
|
+
const nameNode = node.childForFieldName("name");
|
|
111
|
+
if (nameNode?.text === symbolName) {
|
|
112
|
+
foundNode = node;
|
|
113
|
+
foundKind = className ? "method" : "function";
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
// Async function definitions
|
|
117
|
+
else if (node.type === "async_function_definition") {
|
|
118
|
+
const funcDef = node.children.find((c) => c.type === "function_definition");
|
|
119
|
+
const nameNode = funcDef?.childForFieldName("name");
|
|
120
|
+
if (nameNode?.text === symbolName) {
|
|
121
|
+
foundNode = node;
|
|
122
|
+
foundKind = className ? "method" : "async_function";
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
// Class definitions
|
|
126
|
+
else if (node.type === "class_definition") {
|
|
127
|
+
const nameNode = node.childForFieldName("name");
|
|
128
|
+
if (nameNode?.text === symbolName) {
|
|
129
|
+
foundNode = node;
|
|
130
|
+
foundKind = "class";
|
|
131
|
+
}
|
|
132
|
+
else {
|
|
133
|
+
// Search inside class for methods
|
|
134
|
+
const body = node.childForFieldName("body");
|
|
135
|
+
if (body) {
|
|
136
|
+
for (const member of body.children) {
|
|
137
|
+
visit(member, nameNode?.text);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
// Decorated definitions
|
|
143
|
+
else if (node.type === "decorated_definition") {
|
|
144
|
+
const definition = node.children.find((c) => c.type === "function_definition" ||
|
|
145
|
+
c.type === "async_function_definition" ||
|
|
146
|
+
c.type === "class_definition");
|
|
147
|
+
if (definition) {
|
|
148
|
+
if (definition.type === "class_definition") {
|
|
149
|
+
const nameNode = definition.childForFieldName("name");
|
|
150
|
+
if (nameNode?.text === symbolName) {
|
|
151
|
+
foundNode = node;
|
|
152
|
+
foundKind = "class";
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
else {
|
|
156
|
+
const funcDef = definition.type === "async_function_definition"
|
|
157
|
+
? definition.children.find((c) => c.type === "function_definition")
|
|
158
|
+
: definition;
|
|
159
|
+
const nameNode = funcDef?.childForFieldName("name");
|
|
160
|
+
if (nameNode?.text === symbolName) {
|
|
161
|
+
foundNode = node;
|
|
162
|
+
foundKind =
|
|
163
|
+
definition.type === "async_function_definition"
|
|
164
|
+
? "async_function"
|
|
165
|
+
: className
|
|
166
|
+
? "method"
|
|
167
|
+
: "function";
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
// Variable assignments (module-level)
|
|
173
|
+
else if (node.type === "assignment" && !className) {
|
|
174
|
+
const left = node.childForFieldName("left");
|
|
175
|
+
if (left?.type === "identifier" && left.text === symbolName) {
|
|
176
|
+
foundNode = node;
|
|
177
|
+
foundKind = "variable";
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
// Recurse into children
|
|
181
|
+
if (!foundNode) {
|
|
182
|
+
for (const child of node.children) {
|
|
183
|
+
if (child.type !== "class_definition") {
|
|
184
|
+
visit(child, className);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
visit(rootNode);
|
|
190
|
+
if (!foundNode || !foundKind) {
|
|
191
|
+
return null;
|
|
192
|
+
}
|
|
193
|
+
return extractSignature(foundNode, foundKind, source, includeDoc);
|
|
194
|
+
}
|
|
195
|
+
/**
|
|
196
|
+
* Extract signature from a node
|
|
197
|
+
*/
|
|
198
|
+
function extractSignature(node, kind, source, includeDoc) {
|
|
199
|
+
const line = node.startPosition.row + 1;
|
|
200
|
+
switch (kind) {
|
|
201
|
+
case "function":
|
|
202
|
+
case "async_function":
|
|
203
|
+
case "method":
|
|
204
|
+
return extractFunctionSignature(node, source, kind, line, includeDoc);
|
|
205
|
+
case "class":
|
|
206
|
+
return extractClassSignature(node, source, line, includeDoc);
|
|
207
|
+
case "variable":
|
|
208
|
+
return extractVariableSignature(node, source, line);
|
|
209
|
+
default:
|
|
210
|
+
return {
|
|
211
|
+
name: "<unknown>",
|
|
212
|
+
kind,
|
|
213
|
+
line,
|
|
214
|
+
signature: node.text.split("\n")[0],
|
|
215
|
+
formattedSignature: node.text.split("\n")[0],
|
|
216
|
+
};
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
/**
|
|
220
|
+
* Extract function/method signature
|
|
221
|
+
*/
|
|
222
|
+
function extractFunctionSignature(node, source, kind, line, includeDoc) {
|
|
223
|
+
// Handle decorated definitions
|
|
224
|
+
let funcNode;
|
|
225
|
+
const decorators = [];
|
|
226
|
+
const isAsync = kind === "async_function";
|
|
227
|
+
if (node.type === "decorated_definition") {
|
|
228
|
+
// Extract decorators
|
|
229
|
+
for (const child of node.children) {
|
|
230
|
+
if (child.type === "decorator") {
|
|
231
|
+
decorators.push(child.text.trim());
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
const definition = node.children.find((c) => c.type === "function_definition" ||
|
|
235
|
+
c.type === "async_function_definition");
|
|
236
|
+
if (definition?.type === "async_function_definition") {
|
|
237
|
+
funcNode =
|
|
238
|
+
definition.children.find((c) => c.type === "function_definition") ??
|
|
239
|
+
definition;
|
|
240
|
+
}
|
|
241
|
+
else {
|
|
242
|
+
funcNode = definition ?? node;
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
else if (node.type === "async_function_definition") {
|
|
246
|
+
funcNode =
|
|
247
|
+
node.children.find((c) => c.type === "function_definition") ?? node;
|
|
248
|
+
}
|
|
249
|
+
else {
|
|
250
|
+
funcNode = node;
|
|
251
|
+
}
|
|
252
|
+
const funcInfo = parseFunction(funcNode, source);
|
|
253
|
+
const name = funcInfo.name;
|
|
254
|
+
// Extract parameters
|
|
255
|
+
const parameters = extractParameters(funcNode, source);
|
|
256
|
+
// Extract return type
|
|
257
|
+
const returnAnnotation = funcNode.childForFieldName("return_type");
|
|
258
|
+
let returnType;
|
|
259
|
+
if (returnAnnotation) {
|
|
260
|
+
const typeText = returnAnnotation.text;
|
|
261
|
+
returnType = {
|
|
262
|
+
type: typeText,
|
|
263
|
+
isCoroutine: isAsync,
|
|
264
|
+
nullable: typeText.includes("None") ||
|
|
265
|
+
typeText.startsWith("Optional[") ||
|
|
266
|
+
typeText.includes(" | None"),
|
|
267
|
+
};
|
|
268
|
+
}
|
|
269
|
+
// Extract docstring
|
|
270
|
+
let documentation;
|
|
271
|
+
if (includeDoc) {
|
|
272
|
+
const body = funcNode.childForFieldName("body");
|
|
273
|
+
if (body) {
|
|
274
|
+
documentation = extractDocstring(body, source);
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
// Build signature string
|
|
278
|
+
const asyncPrefix = isAsync ? "async " : "";
|
|
279
|
+
const paramsStr = parameters.map((p) => formatParameter(p)).join(", ");
|
|
280
|
+
const returnStr = returnType ? ` -> ${returnType.type}` : "";
|
|
281
|
+
const signature = `${asyncPrefix}def ${name}(${paramsStr})${returnStr}`;
|
|
282
|
+
// Formatted signature with decorators
|
|
283
|
+
const decoratorLines = decorators.length > 0 ? decorators.join("\n") + "\n" : "";
|
|
284
|
+
const formattedSignature = parameters.length > 3
|
|
285
|
+
? `${decoratorLines}${asyncPrefix}def ${name}(\n${parameters.map((p) => ` ${formatParameter(p)}`).join(",\n")}\n)${returnStr}`
|
|
286
|
+
: `${decoratorLines}${signature}`;
|
|
287
|
+
return {
|
|
288
|
+
name,
|
|
289
|
+
kind,
|
|
290
|
+
line,
|
|
291
|
+
signature,
|
|
292
|
+
formattedSignature,
|
|
293
|
+
parameters,
|
|
294
|
+
returnType,
|
|
295
|
+
decorators: decorators.length > 0 ? decorators : undefined,
|
|
296
|
+
documentation,
|
|
297
|
+
async: isAsync,
|
|
298
|
+
};
|
|
299
|
+
}
|
|
300
|
+
/**
|
|
301
|
+
* Extract class signature
|
|
302
|
+
*/
|
|
303
|
+
function extractClassSignature(node, source, line, includeDoc) {
|
|
304
|
+
// Handle decorated definitions
|
|
305
|
+
let classNode;
|
|
306
|
+
const decorators = [];
|
|
307
|
+
if (node.type === "decorated_definition") {
|
|
308
|
+
for (const child of node.children) {
|
|
309
|
+
if (child.type === "decorator") {
|
|
310
|
+
decorators.push(child.text.trim());
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
classNode =
|
|
314
|
+
node.children.find((c) => c.type === "class_definition") ?? node;
|
|
315
|
+
}
|
|
316
|
+
else {
|
|
317
|
+
classNode = node;
|
|
318
|
+
}
|
|
319
|
+
const classInfo = parseClass(classNode, source);
|
|
320
|
+
const name = classInfo.name;
|
|
321
|
+
const bases = classInfo.bases;
|
|
322
|
+
// Extract docstring
|
|
323
|
+
let documentation;
|
|
324
|
+
if (includeDoc) {
|
|
325
|
+
const body = classNode.childForFieldName("body");
|
|
326
|
+
if (body) {
|
|
327
|
+
documentation = extractDocstring(body, source);
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
// Extract members
|
|
331
|
+
const members = extractClassMembers(classNode, source);
|
|
332
|
+
// Build signature
|
|
333
|
+
const basesStr = bases.length > 0 ? `(${bases.join(", ")})` : "";
|
|
334
|
+
const signature = `class ${name}${basesStr}`;
|
|
335
|
+
// Formatted signature with decorators
|
|
336
|
+
const decoratorLines = decorators.length > 0 ? decorators.join("\n") + "\n" : "";
|
|
337
|
+
const formattedSignature = `${decoratorLines}${signature}`;
|
|
338
|
+
return {
|
|
339
|
+
name,
|
|
340
|
+
kind: "class",
|
|
341
|
+
line,
|
|
342
|
+
signature,
|
|
343
|
+
formattedSignature,
|
|
344
|
+
decorators: decorators.length > 0 ? decorators : undefined,
|
|
345
|
+
documentation,
|
|
346
|
+
bases: bases.length > 0 ? bases : undefined,
|
|
347
|
+
members: members.length > 0 ? members : undefined,
|
|
348
|
+
};
|
|
349
|
+
}
|
|
350
|
+
/**
|
|
351
|
+
* Extract variable signature
|
|
352
|
+
*/
|
|
353
|
+
function extractVariableSignature(node, source, line) {
|
|
354
|
+
const left = node.childForFieldName("left");
|
|
355
|
+
const name = left?.text ?? "<unknown>";
|
|
356
|
+
// Check for type annotation
|
|
357
|
+
const annotationNode = node.children.find((c) => c.type === "type");
|
|
358
|
+
const annotation = annotationNode?.text;
|
|
359
|
+
const signature = annotation ? `${name}: ${annotation}` : name;
|
|
360
|
+
return {
|
|
361
|
+
name,
|
|
362
|
+
kind: "variable",
|
|
363
|
+
line,
|
|
364
|
+
signature,
|
|
365
|
+
formattedSignature: signature,
|
|
366
|
+
returnType: annotation
|
|
367
|
+
? {
|
|
368
|
+
type: annotation,
|
|
369
|
+
nullable: annotation.includes("None") ||
|
|
370
|
+
annotation.startsWith("Optional[") ||
|
|
371
|
+
annotation.includes(" | None"),
|
|
372
|
+
}
|
|
373
|
+
: undefined,
|
|
374
|
+
};
|
|
375
|
+
}
|
|
376
|
+
/**
|
|
377
|
+
* Extract parameters from function node
|
|
378
|
+
*/
|
|
379
|
+
function extractParameters(funcNode, _source) {
|
|
380
|
+
const parameters = [];
|
|
381
|
+
const paramsNode = funcNode.childForFieldName("parameters");
|
|
382
|
+
if (!paramsNode)
|
|
383
|
+
return parameters;
|
|
384
|
+
let seenKwOnly = false;
|
|
385
|
+
let seenPosOnly = false;
|
|
386
|
+
for (const child of paramsNode.children) {
|
|
387
|
+
// Skip punctuation
|
|
388
|
+
if (child.type === "," || child.type === "(" || child.type === ")")
|
|
389
|
+
continue;
|
|
390
|
+
// Position-only marker (/)
|
|
391
|
+
if (child.type === "positional_separator") {
|
|
392
|
+
seenPosOnly = true;
|
|
393
|
+
continue;
|
|
394
|
+
}
|
|
395
|
+
// Keyword-only marker (*)
|
|
396
|
+
if (child.type === "keyword_separator") {
|
|
397
|
+
seenKwOnly = true;
|
|
398
|
+
continue;
|
|
399
|
+
}
|
|
400
|
+
// Regular parameter
|
|
401
|
+
if (child.type === "identifier") {
|
|
402
|
+
parameters.push({
|
|
403
|
+
name: child.text,
|
|
404
|
+
optional: false,
|
|
405
|
+
isPosOnly: seenPosOnly,
|
|
406
|
+
isKwOnly: seenKwOnly,
|
|
407
|
+
});
|
|
408
|
+
continue;
|
|
409
|
+
}
|
|
410
|
+
// Typed parameter
|
|
411
|
+
if (child.type === "typed_parameter") {
|
|
412
|
+
const nameNode = child.children.find((c) => c.type === "identifier");
|
|
413
|
+
const typeNode = child.childForFieldName("type");
|
|
414
|
+
parameters.push({
|
|
415
|
+
name: nameNode?.text ?? "<unknown>",
|
|
416
|
+
type: typeNode?.text,
|
|
417
|
+
optional: false,
|
|
418
|
+
isPosOnly: !seenKwOnly && seenPosOnly,
|
|
419
|
+
isKwOnly: seenKwOnly,
|
|
420
|
+
});
|
|
421
|
+
continue;
|
|
422
|
+
}
|
|
423
|
+
// Default parameter
|
|
424
|
+
if (child.type === "default_parameter") {
|
|
425
|
+
const nameNode = child.childForFieldName("name");
|
|
426
|
+
const valueNode = child.childForFieldName("value");
|
|
427
|
+
parameters.push({
|
|
428
|
+
name: nameNode?.text ?? "<unknown>",
|
|
429
|
+
optional: true,
|
|
430
|
+
defaultValue: valueNode?.text,
|
|
431
|
+
isPosOnly: !seenKwOnly && seenPosOnly,
|
|
432
|
+
isKwOnly: seenKwOnly,
|
|
433
|
+
});
|
|
434
|
+
continue;
|
|
435
|
+
}
|
|
436
|
+
// Typed default parameter
|
|
437
|
+
if (child.type === "typed_default_parameter") {
|
|
438
|
+
const nameNode = child.childForFieldName("name");
|
|
439
|
+
const typeNode = child.childForFieldName("type");
|
|
440
|
+
const valueNode = child.childForFieldName("value");
|
|
441
|
+
parameters.push({
|
|
442
|
+
name: nameNode?.text ?? "<unknown>",
|
|
443
|
+
type: typeNode?.text,
|
|
444
|
+
optional: true,
|
|
445
|
+
defaultValue: valueNode?.text,
|
|
446
|
+
isPosOnly: !seenKwOnly && seenPosOnly,
|
|
447
|
+
isKwOnly: seenKwOnly,
|
|
448
|
+
});
|
|
449
|
+
continue;
|
|
450
|
+
}
|
|
451
|
+
// *args
|
|
452
|
+
if (child.type === "list_splat_pattern") {
|
|
453
|
+
const nameNode = child.children.find((c) => c.type === "identifier");
|
|
454
|
+
parameters.push({
|
|
455
|
+
name: nameNode?.text ?? "args",
|
|
456
|
+
optional: true,
|
|
457
|
+
isVarArgs: true,
|
|
458
|
+
});
|
|
459
|
+
seenKwOnly = true; // After *args, all are keyword-only
|
|
460
|
+
continue;
|
|
461
|
+
}
|
|
462
|
+
// **kwargs
|
|
463
|
+
if (child.type === "dictionary_splat_pattern") {
|
|
464
|
+
const nameNode = child.children.find((c) => c.type === "identifier");
|
|
465
|
+
parameters.push({
|
|
466
|
+
name: nameNode?.text ?? "kwargs",
|
|
467
|
+
optional: true,
|
|
468
|
+
isKwArgs: true,
|
|
469
|
+
});
|
|
470
|
+
continue;
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
return parameters;
|
|
474
|
+
}
|
|
475
|
+
/**
|
|
476
|
+
* Extract class members
|
|
477
|
+
*/
|
|
478
|
+
function extractClassMembers(classNode, source) {
|
|
479
|
+
const members = [];
|
|
480
|
+
const body = classNode.childForFieldName("body");
|
|
481
|
+
if (!body)
|
|
482
|
+
return members;
|
|
483
|
+
for (const member of body.children) {
|
|
484
|
+
// Method definitions
|
|
485
|
+
if (member.type === "function_definition") {
|
|
486
|
+
const funcInfo = parseFunction(member, source);
|
|
487
|
+
const paramsNode = member.childForFieldName("parameters");
|
|
488
|
+
const paramsText = paramsNode?.text ?? "()";
|
|
489
|
+
const returnNode = member.childForFieldName("return_type");
|
|
490
|
+
const returnText = returnNode ? ` -> ${returnNode.text}` : "";
|
|
491
|
+
members.push({
|
|
492
|
+
name: funcInfo.name,
|
|
493
|
+
kind: "method",
|
|
494
|
+
signature: `def ${funcInfo.name}${paramsText}${returnText}`,
|
|
495
|
+
async: false,
|
|
496
|
+
});
|
|
497
|
+
}
|
|
498
|
+
// Async method definitions
|
|
499
|
+
else if (member.type === "async_function_definition") {
|
|
500
|
+
const funcDef = member.children.find((c) => c.type === "function_definition");
|
|
501
|
+
if (funcDef) {
|
|
502
|
+
const funcInfo = parseFunction(funcDef, source);
|
|
503
|
+
const paramsNode = funcDef.childForFieldName("parameters");
|
|
504
|
+
const paramsText = paramsNode?.text ?? "()";
|
|
505
|
+
const returnNode = funcDef.childForFieldName("return_type");
|
|
506
|
+
const returnText = returnNode ? ` -> ${returnNode.text}` : "";
|
|
507
|
+
members.push({
|
|
508
|
+
name: funcInfo.name,
|
|
509
|
+
kind: "method",
|
|
510
|
+
signature: `async def ${funcInfo.name}${paramsText}${returnText}`,
|
|
511
|
+
async: true,
|
|
512
|
+
});
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
// Decorated methods
|
|
516
|
+
else if (member.type === "decorated_definition") {
|
|
517
|
+
const decorators = [];
|
|
518
|
+
for (const child of member.children) {
|
|
519
|
+
if (child.type === "decorator") {
|
|
520
|
+
decorators.push(child.text.trim());
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
const definition = member.children.find((c) => c.type === "function_definition" ||
|
|
524
|
+
c.type === "async_function_definition");
|
|
525
|
+
if (definition) {
|
|
526
|
+
const funcDef = definition.type === "async_function_definition"
|
|
527
|
+
? (definition.children.find((c) => c.type === "function_definition") ?? definition)
|
|
528
|
+
: definition;
|
|
529
|
+
const funcInfo = parseFunction(funcDef, source);
|
|
530
|
+
const paramsNode = funcDef.childForFieldName("parameters");
|
|
531
|
+
const paramsText = paramsNode?.text ?? "()";
|
|
532
|
+
const returnNode = funcDef.childForFieldName("return_type");
|
|
533
|
+
const returnText = returnNode ? ` -> ${returnNode.text}` : "";
|
|
534
|
+
const isAsync = definition.type === "async_function_definition";
|
|
535
|
+
const asyncPrefix = isAsync ? "async " : "";
|
|
536
|
+
const isStaticMethod = decorators.some((d) => d === "@staticmethod" || d.includes("staticmethod"));
|
|
537
|
+
const isClassMethod = decorators.some((d) => d === "@classmethod" || d.includes("classmethod"));
|
|
538
|
+
const isProperty = decorators.some((d) => d === "@property" || d.includes("property"));
|
|
539
|
+
members.push({
|
|
540
|
+
name: funcInfo.name,
|
|
541
|
+
kind: isProperty ? "property" : "method",
|
|
542
|
+
signature: `${asyncPrefix}def ${funcInfo.name}${paramsText}${returnText}`,
|
|
543
|
+
async: isAsync,
|
|
544
|
+
static: isStaticMethod,
|
|
545
|
+
classmethod: isClassMethod,
|
|
546
|
+
decorators,
|
|
547
|
+
});
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
// Class variable assignments
|
|
551
|
+
else if (member.type === "assignment") {
|
|
552
|
+
const left = member.childForFieldName("left");
|
|
553
|
+
if (left?.type === "identifier") {
|
|
554
|
+
members.push({
|
|
555
|
+
name: left.text,
|
|
556
|
+
kind: "class_variable",
|
|
557
|
+
signature: member.text.split("\n")[0],
|
|
558
|
+
});
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
// Annotated assignments
|
|
562
|
+
else if (member.type === "expression_statement") {
|
|
563
|
+
const expr = member.children[0];
|
|
564
|
+
if (expr?.type === "assignment") {
|
|
565
|
+
const left = expr.childForFieldName("left");
|
|
566
|
+
if (left?.type === "identifier") {
|
|
567
|
+
members.push({
|
|
568
|
+
name: left.text,
|
|
569
|
+
kind: "class_variable",
|
|
570
|
+
signature: expr.text.split("\n")[0],
|
|
571
|
+
});
|
|
572
|
+
}
|
|
573
|
+
}
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
return members;
|
|
577
|
+
}
|
|
578
|
+
/**
|
|
579
|
+
* Extract docstring from a body node
|
|
580
|
+
*/
|
|
581
|
+
function extractDocstring(bodyNode, _source) {
|
|
582
|
+
// Look for the first expression_statement containing a string
|
|
583
|
+
for (const child of bodyNode.children) {
|
|
584
|
+
if (child.type === "expression_statement") {
|
|
585
|
+
const string = child.children.find((c) => c.type === "string" || c.type === "concatenated_string");
|
|
586
|
+
if (string) {
|
|
587
|
+
return parseDocstring(string.text);
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
// Stop looking after non-expression-statement
|
|
591
|
+
if (child.type !== "expression_statement" && child.type !== "comment") {
|
|
592
|
+
break;
|
|
593
|
+
}
|
|
594
|
+
}
|
|
595
|
+
return undefined;
|
|
596
|
+
}
|
|
597
|
+
/**
|
|
598
|
+
* Parse a docstring into structured documentation
|
|
599
|
+
*/
|
|
600
|
+
function parseDocstring(docstring) {
|
|
601
|
+
// Remove quotes
|
|
602
|
+
let content = docstring.trim();
|
|
603
|
+
if (content.startsWith('"""') && content.endsWith('"""')) {
|
|
604
|
+
content = content.slice(3, -3);
|
|
605
|
+
}
|
|
606
|
+
else if (content.startsWith("'''") && content.endsWith("'''")) {
|
|
607
|
+
content = content.slice(3, -3);
|
|
608
|
+
}
|
|
609
|
+
else if (content.startsWith('"') && content.endsWith('"')) {
|
|
610
|
+
content = content.slice(1, -1);
|
|
611
|
+
}
|
|
612
|
+
else if (content.startsWith("'") && content.endsWith("'")) {
|
|
613
|
+
content = content.slice(1, -1);
|
|
614
|
+
}
|
|
615
|
+
content = content.trim();
|
|
616
|
+
const lines = content.split("\n");
|
|
617
|
+
const doc = {
|
|
618
|
+
summary: "",
|
|
619
|
+
};
|
|
620
|
+
// First non-empty line is summary
|
|
621
|
+
const summaryLines = [];
|
|
622
|
+
let inSection = null;
|
|
623
|
+
const params = {};
|
|
624
|
+
const raises = [];
|
|
625
|
+
const examples = [];
|
|
626
|
+
let returns;
|
|
627
|
+
for (let i = 0; i < lines.length; i++) {
|
|
628
|
+
const line = lines[i];
|
|
629
|
+
const trimmed = line.trim();
|
|
630
|
+
// Check for section headers (Google-style, NumPy-style, or reST-style)
|
|
631
|
+
if (trimmed === "Args:" ||
|
|
632
|
+
trimmed === "Arguments:" ||
|
|
633
|
+
trimmed === "Parameters:" ||
|
|
634
|
+
trimmed === "Parameters" ||
|
|
635
|
+
trimmed.startsWith(":param ")) {
|
|
636
|
+
if (inSection === null &&
|
|
637
|
+
summaryLines.length === 0 &&
|
|
638
|
+
!trimmed.startsWith(":")) {
|
|
639
|
+
continue; // Skip empty leading lines
|
|
640
|
+
}
|
|
641
|
+
if (inSection === null && summaryLines.length > 0) {
|
|
642
|
+
doc.summary = summaryLines.join(" ").trim();
|
|
643
|
+
}
|
|
644
|
+
inSection = "params";
|
|
645
|
+
if (trimmed.startsWith(":param ")) {
|
|
646
|
+
// reST style: :param name: description
|
|
647
|
+
const match = trimmed.match(/:param\s+(\w+):\s*(.*)/);
|
|
648
|
+
if (match) {
|
|
649
|
+
params[match[1]] = match[2];
|
|
650
|
+
}
|
|
651
|
+
}
|
|
652
|
+
continue;
|
|
653
|
+
}
|
|
654
|
+
if (trimmed === "Returns:" ||
|
|
655
|
+
trimmed === "Return:" ||
|
|
656
|
+
trimmed.startsWith(":returns:") ||
|
|
657
|
+
trimmed.startsWith(":return:")) {
|
|
658
|
+
if (inSection === null && summaryLines.length > 0) {
|
|
659
|
+
doc.summary = summaryLines.join(" ").trim();
|
|
660
|
+
}
|
|
661
|
+
inSection = "returns";
|
|
662
|
+
if (trimmed.startsWith(":return")) {
|
|
663
|
+
const match = trimmed.match(/:returns?:\s*(.*)/);
|
|
664
|
+
if (match) {
|
|
665
|
+
returns = match[1];
|
|
666
|
+
}
|
|
667
|
+
}
|
|
668
|
+
continue;
|
|
669
|
+
}
|
|
670
|
+
if (trimmed === "Raises:" ||
|
|
671
|
+
trimmed === "Raise:" ||
|
|
672
|
+
trimmed === "Exceptions:" ||
|
|
673
|
+
trimmed.startsWith(":raises:")) {
|
|
674
|
+
if (inSection === null && summaryLines.length > 0) {
|
|
675
|
+
doc.summary = summaryLines.join(" ").trim();
|
|
676
|
+
}
|
|
677
|
+
inSection = "raises";
|
|
678
|
+
continue;
|
|
679
|
+
}
|
|
680
|
+
if (trimmed === "Examples:" || trimmed === "Example:") {
|
|
681
|
+
if (inSection === null && summaryLines.length > 0) {
|
|
682
|
+
doc.summary = summaryLines.join(" ").trim();
|
|
683
|
+
}
|
|
684
|
+
inSection = "examples";
|
|
685
|
+
continue;
|
|
686
|
+
}
|
|
687
|
+
if (trimmed === "Notes:" ||
|
|
688
|
+
trimmed === "Note:" ||
|
|
689
|
+
trimmed.startsWith(".. note::")) {
|
|
690
|
+
if (inSection === null && summaryLines.length > 0) {
|
|
691
|
+
doc.summary = summaryLines.join(" ").trim();
|
|
692
|
+
}
|
|
693
|
+
inSection = "notes";
|
|
694
|
+
continue;
|
|
695
|
+
}
|
|
696
|
+
if (trimmed.startsWith(".. deprecated::") || trimmed === "Deprecated:") {
|
|
697
|
+
if (inSection === null && summaryLines.length > 0) {
|
|
698
|
+
doc.summary = summaryLines.join(" ").trim();
|
|
699
|
+
}
|
|
700
|
+
inSection = "deprecated";
|
|
701
|
+
continue;
|
|
702
|
+
}
|
|
703
|
+
// Process content based on current section
|
|
704
|
+
if (inSection === null) {
|
|
705
|
+
if (trimmed || summaryLines.length > 0) {
|
|
706
|
+
summaryLines.push(trimmed);
|
|
707
|
+
}
|
|
708
|
+
}
|
|
709
|
+
else if (inSection === "params") {
|
|
710
|
+
// Google-style: name (type): description or name: description
|
|
711
|
+
const paramMatch = trimmed.match(/^(\w+)\s*(?:\([^)]+\))?\s*:\s*(.*)$/);
|
|
712
|
+
if (paramMatch) {
|
|
713
|
+
params[paramMatch[1]] = paramMatch[2];
|
|
714
|
+
}
|
|
715
|
+
else if (trimmed && Object.keys(params).length > 0) {
|
|
716
|
+
// Continuation of previous param description
|
|
717
|
+
const lastParam = Object.keys(params).pop();
|
|
718
|
+
if (lastParam) {
|
|
719
|
+
params[lastParam] += " " + trimmed;
|
|
720
|
+
}
|
|
721
|
+
}
|
|
722
|
+
}
|
|
723
|
+
else if (inSection === "returns") {
|
|
724
|
+
if (trimmed) {
|
|
725
|
+
returns = returns ? returns + " " + trimmed : trimmed;
|
|
726
|
+
}
|
|
727
|
+
}
|
|
728
|
+
else if (inSection === "raises") {
|
|
729
|
+
if (trimmed) {
|
|
730
|
+
raises.push(trimmed);
|
|
731
|
+
}
|
|
732
|
+
}
|
|
733
|
+
else if (inSection === "examples") {
|
|
734
|
+
examples.push(line); // Keep indentation for examples
|
|
735
|
+
}
|
|
736
|
+
else if (inSection === "notes") {
|
|
737
|
+
doc.notes = doc.notes ? doc.notes + " " + trimmed : trimmed;
|
|
738
|
+
}
|
|
739
|
+
else if (inSection === "deprecated") {
|
|
740
|
+
doc.deprecated = doc.deprecated
|
|
741
|
+
? doc.deprecated + " " + trimmed
|
|
742
|
+
: trimmed;
|
|
743
|
+
}
|
|
744
|
+
}
|
|
745
|
+
// Handle case where summary wasn't separated
|
|
746
|
+
if (!doc.summary && summaryLines.length > 0) {
|
|
747
|
+
doc.summary = summaryLines.join(" ").trim();
|
|
748
|
+
}
|
|
749
|
+
// Add collected data
|
|
750
|
+
if (Object.keys(params).length > 0)
|
|
751
|
+
doc.params = params;
|
|
752
|
+
if (returns)
|
|
753
|
+
doc.returns = returns;
|
|
754
|
+
if (raises.length > 0)
|
|
755
|
+
doc.raises = raises;
|
|
756
|
+
if (examples.length > 0)
|
|
757
|
+
doc.examples = examples;
|
|
758
|
+
return doc;
|
|
759
|
+
}
|
|
760
|
+
/**
|
|
761
|
+
* Format a parameter for signature string
|
|
762
|
+
*/
|
|
763
|
+
function formatParameter(p) {
|
|
764
|
+
let result = "";
|
|
765
|
+
// Handle *args and **kwargs
|
|
766
|
+
if (p.isVarArgs) {
|
|
767
|
+
result = `*${p.name}`;
|
|
768
|
+
}
|
|
769
|
+
else if (p.isKwArgs) {
|
|
770
|
+
result = `**${p.name}`;
|
|
771
|
+
}
|
|
772
|
+
else {
|
|
773
|
+
result = p.name;
|
|
774
|
+
}
|
|
775
|
+
// Add type annotation
|
|
776
|
+
if (p.type) {
|
|
777
|
+
result += `: ${p.type}`;
|
|
778
|
+
}
|
|
779
|
+
// Add default value
|
|
780
|
+
if (p.defaultValue !== undefined) {
|
|
781
|
+
result += ` = ${p.defaultValue}`;
|
|
782
|
+
}
|
|
783
|
+
return result;
|
|
784
|
+
}
|
|
785
|
+
/**
|
|
786
|
+
* Factory function to create a customized getSignature tool
|
|
787
|
+
*/
|
|
788
|
+
export function createGetSignatureTool(options) {
|
|
789
|
+
return defineTool({
|
|
790
|
+
name: "get_signature_python",
|
|
791
|
+
description: TOOL_DESCRIPTION,
|
|
792
|
+
inputSchema: TOOL_INPUT_SCHEMA,
|
|
793
|
+
execute: async (input) => {
|
|
794
|
+
return executeGetSignature({
|
|
795
|
+
...input,
|
|
796
|
+
includeDoc: input.includeDoc ?? options?.defaultIncludeDoc ?? true,
|
|
797
|
+
});
|
|
798
|
+
},
|
|
799
|
+
});
|
|
800
|
+
}
|