@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.
Files changed (83) hide show
  1. package/LICENSE +21 -0
  2. package/dist/index.d.ts +40 -0
  3. package/dist/index.d.ts.map +1 -0
  4. package/dist/index.js +27 -0
  5. package/dist/parser/index.d.ts +6 -0
  6. package/dist/parser/index.d.ts.map +1 -0
  7. package/dist/parser/index.js +5 -0
  8. package/dist/parser/node-types.d.ts +119 -0
  9. package/dist/parser/node-types.d.ts.map +1 -0
  10. package/dist/parser/node-types.js +4 -0
  11. package/dist/parser/python-parser.d.ts +85 -0
  12. package/dist/parser/python-parser.d.ts.map +1 -0
  13. package/dist/parser/python-parser.js +477 -0
  14. package/dist/skills/index.d.ts +26 -0
  15. package/dist/skills/index.d.ts.map +1 -0
  16. package/dist/skills/index.js +36 -0
  17. package/dist/skills/python-best-practices.d.ts +7 -0
  18. package/dist/skills/python-best-practices.d.ts.map +1 -0
  19. package/dist/skills/python-best-practices.js +78 -0
  20. package/dist/skills/python-code-health.d.ts +7 -0
  21. package/dist/skills/python-code-health.d.ts.map +1 -0
  22. package/dist/skills/python-code-health.js +209 -0
  23. package/dist/skills/python-code-structure.d.ts +7 -0
  24. package/dist/skills/python-code-structure.d.ts.map +1 -0
  25. package/dist/skills/python-code-structure.js +155 -0
  26. package/dist/skills/python-dependency-audit.d.ts +7 -0
  27. package/dist/skills/python-dependency-audit.d.ts.map +1 -0
  28. package/dist/skills/python-dependency-audit.js +246 -0
  29. package/dist/skills/python-refactor-impact.d.ts +7 -0
  30. package/dist/skills/python-refactor-impact.d.ts.map +1 -0
  31. package/dist/skills/python-refactor-impact.js +232 -0
  32. package/dist/tools/extract-docstrings.d.ts +70 -0
  33. package/dist/tools/extract-docstrings.d.ts.map +1 -0
  34. package/dist/tools/extract-docstrings.js +575 -0
  35. package/dist/tools/find-dead-code.d.ts +62 -0
  36. package/dist/tools/find-dead-code.d.ts.map +1 -0
  37. package/dist/tools/find-dead-code.js +422 -0
  38. package/dist/tools/find-duplicates.d.ts +65 -0
  39. package/dist/tools/find-duplicates.d.ts.map +1 -0
  40. package/dist/tools/find-duplicates.js +289 -0
  41. package/dist/tools/find-implementations.d.ts +71 -0
  42. package/dist/tools/find-implementations.d.ts.map +1 -0
  43. package/dist/tools/find-implementations.js +342 -0
  44. package/dist/tools/find-patterns.d.ts +71 -0
  45. package/dist/tools/find-patterns.d.ts.map +1 -0
  46. package/dist/tools/find-patterns.js +477 -0
  47. package/dist/tools/find-references.d.ts +66 -0
  48. package/dist/tools/find-references.d.ts.map +1 -0
  49. package/dist/tools/find-references.js +306 -0
  50. package/dist/tools/find-symbol.d.ts +86 -0
  51. package/dist/tools/find-symbol.d.ts.map +1 -0
  52. package/dist/tools/find-symbol.js +414 -0
  53. package/dist/tools/get-call-graph.d.ts +89 -0
  54. package/dist/tools/get-call-graph.d.ts.map +1 -0
  55. package/dist/tools/get-call-graph.js +431 -0
  56. package/dist/tools/get-class-hierarchy.d.ts +38 -0
  57. package/dist/tools/get-class-hierarchy.d.ts.map +1 -0
  58. package/dist/tools/get-class-hierarchy.js +289 -0
  59. package/dist/tools/get-complexity.d.ts +61 -0
  60. package/dist/tools/get-complexity.d.ts.map +1 -0
  61. package/dist/tools/get-complexity.js +384 -0
  62. package/dist/tools/get-dependency-graph.d.ts +85 -0
  63. package/dist/tools/get-dependency-graph.d.ts.map +1 -0
  64. package/dist/tools/get-dependency-graph.js +387 -0
  65. package/dist/tools/get-exports.d.ts +78 -0
  66. package/dist/tools/get-exports.d.ts.map +1 -0
  67. package/dist/tools/get-exports.js +437 -0
  68. package/dist/tools/get-file-structure.d.ts +28 -0
  69. package/dist/tools/get-file-structure.d.ts.map +1 -0
  70. package/dist/tools/get-file-structure.js +186 -0
  71. package/dist/tools/get-imports.d.ts +34 -0
  72. package/dist/tools/get-imports.d.ts.map +1 -0
  73. package/dist/tools/get-imports.js +455 -0
  74. package/dist/tools/get-signature.d.ts +100 -0
  75. package/dist/tools/get-signature.d.ts.map +1 -0
  76. package/dist/tools/get-signature.js +800 -0
  77. package/dist/tools/index.d.ts +55 -0
  78. package/dist/tools/index.d.ts.map +1 -0
  79. package/dist/tools/index.js +75 -0
  80. package/dist/tools/types.d.ts +378 -0
  81. package/dist/tools/types.d.ts.map +1 -0
  82. package/dist/tools/types.js +4 -0
  83. 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
+ }