@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,488 @@
1
+ /**
2
+ * findReferences Tool
3
+ *
4
+ * Find all usages 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 all usages of a symbol across the codebase.
29
+ Returns structured JSON with file paths, line numbers, and reference types.
30
+ Use this to understand how a function, class, or variable is used throughout the project.`;
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 references for',
38
+ },
39
+ definitionFile: {
40
+ type: 'string',
41
+ description: 'File where the symbol is defined (improves accuracy)',
42
+ },
43
+ definitionLine: {
44
+ type: 'number',
45
+ description: 'Line where symbol is defined (improves accuracy)',
46
+ },
47
+ scope: {
48
+ type: 'string',
49
+ description: 'Scope the search to a specific file or directory',
50
+ },
51
+ limit: {
52
+ type: 'number',
53
+ description: 'Maximum results to return (default: 50)',
54
+ default: 50,
55
+ },
56
+ includeDefinition: {
57
+ type: 'boolean',
58
+ description: 'Include the definition itself (default: false)',
59
+ default: false,
60
+ },
61
+ groupByFile: {
62
+ type: 'boolean',
63
+ description: 'Group results by file (default: true)',
64
+ default: true,
65
+ },
66
+ },
67
+ required: ['symbol'],
68
+ };
69
+ /**
70
+ * findReferences tool - Find all usages of a symbol
71
+ */
72
+ export const findReferencesTool = defineTool({
73
+ name: 'find_references',
74
+ description: TOOL_DESCRIPTION,
75
+ inputSchema: TOOL_INPUT_SCHEMA,
76
+ execute: executeFindReferences,
77
+ });
78
+ /**
79
+ * Execute the findReferences tool
80
+ */
81
+ async function executeFindReferences(input) {
82
+ const { symbol, definitionFile, definitionLine, scope, limit = 50, includeDefinition = false, groupByFile = true, } = input;
83
+ // Validate input
84
+ if (!symbol || symbol.trim().length === 0) {
85
+ return createErrorResult('Symbol name is required');
86
+ }
87
+ const startTime = Date.now();
88
+ const allReferences = [];
89
+ let filesSearched = 0;
90
+ let definition;
91
+ try {
92
+ // Determine search path
93
+ const searchPath = scope || process.cwd();
94
+ // Check if path exists
95
+ try {
96
+ await fs.access(searchPath);
97
+ }
98
+ catch {
99
+ return createErrorResult(`Path not found: ${searchPath}`);
100
+ }
101
+ const stats = await fs.stat(searchPath);
102
+ // First, try to find the definition if definitionFile is provided
103
+ if (definitionFile) {
104
+ try {
105
+ await fs.access(definitionFile);
106
+ const detection = detectLanguage(definitionFile);
107
+ if (detection.language && isLanguageSupported(detection.language)) {
108
+ definition = await findDefinitionInFile(definitionFile, symbol, definitionLine);
109
+ }
110
+ }
111
+ catch {
112
+ // Definition file not found, continue without it
113
+ }
114
+ }
115
+ // Collect files to search
116
+ let filesToSearch = [];
117
+ if (stats.isFile()) {
118
+ const detection = detectLanguage(searchPath);
119
+ if (detection.language && isLanguageSupported(detection.language)) {
120
+ filesToSearch = [searchPath];
121
+ }
122
+ }
123
+ else if (stats.isDirectory()) {
124
+ filesToSearch = await collectFiles(searchPath);
125
+ }
126
+ else {
127
+ return createErrorResult(`Path is neither a file nor directory: ${searchPath}`);
128
+ }
129
+ filesSearched = filesToSearch.length;
130
+ // Search each file for references
131
+ for (const filePath of filesToSearch) {
132
+ if (allReferences.length >= limit)
133
+ break;
134
+ const fileRefs = await searchFileForReferences(filePath, symbol, definition, includeDefinition);
135
+ const remaining = limit - allReferences.length;
136
+ allReferences.push(...fileRefs.slice(0, remaining));
137
+ }
138
+ const endTime = Date.now();
139
+ const truncated = allReferences.length >= limit;
140
+ // Build result
141
+ const result = {
142
+ symbol,
143
+ definition,
144
+ totalCount: allReferences.length,
145
+ truncated,
146
+ stats: {
147
+ filesSearched,
148
+ timeMs: endTime - startTime,
149
+ },
150
+ };
151
+ if (groupByFile) {
152
+ result.byFile = groupReferencesByFile(allReferences);
153
+ }
154
+ else {
155
+ result.references = allReferences;
156
+ }
157
+ return createSuccessResult(result);
158
+ }
159
+ catch (error) {
160
+ const message = error instanceof Error ? error.message : String(error);
161
+ return createErrorResult(`Failed to search for references: ${message}`);
162
+ }
163
+ }
164
+ /**
165
+ * Collect all supported files in a directory recursively
166
+ */
167
+ async function collectFiles(dir) {
168
+ const files = [];
169
+ async function walk(currentDir) {
170
+ const entries = await fs.readdir(currentDir, { withFileTypes: true });
171
+ for (const entry of entries) {
172
+ const fullPath = path.join(currentDir, entry.name);
173
+ if (entry.isDirectory()) {
174
+ // Skip node_modules and common non-source directories
175
+ if (entry.name === 'node_modules' ||
176
+ entry.name.startsWith('.') ||
177
+ entry.name === 'dist' ||
178
+ entry.name === 'build' ||
179
+ entry.name === 'coverage') {
180
+ continue;
181
+ }
182
+ await walk(fullPath);
183
+ }
184
+ else if (entry.isFile()) {
185
+ const ext = path.extname(entry.name).toLowerCase();
186
+ if (SUPPORTED_EXTENSIONS.has(ext)) {
187
+ files.push(fullPath);
188
+ }
189
+ }
190
+ }
191
+ }
192
+ await walk(dir);
193
+ return files;
194
+ }
195
+ /**
196
+ * Find the definition of a symbol in a file
197
+ */
198
+ async function findDefinitionInFile(filePath, symbolName, targetLine) {
199
+ try {
200
+ const sourceCode = await fs.readFile(filePath, 'utf-8');
201
+ const scriptKind = getScriptKind(filePath);
202
+ const sourceFile = ts.createSourceFile(filePath, sourceCode, ts.ScriptTarget.Latest, true, scriptKind);
203
+ let foundDef;
204
+ function visit(node) {
205
+ if (foundDef)
206
+ return;
207
+ const def = tryExtractDefinition(node, sourceFile, symbolName, filePath);
208
+ if (def) {
209
+ // If targetLine is specified, only match if on that line
210
+ if (targetLine === undefined || def.line === targetLine) {
211
+ foundDef = def;
212
+ return;
213
+ }
214
+ }
215
+ ts.forEachChild(node, visit);
216
+ }
217
+ visit(sourceFile);
218
+ return foundDef;
219
+ }
220
+ catch {
221
+ return undefined;
222
+ }
223
+ }
224
+ /**
225
+ * Try to extract a definition from a node
226
+ */
227
+ function tryExtractDefinition(node, sourceFile, symbolName, filePath) {
228
+ let name;
229
+ let kind;
230
+ const modifiers = ts.canHaveModifiers(node) ? ts.getModifiers(node) : undefined;
231
+ const exported = hasModifierKind(modifiers, ts.SyntaxKind.ExportKeyword);
232
+ if (ts.isFunctionDeclaration(node) && node.name?.text === symbolName) {
233
+ name = node.name.text;
234
+ kind = 'function';
235
+ }
236
+ else if (ts.isClassDeclaration(node) && node.name?.text === symbolName) {
237
+ name = node.name.text;
238
+ kind = 'class';
239
+ }
240
+ else if (ts.isInterfaceDeclaration(node) && node.name.text === symbolName) {
241
+ name = node.name.text;
242
+ kind = 'interface';
243
+ }
244
+ else if (ts.isTypeAliasDeclaration(node) && node.name.text === symbolName) {
245
+ name = node.name.text;
246
+ kind = 'type';
247
+ }
248
+ else if (ts.isEnumDeclaration(node) && node.name.text === symbolName) {
249
+ name = node.name.text;
250
+ kind = 'enum';
251
+ }
252
+ else if (ts.isVariableDeclaration(node) &&
253
+ ts.isIdentifier(node.name) &&
254
+ node.name.text === symbolName) {
255
+ name = node.name.text;
256
+ kind = 'variable';
257
+ }
258
+ if (name && kind) {
259
+ const { line, character } = sourceFile.getLineAndCharacterOfPosition(node.getStart(sourceFile));
260
+ return {
261
+ name,
262
+ path: filePath,
263
+ line: line + 1,
264
+ column: character + 1,
265
+ kind,
266
+ exported,
267
+ confidence: 1.0,
268
+ };
269
+ }
270
+ return undefined;
271
+ }
272
+ /**
273
+ * Search a single file for references to a symbol
274
+ */
275
+ async function searchFileForReferences(filePath, symbolName, definition, includeDefinition) {
276
+ const references = [];
277
+ try {
278
+ const sourceCode = await fs.readFile(filePath, 'utf-8');
279
+ const lines = sourceCode.split('\n');
280
+ const scriptKind = getScriptKind(filePath);
281
+ const sourceFile = ts.createSourceFile(filePath, sourceCode, ts.ScriptTarget.Latest, true, scriptKind);
282
+ // Track current containing function for context
283
+ let containingFunction;
284
+ function visit(node) {
285
+ // Track containing function
286
+ if (ts.isFunctionDeclaration(node) && node.name) {
287
+ containingFunction = node.name.text;
288
+ }
289
+ else if (ts.isMethodDeclaration(node) && ts.isIdentifier(node.name)) {
290
+ containingFunction = node.name.text;
291
+ }
292
+ else if (ts.isArrowFunction(node)) {
293
+ const parent = node.parent;
294
+ if (ts.isVariableDeclaration(parent) && ts.isIdentifier(parent.name)) {
295
+ containingFunction = parent.name.text;
296
+ }
297
+ }
298
+ // Check for references
299
+ const ref = tryExtractReference(node, sourceFile, symbolName, filePath, lines, containingFunction);
300
+ if (ref) {
301
+ // Skip if this is the definition and we don't want to include it
302
+ if (!includeDefinition &&
303
+ definition &&
304
+ ref.path === definition.path &&
305
+ ref.line === definition.line) {
306
+ // Skip definition
307
+ }
308
+ else {
309
+ references.push(ref);
310
+ }
311
+ }
312
+ ts.forEachChild(node, visit);
313
+ // Reset containing function when leaving
314
+ if (ts.isFunctionDeclaration(node) ||
315
+ ts.isMethodDeclaration(node) ||
316
+ ts.isArrowFunction(node)) {
317
+ containingFunction = undefined;
318
+ }
319
+ }
320
+ visit(sourceFile);
321
+ }
322
+ catch {
323
+ // Silently skip files that can't be parsed
324
+ }
325
+ return references;
326
+ }
327
+ /**
328
+ * Try to extract a reference from a node
329
+ */
330
+ function tryExtractReference(node, sourceFile, symbolName, filePath, lines, containingFunction) {
331
+ // Check if this is an identifier matching our symbol
332
+ if (!ts.isIdentifier(node) || node.text !== symbolName) {
333
+ return undefined;
334
+ }
335
+ // Determine reference type based on context
336
+ const refType = determineReferenceType(node);
337
+ if (!refType) {
338
+ return undefined;
339
+ }
340
+ const { line, character } = sourceFile.getLineAndCharacterOfPosition(node.getStart(sourceFile));
341
+ const lineIndex = line;
342
+ const context = lineIndex < lines.length ? lines[lineIndex].trim() : '';
343
+ return {
344
+ path: filePath,
345
+ line: line + 1,
346
+ column: character + 1,
347
+ context,
348
+ type: refType,
349
+ containingFunction,
350
+ };
351
+ }
352
+ /**
353
+ * Determine the type of reference based on AST context
354
+ */
355
+ function determineReferenceType(node) {
356
+ const parent = node.parent;
357
+ // Import statement
358
+ if (ts.isImportSpecifier(parent) || ts.isImportClause(parent) || ts.isNamespaceImport(parent)) {
359
+ return 'import';
360
+ }
361
+ // Export statement
362
+ if (ts.isExportSpecifier(parent) || ts.isExportAssignment(parent)) {
363
+ return 'export';
364
+ }
365
+ // Type reference (in type annotations)
366
+ if (ts.isTypeReferenceNode(parent)) {
367
+ return 'type';
368
+ }
369
+ // Extends clause
370
+ if (ts.isExpressionWithTypeArguments(parent)) {
371
+ const grandparent = parent.parent;
372
+ if (ts.isHeritageClause(grandparent)) {
373
+ if (grandparent.token === ts.SyntaxKind.ExtendsKeyword) {
374
+ return 'extend';
375
+ }
376
+ // Use else for implements to avoid the "always true" comparison error
377
+ return 'implement';
378
+ }
379
+ }
380
+ // Call expression
381
+ if (ts.isCallExpression(parent) && parent.expression === node) {
382
+ return 'call';
383
+ }
384
+ // New expression
385
+ if (ts.isNewExpression(parent) && parent.expression === node) {
386
+ return 'call';
387
+ }
388
+ // Property access (reading)
389
+ if (ts.isPropertyAccessExpression(parent) && parent.name === node) {
390
+ // Check if this is a write (assignment target)
391
+ const grandparent = parent.parent;
392
+ if (ts.isBinaryExpression(grandparent) &&
393
+ grandparent.operatorToken.kind === ts.SyntaxKind.EqualsToken &&
394
+ grandparent.left === parent) {
395
+ return 'write';
396
+ }
397
+ return 'read';
398
+ }
399
+ // Variable declaration (definition, but also a "write")
400
+ if (ts.isVariableDeclaration(parent) && parent.name === node) {
401
+ return 'write';
402
+ }
403
+ // Assignment expression
404
+ if (ts.isBinaryExpression(parent) &&
405
+ parent.operatorToken.kind === ts.SyntaxKind.EqualsToken &&
406
+ parent.left === node) {
407
+ return 'write';
408
+ }
409
+ // Parameter declaration
410
+ if (ts.isParameter(parent)) {
411
+ return 'write';
412
+ }
413
+ // Function/class/interface declaration (this is a definition)
414
+ if (ts.isFunctionDeclaration(parent) ||
415
+ ts.isClassDeclaration(parent) ||
416
+ ts.isInterfaceDeclaration(parent) ||
417
+ ts.isTypeAliasDeclaration(parent) ||
418
+ ts.isEnumDeclaration(parent) ||
419
+ ts.isMethodDeclaration(parent) ||
420
+ ts.isPropertyDeclaration(parent)) {
421
+ // This is a definition, we'll include it based on includeDefinition flag
422
+ return 'write';
423
+ }
424
+ // Default to read for other usages
425
+ return 'read';
426
+ }
427
+ /**
428
+ * Group references by file
429
+ */
430
+ function groupReferencesByFile(references) {
431
+ const byFile = new Map();
432
+ for (const ref of references) {
433
+ const existing = byFile.get(ref.path);
434
+ if (existing) {
435
+ existing.push(ref);
436
+ }
437
+ else {
438
+ byFile.set(ref.path, [ref]);
439
+ }
440
+ }
441
+ return Array.from(byFile.entries()).map(([path, refs]) => ({
442
+ path,
443
+ references: refs,
444
+ }));
445
+ }
446
+ /**
447
+ * Get TypeScript script kind from file extension
448
+ */
449
+ function getScriptKind(filePath) {
450
+ const ext = filePath.toLowerCase().split('.').pop();
451
+ switch (ext) {
452
+ case 'ts':
453
+ return ts.ScriptKind.TS;
454
+ case 'tsx':
455
+ return ts.ScriptKind.TSX;
456
+ case 'js':
457
+ return ts.ScriptKind.JS;
458
+ case 'jsx':
459
+ return ts.ScriptKind.JSX;
460
+ case 'mts':
461
+ case 'cts':
462
+ return ts.ScriptKind.TS;
463
+ case 'mjs':
464
+ case 'cjs':
465
+ return ts.ScriptKind.JS;
466
+ default:
467
+ return ts.ScriptKind.TS;
468
+ }
469
+ }
470
+ /**
471
+ * Factory function to create a customized findReferences tool
472
+ */
473
+ export function createFindReferencesTool(options) {
474
+ const { defaultLimit = 50, defaultGroupByFile = true, defaultIncludeDefinition = false, } = options ?? {};
475
+ return defineTool({
476
+ name: 'find_references',
477
+ description: TOOL_DESCRIPTION,
478
+ inputSchema: TOOL_INPUT_SCHEMA,
479
+ execute: async (input) => {
480
+ return executeFindReferences({
481
+ ...input,
482
+ limit: input.limit ?? defaultLimit,
483
+ groupByFile: input.groupByFile ?? defaultGroupByFile,
484
+ includeDefinition: input.includeDefinition ?? defaultIncludeDefinition,
485
+ });
486
+ },
487
+ });
488
+ }
@@ -0,0 +1,21 @@
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 type { Tool } from '@compilr-dev/agents';
8
+ import type { FindSymbolInput } from './types.js';
9
+ /**
10
+ * findSymbol tool - Find symbol definitions
11
+ */
12
+ export declare const findSymbolTool: Tool<FindSymbolInput>;
13
+ /**
14
+ * Factory function to create a customized findSymbol tool
15
+ */
16
+ export declare function createFindSymbolTool(options?: {
17
+ /** Default limit */
18
+ defaultLimit?: number;
19
+ /** Default includeNodeModules */
20
+ defaultIncludeNodeModules?: boolean;
21
+ }): Tool<FindSymbolInput>;