@compilr-dev/agents-coding-ts 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 (54) hide show
  1. package/LICENSE +21 -0
  2. package/dist/index.d.ts +34 -0
  3. package/dist/index.js +66 -0
  4. package/dist/parser/index.d.ts +7 -0
  5. package/dist/parser/index.js +6 -0
  6. package/dist/parser/typescript-parser.d.ts +22 -0
  7. package/dist/parser/typescript-parser.js +423 -0
  8. package/dist/skills/code-health.d.ts +9 -0
  9. package/dist/skills/code-health.js +167 -0
  10. package/dist/skills/code-structure.d.ts +9 -0
  11. package/dist/skills/code-structure.js +97 -0
  12. package/dist/skills/dependency-audit.d.ts +9 -0
  13. package/dist/skills/dependency-audit.js +110 -0
  14. package/dist/skills/index.d.ts +16 -0
  15. package/dist/skills/index.js +27 -0
  16. package/dist/skills/refactor-impact.d.ts +9 -0
  17. package/dist/skills/refactor-impact.js +135 -0
  18. package/dist/skills/type-analysis.d.ts +9 -0
  19. package/dist/skills/type-analysis.js +150 -0
  20. package/dist/tools/find-dead-code.d.ts +20 -0
  21. package/dist/tools/find-dead-code.js +375 -0
  22. package/dist/tools/find-duplicates.d.ts +21 -0
  23. package/dist/tools/find-duplicates.js +274 -0
  24. package/dist/tools/find-implementations.d.ts +21 -0
  25. package/dist/tools/find-implementations.js +436 -0
  26. package/dist/tools/find-patterns.d.ts +21 -0
  27. package/dist/tools/find-patterns.js +457 -0
  28. package/dist/tools/find-references.d.ts +23 -0
  29. package/dist/tools/find-references.js +488 -0
  30. package/dist/tools/find-symbol.d.ts +21 -0
  31. package/dist/tools/find-symbol.js +458 -0
  32. package/dist/tools/get-call-graph.d.ts +23 -0
  33. package/dist/tools/get-call-graph.js +469 -0
  34. package/dist/tools/get-complexity.d.ts +21 -0
  35. package/dist/tools/get-complexity.js +394 -0
  36. package/dist/tools/get-dependency-graph.d.ts +23 -0
  37. package/dist/tools/get-dependency-graph.js +482 -0
  38. package/dist/tools/get-documentation.d.ts +21 -0
  39. package/dist/tools/get-documentation.js +613 -0
  40. package/dist/tools/get-exports.d.ts +21 -0
  41. package/dist/tools/get-exports.js +427 -0
  42. package/dist/tools/get-file-structure.d.ts +27 -0
  43. package/dist/tools/get-file-structure.js +120 -0
  44. package/dist/tools/get-imports.d.ts +23 -0
  45. package/dist/tools/get-imports.js +350 -0
  46. package/dist/tools/get-signature.d.ts +20 -0
  47. package/dist/tools/get-signature.js +758 -0
  48. package/dist/tools/get-type-hierarchy.d.ts +22 -0
  49. package/dist/tools/get-type-hierarchy.js +485 -0
  50. package/dist/tools/index.d.ts +23 -0
  51. package/dist/tools/index.js +25 -0
  52. package/dist/tools/types.d.ts +1302 -0
  53. package/dist/tools/types.js +7 -0
  54. package/package.json +84 -0
