@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,436 @@
1
+ /**
2
+ * findImplementations Tool
3
+ *
4
+ * Find classes that implement an interface or extend an abstract class.
5
+ * Useful for understanding interface usage and finding concrete implementations.
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 = `Find classes that implement an interface or extend an abstract class.
14
+ Returns information about each implementation including which methods are implemented.
15
+ Useful for understanding how interfaces are used across the codebase.`;
16
+ // Tool input schema
17
+ const TOOL_INPUT_SCHEMA = {
18
+ type: 'object',
19
+ properties: {
20
+ name: {
21
+ type: 'string',
22
+ description: 'Interface or abstract class name to find implementations of',
23
+ },
24
+ scope: {
25
+ type: 'string',
26
+ description: 'Directory or file to search in (defaults to current directory)',
27
+ },
28
+ includeAbstract: {
29
+ type: 'boolean',
30
+ description: 'Include abstract classes that partially implement (default: false)',
31
+ default: false,
32
+ },
33
+ maxFiles: {
34
+ type: 'number',
35
+ description: 'Maximum files to search (default: 100)',
36
+ default: 100,
37
+ },
38
+ },
39
+ required: ['name'],
40
+ };
41
+ // Default file patterns
42
+ const DEFAULT_INCLUDE = ['**/*.ts', '**/*.tsx', '**/*.js', '**/*.jsx'];
43
+ const DEFAULT_EXCLUDE = ['**/node_modules/**', '**/dist/**', '**/build/**', '**/.git/**'];
44
+ /**
45
+ * findImplementations tool - Find implementations of interfaces/abstract classes
46
+ */
47
+ export const findImplementationsTool = defineTool({
48
+ name: 'find_implementations',
49
+ description: TOOL_DESCRIPTION,
50
+ inputSchema: TOOL_INPUT_SCHEMA,
51
+ execute: executeFindImplementations,
52
+ });
53
+ /**
54
+ * Execute the findImplementations tool
55
+ */
56
+ async function executeFindImplementations(input) {
57
+ const { name, scope = '.', includeAbstract = false, maxFiles = 100 } = input;
58
+ const startTime = Date.now();
59
+ // Validate input
60
+ if (!name || name.trim().length === 0) {
61
+ return createErrorResult('Interface or class name is required');
62
+ }
63
+ try {
64
+ // Resolve scope path
65
+ const scopePath = path.resolve(scope);
66
+ // Check if scope exists
67
+ try {
68
+ await fs.access(scopePath);
69
+ }
70
+ catch {
71
+ return createErrorResult(`Scope path not found: ${scopePath}`);
72
+ }
73
+ const stats = await fs.stat(scopePath);
74
+ // Collect files to search
75
+ const files = stats.isDirectory()
76
+ ? await collectFiles(scopePath, DEFAULT_INCLUDE, DEFAULT_EXCLUDE, 10, maxFiles)
77
+ : [scopePath];
78
+ // Find the target interface/abstract class definition
79
+ let targetLocation;
80
+ let targetMethods = [];
81
+ let isInterface = false;
82
+ let isAbstractClass = false;
83
+ // First pass: find the target definition
84
+ for (const file of files) {
85
+ const result = await findTargetDefinition(file, name);
86
+ if (result) {
87
+ targetLocation = { path: file, line: result.line };
88
+ targetMethods = result.methods;
89
+ isInterface = result.isInterface;
90
+ isAbstractClass = result.isAbstractClass;
91
+ break;
92
+ }
93
+ }
94
+ // Second pass: find implementations
95
+ const implementations = [];
96
+ for (const file of files) {
97
+ const fileImplementations = await findImplementationsInFile(file, name, targetMethods, isInterface, isAbstractClass, includeAbstract);
98
+ implementations.push(...fileImplementations);
99
+ }
100
+ const timeMs = Date.now() - startTime;
101
+ const result = {
102
+ target: name,
103
+ targetLocation,
104
+ implementations,
105
+ stats: {
106
+ filesSearched: files.length,
107
+ implementationsFound: implementations.length,
108
+ timeMs,
109
+ },
110
+ };
111
+ return createSuccessResult(result);
112
+ }
113
+ catch (error) {
114
+ return createErrorResult(`Failed to find implementations: ${error instanceof Error ? error.message : String(error)}`);
115
+ }
116
+ }
117
+ /**
118
+ * Find the target interface or abstract class definition
119
+ */
120
+ async function findTargetDefinition(filePath, targetName) {
121
+ try {
122
+ const content = await fs.readFile(filePath, 'utf-8');
123
+ const detection = detectLanguage(filePath);
124
+ if (!detection.language) {
125
+ return null;
126
+ }
127
+ if (!isLanguageSupported(detection.language)) {
128
+ return null;
129
+ }
130
+ const sourceFile = ts.createSourceFile(filePath, content, ts.ScriptTarget.Latest, true, filePath.endsWith('.tsx') ? ts.ScriptKind.TSX : ts.ScriptKind.TS);
131
+ let result = null;
132
+ const visit = (node) => {
133
+ // Check for interface declaration
134
+ if (ts.isInterfaceDeclaration(node) && node.name.text === targetName) {
135
+ const methods = extractInterfaceMethods(node);
136
+ const { line } = sourceFile.getLineAndCharacterOfPosition(node.getStart());
137
+ result = {
138
+ line: line + 1,
139
+ methods,
140
+ isInterface: true,
141
+ isAbstractClass: false,
142
+ };
143
+ return;
144
+ }
145
+ // Check for abstract class declaration
146
+ if (ts.isClassDeclaration(node) && node.name?.text === targetName) {
147
+ const isAbstract = node.modifiers?.some((m) => m.kind === ts.SyntaxKind.AbstractKeyword) ?? false;
148
+ if (isAbstract) {
149
+ const methods = extractClassMethods(node, true);
150
+ const { line } = sourceFile.getLineAndCharacterOfPosition(node.getStart());
151
+ result = {
152
+ line: line + 1,
153
+ methods,
154
+ isInterface: false,
155
+ isAbstractClass: true,
156
+ };
157
+ }
158
+ return;
159
+ }
160
+ ts.forEachChild(node, visit);
161
+ };
162
+ visit(sourceFile);
163
+ return result;
164
+ }
165
+ catch {
166
+ return null;
167
+ }
168
+ }
169
+ /**
170
+ * Extract method names from an interface
171
+ */
172
+ function extractInterfaceMethods(node) {
173
+ const methods = [];
174
+ for (const member of node.members) {
175
+ if (ts.isMethodSignature(member)) {
176
+ const name = member.name.getText();
177
+ methods.push(name);
178
+ }
179
+ if (ts.isPropertySignature(member)) {
180
+ // Check if it's a function type property
181
+ if (member.type && ts.isFunctionTypeNode(member.type)) {
182
+ const name = member.name.getText();
183
+ methods.push(name);
184
+ }
185
+ }
186
+ }
187
+ return methods;
188
+ }
189
+ /**
190
+ * Extract method names from a class
191
+ */
192
+ function extractClassMethods(node, abstractOnly) {
193
+ const methods = [];
194
+ for (const member of node.members) {
195
+ if (ts.isMethodDeclaration(member)) {
196
+ const isAbstract = member.modifiers?.some((m) => m.kind === ts.SyntaxKind.AbstractKeyword) ?? false;
197
+ if (!abstractOnly || isAbstract) {
198
+ const name = member.name.getText();
199
+ methods.push(name);
200
+ }
201
+ }
202
+ }
203
+ return methods;
204
+ }
205
+ /**
206
+ * Find implementations in a single file
207
+ */
208
+ async function findImplementationsInFile(filePath, targetName, targetMethods, isInterface, isAbstractClass, includeAbstract) {
209
+ try {
210
+ const content = await fs.readFile(filePath, 'utf-8');
211
+ const detection = detectLanguage(filePath);
212
+ if (!detection.language) {
213
+ return [];
214
+ }
215
+ if (!isLanguageSupported(detection.language)) {
216
+ return [];
217
+ }
218
+ const sourceFile = ts.createSourceFile(filePath, content, ts.ScriptTarget.Latest, true, filePath.endsWith('.tsx') ? ts.ScriptKind.TSX : ts.ScriptKind.TS);
219
+ const implementations = [];
220
+ const visit = (node) => {
221
+ // Check for class declaration
222
+ if (ts.isClassDeclaration(node) && node.name) {
223
+ const implementation = checkClassImplementation(node, sourceFile, filePath, targetName, targetMethods, isInterface, isAbstractClass, includeAbstract);
224
+ if (implementation) {
225
+ implementations.push(implementation);
226
+ }
227
+ }
228
+ // Check for type alias that implements the interface (for structural typing)
229
+ if (ts.isTypeAliasDeclaration(node) && isInterface) {
230
+ const typeImpl = checkTypeImplementation(node, sourceFile, filePath, targetName);
231
+ if (typeImpl) {
232
+ implementations.push(typeImpl);
233
+ }
234
+ }
235
+ // Check for object literal with type annotation
236
+ if (ts.isVariableStatement(node) && isInterface) {
237
+ for (const decl of node.declarationList.declarations) {
238
+ if (decl.type && ts.isTypeLiteralNode(decl.type)) {
239
+ continue;
240
+ }
241
+ if (decl.type && ts.isTypeReferenceNode(decl.type)) {
242
+ const typeName = decl.type.typeName.getText();
243
+ if (typeName === targetName && ts.isIdentifier(decl.name)) {
244
+ const { line, character } = sourceFile.getLineAndCharacterOfPosition(node.getStart());
245
+ const isExported = node.modifiers?.some((m) => m.kind === ts.SyntaxKind.ExportKeyword) ?? false;
246
+ implementations.push({
247
+ name: decl.name.text,
248
+ path: filePath,
249
+ line: line + 1,
250
+ column: character + 1,
251
+ kind: 'object',
252
+ exported: isExported,
253
+ implementedMethods: targetMethods,
254
+ missingMethods: [],
255
+ });
256
+ }
257
+ }
258
+ }
259
+ }
260
+ ts.forEachChild(node, visit);
261
+ };
262
+ visit(sourceFile);
263
+ return implementations;
264
+ }
265
+ catch {
266
+ return [];
267
+ }
268
+ }
269
+ /**
270
+ * Check if a class implements the target interface/abstract class
271
+ */
272
+ function checkClassImplementation(node, sourceFile, filePath, targetName, targetMethods, isInterface, isAbstractClass, includeAbstract) {
273
+ if (!node.name)
274
+ return null;
275
+ const className = node.name.text;
276
+ // Skip the target class itself
277
+ if (className === targetName)
278
+ return null;
279
+ // Check if it's abstract
280
+ const classIsAbstract = node.modifiers?.some((m) => m.kind === ts.SyntaxKind.AbstractKeyword) ?? false;
281
+ // Skip abstract classes if not including them
282
+ if (classIsAbstract && !includeAbstract)
283
+ return null;
284
+ // Check heritage clauses (implements and extends)
285
+ let implementsTarget = false;
286
+ let extendsTarget = false;
287
+ if (node.heritageClauses) {
288
+ for (const clause of node.heritageClauses) {
289
+ if (clause.token === ts.SyntaxKind.ImplementsKeyword) {
290
+ for (const type of clause.types) {
291
+ const typeName = type.expression.getText();
292
+ if (typeName === targetName) {
293
+ implementsTarget = true;
294
+ break;
295
+ }
296
+ }
297
+ }
298
+ if (clause.token === ts.SyntaxKind.ExtendsKeyword) {
299
+ for (const type of clause.types) {
300
+ const typeName = type.expression.getText();
301
+ if (typeName === targetName) {
302
+ extendsTarget = true;
303
+ break;
304
+ }
305
+ }
306
+ }
307
+ }
308
+ }
309
+ // Must either implement or extend the target
310
+ if (!implementsTarget && !extendsTarget)
311
+ return null;
312
+ // Get implemented methods
313
+ const implementedMethods = [];
314
+ for (const member of node.members) {
315
+ if (ts.isMethodDeclaration(member)) {
316
+ const methodName = member.name.getText();
317
+ if (targetMethods.includes(methodName)) {
318
+ implementedMethods.push(methodName);
319
+ }
320
+ }
321
+ // Also check getters/setters
322
+ if (ts.isGetAccessor(member) || ts.isSetAccessor(member)) {
323
+ const methodName = member.name.getText();
324
+ if (targetMethods.includes(methodName)) {
325
+ implementedMethods.push(methodName);
326
+ }
327
+ }
328
+ }
329
+ // Calculate missing methods
330
+ const missingMethods = targetMethods.filter((m) => !implementedMethods.includes(m));
331
+ const { line, character } = sourceFile.getLineAndCharacterOfPosition(node.getStart());
332
+ const isExported = node.modifiers?.some((m) => m.kind === ts.SyntaxKind.ExportKeyword) ?? false;
333
+ return {
334
+ name: className,
335
+ path: filePath,
336
+ line: line + 1,
337
+ column: character + 1,
338
+ kind: 'class',
339
+ isAbstract: classIsAbstract,
340
+ exported: isExported,
341
+ implementedMethods,
342
+ missingMethods,
343
+ };
344
+ }
345
+ /**
346
+ * Check if a type alias structurally implements an interface
347
+ */
348
+ function checkTypeImplementation(node, sourceFile, filePath, targetName) {
349
+ // Check if type extends/intersects with the target
350
+ const typeText = node.type.getText();
351
+ // Simple check: does it reference the target type?
352
+ if (!typeText.includes(targetName))
353
+ return null;
354
+ // Check for intersection or extension patterns
355
+ const isIntersection = ts.isIntersectionTypeNode(node.type);
356
+ const extendsTarget = typeText === targetName ||
357
+ (isIntersection && node.type.types.some((t) => t.getText() === targetName));
358
+ if (!extendsTarget)
359
+ return null;
360
+ const { line, character } = sourceFile.getLineAndCharacterOfPosition(node.getStart());
361
+ const isExported = node.modifiers?.some((m) => m.kind === ts.SyntaxKind.ExportKeyword) ?? false;
362
+ return {
363
+ name: node.name.text,
364
+ path: filePath,
365
+ line: line + 1,
366
+ column: character + 1,
367
+ kind: 'type',
368
+ exported: isExported,
369
+ };
370
+ }
371
+ /**
372
+ * Collect files matching patterns
373
+ */
374
+ async function collectFiles(dirPath, include, exclude, maxDepth, maxFiles, currentDepth = 0) {
375
+ if (currentDepth > maxDepth)
376
+ return [];
377
+ const files = [];
378
+ try {
379
+ const entries = await fs.readdir(dirPath, { withFileTypes: true });
380
+ for (const entry of entries) {
381
+ if (files.length >= maxFiles)
382
+ break;
383
+ const fullPath = path.join(dirPath, entry.name);
384
+ const relativePath = fullPath;
385
+ // Check exclusions
386
+ const isExcluded = exclude.some((pattern) => {
387
+ if (pattern.includes('**')) {
388
+ const simplePattern = pattern.replace(/\*\*/g, '');
389
+ return relativePath.includes(simplePattern.replace(/\*/g, ''));
390
+ }
391
+ return entry.name === pattern || relativePath.includes(pattern);
392
+ });
393
+ if (isExcluded)
394
+ continue;
395
+ if (entry.isDirectory()) {
396
+ const subFiles = await collectFiles(fullPath, include, exclude, maxDepth, maxFiles - files.length, currentDepth + 1);
397
+ files.push(...subFiles);
398
+ }
399
+ else if (entry.isFile()) {
400
+ // Check if file matches include patterns
401
+ const isIncluded = include.some((pattern) => {
402
+ if (pattern.includes('*')) {
403
+ const ext = pattern.replace('**/', '').replace('*', '');
404
+ return entry.name.endsWith(ext);
405
+ }
406
+ return entry.name === pattern;
407
+ });
408
+ if (isIncluded) {
409
+ files.push(fullPath);
410
+ }
411
+ }
412
+ }
413
+ }
414
+ catch {
415
+ // Ignore permission errors
416
+ }
417
+ return files;
418
+ }
419
+ /**
420
+ * Create customizable findImplementations tool
421
+ */
422
+ export function createFindImplementationsTool(options) {
423
+ return defineTool({
424
+ name: options?.name ?? 'find_implementations',
425
+ description: options?.description ?? TOOL_DESCRIPTION,
426
+ inputSchema: TOOL_INPUT_SCHEMA,
427
+ execute: async (input) => {
428
+ const modifiedInput = {
429
+ ...input,
430
+ scope: input.scope ?? options?.defaultScope,
431
+ maxFiles: input.maxFiles ?? options?.defaultMaxFiles,
432
+ };
433
+ return executeFindImplementations(modifiedInput);
434
+ },
435
+ });
436
+ }
@@ -0,0 +1,21 @@
1
+ /**
2
+ * findPatterns Tool
3
+ *
4
+ * Find code patterns, anti-patterns, and code smells in the codebase.
5
+ * Uses regex and AST-based pattern matching.
6
+ */
7
+ import type { Tool } from '@compilr-dev/agents';
8
+ import type { FindPatternsInput, CodePattern } from './types.js';
9
+ /**
10
+ * findPatterns tool
11
+ */
12
+ export declare const findPatternsTool: Tool<FindPatternsInput>;
13
+ /**
14
+ * Create customizable findPatterns tool
15
+ */
16
+ export declare function createFindPatternsTool(options?: {
17
+ name?: string;
18
+ description?: string;
19
+ defaultMaxFiles?: number;
20
+ additionalPatterns?: CodePattern[];
21
+ }): Tool<FindPatternsInput>;