@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,469 @@
1
+ /**
2
+ * getCallGraph Tool
3
+ *
4
+ * Analyze function call relationships using TypeScript Compiler API.
5
+ * Returns a call graph showing what functions call what.
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 = `Analyze function call relationships in source code.
14
+ Returns a call graph showing which functions call which other functions.
15
+ Use this to understand code flow, dependencies, and refactoring impact.`;
16
+ // Tool input schema
17
+ const TOOL_INPUT_SCHEMA = {
18
+ type: 'object',
19
+ properties: {
20
+ path: {
21
+ type: 'string',
22
+ description: 'File to analyze',
23
+ },
24
+ functionName: {
25
+ type: 'string',
26
+ description: 'Specific function to analyze (optional)',
27
+ },
28
+ direction: {
29
+ type: 'string',
30
+ enum: ['callers', 'callees', 'both'],
31
+ description: "Direction: 'callers' | 'callees' | 'both' (default: 'both')",
32
+ },
33
+ maxDepth: {
34
+ type: 'number',
35
+ description: 'Maximum depth to traverse (default: 2)',
36
+ default: 2,
37
+ },
38
+ includeExternal: {
39
+ type: 'boolean',
40
+ description: 'Include external package calls (default: false)',
41
+ default: false,
42
+ },
43
+ scope: {
44
+ type: 'string',
45
+ description: "Scope directory for finding callers (default: file's directory)",
46
+ },
47
+ },
48
+ required: ['path'],
49
+ };
50
+ /**
51
+ * getCallGraph tool - Analyze function call relationships
52
+ */
53
+ export const getCallGraphTool = defineTool({
54
+ name: 'get_call_graph',
55
+ description: TOOL_DESCRIPTION,
56
+ inputSchema: TOOL_INPUT_SCHEMA,
57
+ execute: executeGetCallGraph,
58
+ });
59
+ /**
60
+ * Execute the getCallGraph tool
61
+ */
62
+ async function executeGetCallGraph(input) {
63
+ const { path: inputPath, functionName, direction = 'both', maxDepth = 2, includeExternal = false, scope, } = input;
64
+ // Validate input
65
+ if (!inputPath || inputPath.trim().length === 0) {
66
+ return createErrorResult('Path is required');
67
+ }
68
+ try {
69
+ // Check if path exists
70
+ try {
71
+ await fs.access(inputPath);
72
+ }
73
+ catch {
74
+ return createErrorResult(`Path not found: ${inputPath}`);
75
+ }
76
+ const stats = await fs.stat(inputPath);
77
+ if (!stats.isFile()) {
78
+ return createErrorResult(`Path must be a file: ${inputPath}`);
79
+ }
80
+ // Check for supported language
81
+ const detection = detectLanguage(inputPath);
82
+ if (!detection.language || !isLanguageSupported(detection.language)) {
83
+ return createErrorResult(`Unsupported file type: ${path.extname(inputPath)}`);
84
+ }
85
+ // Analyze the file
86
+ const scopeDir = scope ?? path.dirname(inputPath);
87
+ const result = await analyzeCallGraph(inputPath, functionName, direction, maxDepth, includeExternal, scopeDir);
88
+ return createSuccessResult(result);
89
+ }
90
+ catch (error) {
91
+ const message = error instanceof Error ? error.message : String(error);
92
+ return createErrorResult(`Failed to analyze call graph: ${message}`);
93
+ }
94
+ }
95
+ /**
96
+ * Analyze call graph for a file
97
+ */
98
+ async function analyzeCallGraph(filePath, functionName, direction, maxDepth, includeExternal, _scopeDir) {
99
+ const sourceCode = await fs.readFile(filePath, 'utf-8');
100
+ const scriptKind = getScriptKind(filePath);
101
+ const sourceFile = ts.createSourceFile(filePath, sourceCode, ts.ScriptTarget.Latest, true, scriptKind);
102
+ // Find all functions in the file
103
+ const functions = extractFunctions(sourceFile, filePath);
104
+ // Build the call graph
105
+ const graphNodes = new Map();
106
+ let rootNode;
107
+ let externalCallCount = 0;
108
+ // If functionName is specified, find it
109
+ if (functionName) {
110
+ const targetFunc = functions.find((f) => f.node.name === functionName);
111
+ if (!targetFunc) {
112
+ // Return empty result if function not found
113
+ return {
114
+ graph: [],
115
+ stats: {
116
+ totalFunctions: 0,
117
+ totalCalls: 0,
118
+ maxDepthReached: 0,
119
+ },
120
+ };
121
+ }
122
+ rootNode = targetFunc.node;
123
+ }
124
+ // Analyze callees (what each function calls)
125
+ if (direction === 'callees' || direction === 'both') {
126
+ const functionsToAnalyze = functionName
127
+ ? functions.filter((f) => f.node.name === functionName)
128
+ : functions;
129
+ for (const func of functionsToAnalyze) {
130
+ const callees = extractCallees(sourceFile, func, functions, includeExternal);
131
+ let graphNode = graphNodes.get(func.node.name);
132
+ if (!graphNode) {
133
+ graphNode = {
134
+ function: func.node,
135
+ calls: [],
136
+ calledBy: [],
137
+ };
138
+ graphNodes.set(func.node.name, graphNode);
139
+ }
140
+ graphNode.calls = callees;
141
+ // Count external calls
142
+ if (includeExternal) {
143
+ externalCallCount += callees.filter((c) => c.function.path === 'external').length;
144
+ }
145
+ }
146
+ }
147
+ // Analyze callers (what calls each function)
148
+ if (direction === 'callers' || direction === 'both') {
149
+ const functionsToAnalyze = functionName
150
+ ? functions.filter((f) => f.node.name === functionName)
151
+ : functions;
152
+ for (const func of functionsToAnalyze) {
153
+ // Find callers within the same file
154
+ const callers = findCallersInFile(sourceFile, func.node.name, functions, filePath);
155
+ let graphNode = graphNodes.get(func.node.name);
156
+ if (!graphNode) {
157
+ graphNode = {
158
+ function: func.node,
159
+ calls: [],
160
+ calledBy: [],
161
+ };
162
+ graphNodes.set(func.node.name, graphNode);
163
+ }
164
+ graphNode.calledBy = callers;
165
+ }
166
+ }
167
+ // Calculate statistics
168
+ const graph = Array.from(graphNodes.values());
169
+ const totalCalls = graph.reduce((sum, node) => sum + node.calls.length, 0);
170
+ const callGraphStats = {
171
+ totalFunctions: graph.length,
172
+ totalCalls,
173
+ maxDepthReached: Math.min(maxDepth, 1), // Currently we only go 1 level deep
174
+ externalCalls: includeExternal ? externalCallCount : undefined,
175
+ };
176
+ return {
177
+ root: rootNode,
178
+ graph,
179
+ stats: callGraphStats,
180
+ };
181
+ }
182
+ /**
183
+ * Extract all functions from a source file
184
+ */
185
+ function extractFunctions(sourceFile, filePath) {
186
+ const functions = [];
187
+ function visit(node, className) {
188
+ // Function declarations
189
+ if (ts.isFunctionDeclaration(node) && node.name) {
190
+ const { line, character } = sourceFile.getLineAndCharacterOfPosition(node.getStart(sourceFile));
191
+ const isExported = hasExportModifier(node);
192
+ const isAsync = hasAsyncModifier(node);
193
+ functions.push({
194
+ node: {
195
+ name: node.name.text,
196
+ path: filePath,
197
+ line: line + 1,
198
+ column: character + 1,
199
+ async: isAsync,
200
+ exported: isExported,
201
+ },
202
+ bodyStart: node.body?.getStart(sourceFile) ?? node.getStart(sourceFile),
203
+ bodyEnd: node.body?.getEnd() ?? node.getEnd(),
204
+ });
205
+ }
206
+ // Arrow functions assigned to variables
207
+ if (ts.isVariableStatement(node)) {
208
+ const decls = node.declarationList.declarations;
209
+ for (const decl of decls) {
210
+ if (ts.isIdentifier(decl.name) && decl.initializer) {
211
+ if (ts.isArrowFunction(decl.initializer) || ts.isFunctionExpression(decl.initializer)) {
212
+ const { line, character } = sourceFile.getLineAndCharacterOfPosition(decl.getStart(sourceFile));
213
+ const isExported = hasExportModifier(node);
214
+ const isAsync = hasAsyncModifier(decl.initializer);
215
+ functions.push({
216
+ node: {
217
+ name: decl.name.text,
218
+ path: filePath,
219
+ line: line + 1,
220
+ column: character + 1,
221
+ async: isAsync,
222
+ exported: isExported,
223
+ },
224
+ bodyStart: decl.initializer.body.getStart(sourceFile),
225
+ bodyEnd: decl.initializer.body.getEnd(),
226
+ });
227
+ }
228
+ }
229
+ }
230
+ }
231
+ // Class methods
232
+ if (ts.isClassDeclaration(node) && node.name) {
233
+ const currentClassName = node.name.text;
234
+ for (const member of node.members) {
235
+ if (ts.isMethodDeclaration(member) && ts.isIdentifier(member.name)) {
236
+ const { line, character } = sourceFile.getLineAndCharacterOfPosition(member.getStart(sourceFile));
237
+ const isAsync = hasAsyncModifier(member);
238
+ functions.push({
239
+ node: {
240
+ name: member.name.text,
241
+ path: filePath,
242
+ line: line + 1,
243
+ column: character + 1,
244
+ className: currentClassName,
245
+ async: isAsync,
246
+ exported: hasExportModifier(node), // Class export implies method access
247
+ },
248
+ bodyStart: member.body?.getStart(sourceFile) ?? member.getStart(sourceFile),
249
+ bodyEnd: member.body?.getEnd() ?? member.getEnd(),
250
+ });
251
+ }
252
+ }
253
+ }
254
+ ts.forEachChild(node, (child) => {
255
+ visit(child, className);
256
+ });
257
+ }
258
+ visit(sourceFile);
259
+ return functions;
260
+ }
261
+ /**
262
+ * Extract callees (functions called by a function)
263
+ */
264
+ function extractCallees(sourceFile, func, allFunctions, includeExternal) {
265
+ const callCounts = new Map();
266
+ function visit(node) {
267
+ // Skip nodes outside the function body
268
+ const nodeStart = node.getStart(sourceFile);
269
+ const nodeEnd = node.getEnd();
270
+ if (nodeStart < func.bodyStart || nodeEnd > func.bodyEnd) {
271
+ ts.forEachChild(node, visit);
272
+ return;
273
+ }
274
+ if (ts.isCallExpression(node)) {
275
+ const callInfo = extractCallInfo(node, sourceFile, allFunctions, includeExternal);
276
+ if (callInfo) {
277
+ const key = `${callInfo.function.name}:${callInfo.function.path}`;
278
+ const existing = callCounts.get(key);
279
+ if (existing) {
280
+ existing.count++;
281
+ }
282
+ else {
283
+ callCounts.set(key, callInfo);
284
+ }
285
+ }
286
+ }
287
+ ts.forEachChild(node, visit);
288
+ }
289
+ visit(sourceFile);
290
+ return Array.from(callCounts.values());
291
+ }
292
+ /**
293
+ * Extract call info from a call expression
294
+ */
295
+ function extractCallInfo(node, sourceFile, allFunctions, includeExternal) {
296
+ const { line, character } = sourceFile.getLineAndCharacterOfPosition(node.getStart(sourceFile));
297
+ let funcName;
298
+ let callType = 'direct';
299
+ let className;
300
+ // Simple function call: funcName()
301
+ if (ts.isIdentifier(node.expression)) {
302
+ funcName = node.expression.text;
303
+ }
304
+ // Method call: obj.method() or this.method() or promise chain
305
+ else if (ts.isPropertyAccessExpression(node.expression)) {
306
+ funcName = node.expression.name.text;
307
+ const propName = node.expression.name.text;
308
+ // Check for promise chain
309
+ if (propName === 'then' || propName === 'catch' || propName === 'finally') {
310
+ callType = 'promise';
311
+ }
312
+ else {
313
+ callType = 'method';
314
+ if (ts.isIdentifier(node.expression.expression)) {
315
+ const objName = node.expression.expression.text;
316
+ if (objName !== 'this') {
317
+ // Could be instance.method or ClassName.staticMethod
318
+ className = objName;
319
+ }
320
+ }
321
+ }
322
+ }
323
+ if (!funcName) {
324
+ return null;
325
+ }
326
+ // Check if it's a known function in the file
327
+ const knownFunc = allFunctions.find((f) => f.node.name === funcName);
328
+ if (knownFunc) {
329
+ return {
330
+ function: knownFunc.node,
331
+ callLine: line + 1,
332
+ callColumn: character + 1,
333
+ type: callType,
334
+ count: 1,
335
+ };
336
+ }
337
+ // External call (not in this file)
338
+ if (includeExternal) {
339
+ return {
340
+ function: {
341
+ name: funcName,
342
+ path: 'external',
343
+ line: 0,
344
+ column: 0,
345
+ className,
346
+ },
347
+ callLine: line + 1,
348
+ callColumn: character + 1,
349
+ type: callType,
350
+ count: 1,
351
+ };
352
+ }
353
+ return null;
354
+ }
355
+ /**
356
+ * Find callers of a function within the same file
357
+ */
358
+ function findCallersInFile(sourceFile, targetFuncName, allFunctions, _filePath) {
359
+ const callerCounts = new Map();
360
+ for (const func of allFunctions) {
361
+ // Skip the function itself
362
+ if (func.node.name === targetFuncName) {
363
+ continue;
364
+ }
365
+ // Check if this function calls the target
366
+ let callsTarget = false;
367
+ let callLine = 0;
368
+ let callColumn = 0;
369
+ let callCount = 0;
370
+ function visit(node) {
371
+ // Skip nodes outside the function body
372
+ const nodeStart = node.getStart(sourceFile);
373
+ const nodeEnd = node.getEnd();
374
+ if (nodeStart < func.bodyStart || nodeEnd > func.bodyEnd) {
375
+ ts.forEachChild(node, visit);
376
+ return;
377
+ }
378
+ if (ts.isCallExpression(node)) {
379
+ let calledName;
380
+ if (ts.isIdentifier(node.expression)) {
381
+ calledName = node.expression.text;
382
+ }
383
+ else if (ts.isPropertyAccessExpression(node.expression)) {
384
+ calledName = node.expression.name.text;
385
+ }
386
+ if (calledName === targetFuncName) {
387
+ callsTarget = true;
388
+ callCount++;
389
+ if (callLine === 0) {
390
+ const pos = sourceFile.getLineAndCharacterOfPosition(node.getStart(sourceFile));
391
+ callLine = pos.line + 1;
392
+ callColumn = pos.character + 1;
393
+ }
394
+ }
395
+ }
396
+ ts.forEachChild(node, visit);
397
+ }
398
+ visit(sourceFile);
399
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- Modified in nested visit function
400
+ if (callsTarget) {
401
+ const key = func.node.name;
402
+ callerCounts.set(key, {
403
+ function: func.node,
404
+ callLine,
405
+ callColumn,
406
+ type: 'direct',
407
+ count: callCount,
408
+ });
409
+ }
410
+ }
411
+ return Array.from(callerCounts.values());
412
+ }
413
+ /**
414
+ * Check if a node has export modifier
415
+ */
416
+ function hasExportModifier(node) {
417
+ const modifiers = ts.canHaveModifiers(node) ? ts.getModifiers(node) : undefined;
418
+ return modifiers?.some((m) => m.kind === ts.SyntaxKind.ExportKeyword) ?? false;
419
+ }
420
+ /**
421
+ * Check if a node has async modifier
422
+ */
423
+ function hasAsyncModifier(node) {
424
+ const modifiers = ts.canHaveModifiers(node) ? ts.getModifiers(node) : undefined;
425
+ return modifiers?.some((m) => m.kind === ts.SyntaxKind.AsyncKeyword) ?? false;
426
+ }
427
+ /**
428
+ * Get TypeScript script kind from file extension
429
+ */
430
+ function getScriptKind(filePath) {
431
+ const ext = filePath.toLowerCase().split('.').pop();
432
+ switch (ext) {
433
+ case 'ts':
434
+ return ts.ScriptKind.TS;
435
+ case 'tsx':
436
+ return ts.ScriptKind.TSX;
437
+ case 'js':
438
+ return ts.ScriptKind.JS;
439
+ case 'jsx':
440
+ return ts.ScriptKind.JSX;
441
+ case 'mts':
442
+ case 'cts':
443
+ return ts.ScriptKind.TS;
444
+ case 'mjs':
445
+ case 'cjs':
446
+ return ts.ScriptKind.JS;
447
+ default:
448
+ return ts.ScriptKind.TS;
449
+ }
450
+ }
451
+ /**
452
+ * Factory function to create a customized getCallGraph tool
453
+ */
454
+ export function createGetCallGraphTool(options) {
455
+ const { defaultDirection = 'both', defaultMaxDepth = 2, defaultIncludeExternal = false, } = options ?? {};
456
+ return defineTool({
457
+ name: 'get_call_graph',
458
+ description: TOOL_DESCRIPTION,
459
+ inputSchema: TOOL_INPUT_SCHEMA,
460
+ execute: async (input) => {
461
+ return executeGetCallGraph({
462
+ ...input,
463
+ direction: input.direction ?? defaultDirection,
464
+ maxDepth: input.maxDepth ?? defaultMaxDepth,
465
+ includeExternal: input.includeExternal ?? defaultIncludeExternal,
466
+ });
467
+ },
468
+ });
469
+ }
@@ -0,0 +1,21 @@
1
+ /**
2
+ * getComplexity Tool
3
+ *
4
+ * Calculate code complexity metrics including cyclomatic complexity,
5
+ * cognitive complexity, nesting depth, and identify complexity hotspots.
6
+ */
7
+ import type { Tool } from '@compilr-dev/agents';
8
+ import type { GetComplexityInput } from './types.js';
9
+ /**
10
+ * getComplexity tool
11
+ */
12
+ export declare const getComplexityTool: Tool<GetComplexityInput>;
13
+ /**
14
+ * Create customizable getComplexity tool
15
+ */
16
+ export declare function createGetComplexityTool(options?: {
17
+ name?: string;
18
+ description?: string;
19
+ defaultThreshold?: number;
20
+ defaultMaxFiles?: number;
21
+ }): Tool<GetComplexityInput>;