@@ -0,0 +1,758 @@
1
+ /**
2
+ * getSignature Tool
3
+ *
4
+ * Get detailed signature information for a function, method, class, or type.
5
+ * Includes parameters, return types, generics, and documentation.
6
+ */
7
+ import * as fs from 'node:fs/promises';
8
+ import * as path from 'node:path';
9
+ import * as ts from 'typescript';
10
+ import { defineTool, createSuccessResult, createErrorResult } from '@compilr-dev/agents';
11
+ import { detectLanguage, isLanguageSupported } from '../parser/typescript-parser.js';
12
+ // Tool description
13
+ const TOOL_DESCRIPTION = `Get detailed signature information for a function, method, class, interface, or type.
14
+ Returns parameters with types, return type, generics, and extracted documentation (JSDoc/TSDoc).
15
+ Useful for understanding API contracts without reading full source code.`;
16
+ // Tool input schema
17
+ const TOOL_INPUT_SCHEMA = {
18
+ type: 'object',
19
+ properties: {
20
+ path: {
21
+ type: 'string',
22
+ description: 'File containing the symbol',
23
+ },
24
+ name: {
25
+ type: 'string',
26
+ description: 'Symbol name to get signature for',
27
+ },
28
+ line: {
29
+ type: 'number',
30
+ description: 'Line number for disambiguation (optional)',
31
+ },
32
+ includeDoc: {
33
+ type: 'boolean',
34
+ description: 'Include full documentation (default: true)',
35
+ default: true,
36
+ },
37
+ expandTypes: {
38
+ type: 'boolean',
39
+ description: 'Expand type aliases (default: false)',
40
+ default: false,
41
+ },
42
+ },
43
+ required: ['path', 'name'],
44
+ };
45
+ /**
46
+ * getSignature tool
47
+ */
48
+ export const getSignatureTool = defineTool({
49
+ name: 'get_signature',
50
+ description: TOOL_DESCRIPTION,
51
+ inputSchema: TOOL_INPUT_SCHEMA,
52
+ execute: executeGetSignature,
53
+ });
54
+ /**
55
+ * Execute the getSignature tool
56
+ */
57
+ async function executeGetSignature(input) {
58
+ const { path: inputPath, name, line, includeDoc = true, expandTypes = false } = input;
59
+ try {
60
+ const resolvedPath = path.resolve(inputPath);
61
+ // Check if file exists
62
+ try {
63
+ await fs.access(resolvedPath);
64
+ }
65
+ catch {
66
+ return createErrorResult(`File not found: ${resolvedPath}`);
67
+ }
68
+ // Check language support
69
+ const detection = detectLanguage(resolvedPath);
70
+ if (!detection.language || !isLanguageSupported(detection.language)) {
71
+ return createErrorResult(`Unsupported language for file: ${resolvedPath}`);
72
+ }
73
+ const content = await fs.readFile(resolvedPath, 'utf-8');
74
+ const sourceFile = ts.createSourceFile(resolvedPath, content, ts.ScriptTarget.Latest, true, resolvedPath.endsWith('.tsx') ? ts.ScriptKind.TSX : ts.ScriptKind.TS);
75
+ // Find the symbol
76
+ const result = findSymbolSignature(sourceFile, name, line, includeDoc, expandTypes);
77
+ if (!result) {
78
+ return createErrorResult(`Symbol "${name}" not found in ${resolvedPath}`);
79
+ }
80
+ return createSuccessResult({
81
+ ...result,
82
+ path: resolvedPath,
83
+ });
84
+ }
85
+ catch (error) {
86
+ return createErrorResult(`Failed to get signature: ${error instanceof Error ? error.message : String(error)}`);
87
+ }
88
+ }
89
+ /**
90
+ * Find a symbol and extract its signature
91
+ */
92
+ function findSymbolSignature(sourceFile, symbolName, targetLine, includeDoc, _expandTypes) {
93
+ // Use object to track found values (ESLint tracks object mutations better)
94
+ const found = {
95
+ node: null,
96
+ kind: null,
97
+ };
98
+ const visit = (node) => {
99
+ if (found.node && !targetLine)
100
+ return; // Already found, stop if no line disambiguation
101
+ const { line } = sourceFile.getLineAndCharacterOfPosition(node.getStart());
102
+ const nodeLine = line + 1;
103
+ // Skip if we have a target line and this isn't it
104
+ if (targetLine && nodeLine !== targetLine) {
105
+ ts.forEachChild(node, visit);
106
+ return;
107
+ }
108
+ // Function declarations
109
+ if (ts.isFunctionDeclaration(node) && node.name?.text === symbolName) {
110
+ found.node = node;
111
+ found.kind = 'function';
112
+ }
113
+ // Variable declarations (arrow functions, const functions)
114
+ else if (ts.isVariableDeclaration(node) &&
115
+ ts.isIdentifier(node.name) &&
116
+ node.name.text === symbolName) {
117
+ if (node.initializer &&
118
+ (ts.isArrowFunction(node.initializer) || ts.isFunctionExpression(node.initializer))) {
119
+ found.node = node;
120
+ found.kind = 'function';
121
+ }
122
+ else {
123
+ found.node = node;
124
+ found.kind = 'variable';
125
+ }
126
+ }
127
+ // Class declarations
128
+ else if (ts.isClassDeclaration(node) && node.name?.text === symbolName) {
129
+ found.node = node;
130
+ found.kind = 'class';
131
+ }
132
+ // Interface declarations
133
+ else if (ts.isInterfaceDeclaration(node) && node.name.text === symbolName) {
134
+ found.node = node;
135
+ found.kind = 'interface';
136
+ }
137
+ // Type alias declarations
138
+ else if (ts.isTypeAliasDeclaration(node) && node.name.text === symbolName) {
139
+ found.node = node;
140
+ found.kind = 'type';
141
+ }
142
+ // Enum declarations
143
+ else if (ts.isEnumDeclaration(node) && node.name.text === symbolName) {
144
+ found.node = node;
145
+ found.kind = 'enum';
146
+ }
147
+ // Method declarations (inside classes)
148
+ else if (ts.isMethodDeclaration(node) &&
149
+ ts.isIdentifier(node.name) &&
150
+ node.name.text === symbolName) {
151
+ found.node = node;
152
+ found.kind = 'method';
153
+ }
154
+ ts.forEachChild(node, visit);
155
+ };
156
+ visit(sourceFile);
157
+ if (found.node === null || found.kind === null) {
158
+ return null;
159
+ }
160
+ return extractSignature(found.node, found.kind, sourceFile, includeDoc);
161
+ }
162
+ /**
163
+ * Extract signature from a node
164
+ */
165
+ function extractSignature(node, kind, sourceFile, includeDoc) {
166
+ const { line } = sourceFile.getLineAndCharacterOfPosition(node.getStart());
167
+ const exported = isExported(node);
168
+ const name = getNodeName(node);
169
+ const documentation = includeDoc ? extractDocumentation(node, sourceFile) : undefined;
170
+ switch (kind) {
171
+ case 'function':
172
+ return extractFunctionSignature(node, sourceFile, name, line + 1, exported, documentation);
173
+ case 'method':
174
+ return extractMethodSignature(node, sourceFile, name, line + 1, exported, documentation);
175
+ case 'class':
176
+ return extractClassSignature(node, sourceFile, name, line + 1, exported, documentation);
177
+ case 'interface':
178
+ return extractInterfaceSignature(node, sourceFile, name, line + 1, exported, documentation);
179
+ case 'type':
180
+ return extractTypeSignature(node, sourceFile, name, line + 1, exported, documentation);
181
+ case 'enum':
182
+ return extractEnumSignature(node, sourceFile, name, line + 1, exported, documentation);
183
+ case 'variable':
184
+ return extractVariableSignature(node, sourceFile, name, line + 1, exported, documentation);
185
+ default:
186
+ return {
187
+ name,
188
+ kind,
189
+ line: line + 1,
190
+ signature: node.getText(sourceFile),
191
+ formattedSignature: node.getText(sourceFile),
192
+ exported,
193
+ documentation,
194
+ };
195
+ }
196
+ }
197
+ /**
198
+ * Extract function signature
199
+ */
200
+ function extractFunctionSignature(node, sourceFile, name, line, exported, documentation) {
201
+ let funcNode;
202
+ if (ts.isFunctionDeclaration(node)) {
203
+ funcNode = node;
204
+ }
205
+ else if (ts.isVariableDeclaration(node) && node.initializer) {
206
+ funcNode = node.initializer;
207
+ }
208
+ else {
209
+ return {
210
+ name,
211
+ kind: 'function',
212
+ line,
213
+ signature: node.getText(sourceFile),
214
+ formattedSignature: node.getText(sourceFile),
215
+ exported,
216
+ documentation,
217
+ };
218
+ }
219
+ const parameters = extractParameters(funcNode.parameters, sourceFile, documentation);
220
+ const returnType = extractReturnType(funcNode, sourceFile);
221
+ const generics = extractGenerics(funcNode.typeParameters, sourceFile);
222
+ const isAsync = funcNode.modifiers?.some((m) => m.kind === ts.SyntaxKind.AsyncKeyword) ?? false;
223
+ // Build signature string
224
+ const asyncPrefix = isAsync ? 'async ' : '';
225
+ const genericsStr = generics.length > 0 ? `<${generics.map((g) => formatGeneric(g)).join(', ')}>` : '';
226
+ const paramsStr = parameters.map((p) => formatParameter(p)).join(', ');
227
+ const returnStr = returnType ? `: ${returnType.type}` : '';
228
+ const signature = `${asyncPrefix}function ${name}${genericsStr}(${paramsStr})${returnStr}`;
229
+ const formattedSignature = formatMultilineSignature(asyncPrefix, name, genericsStr, parameters, returnType);
230
+ return {
231
+ name,
232
+ kind: 'function',
233
+ line,
234
+ signature,
235
+ formattedSignature,
236
+ exported,
237
+ parameters,
238
+ returnType,
239
+ generics: generics.length > 0 ? generics : undefined,
240
+ documentation,
241
+ };
242
+ }
243
+ /**
244
+ * Extract method signature
245
+ */
246
+ function extractMethodSignature(node, sourceFile, name, line, exported, documentation) {
247
+ const parameters = extractParameters(node.parameters, sourceFile, documentation);
248
+ const returnType = extractReturnType(node, sourceFile);
249
+ const generics = extractGenerics(node.typeParameters, sourceFile);
250
+ const isAsync = node.modifiers?.some((m) => m.kind === ts.SyntaxKind.AsyncKeyword) ?? false;
251
+ const isStatic = node.modifiers?.some((m) => m.kind === ts.SyntaxKind.StaticKeyword) ?? false;
252
+ const asyncPrefix = isAsync ? 'async ' : '';
253
+ const staticPrefix = isStatic ? 'static ' : '';
254
+ const genericsStr = generics.length > 0 ? `<${generics.map((g) => formatGeneric(g)).join(', ')}>` : '';
255
+ const paramsStr = parameters.map((p) => formatParameter(p)).join(', ');
256
+ const returnStr = returnType ? `: ${returnType.type}` : '';
257
+ const signature = `${staticPrefix}${asyncPrefix}${name}${genericsStr}(${paramsStr})${returnStr}`;
258
+ const formattedSignature = signature;
259
+ return {
260
+ name,
261
+ kind: 'method',
262
+ line,
263
+ signature,
264
+ formattedSignature,
265
+ exported,
266
+ parameters,
267
+ returnType,
268
+ generics: generics.length > 0 ? generics : undefined,
269
+ documentation,
270
+ };
271
+ }
272
+ /**
273
+ * Extract class signature
274
+ */
275
+ function extractClassSignature(node, sourceFile, name, line, exported, documentation) {
276
+ const generics = extractGenerics(node.typeParameters, sourceFile);
277
+ const members = extractClassMembers(node, sourceFile);
278
+ // Find constructor
279
+ const constructor = node.members.find(ts.isConstructorDeclaration);
280
+ let constructorSignature;
281
+ if (constructor) {
282
+ const params = extractParameters(constructor.parameters, sourceFile, undefined);
283
+ constructorSignature = `constructor(${params.map((p) => formatParameter(p)).join(', ')})`;
284
+ }
285
+ // Build extends/implements
286
+ let heritage = '';
287
+ if (node.heritageClauses) {
288
+ for (const clause of node.heritageClauses) {
289
+ const tokenKind = clause.token;
290
+ if (tokenKind === ts.SyntaxKind.ExtendsKeyword) {
291
+ heritage += ` extends ${clause.types.map((t) => t.getText(sourceFile)).join(', ')}`;
292
+ }
293
+ else if (tokenKind === ts.SyntaxKind.ImplementsKeyword) {
294
+ heritage += ` implements ${clause.types.map((t) => t.getText(sourceFile)).join(', ')}`;
295
+ }
296
+ }
297
+ }
298
+ const isAbstract = node.modifiers?.some((m) => m.kind === ts.SyntaxKind.AbstractKeyword) ?? false;
299
+ const abstractPrefix = isAbstract ? 'abstract ' : '';
300
+ const genericsStr = generics.length > 0 ? `<${generics.map((g) => formatGeneric(g)).join(', ')}>` : '';
301
+ const signature = `${abstractPrefix}class ${name}${genericsStr}${heritage}`;
302
+ const formattedSignature = signature;
303
+ return {
304
+ name,
305
+ kind: 'class',
306
+ line,
307
+ signature,
308
+ formattedSignature,
309
+ exported,
310
+ generics: generics.length > 0 ? generics : undefined,
311
+ documentation,
312
+ constructorSignature,
313
+ members,
314
+ };
315
+ }
316
+ /**
317
+ * Extract interface signature
318
+ */
319
+ function extractInterfaceSignature(node, sourceFile, name, line, exported, documentation) {
320
+ const generics = extractGenerics(node.typeParameters, sourceFile);
321
+ const members = extractInterfaceMembers(node, sourceFile);
322
+ // Build extends
323
+ let heritage = '';
324
+ if (node.heritageClauses) {
325
+ for (const clause of node.heritageClauses) {
326
+ heritage += ` extends ${clause.types.map((t) => t.getText(sourceFile)).join(', ')}`;
327
+ }
328
+ }
329
+ const genericsStr = generics.length > 0 ? `<${generics.map((g) => formatGeneric(g)).join(', ')}>` : '';
330
+ const signature = `interface ${name}${genericsStr}${heritage}`;
331
+ const formattedSignature = signature;
332
+ return {
333
+ name,
334
+ kind: 'interface',
335
+ line,
336
+ signature,
337
+ formattedSignature,
338
+ exported,
339
+ generics: generics.length > 0 ? generics : undefined,
340
+ documentation,
341
+ members,
342
+ };
343
+ }
344
+ /**
345
+ * Extract type alias signature
346
+ */
347
+ function extractTypeSignature(node, sourceFile, name, line, exported, documentation) {
348
+ const generics = extractGenerics(node.typeParameters, sourceFile);
349
+ const typeText = node.type.getText(sourceFile);
350
+ const genericsStr = generics.length > 0 ? `<${generics.map((g) => formatGeneric(g)).join(', ')}>` : '';
351
+ const signature = `type ${name}${genericsStr} = ${typeText}`;
352
+ // Format multi-line for complex types
353
+ const formattedSignature = typeText.length > 60
354
+ ? `type ${name}${genericsStr} = \n ${typeText.replace(/\n/g, '\n ')}`
355
+ : signature;
356
+ return {
357
+ name,
358
+ kind: 'type',
359
+ line,
360
+ signature,
361
+ formattedSignature,
362
+ exported,
363
+ generics: generics.length > 0 ? generics : undefined,
364
+ documentation,
365
+ };
366
+ }
367
+ /**
368
+ * Extract enum signature
369
+ */
370
+ function extractEnumSignature(node, sourceFile, name, line, exported, documentation) {
371
+ const members = node.members.map((member) => ({
372
+ name: member.name.getText(sourceFile),
373
+ kind: 'property',
374
+ signature: member.initializer
375
+ ? `${member.name.getText(sourceFile)} = ${member.initializer.getText(sourceFile)}`
376
+ : member.name.getText(sourceFile),
377
+ optional: false,
378
+ readonly: true,
379
+ }));
380
+ const isConst = node.modifiers?.some((m) => m.kind === ts.SyntaxKind.ConstKeyword) ?? false;
381
+ const constPrefix = isConst ? 'const ' : '';
382
+ const signature = `${constPrefix}enum ${name}`;
383
+ return {
384
+ name,
385
+ kind: 'enum',
386
+ line,
387
+ signature,
388
+ formattedSignature: signature,
389
+ exported,
390
+ documentation,
391
+ members,
392
+ };
393
+ }
394
+ /**
395
+ * Extract variable signature
396
+ */
397
+ function extractVariableSignature(node, sourceFile, name, line, exported, documentation) {
398
+ const typeNode = node.type;
399
+ const typeText = typeNode ? typeNode.getText(sourceFile) : undefined;
400
+ // Determine const/let/var
401
+ const parent = node.parent;
402
+ let declKind = 'const';
403
+ if (ts.isVariableDeclarationList(parent)) {
404
+ if (parent.flags & ts.NodeFlags.Let)
405
+ declKind = 'let';
406
+ else if (parent.flags & ts.NodeFlags.Const)
407
+ declKind = 'const';
408
+ else
409
+ declKind = 'var';
410
+ }
411
+ const signature = typeText ? `${declKind} ${name}: ${typeText}` : `${declKind} ${name}`;
412
+ return {
413
+ name,
414
+ kind: 'variable',
415
+ line,
416
+ signature,
417
+ formattedSignature: signature,
418
+ exported,
419
+ documentation,
420
+ returnType: typeText ? { type: typeText, isPromise: false, nullable: false } : undefined,
421
+ };
422
+ }
423
+ /**
424
+ * Extract parameters from parameter list
425
+ */
426
+ function extractParameters(params, sourceFile, documentation) {
427
+ return params.map((param) => {
428
+ const paramName = param.name.getText(sourceFile);
429
+ const type = param.type ? param.type.getText(sourceFile) : 'any';
430
+ const optional = param.questionToken !== undefined || param.initializer !== undefined;
431
+ const rest = param.dotDotDotToken !== undefined;
432
+ const defaultValue = param.initializer ? param.initializer.getText(sourceFile) : undefined;
433
+ const description = documentation?.params?.[paramName];
434
+ return {
435
+ name: paramName,
436
+ type,
437
+ optional,
438
+ rest,
439
+ defaultValue,
440
+ description,
441
+ };
442
+ });
443
+ }
444
+ /**
445
+ * Extract return type
446
+ */
447
+ function extractReturnType(node, sourceFile) {
448
+ if (!node.type)
449
+ return undefined;
450
+ const typeText = node.type.getText(sourceFile);
451
+ const isPromise = typeText.startsWith('Promise<') || typeText.startsWith('Promise ');
452
+ const nullable = typeText.includes('| null') || typeText.includes('| undefined');
453
+ return {
454
+ type: typeText,
455
+ isPromise,
456
+ nullable,
457
+ };
458
+ }
459
+ /**
460
+ * Extract generics
461
+ */
462
+ function extractGenerics(typeParams, sourceFile) {
463
+ if (!typeParams)
464
+ return [];
465
+ return typeParams.map((param) => ({
466
+ name: param.name.getText(sourceFile),
467
+ constraint: param.constraint ? param.constraint.getText(sourceFile) : undefined,
468
+ default: param.default ? param.default.getText(sourceFile) : undefined,
469
+ }));
470
+ }
471
+ /**
472
+ * Extract class members
473
+ */
474
+ function extractClassMembers(node, sourceFile) {
475
+ const members = [];
476
+ for (const member of node.members) {
477
+ if (ts.isConstructorDeclaration(member))
478
+ continue; // Skip constructor
479
+ // Handle property declarations
480
+ if (ts.isPropertyDeclaration(member)) {
481
+ const memberName = member.name.getText(sourceFile);
482
+ const mods = ts.canHaveModifiers(member) ? ts.getModifiers(member) : undefined;
483
+ const isStatic = mods?.some((m) => m.kind === ts.SyntaxKind.StaticKeyword) ?? false;
484
+ const isReadonly = mods?.some((m) => m.kind === ts.SyntaxKind.ReadonlyKeyword) ?? false;
485
+ const visibility = getVisibility(member);
486
+ const type = member.type ? member.type.getText(sourceFile) : 'any';
487
+ members.push({
488
+ name: memberName,
489
+ kind: 'property',
490
+ signature: `${memberName}: ${type}`,
491
+ optional: member.questionToken !== undefined,
492
+ readonly: isReadonly,
493
+ visibility,
494
+ static: isStatic,
495
+ });
496
+ }
497
+ // Handle method declarations
498
+ else if (ts.isMethodDeclaration(member)) {
499
+ const memberName = member.name.getText(sourceFile);
500
+ const mods = ts.canHaveModifiers(member) ? ts.getModifiers(member) : undefined;
501
+ const isStatic = mods?.some((m) => m.kind === ts.SyntaxKind.StaticKeyword) ?? false;
502
+ const visibility = getVisibility(member);
503
+ const params = member.parameters.map((p) => p.getText(sourceFile)).join(', ');
504
+ const returnType = member.type ? `: ${member.type.getText(sourceFile)}` : '';
505
+ members.push({
506
+ name: memberName,
507
+ kind: 'method',
508
+ signature: `${memberName}(${params})${returnType}`,
509
+ optional: member.questionToken !== undefined,
510
+ readonly: false,
511
+ visibility,
512
+ static: isStatic,
513
+ });
514
+ }
515
+ // Handle getter accessors
516
+ else if (ts.isGetAccessor(member)) {
517
+ const memberName = member.name.getText(sourceFile);
518
+ const mods = ts.canHaveModifiers(member) ? ts.getModifiers(member) : undefined;
519
+ const isStatic = mods?.some((m) => m.kind === ts.SyntaxKind.StaticKeyword) ?? false;
520
+ const visibility = getVisibility(member);
521
+ const returnType = member.type ? `: ${member.type.getText(sourceFile)}` : '';
522
+ members.push({
523
+ name: memberName,
524
+ kind: 'getter',
525
+ signature: `get ${memberName}()${returnType}`,
526
+ optional: false,
527
+ readonly: true,
528
+ visibility,
529
+ static: isStatic,
530
+ });
531
+ }
532
+ // Handle setter accessors
533
+ else if (ts.isSetAccessor(member)) {
534
+ const memberName = member.name.getText(sourceFile);
535
+ const mods = ts.canHaveModifiers(member) ? ts.getModifiers(member) : undefined;
536
+ const isStatic = mods?.some((m) => m.kind === ts.SyntaxKind.StaticKeyword) ?? false;
537
+ const visibility = getVisibility(member);
538
+ const param = member.parameters[0];
539
+ const paramStr = param.getText(sourceFile);
540
+ members.push({
541
+ name: memberName,
542
+ kind: 'setter',
543
+ signature: `set ${memberName}(${paramStr})`,
544
+ optional: false,
545
+ readonly: false,
546
+ visibility,
547
+ static: isStatic,
548
+ });
549
+ }
550
+ }
551
+ return members;
552
+ }
553
+ /**
554
+ * Extract interface members
555
+ */
556
+ function extractInterfaceMembers(node, sourceFile) {
557
+ const members = [];
558
+ for (const member of node.members) {
559
+ if (ts.isPropertySignature(member)) {
560
+ const memberName = member.name.getText(sourceFile);
561
+ const type = member.type ? member.type.getText(sourceFile) : 'any';
562
+ const isReadonly = member.modifiers?.some((m) => m.kind === ts.SyntaxKind.ReadonlyKeyword) ?? false;
563
+ members.push({
564
+ name: memberName,
565
+ kind: 'property',
566
+ signature: `${memberName}: ${type}`,
567
+ optional: member.questionToken !== undefined,
568
+ readonly: isReadonly,
569
+ });
570
+ }
571
+ else if (ts.isMethodSignature(member)) {
572
+ const memberName = member.name.getText(sourceFile);
573
+ const params = member.parameters.map((p) => p.getText(sourceFile)).join(', ');
574
+ const returnType = member.type ? `: ${member.type.getText(sourceFile)}` : '';
575
+ members.push({
576
+ name: memberName,
577
+ kind: 'method',
578
+ signature: `${memberName}(${params})${returnType}`,
579
+ optional: member.questionToken !== undefined,
580
+ readonly: false,
581
+ });
582
+ }
583
+ }
584
+ return members;
585
+ }
586
+ /**
587
+ * Extract JSDoc documentation
588
+ */
589
+ function extractDocumentation(node, sourceFile) {
590
+ const jsDocTags = ts.getJSDocTags(node);
591
+ const jsDocComments = node.jsDoc;
592
+ if (!jsDocComments?.length && !jsDocTags.length)
593
+ return undefined;
594
+ const doc = {
595
+ summary: '',
596
+ };
597
+ // Get comment text
598
+ if (jsDocComments?.length) {
599
+ const firstDoc = jsDocComments[0];
600
+ if (typeof firstDoc.comment === 'string') {
601
+ const lines = firstDoc.comment.split('\n');
602
+ doc.summary = lines[0].trim();
603
+ if (lines.length > 1) {
604
+ doc.description = lines.slice(1).join('\n').trim();
605
+ }
606
+ }
607
+ }
608
+ // Process tags
609
+ const params = {};
610
+ const throws = [];
611
+ const examples = [];
612
+ const see = [];
613
+ for (const tag of jsDocTags) {
614
+ const tagName = tag.tagName.text;
615
+ const tagComment = typeof tag.comment === 'string' ? tag.comment : '';
616
+ if (tagName === 'param' && ts.isJSDocParameterTag(tag)) {
617
+ const paramName = tag.name.getText(sourceFile);
618
+ params[paramName] = tagComment;
619
+ }
620
+ else if (tagName === 'returns' || tagName === 'return') {
621
+ doc.returns = tagComment;
622
+ }
623
+ else if (tagName === 'throws' || tagName === 'exception') {
624
+ throws.push(tagComment);
625
+ }
626
+ else if (tagName === 'example') {
627
+ examples.push(tagComment);
628
+ }
629
+ else if (tagName === 'see') {
630
+ see.push(tagComment);
631
+ }
632
+ else if (ts.isJSDocDeprecatedTag(tag)) {
633
+ doc.deprecatedMessage = tagComment || 'This API is deprecated';
634
+ }
635
+ else if (tagName === 'since') {
636
+ doc.since = tagComment;
637
+ }
638
+ }
639
+ if (Object.keys(params).length > 0)
640
+ doc.params = params;
641
+ if (throws.length > 0)
642
+ doc.throws = throws;
643
+ if (examples.length > 0)
644
+ doc.examples = examples;
645
+ if (see.length > 0)
646
+ doc.see = see;
647
+ // Return undefined if no useful info
648
+ if (!doc.summary && !doc.params && !doc.returns)
649
+ return undefined;
650
+ return doc;
651
+ }
652
+ /**
653
+ * Get node name
654
+ */
655
+ function getNodeName(node) {
656
+ if (ts.isFunctionDeclaration(node) && node.name)
657
+ return node.name.text;
658
+ if (ts.isClassDeclaration(node) && node.name)
659
+ return node.name.text;
660
+ if (ts.isInterfaceDeclaration(node))
661
+ return node.name.text;
662
+ if (ts.isTypeAliasDeclaration(node))
663
+ return node.name.text;
664
+ if (ts.isEnumDeclaration(node))
665
+ return node.name.text;
666
+ if (ts.isMethodDeclaration(node) && ts.isIdentifier(node.name))
667
+ return node.name.text;
668
+ if (ts.isVariableDeclaration(node) && ts.isIdentifier(node.name))
669
+ return node.name.text;
670
+ return '<anonymous>';
671
+ }
672
+ /**
673
+ * Check if node is exported
674
+ */
675
+ function isExported(node) {
676
+ // For variable declarations, check the parent VariableStatement
677
+ if (ts.isVariableDeclaration(node)) {
678
+ // VariableDeclaration -> VariableDeclarationList -> VariableStatement
679
+ const varDeclList = node.parent;
680
+ const varStatement = varDeclList.parent;
681
+ if (ts.canHaveModifiers(varStatement)) {
682
+ const modifiers = ts.getModifiers(varStatement);
683
+ return modifiers?.some((m) => m.kind === ts.SyntaxKind.ExportKeyword) ?? false;
684
+ }
685
+ return false;
686
+ }
687
+ if (!ts.canHaveModifiers(node))
688
+ return false;
689
+ const modifiers = ts.getModifiers(node);
690
+ return modifiers?.some((m) => m.kind === ts.SyntaxKind.ExportKeyword) ?? false;
691
+ }
692
+ /**
693
+ * Get visibility modifier
694
+ */
695
+ function getVisibility(node) {
696
+ if (!ts.canHaveModifiers(node))
697
+ return 'public';
698
+ const modifiers = ts.getModifiers(node);
699
+ if (modifiers?.some((m) => m.kind === ts.SyntaxKind.PrivateKeyword))
700
+ return 'private';
701
+ if (modifiers?.some((m) => m.kind === ts.SyntaxKind.ProtectedKeyword))
702
+ return 'protected';
703
+ return 'public';
704
+ }
705
+ /**
706
+ * Format a generic parameter
707
+ */
708
+ function formatGeneric(g) {
709
+ let result = g.name;
710
+ if (g.constraint)
711
+ result += ` extends ${g.constraint}`;
712
+ if (g.default)
713
+ result += ` = ${g.default}`;
714
+ return result;
715
+ }
716
+ /**
717
+ * Format a parameter
718
+ */
719
+ function formatParameter(p) {
720
+ let result = p.rest ? `...${p.name}` : p.name;
721
+ if (p.optional && !p.defaultValue)
722
+ result += '?';
723
+ result += `: ${p.type}`;
724
+ if (p.defaultValue)
725
+ result += ` = ${p.defaultValue}`;
726
+ return result;
727
+ }
728
+ /**
729
+ * Format multi-line signature
730
+ */
731
+ function formatMultilineSignature(asyncPrefix, name, genericsStr, parameters, returnType) {
732
+ if (parameters.length <= 2) {
733
+ const paramsStr = parameters.map((p) => formatParameter(p)).join(', ');
734
+ const returnStr = returnType ? `: ${returnType.type}` : '';
735
+ return `${asyncPrefix}function ${name}${genericsStr}(${paramsStr})${returnStr}`;
736
+ }
737
+ // Multi-line for many parameters
738
+ const paramsLines = parameters.map((p) => ` ${formatParameter(p)}`).join(',\n');
739
+ const returnStr = returnType ? `: ${returnType.type}` : '';
740
+ return `${asyncPrefix}function ${name}${genericsStr}(\n${paramsLines}\n)${returnStr}`;
741
+ }
742
+ /**
743
+ * Create customizable getSignature tool
744
+ */
745
+ export function createGetSignatureTool(options) {
746
+ return defineTool({
747
+ name: options?.name ?? 'get_signature',
748
+ description: options?.description ?? TOOL_DESCRIPTION,
749
+ inputSchema: TOOL_INPUT_SCHEMA,
750
+ execute: async (input) => {
751
+ const modifiedInput = {
752
+ ...input,
753
+ includeDoc: input.includeDoc ?? options?.defaultIncludeDoc,
754
+ };
755
+ return executeGetSignature(modifiedInput);
756
+ },
757
+ });
758
+ }