@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,458 @@
1
+ /**
2
+ * findSymbol Tool
3
+ *
4
+ * Find the definition location of a symbol across the codebase.
5
+ * Uses TypeScript Compiler API for accurate AST analysis.
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
+ // Supported file extensions for searching
13
+ const SUPPORTED_EXTENSIONS = new Set([
14
+ '.ts',
15
+ '.tsx',
16
+ '.js',
17
+ '.jsx',
18
+ '.mts',
19
+ '.mjs',
20
+ '.cts',
21
+ '.cjs',
22
+ ]);
23
+ // Helper to check for a specific modifier kind
24
+ function hasModifierKind(modifiers, kind) {
25
+ return modifiers?.some((m) => m.kind === kind) ?? false;
26
+ }
27
+ // Tool description
28
+ const TOOL_DESCRIPTION = `Find the definition location of a symbol by name across the codebase.
29
+ Returns structured JSON with file path, line number, symbol kind, and other metadata.
30
+ Use this to locate where functions, classes, variables, types, etc. are defined.`;
31
+ // Tool input schema
32
+ const TOOL_INPUT_SCHEMA = {
33
+ type: 'object',
34
+ properties: {
35
+ symbol: {
36
+ type: 'string',
37
+ description: 'Symbol name to find',
38
+ },
39
+ scope: {
40
+ type: 'string',
41
+ description: 'Scope the search to a specific file or directory',
42
+ },
43
+ kind: {
44
+ type: 'string',
45
+ enum: [
46
+ 'function',
47
+ 'class',
48
+ 'variable',
49
+ 'type',
50
+ 'interface',
51
+ 'enum',
52
+ 'method',
53
+ 'property',
54
+ 'any',
55
+ ],
56
+ description: 'Filter by symbol kind (default: any)',
57
+ default: 'any',
58
+ },
59
+ limit: {
60
+ type: 'number',
61
+ description: 'Maximum results to return (default: 10)',
62
+ default: 10,
63
+ },
64
+ includeNodeModules: {
65
+ type: 'boolean',
66
+ description: 'Include symbols from node_modules (default: false)',
67
+ default: false,
68
+ },
69
+ },
70
+ required: ['symbol'],
71
+ };
72
+ /**
73
+ * findSymbol tool - Find symbol definitions
74
+ */
75
+ export const findSymbolTool = defineTool({
76
+ name: 'find_symbol',
77
+ description: TOOL_DESCRIPTION,
78
+ inputSchema: TOOL_INPUT_SCHEMA,
79
+ execute: executeFindSymbol,
80
+ });
81
+ /**
82
+ * Execute the findSymbol tool
83
+ */
84
+ async function executeFindSymbol(input) {
85
+ const { symbol, scope, kind = 'any', limit = 10, includeNodeModules = false } = input;
86
+ // Validate input
87
+ if (!symbol || symbol.trim().length === 0) {
88
+ return createErrorResult('Symbol name is required');
89
+ }
90
+ const startTime = Date.now();
91
+ const definitions = [];
92
+ let filesSearched = 0;
93
+ try {
94
+ // Determine search path
95
+ const searchPath = scope || process.cwd();
96
+ // Check if path exists
97
+ try {
98
+ await fs.access(searchPath);
99
+ }
100
+ catch {
101
+ return createErrorResult(`Path not found: ${searchPath}`);
102
+ }
103
+ const stats = await fs.stat(searchPath);
104
+ if (stats.isFile()) {
105
+ // Search single file
106
+ const detection = detectLanguage(searchPath);
107
+ if (detection.language && isLanguageSupported(detection.language)) {
108
+ filesSearched = 1;
109
+ const fileDefinitions = await searchFileForSymbol(searchPath, symbol, kind);
110
+ definitions.push(...fileDefinitions);
111
+ }
112
+ }
113
+ else if (stats.isDirectory()) {
114
+ // Search directory recursively
115
+ const files = await collectFiles(searchPath, includeNodeModules);
116
+ filesSearched = files.length;
117
+ for (const filePath of files) {
118
+ if (definitions.length >= limit)
119
+ break;
120
+ const fileDefinitions = await searchFileForSymbol(filePath, symbol, kind);
121
+ const remaining = limit - definitions.length;
122
+ definitions.push(...fileDefinitions.slice(0, remaining));
123
+ }
124
+ }
125
+ else {
126
+ return createErrorResult(`Path is neither a file nor directory: ${searchPath}`);
127
+ }
128
+ const endTime = Date.now();
129
+ const result = {
130
+ query: symbol,
131
+ definitions: definitions.slice(0, limit),
132
+ stats: {
133
+ filesSearched,
134
+ timeMs: endTime - startTime,
135
+ },
136
+ };
137
+ return createSuccessResult(result);
138
+ }
139
+ catch (error) {
140
+ const message = error instanceof Error ? error.message : String(error);
141
+ return createErrorResult(`Failed to search for symbol: ${message}`);
142
+ }
143
+ }
144
+ /**
145
+ * Collect all supported files in a directory recursively
146
+ */
147
+ async function collectFiles(dir, includeNodeModules) {
148
+ const files = [];
149
+ async function walk(currentDir) {
150
+ const entries = await fs.readdir(currentDir, { withFileTypes: true });
151
+ for (const entry of entries) {
152
+ const fullPath = path.join(currentDir, entry.name);
153
+ if (entry.isDirectory()) {
154
+ // Skip node_modules unless explicitly included
155
+ if (entry.name === 'node_modules' && !includeNodeModules) {
156
+ continue;
157
+ }
158
+ // Skip common non-source directories
159
+ if (entry.name.startsWith('.') ||
160
+ entry.name === 'dist' ||
161
+ entry.name === 'build' ||
162
+ entry.name === 'coverage') {
163
+ continue;
164
+ }
165
+ await walk(fullPath);
166
+ }
167
+ else if (entry.isFile()) {
168
+ const ext = path.extname(entry.name).toLowerCase();
169
+ if (SUPPORTED_EXTENSIONS.has(ext)) {
170
+ files.push(fullPath);
171
+ }
172
+ }
173
+ }
174
+ }
175
+ await walk(dir);
176
+ return files;
177
+ }
178
+ /**
179
+ * Search a single file for symbol definitions
180
+ */
181
+ async function searchFileForSymbol(filePath, symbolName, kindFilter) {
182
+ const definitions = [];
183
+ try {
184
+ const sourceCode = await fs.readFile(filePath, 'utf-8');
185
+ const scriptKind = getScriptKind(filePath);
186
+ const sourceFile = ts.createSourceFile(filePath, sourceCode, ts.ScriptTarget.Latest, true, scriptKind);
187
+ // Search for symbols
188
+ findSymbolsInNode(sourceFile, sourceFile, symbolName, kindFilter, filePath, definitions);
189
+ }
190
+ catch {
191
+ // Silently skip files that can't be parsed
192
+ }
193
+ return definitions;
194
+ }
195
+ /**
196
+ * Get TypeScript script kind from file extension
197
+ */
198
+ function getScriptKind(filePath) {
199
+ const ext = filePath.toLowerCase().split('.').pop();
200
+ switch (ext) {
201
+ case 'ts':
202
+ return ts.ScriptKind.TS;
203
+ case 'tsx':
204
+ return ts.ScriptKind.TSX;
205
+ case 'js':
206
+ return ts.ScriptKind.JS;
207
+ case 'jsx':
208
+ return ts.ScriptKind.JSX;
209
+ case 'mts':
210
+ case 'cts':
211
+ return ts.ScriptKind.TS;
212
+ case 'mjs':
213
+ case 'cjs':
214
+ return ts.ScriptKind.JS;
215
+ default:
216
+ return ts.ScriptKind.TS;
217
+ }
218
+ }
219
+ /**
220
+ * Recursively find symbols in AST nodes
221
+ */
222
+ function findSymbolsInNode(node, sourceFile, symbolName, kindFilter, filePath, definitions, container) {
223
+ // Check current node for symbol definition
224
+ const def = extractSymbolDefinition(node, sourceFile, symbolName, kindFilter, filePath, container);
225
+ if (def) {
226
+ definitions.push(def);
227
+ }
228
+ // Recurse into children
229
+ ts.forEachChild(node, (child) => {
230
+ // Track container context for methods/properties
231
+ let newContainer = container;
232
+ if (ts.isClassDeclaration(node) && node.name) {
233
+ newContainer = node.name.text;
234
+ }
235
+ findSymbolsInNode(child, sourceFile, symbolName, kindFilter, filePath, definitions, newContainer);
236
+ });
237
+ }
238
+ /**
239
+ * Extract symbol definition from a node if it matches
240
+ */
241
+ function extractSymbolDefinition(node, sourceFile, symbolName, kindFilter, filePath, container) {
242
+ let name;
243
+ let kind;
244
+ let exported = false;
245
+ let signature;
246
+ let docSummary;
247
+ const modifiers = ts.canHaveModifiers(node) ? ts.getModifiers(node) : undefined;
248
+ exported = hasModifierKind(modifiers, ts.SyntaxKind.ExportKeyword);
249
+ // Function declarations
250
+ if (ts.isFunctionDeclaration(node) && node.name) {
251
+ name = node.name.text;
252
+ kind = 'function';
253
+ signature = buildFunctionSignature(node);
254
+ docSummary = getJsDocSummary(node, sourceFile);
255
+ }
256
+ // Class declarations
257
+ else if (ts.isClassDeclaration(node) && node.name) {
258
+ name = node.name.text;
259
+ kind = 'class';
260
+ signature = `class ${name}`;
261
+ docSummary = getJsDocSummary(node, sourceFile);
262
+ }
263
+ // Interface declarations
264
+ else if (ts.isInterfaceDeclaration(node)) {
265
+ name = node.name.text;
266
+ kind = 'interface';
267
+ signature = `interface ${name}`;
268
+ docSummary = getJsDocSummary(node, sourceFile);
269
+ }
270
+ // Type alias declarations
271
+ else if (ts.isTypeAliasDeclaration(node)) {
272
+ name = node.name.text;
273
+ kind = 'type';
274
+ signature = `type ${name}`;
275
+ docSummary = getJsDocSummary(node, sourceFile);
276
+ }
277
+ // Enum declarations
278
+ else if (ts.isEnumDeclaration(node)) {
279
+ name = node.name.text;
280
+ kind = 'enum';
281
+ signature = `enum ${name}`;
282
+ docSummary = getJsDocSummary(node, sourceFile);
283
+ }
284
+ // Variable declarations
285
+ else if (ts.isVariableDeclaration(node) && ts.isIdentifier(node.name)) {
286
+ // Skip if this is a function (arrow function or function expression)
287
+ if (node.initializer &&
288
+ (ts.isArrowFunction(node.initializer) || ts.isFunctionExpression(node.initializer))) {
289
+ name = node.name.text;
290
+ kind = 'function';
291
+ signature = buildArrowFunctionSignature(node);
292
+ }
293
+ else {
294
+ name = node.name.text;
295
+ kind = 'variable';
296
+ signature = `${getVariableKind(node)} ${name}`;
297
+ }
298
+ // Check parent for export status
299
+ const parent = node.parent.parent;
300
+ if (ts.isVariableStatement(parent)) {
301
+ const parentMods = ts.canHaveModifiers(parent) ? ts.getModifiers(parent) : undefined;
302
+ exported = hasModifierKind(parentMods, ts.SyntaxKind.ExportKeyword);
303
+ docSummary = getJsDocSummary(parent, sourceFile);
304
+ }
305
+ }
306
+ // Method declarations (inside classes)
307
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- node.name can be undefined for computed properties
308
+ else if (ts.isMethodDeclaration(node) && node.name && ts.isIdentifier(node.name)) {
309
+ name = node.name.text;
310
+ kind = 'method';
311
+ signature = buildMethodSignature(node);
312
+ docSummary = getJsDocSummary(node, sourceFile);
313
+ // Methods are exported if their containing class is exported
314
+ }
315
+ // Property declarations (inside classes)
316
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- node.name can be undefined for computed properties
317
+ else if (ts.isPropertyDeclaration(node) && node.name && ts.isIdentifier(node.name)) {
318
+ name = node.name.text;
319
+ kind = 'property';
320
+ const typeAnnotation = node.type ? `: ${node.type.getText(sourceFile)}` : '';
321
+ signature = `${name}${typeAnnotation}`;
322
+ docSummary = getJsDocSummary(node, sourceFile);
323
+ }
324
+ // Check if this matches our search
325
+ if (!name || name !== symbolName) {
326
+ return null;
327
+ }
328
+ if (!kind) {
329
+ return null;
330
+ }
331
+ // Apply kind filter
332
+ if (kindFilter !== 'any' && kind !== kindFilter) {
333
+ // Handle type filter matching both 'type' and 'interface'
334
+ if (kindFilter === 'type' && kind === 'interface') {
335
+ // Allow interface to match type filter
336
+ }
337
+ else {
338
+ return null;
339
+ }
340
+ }
341
+ // Get position information
342
+ const { line, character } = sourceFile.getLineAndCharacterOfPosition(node.getStart(sourceFile));
343
+ return {
344
+ name,
345
+ path: filePath,
346
+ line: line + 1, // Convert to 1-based
347
+ column: character + 1, // Convert to 1-based
348
+ kind,
349
+ container,
350
+ exported,
351
+ signature,
352
+ docSummary,
353
+ confidence: 1.0, // Exact match
354
+ };
355
+ }
356
+ /**
357
+ * Get variable declaration kind (const, let, var)
358
+ */
359
+ function getVariableKind(node) {
360
+ const parent = node.parent;
361
+ if (ts.isVariableDeclarationList(parent)) {
362
+ if (parent.flags & ts.NodeFlags.Const)
363
+ return 'const';
364
+ if (parent.flags & ts.NodeFlags.Let)
365
+ return 'let';
366
+ }
367
+ return 'var';
368
+ }
369
+ /**
370
+ * Build a function signature string
371
+ */
372
+ function buildFunctionSignature(node) {
373
+ const name = node.name?.text ?? 'anonymous';
374
+ const params = node.parameters
375
+ .map((p) => {
376
+ const paramName = ts.isIdentifier(p.name) ? p.name.text : 'param';
377
+ const paramType = p.type ? p.type.getText() : '';
378
+ return paramType ? `${paramName}: ${paramType}` : paramName;
379
+ })
380
+ .join(', ');
381
+ const returnType = node.type ? `: ${node.type.getText()}` : '';
382
+ const asyncPrefix = hasModifierKind(ts.getModifiers(node), ts.SyntaxKind.AsyncKeyword)
383
+ ? 'async '
384
+ : '';
385
+ return `${asyncPrefix}function ${name}(${params})${returnType}`;
386
+ }
387
+ /**
388
+ * Build a method signature string
389
+ */
390
+ function buildMethodSignature(node) {
391
+ const name = ts.isIdentifier(node.name) ? node.name.text : 'method';
392
+ const params = node.parameters
393
+ .map((p) => {
394
+ const paramName = ts.isIdentifier(p.name) ? p.name.text : 'param';
395
+ const paramType = p.type ? p.type.getText() : '';
396
+ return paramType ? `${paramName}: ${paramType}` : paramName;
397
+ })
398
+ .join(', ');
399
+ const returnType = node.type ? `: ${node.type.getText()}` : '';
400
+ const modifiers = ts.getModifiers(node);
401
+ const asyncPrefix = hasModifierKind(modifiers, ts.SyntaxKind.AsyncKeyword) ? 'async ' : '';
402
+ const staticPrefix = hasModifierKind(modifiers, ts.SyntaxKind.StaticKeyword) ? 'static ' : '';
403
+ return `${staticPrefix}${asyncPrefix}${name}(${params})${returnType}`;
404
+ }
405
+ /**
406
+ * Build an arrow function signature string from variable declaration
407
+ */
408
+ function buildArrowFunctionSignature(node) {
409
+ const name = ts.isIdentifier(node.name) ? node.name.text : 'anonymous';
410
+ if (node.initializer &&
411
+ (ts.isArrowFunction(node.initializer) || ts.isFunctionExpression(node.initializer))) {
412
+ const func = node.initializer;
413
+ const params = func.parameters
414
+ .map((p) => {
415
+ const paramName = ts.isIdentifier(p.name) ? p.name.text : 'param';
416
+ const paramType = p.type ? p.type.getText() : '';
417
+ return paramType ? `${paramName}: ${paramType}` : paramName;
418
+ })
419
+ .join(', ');
420
+ const returnType = func.type ? `: ${func.type.getText()}` : '';
421
+ return `const ${name} = (${params})${returnType}`;
422
+ }
423
+ return `const ${name}`;
424
+ }
425
+ /**
426
+ * Extract JSDoc summary (first line) from a node
427
+ */
428
+ function getJsDocSummary(node, sourceFile) {
429
+ // Get leading trivia (comments before the node)
430
+ const fullText = sourceFile.getFullText();
431
+ const nodeStart = node.getFullStart();
432
+ const nodeTextStart = node.getStart(sourceFile);
433
+ const trivia = fullText.substring(nodeStart, nodeTextStart);
434
+ // Look for JSDoc comment
435
+ const jsDocMatch = trivia.match(/\/\*\*\s*\n?\s*\*?\s*([^\n*]+)/);
436
+ if (jsDocMatch) {
437
+ return jsDocMatch[1].trim();
438
+ }
439
+ return undefined;
440
+ }
441
+ /**
442
+ * Factory function to create a customized findSymbol tool
443
+ */
444
+ export function createFindSymbolTool(options) {
445
+ const { defaultLimit = 10, defaultIncludeNodeModules = false } = options ?? {};
446
+ return defineTool({
447
+ name: 'find_symbol',
448
+ description: TOOL_DESCRIPTION,
449
+ inputSchema: TOOL_INPUT_SCHEMA,
450
+ execute: async (input) => {
451
+ return executeFindSymbol({
452
+ ...input,
453
+ limit: input.limit ?? defaultLimit,
454
+ includeNodeModules: input.includeNodeModules ?? defaultIncludeNodeModules,
455
+ });
456
+ },
457
+ });
458
+ }
@@ -0,0 +1,23 @@
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 type { Tool } from '@compilr-dev/agents';
8
+ import type { GetCallGraphInput, CallGraphDirection } from './types.js';
9
+ /**
10
+ * getCallGraph tool - Analyze function call relationships
11
+ */
12
+ export declare const getCallGraphTool: Tool<GetCallGraphInput>;
13
+ /**
14
+ * Factory function to create a customized getCallGraph tool
15
+ */
16
+ export declare function createGetCallGraphTool(options?: {
17
+ /** Default direction */
18
+ defaultDirection?: CallGraphDirection;
19
+ /** Default max depth */
20
+ defaultMaxDepth?: number;
21
+ /** Default includeExternal */
22
+ defaultIncludeExternal?: boolean;
23
+ }): Tool<GetCallGraphInput>;