@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,457 @@
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 * 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 code patterns, anti-patterns, and code smells.
14
+ Searches for common issues like security vulnerabilities, performance problems, and maintainability concerns.
15
+ Supports built-in patterns or custom pattern definitions.`;
16
+ // Tool input schema
17
+ const TOOL_INPUT_SCHEMA = {
18
+ type: 'object',
19
+ properties: {
20
+ path: {
21
+ type: 'string',
22
+ description: 'Directory to analyze',
23
+ },
24
+ patterns: {
25
+ type: 'array',
26
+ description: 'Custom patterns to search for (uses built-in if not specified)',
27
+ items: {
28
+ type: 'object',
29
+ properties: {
30
+ name: { type: 'string' },
31
+ description: { type: 'string' },
32
+ type: { type: 'string' },
33
+ matcher: { type: 'string' },
34
+ severity: { type: 'string' },
35
+ },
36
+ },
37
+ },
38
+ categories: {
39
+ type: 'array',
40
+ description: 'Pattern categories to include (default: all)',
41
+ items: {
42
+ type: 'string',
43
+ enum: ['security', 'performance', 'maintainability', 'all'],
44
+ },
45
+ },
46
+ maxFiles: {
47
+ type: 'number',
48
+ description: 'Maximum files to analyze (default: 100)',
49
+ default: 100,
50
+ },
51
+ },
52
+ required: ['path'],
53
+ };
54
+ // Default exclusions
55
+ const DEFAULT_EXCLUDE = ['node_modules', 'dist', 'build', '.git', 'coverage'];
56
+ // Built-in patterns
57
+ const BUILTIN_PATTERNS = [
58
+ // Security patterns
59
+ {
60
+ name: 'eval-usage',
61
+ description: 'Use of eval() can lead to code injection vulnerabilities',
62
+ type: 'anti-pattern',
63
+ matcher: '\\beval\\s*\\(',
64
+ severity: 'error',
65
+ category: 'security',
66
+ },
67
+ {
68
+ name: 'innerHTML-usage',
69
+ description: 'innerHTML can lead to XSS vulnerabilities',
70
+ type: 'anti-pattern',
71
+ matcher: '\\.innerHTML\\s*=',
72
+ severity: 'warning',
73
+ category: 'security',
74
+ },
75
+ {
76
+ name: 'hardcoded-secret',
77
+ description: 'Potential hardcoded secret or API key',
78
+ type: 'anti-pattern',
79
+ matcher: '(password|secret|api_key|apikey|token)\\s*[=:]\\s*["\'][^"\']+["\']',
80
+ severity: 'error',
81
+ category: 'security',
82
+ },
83
+ {
84
+ name: 'sql-injection-risk',
85
+ description: 'String concatenation in SQL query (potential injection)',
86
+ type: 'anti-pattern',
87
+ matcher: '(query|execute)\\s*\\(\\s*[`"\'].*\\$\\{',
88
+ severity: 'error',
89
+ category: 'security',
90
+ },
91
+ // Performance patterns
92
+ {
93
+ name: 'console-log',
94
+ description: 'console.log left in code (should be removed in production)',
95
+ type: 'smell',
96
+ matcher: '\\bconsole\\.(log|debug|info)\\s*\\(',
97
+ severity: 'info',
98
+ category: 'performance',
99
+ },
100
+ {
101
+ name: 'sync-fs-operations',
102
+ description: 'Synchronous file system operations block the event loop',
103
+ type: 'anti-pattern',
104
+ matcher: '\\b(readFileSync|writeFileSync|existsSync|statSync|readdirSync)\\s*\\(',
105
+ severity: 'warning',
106
+ category: 'performance',
107
+ },
108
+ {
109
+ name: 'nested-loops',
110
+ description: 'Deeply nested loops may indicate O(n²) or worse complexity',
111
+ type: 'smell',
112
+ matcher: 'for\\s*\\([^)]+\\)\\s*\\{[^}]*for\\s*\\([^)]+\\)\\s*\\{[^}]*for\\s*\\(',
113
+ severity: 'warning',
114
+ category: 'performance',
115
+ },
116
+ {
117
+ name: 'await-in-loop',
118
+ description: 'await inside loop may cause sequential execution instead of parallel',
119
+ type: 'smell',
120
+ matcher: '(for|while)\\s*\\([^)]*\\)\\s*\\{[^}]*await\\b',
121
+ severity: 'info',
122
+ category: 'performance',
123
+ },
124
+ // Maintainability patterns
125
+ {
126
+ name: 'magic-number',
127
+ description: 'Magic numbers should be named constants',
128
+ type: 'smell',
129
+ matcher: '(?<!\\.)\\b(?!(0|1|2|10|100|1000)\\b)\\d{2,}\\b(?!\\.)',
130
+ severity: 'info',
131
+ category: 'maintainability',
132
+ },
133
+ {
134
+ name: 'todo-fixme',
135
+ description: 'TODO or FIXME comment indicating incomplete work',
136
+ type: 'smell',
137
+ matcher: '(TODO|FIXME|HACK|XXX)\\s*:?',
138
+ severity: 'info',
139
+ category: 'maintainability',
140
+ },
141
+ {
142
+ name: 'any-type',
143
+ description: 'Use of "any" type defeats TypeScript type checking',
144
+ type: 'smell',
145
+ matcher: ':\\s*any\\b',
146
+ severity: 'warning',
147
+ category: 'maintainability',
148
+ },
149
+ {
150
+ name: 'ts-ignore',
151
+ description: '@ts-ignore suppresses TypeScript errors',
152
+ type: 'smell',
153
+ matcher: '@ts-ignore',
154
+ severity: 'warning',
155
+ category: 'maintainability',
156
+ },
157
+ {
158
+ name: 'eslint-disable',
159
+ description: 'eslint-disable suppresses linting rules',
160
+ type: 'smell',
161
+ matcher: 'eslint-disable(?!-next-line)',
162
+ severity: 'info',
163
+ category: 'maintainability',
164
+ },
165
+ {
166
+ name: 'long-function',
167
+ description: 'Function body exceeds recommended length',
168
+ type: 'smell',
169
+ matcher: 'AST:long-function',
170
+ severity: 'warning',
171
+ category: 'maintainability',
172
+ },
173
+ {
174
+ name: 'many-parameters',
175
+ description: 'Function has too many parameters (consider using options object)',
176
+ type: 'smell',
177
+ matcher: 'AST:many-parameters',
178
+ severity: 'info',
179
+ category: 'maintainability',
180
+ },
181
+ ];
182
+ /**
183
+ * findPatterns tool
184
+ */
185
+ export const findPatternsTool = defineTool({
186
+ name: 'find_patterns',
187
+ description: TOOL_DESCRIPTION,
188
+ inputSchema: TOOL_INPUT_SCHEMA,
189
+ execute: executeFindPatterns,
190
+ });
191
+ /**
192
+ * Execute the findPatterns tool
193
+ */
194
+ async function executeFindPatterns(input) {
195
+ const { path: inputPath, patterns, categories = ['all'], maxFiles = 100 } = input;
196
+ try {
197
+ const resolvedPath = path.resolve(inputPath);
198
+ // Check if path exists
199
+ try {
200
+ await fs.access(resolvedPath);
201
+ }
202
+ catch {
203
+ return createErrorResult(`Path not found: ${resolvedPath}`);
204
+ }
205
+ const stats = await fs.stat(resolvedPath);
206
+ if (!stats.isDirectory()) {
207
+ return createErrorResult('findPatterns requires a directory path');
208
+ }
209
+ // Determine which patterns to use
210
+ const patternsToUse = patterns ?? getBuiltinPatterns(categories);
211
+ // Collect files
212
+ const files = [];
213
+ await collectFiles(resolvedPath, files, maxFiles);
214
+ // Find matches
215
+ const allMatches = [];
216
+ const byPattern = {};
217
+ let errorCount = 0;
218
+ let warningCount = 0;
219
+ let infoCount = 0;
220
+ for (const file of files) {
221
+ const fileMatches = await analyzeFileForPatterns(file, patternsToUse);
222
+ allMatches.push(...fileMatches);
223
+ }
224
+ // Count by pattern and severity
225
+ for (const match of allMatches) {
226
+ byPattern[match.pattern] = (byPattern[match.pattern] ?? 0) + 1;
227
+ if (match.severity === 'error')
228
+ errorCount++;
229
+ else if (match.severity === 'warning')
230
+ warningCount++;
231
+ else
232
+ infoCount++;
233
+ }
234
+ // Sort by severity (errors first)
235
+ allMatches.sort((a, b) => {
236
+ const severityOrder = { error: 0, warning: 1, info: 2 };
237
+ return severityOrder[a.severity] - severityOrder[b.severity];
238
+ });
239
+ // Limit results
240
+ const limitedMatches = allMatches.slice(0, 100);
241
+ const result = {
242
+ path: resolvedPath,
243
+ matches: limitedMatches,
244
+ stats: {
245
+ filesAnalyzed: files.length,
246
+ totalMatches: allMatches.length,
247
+ byPattern,
248
+ bySeverity: {
249
+ error: errorCount,
250
+ warning: warningCount,
251
+ info: infoCount,
252
+ },
253
+ },
254
+ };
255
+ return createSuccessResult(result);
256
+ }
257
+ catch (error) {
258
+ return createErrorResult(`Failed to find patterns: ${error instanceof Error ? error.message : String(error)}`);
259
+ }
260
+ }
261
+ /**
262
+ * Get built-in patterns filtered by category
263
+ */
264
+ function getBuiltinPatterns(categories) {
265
+ if (categories.includes('all')) {
266
+ return BUILTIN_PATTERNS;
267
+ }
268
+ return BUILTIN_PATTERNS.filter((p) => categories.includes(p.category));
269
+ }
270
+ /**
271
+ * Collect files to analyze
272
+ */
273
+ async function collectFiles(dirPath, files, maxFiles, currentDepth = 0) {
274
+ if (currentDepth > 10 || files.length >= maxFiles)
275
+ return;
276
+ try {
277
+ const entries = await fs.readdir(dirPath, { withFileTypes: true });
278
+ for (const entry of entries) {
279
+ if (files.length >= maxFiles)
280
+ break;
281
+ const fullPath = path.join(dirPath, entry.name);
282
+ if (entry.isDirectory()) {
283
+ if (DEFAULT_EXCLUDE.includes(entry.name))
284
+ continue;
285
+ await collectFiles(fullPath, files, maxFiles, currentDepth + 1);
286
+ }
287
+ else if (entry.isFile()) {
288
+ // Only include TypeScript/JavaScript files
289
+ if (/\.(ts|tsx|js|jsx)$/.test(entry.name) && !entry.name.endsWith('.d.ts')) {
290
+ files.push(fullPath);
291
+ }
292
+ }
293
+ }
294
+ }
295
+ catch {
296
+ // Ignore permission errors
297
+ }
298
+ }
299
+ /**
300
+ * Analyze a file for patterns
301
+ */
302
+ async function analyzeFileForPatterns(filePath, patterns) {
303
+ try {
304
+ const content = await fs.readFile(filePath, 'utf-8');
305
+ const lines = content.split('\n');
306
+ const matches = [];
307
+ // Separate regex patterns from AST patterns
308
+ const regexPatterns = patterns.filter((p) => !p.matcher.startsWith('AST:'));
309
+ const astPatterns = patterns.filter((p) => p.matcher.startsWith('AST:'));
310
+ // Check regex patterns line by line
311
+ for (let i = 0; i < lines.length; i++) {
312
+ const line = lines[i];
313
+ for (const pattern of regexPatterns) {
314
+ try {
315
+ const regex = new RegExp(pattern.matcher, 'gi');
316
+ if (regex.test(line)) {
317
+ matches.push({
318
+ pattern: pattern.name,
319
+ description: pattern.description,
320
+ severity: pattern.severity,
321
+ path: filePath,
322
+ line: i + 1,
323
+ snippet: line.trim().slice(0, 100),
324
+ suggestion: getSuggestion(pattern.name),
325
+ });
326
+ }
327
+ }
328
+ catch {
329
+ // Invalid regex, skip
330
+ }
331
+ }
332
+ }
333
+ // Check AST patterns
334
+ if (astPatterns.length > 0) {
335
+ const astMatches = analyzeFileForAstPatterns(filePath, content, astPatterns);
336
+ matches.push(...astMatches);
337
+ }
338
+ return matches;
339
+ }
340
+ catch {
341
+ return [];
342
+ }
343
+ }
344
+ /**
345
+ * Analyze file for AST-based patterns
346
+ */
347
+ function analyzeFileForAstPatterns(filePath, content, patterns) {
348
+ const detection = detectLanguage(filePath);
349
+ if (!detection.language || !isLanguageSupported(detection.language)) {
350
+ return [];
351
+ }
352
+ const matches = [];
353
+ try {
354
+ const sourceFile = ts.createSourceFile(filePath, content, ts.ScriptTarget.Latest, true, filePath.endsWith('.tsx') ? ts.ScriptKind.TSX : ts.ScriptKind.TS);
355
+ const checkLongFunction = patterns.some((p) => p.matcher === 'AST:long-function');
356
+ const checkManyParameters = patterns.some((p) => p.matcher === 'AST:many-parameters');
357
+ const visit = (node) => {
358
+ // Check long functions (>50 lines)
359
+ if (checkLongFunction) {
360
+ if (ts.isFunctionDeclaration(node) ||
361
+ ts.isMethodDeclaration(node) ||
362
+ ts.isArrowFunction(node) ||
363
+ ts.isFunctionExpression(node)) {
364
+ const { line: startLine } = sourceFile.getLineAndCharacterOfPosition(node.getStart());
365
+ const { line: endLine } = sourceFile.getLineAndCharacterOfPosition(node.getEnd());
366
+ const lines = endLine - startLine;
367
+ if (lines > 50) {
368
+ let name = '<anonymous>';
369
+ if (ts.isFunctionDeclaration(node) && node.name) {
370
+ name = node.name.text;
371
+ }
372
+ else if (ts.isMethodDeclaration(node) && ts.isIdentifier(node.name)) {
373
+ name = node.name.text;
374
+ }
375
+ matches.push({
376
+ pattern: 'long-function',
377
+ description: `Function "${name}" has ${String(lines)} lines (recommended: <50)`,
378
+ severity: 'warning',
379
+ path: filePath,
380
+ line: startLine + 1,
381
+ snippet: `function ${name}(...) { /* ${String(lines)} lines */ }`,
382
+ suggestion: 'Consider breaking this function into smaller functions',
383
+ });
384
+ }
385
+ }
386
+ }
387
+ // Check many parameters (>5)
388
+ if (checkManyParameters) {
389
+ if (ts.isFunctionDeclaration(node) ||
390
+ ts.isMethodDeclaration(node) ||
391
+ ts.isArrowFunction(node) ||
392
+ ts.isFunctionExpression(node)) {
393
+ const paramCount = node.parameters.length;
394
+ if (paramCount > 5) {
395
+ const { line } = sourceFile.getLineAndCharacterOfPosition(node.getStart());
396
+ let name = '<anonymous>';
397
+ if (ts.isFunctionDeclaration(node) && node.name) {
398
+ name = node.name.text;
399
+ }
400
+ else if (ts.isMethodDeclaration(node) && ts.isIdentifier(node.name)) {
401
+ name = node.name.text;
402
+ }
403
+ matches.push({
404
+ pattern: 'many-parameters',
405
+ description: `Function "${name}" has ${String(paramCount)} parameters (recommended: <5)`,
406
+ severity: 'info',
407
+ path: filePath,
408
+ line: line + 1,
409
+ snippet: `function ${name}(${String(paramCount)} params...)`,
410
+ suggestion: 'Consider using an options object instead of many parameters',
411
+ });
412
+ }
413
+ }
414
+ }
415
+ ts.forEachChild(node, visit);
416
+ };
417
+ visit(sourceFile);
418
+ }
419
+ catch {
420
+ // Ignore parse errors
421
+ }
422
+ return matches;
423
+ }
424
+ /**
425
+ * Get suggestion for a pattern
426
+ */
427
+ function getSuggestion(patternName) {
428
+ const suggestions = {
429
+ 'eval-usage': 'Use safer alternatives like JSON.parse() or Function constructor',
430
+ 'innerHTML-usage': 'Use textContent or sanitize HTML before insertion',
431
+ 'hardcoded-secret': 'Use environment variables or a secrets manager',
432
+ 'sql-injection-risk': 'Use parameterized queries or an ORM',
433
+ 'console-log': 'Remove or replace with proper logging',
434
+ 'sync-fs-operations': 'Use async versions like readFile, writeFile',
435
+ 'any-type': 'Use a specific type or unknown if type is truly unknown',
436
+ 'ts-ignore': 'Fix the underlying type issue instead of suppressing',
437
+ };
438
+ return suggestions[patternName];
439
+ }
440
+ /**
441
+ * Create customizable findPatterns tool
442
+ */
443
+ export function createFindPatternsTool(options) {
444
+ return defineTool({
445
+ name: options?.name ?? 'find_patterns',
446
+ description: options?.description ?? TOOL_DESCRIPTION,
447
+ inputSchema: TOOL_INPUT_SCHEMA,
448
+ execute: async (input) => {
449
+ const modifiedInput = {
450
+ ...input,
451
+ maxFiles: input.maxFiles ?? options?.defaultMaxFiles,
452
+ patterns: input.patterns ?? [...BUILTIN_PATTERNS, ...(options?.additionalPatterns ?? [])],
453
+ };
454
+ return executeFindPatterns(modifiedInput);
455
+ },
456
+ });
457
+ }
@@ -0,0 +1,23 @@
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 type { Tool } from '@compilr-dev/agents';
8
+ import type { FindReferencesInput } from './types.js';
9
+ /**
10
+ * findReferences tool - Find all usages of a symbol
11
+ */
12
+ export declare const findReferencesTool: Tool<FindReferencesInput>;
13
+ /**
14
+ * Factory function to create a customized findReferences tool
15
+ */
16
+ export declare function createFindReferencesTool(options?: {
17
+ /** Default limit */
18
+ defaultLimit?: number;
19
+ /** Default groupByFile */
20
+ defaultGroupByFile?: boolean;
21
+ /** Default includeDefinition */
22
+ defaultIncludeDefinition?: boolean;
23
+ }): Tool<FindReferencesInput>;