@compilr-dev/agents-coding-ts 0.1.3 → 0.1.5

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 (51) hide show
  1. package/LICENSE +95 -8
  2. package/README.md +4 -1
  3. package/dist/index.d.ts +8 -5
  4. package/dist/index.js +14 -4
  5. package/dist/parser/index.d.ts +2 -2
  6. package/dist/parser/index.js +1 -1
  7. package/dist/parser/typescript-parser.d.ts +2 -2
  8. package/dist/parser/typescript-parser.js +77 -54
  9. package/dist/skills/code-health.js +5 -5
  10. package/dist/skills/code-structure.js +5 -5
  11. package/dist/skills/dependency-audit.js +5 -5
  12. package/dist/skills/index.d.ts +7 -7
  13. package/dist/skills/index.js +11 -11
  14. package/dist/skills/refactor-impact.js +5 -5
  15. package/dist/skills/type-analysis.js +5 -5
  16. package/dist/tools/find-dead-code.d.ts +2 -2
  17. package/dist/tools/find-dead-code.js +82 -58
  18. package/dist/tools/find-duplicates.d.ts +2 -2
  19. package/dist/tools/find-duplicates.js +41 -38
  20. package/dist/tools/find-implementations.d.ts +2 -2
  21. package/dist/tools/find-implementations.js +44 -36
  22. package/dist/tools/find-patterns.d.ts +2 -2
  23. package/dist/tools/find-patterns.js +154 -148
  24. package/dist/tools/find-references.d.ts +2 -2
  25. package/dist/tools/find-references.js +76 -72
  26. package/dist/tools/find-symbol.d.ts +2 -2
  27. package/dist/tools/find-symbol.js +106 -96
  28. package/dist/tools/get-call-graph.d.ts +2 -2
  29. package/dist/tools/get-call-graph.js +52 -47
  30. package/dist/tools/get-complexity.d.ts +2 -2
  31. package/dist/tools/get-complexity.js +94 -46
  32. package/dist/tools/get-dependency-graph.d.ts +2 -2
  33. package/dist/tools/get-dependency-graph.js +66 -52
  34. package/dist/tools/get-documentation.d.ts +2 -2
  35. package/dist/tools/get-documentation.js +154 -122
  36. package/dist/tools/get-exports.d.ts +2 -2
  37. package/dist/tools/get-exports.js +73 -61
  38. package/dist/tools/get-file-structure.d.ts +2 -2
  39. package/dist/tools/get-file-structure.js +16 -16
  40. package/dist/tools/get-imports.d.ts +2 -2
  41. package/dist/tools/get-imports.js +46 -46
  42. package/dist/tools/get-signature.d.ts +2 -2
  43. package/dist/tools/get-signature.js +168 -124
  44. package/dist/tools/get-type-hierarchy.d.ts +2 -2
  45. package/dist/tools/get-type-hierarchy.js +53 -44
  46. package/dist/tools/index.d.ts +18 -16
  47. package/dist/tools/index.js +17 -15
  48. package/dist/tools/read-symbol.d.ts +62 -0
  49. package/dist/tools/read-symbol.js +464 -0
  50. package/dist/tools/types.d.ts +27 -27
  51. package/package.json +8 -8
@@ -4,52 +4,52 @@
4
4
  * Detect duplicate code blocks across the codebase using content hashing.
5
5
  * Helps identify opportunities for refactoring and code reuse.
6
6
  */
7
- import * as fs from 'node:fs/promises';
8
- import * as path from 'node:path';
9
- import * as crypto from 'node:crypto';
10
- import { defineTool, createSuccessResult, createErrorResult } from '@compilr-dev/agents';
7
+ import * as fs from "node:fs/promises";
8
+ import * as path from "node:path";
9
+ import * as crypto from "node:crypto";
10
+ import { defineTool, createSuccessResult, createErrorResult, } from "@compilr-dev/agents";
11
11
  // Tool description
12
12
  const TOOL_DESCRIPTION = `Detect duplicate code blocks across the codebase.
13
13
  Uses content hashing to find similar code patterns.
14
14
  Useful for identifying refactoring opportunities and reducing code duplication.`;
15
15
  // Tool input schema
16
16
  const TOOL_INPUT_SCHEMA = {
17
- type: 'object',
17
+ type: "object",
18
18
  properties: {
19
19
  path: {
20
- type: 'string',
21
- description: 'Directory to analyze',
20
+ type: "string",
21
+ description: "Directory to analyze",
22
22
  },
23
23
  minLines: {
24
- type: 'number',
25
- description: 'Minimum lines for a duplicate (default: 6)',
24
+ type: "number",
25
+ description: "Minimum lines for a duplicate (default: 6)",
26
26
  default: 6,
27
27
  },
28
28
  minTokens: {
29
- type: 'number',
30
- description: 'Minimum tokens for a duplicate (default: 50)',
29
+ type: "number",
30
+ description: "Minimum tokens for a duplicate (default: 50)",
31
31
  default: 50,
32
32
  },
33
33
  ignoreIdenticalFiles: {
34
- type: 'boolean',
35
- description: 'Ignore identical files (default: true)',
34
+ type: "boolean",
35
+ description: "Ignore identical files (default: true)",
36
36
  default: true,
37
37
  },
38
38
  maxFiles: {
39
- type: 'number',
40
- description: 'Maximum files to analyze (default: 100)',
39
+ type: "number",
40
+ description: "Maximum files to analyze (default: 100)",
41
41
  default: 100,
42
42
  },
43
43
  },
44
- required: ['path'],
44
+ required: ["path"],
45
45
  };
46
46
  // Default exclusions
47
- const DEFAULT_EXCLUDE = ['node_modules', 'dist', 'build', '.git', 'coverage'];
47
+ const DEFAULT_EXCLUDE = ["node_modules", "dist", "build", ".git", "coverage"];
48
48
  /**
49
49
  * findDuplicates tool
50
50
  */
51
51
  export const findDuplicatesTool = defineTool({
52
- name: 'find_duplicates',
52
+ name: "find_duplicates",
53
53
  description: TOOL_DESCRIPTION,
54
54
  inputSchema: TOOL_INPUT_SCHEMA,
55
55
  execute: executeFindDuplicates,
@@ -70,7 +70,7 @@ async function executeFindDuplicates(input) {
70
70
  }
71
71
  const stats = await fs.stat(resolvedPath);
72
72
  if (!stats.isDirectory()) {
73
- return createErrorResult('findDuplicates requires a directory path');
73
+ return createErrorResult("findDuplicates requires a directory path");
74
74
  }
75
75
  // Collect files
76
76
  const files = [];
@@ -79,8 +79,8 @@ async function executeFindDuplicates(input) {
79
79
  const fileHashes = new Map();
80
80
  if (!ignoreIdenticalFiles) {
81
81
  for (const file of files) {
82
- const content = await fs.readFile(file, 'utf-8');
83
- const hash = crypto.createHash('md5').update(content).digest('hex');
82
+ const content = await fs.readFile(file, "utf-8");
83
+ const hash = crypto.createHash("md5").update(content).digest("hex");
84
84
  const existing = fileHashes.get(hash) ?? [];
85
85
  existing.push(file);
86
86
  fileHashes.set(hash, existing);
@@ -93,8 +93,8 @@ async function executeFindDuplicates(input) {
93
93
  const blocks = await extractCodeBlocks(file, minLines, minTokens);
94
94
  allBlocks.push(...blocks);
95
95
  // Count total lines
96
- const content = await fs.readFile(file, 'utf-8');
97
- totalLines += content.split('\n').length;
96
+ const content = await fs.readFile(file, "utf-8");
97
+ totalLines += content.split("\n").length;
98
98
  }
99
99
  // Group by hash
100
100
  const hashGroups = new Map();
@@ -133,7 +133,9 @@ async function executeFindDuplicates(input) {
133
133
  duplicateGroups.sort((a, b) => b.lines - a.lines);
134
134
  // Limit results
135
135
  const limitedGroups = duplicateGroups.slice(0, 20);
136
- const percentageDuplicate = totalLines > 0 ? Math.round((totalDuplicateLines / totalLines) * 10000) / 100 : 0;
136
+ const percentageDuplicate = totalLines > 0
137
+ ? Math.round((totalDuplicateLines / totalLines) * 10000) / 100
138
+ : 0;
137
139
  const result = {
138
140
  path: resolvedPath,
139
141
  duplicates: limitedGroups,
@@ -169,7 +171,8 @@ async function collectFiles(dirPath, files, maxFiles, currentDepth = 0) {
169
171
  }
170
172
  else if (entry.isFile()) {
171
173
  // Only include TypeScript/JavaScript files
172
- if (/\.(ts|tsx|js|jsx)$/.test(entry.name) && !entry.name.endsWith('.d.ts')) {
174
+ if (/\.(ts|tsx|js|jsx)$/.test(entry.name) &&
175
+ !entry.name.endsWith(".d.ts")) {
173
176
  files.push(fullPath);
174
177
  }
175
178
  }
@@ -184,15 +187,15 @@ async function collectFiles(dirPath, files, maxFiles, currentDepth = 0) {
184
187
  */
185
188
  async function extractCodeBlocks(filePath, minLines, minTokens) {
186
189
  try {
187
- const content = await fs.readFile(filePath, 'utf-8');
188
- const lines = content.split('\n');
190
+ const content = await fs.readFile(filePath, "utf-8");
191
+ const lines = content.split("\n");
189
192
  const blocks = [];
190
193
  // Use sliding window approach
191
194
  for (let start = 0; start <= lines.length - minLines; start++) {
192
195
  // Try different block sizes
193
196
  for (let size = minLines; size <= Math.min(minLines * 3, lines.length - start); size++) {
194
197
  const blockLines = lines.slice(start, start + size);
195
- const blockContent = blockLines.join('\n');
198
+ const blockContent = blockLines.join("\n");
196
199
  // Normalize content for comparison
197
200
  const normalized = normalizeCode(blockContent);
198
201
  // Count tokens (simplified: split on whitespace and punctuation)
@@ -200,10 +203,10 @@ async function extractCodeBlocks(filePath, minLines, minTokens) {
200
203
  if (tokens < minTokens)
201
204
  continue;
202
205
  // Skip if mostly empty/comments
203
- const significantLines = blockLines.filter((l) => l.trim() && !l.trim().startsWith('//') && !l.trim().startsWith('*'));
206
+ const significantLines = blockLines.filter((l) => l.trim() && !l.trim().startsWith("//") && !l.trim().startsWith("*"));
204
207
  if (significantLines.length < minLines / 2)
205
208
  continue;
206
- const hash = crypto.createHash('md5').update(normalized).digest('hex');
209
+ const hash = crypto.createHash("md5").update(normalized).digest("hex");
207
210
  blocks.push({
208
211
  hash,
209
212
  path: filePath,
@@ -226,16 +229,16 @@ async function extractCodeBlocks(filePath, minLines, minTokens) {
226
229
  function normalizeCode(code) {
227
230
  return (code
228
231
  // Remove comments
229
- .replace(/\/\/.*$/gm, '')
230
- .replace(/\/\*[\s\S]*?\*\//g, '')
232
+ .replace(/\/\/.*$/gm, "")
233
+ .replace(/\/\*[\s\S]*?\*\//g, "")
231
234
  // Normalize whitespace
232
- .replace(/\s+/g, ' ')
235
+ .replace(/\s+/g, " ")
233
236
  // Remove string literals (replace with placeholder)
234
237
  .replace(/"[^"]*"/g, '""')
235
238
  .replace(/'[^']*'/g, "''")
236
- .replace(/`[^`]*`/g, '``')
239
+ .replace(/`[^`]*`/g, "``")
237
240
  // Normalize numbers
238
- .replace(/\b\d+\b/g, '0')
241
+ .replace(/\b\d+\b/g, "0")
239
242
  .trim());
240
243
  }
241
244
  /**
@@ -249,17 +252,17 @@ function countTokens(code) {
249
252
  * Truncate sample to first N lines
250
253
  */
251
254
  function truncateSample(content, maxLines) {
252
- const lines = content.split('\n');
255
+ const lines = content.split("\n");
253
256
  if (lines.length <= maxLines)
254
257
  return content;
255
- return lines.slice(0, maxLines).join('\n') + '\n...';
258
+ return lines.slice(0, maxLines).join("\n") + "\n...";
256
259
  }
257
260
  /**
258
261
  * Create customizable findDuplicates tool
259
262
  */
260
263
  export function createFindDuplicatesTool(options) {
261
264
  return defineTool({
262
- name: options?.name ?? 'find_duplicates',
265
+ name: options?.name ?? "find_duplicates",
263
266
  description: options?.description ?? TOOL_DESCRIPTION,
264
267
  inputSchema: TOOL_INPUT_SCHEMA,
265
268
  execute: async (input) => {
@@ -4,8 +4,8 @@
4
4
  * Find classes that implement an interface or extend an abstract class.
5
5
  * Useful for understanding interface usage and finding concrete implementations.
6
6
  */
7
- import type { Tool } from '@compilr-dev/agents';
8
- import type { FindImplementationsInput } from './types.js';
7
+ import type { Tool } from "@compilr-dev/agents";
8
+ import type { FindImplementationsInput } from "./types.js";
9
9
  /**
10
10
  * findImplementations tool - Find implementations of interfaces/abstract classes
11
11
  */
@@ -4,48 +4,53 @@
4
4
  * Find classes that implement an interface or extend an abstract class.
5
5
  * Useful for understanding interface usage and finding concrete implementations.
6
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';
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
12
  // Tool description
13
13
  const TOOL_DESCRIPTION = `Find classes that implement an interface or extend an abstract class.
14
14
  Returns information about each implementation including which methods are implemented.
15
15
  Useful for understanding how interfaces are used across the codebase.`;
16
16
  // Tool input schema
17
17
  const TOOL_INPUT_SCHEMA = {
18
- type: 'object',
18
+ type: "object",
19
19
  properties: {
20
20
  name: {
21
- type: 'string',
22
- description: 'Interface or abstract class name to find implementations of',
21
+ type: "string",
22
+ description: "Interface or abstract class name to find implementations of",
23
23
  },
24
24
  scope: {
25
- type: 'string',
26
- description: 'Directory or file to search in (defaults to current directory)',
25
+ type: "string",
26
+ description: "Directory or file to search in (defaults to current directory)",
27
27
  },
28
28
  includeAbstract: {
29
- type: 'boolean',
30
- description: 'Include abstract classes that partially implement (default: false)',
29
+ type: "boolean",
30
+ description: "Include abstract classes that partially implement (default: false)",
31
31
  default: false,
32
32
  },
33
33
  maxFiles: {
34
- type: 'number',
35
- description: 'Maximum files to search (default: 100)',
34
+ type: "number",
35
+ description: "Maximum files to search (default: 100)",
36
36
  default: 100,
37
37
  },
38
38
  },
39
- required: ['name'],
39
+ required: ["name"],
40
40
  };
41
41
  // Default file patterns
42
- const DEFAULT_INCLUDE = ['**/*.ts', '**/*.tsx', '**/*.js', '**/*.jsx'];
43
- const DEFAULT_EXCLUDE = ['**/node_modules/**', '**/dist/**', '**/build/**', '**/.git/**'];
42
+ const DEFAULT_INCLUDE = ["**/*.ts", "**/*.tsx", "**/*.js", "**/*.jsx"];
43
+ const DEFAULT_EXCLUDE = [
44
+ "**/node_modules/**",
45
+ "**/dist/**",
46
+ "**/build/**",
47
+ "**/.git/**",
48
+ ];
44
49
  /**
45
50
  * findImplementations tool - Find implementations of interfaces/abstract classes
46
51
  */
47
52
  export const findImplementationsTool = defineTool({
48
- name: 'find_implementations',
53
+ name: "find_implementations",
49
54
  description: TOOL_DESCRIPTION,
50
55
  inputSchema: TOOL_INPUT_SCHEMA,
51
56
  execute: executeFindImplementations,
@@ -54,11 +59,11 @@ export const findImplementationsTool = defineTool({
54
59
  * Execute the findImplementations tool
55
60
  */
56
61
  async function executeFindImplementations(input) {
57
- const { name, scope = '.', includeAbstract = false, maxFiles = 100 } = input;
62
+ const { name, scope = ".", includeAbstract = false, maxFiles = 100 } = input;
58
63
  const startTime = Date.now();
59
64
  // Validate input
60
65
  if (!name || name.trim().length === 0) {
61
- return createErrorResult('Interface or class name is required');
66
+ return createErrorResult("Interface or class name is required");
62
67
  }
63
68
  try {
64
69
  // Resolve scope path
@@ -119,7 +124,7 @@ async function executeFindImplementations(input) {
119
124
  */
120
125
  async function findTargetDefinition(filePath, targetName) {
121
126
  try {
122
- const content = await fs.readFile(filePath, 'utf-8');
127
+ const content = await fs.readFile(filePath, "utf-8");
123
128
  const detection = detectLanguage(filePath);
124
129
  if (!detection.language) {
125
130
  return null;
@@ -127,7 +132,7 @@ async function findTargetDefinition(filePath, targetName) {
127
132
  if (!isLanguageSupported(detection.language)) {
128
133
  return null;
129
134
  }
130
- const sourceFile = ts.createSourceFile(filePath, content, ts.ScriptTarget.Latest, true, filePath.endsWith('.tsx') ? ts.ScriptKind.TSX : ts.ScriptKind.TS);
135
+ const sourceFile = ts.createSourceFile(filePath, content, ts.ScriptTarget.Latest, true, filePath.endsWith(".tsx") ? ts.ScriptKind.TSX : ts.ScriptKind.TS);
131
136
  let result = null;
132
137
  const visit = (node) => {
133
138
  // Check for interface declaration
@@ -207,7 +212,7 @@ function extractClassMethods(node, abstractOnly) {
207
212
  */
208
213
  async function findImplementationsInFile(filePath, targetName, targetMethods, isInterface, isAbstractClass, includeAbstract) {
209
214
  try {
210
- const content = await fs.readFile(filePath, 'utf-8');
215
+ const content = await fs.readFile(filePath, "utf-8");
211
216
  const detection = detectLanguage(filePath);
212
217
  if (!detection.language) {
213
218
  return [];
@@ -215,7 +220,7 @@ async function findImplementationsInFile(filePath, targetName, targetMethods, is
215
220
  if (!isLanguageSupported(detection.language)) {
216
221
  return [];
217
222
  }
218
- const sourceFile = ts.createSourceFile(filePath, content, ts.ScriptTarget.Latest, true, filePath.endsWith('.tsx') ? ts.ScriptKind.TSX : ts.ScriptKind.TS);
223
+ const sourceFile = ts.createSourceFile(filePath, content, ts.ScriptTarget.Latest, true, filePath.endsWith(".tsx") ? ts.ScriptKind.TSX : ts.ScriptKind.TS);
219
224
  const implementations = [];
220
225
  const visit = (node) => {
221
226
  // Check for class declaration
@@ -248,7 +253,7 @@ async function findImplementationsInFile(filePath, targetName, targetMethods, is
248
253
  path: filePath,
249
254
  line: line + 1,
250
255
  column: character + 1,
251
- kind: 'object',
256
+ kind: "object",
252
257
  exported: isExported,
253
258
  implementedMethods: targetMethods,
254
259
  missingMethods: [],
@@ -277,7 +282,8 @@ function checkClassImplementation(node, sourceFile, filePath, targetName, target
277
282
  if (className === targetName)
278
283
  return null;
279
284
  // Check if it's abstract
280
- const classIsAbstract = node.modifiers?.some((m) => m.kind === ts.SyntaxKind.AbstractKeyword) ?? false;
285
+ const classIsAbstract = node.modifiers?.some((m) => m.kind === ts.SyntaxKind.AbstractKeyword) ??
286
+ false;
281
287
  // Skip abstract classes if not including them
282
288
  if (classIsAbstract && !includeAbstract)
283
289
  return null;
@@ -329,13 +335,14 @@ function checkClassImplementation(node, sourceFile, filePath, targetName, target
329
335
  // Calculate missing methods
330
336
  const missingMethods = targetMethods.filter((m) => !implementedMethods.includes(m));
331
337
  const { line, character } = sourceFile.getLineAndCharacterOfPosition(node.getStart());
332
- const isExported = node.modifiers?.some((m) => m.kind === ts.SyntaxKind.ExportKeyword) ?? false;
338
+ const isExported = node.modifiers?.some((m) => m.kind === ts.SyntaxKind.ExportKeyword) ??
339
+ false;
333
340
  return {
334
341
  name: className,
335
342
  path: filePath,
336
343
  line: line + 1,
337
344
  column: character + 1,
338
- kind: 'class',
345
+ kind: "class",
339
346
  isAbstract: classIsAbstract,
340
347
  exported: isExported,
341
348
  implementedMethods,
@@ -358,13 +365,14 @@ function checkTypeImplementation(node, sourceFile, filePath, targetName) {
358
365
  if (!extendsTarget)
359
366
  return null;
360
367
  const { line, character } = sourceFile.getLineAndCharacterOfPosition(node.getStart());
361
- const isExported = node.modifiers?.some((m) => m.kind === ts.SyntaxKind.ExportKeyword) ?? false;
368
+ const isExported = node.modifiers?.some((m) => m.kind === ts.SyntaxKind.ExportKeyword) ??
369
+ false;
362
370
  return {
363
371
  name: node.name.text,
364
372
  path: filePath,
365
373
  line: line + 1,
366
374
  column: character + 1,
367
- kind: 'type',
375
+ kind: "type",
368
376
  exported: isExported,
369
377
  };
370
378
  }
@@ -384,9 +392,9 @@ async function collectFiles(dirPath, include, exclude, maxDepth, maxFiles, curre
384
392
  const relativePath = fullPath;
385
393
  // Check exclusions
386
394
  const isExcluded = exclude.some((pattern) => {
387
- if (pattern.includes('**')) {
388
- const simplePattern = pattern.replace(/\*\*/g, '');
389
- return relativePath.includes(simplePattern.replace(/\*/g, ''));
395
+ if (pattern.includes("**")) {
396
+ const simplePattern = pattern.replace(/\*\*/g, "");
397
+ return relativePath.includes(simplePattern.replace(/\*/g, ""));
390
398
  }
391
399
  return entry.name === pattern || relativePath.includes(pattern);
392
400
  });
@@ -399,8 +407,8 @@ async function collectFiles(dirPath, include, exclude, maxDepth, maxFiles, curre
399
407
  else if (entry.isFile()) {
400
408
  // Check if file matches include patterns
401
409
  const isIncluded = include.some((pattern) => {
402
- if (pattern.includes('*')) {
403
- const ext = pattern.replace('**/', '').replace('*', '');
410
+ if (pattern.includes("*")) {
411
+ const ext = pattern.replace("**/", "").replace("*", "");
404
412
  return entry.name.endsWith(ext);
405
413
  }
406
414
  return entry.name === pattern;
@@ -421,7 +429,7 @@ async function collectFiles(dirPath, include, exclude, maxDepth, maxFiles, curre
421
429
  */
422
430
  export function createFindImplementationsTool(options) {
423
431
  return defineTool({
424
- name: options?.name ?? 'find_implementations',
432
+ name: options?.name ?? "find_implementations",
425
433
  description: options?.description ?? TOOL_DESCRIPTION,
426
434
  inputSchema: TOOL_INPUT_SCHEMA,
427
435
  execute: async (input) => {
@@ -4,8 +4,8 @@
4
4
  * Find code patterns, anti-patterns, and code smells in the codebase.
5
5
  * Uses regex and AST-based pattern matching.
6
6
  */
7
- import type { Tool } from '@compilr-dev/agents';
8
- import type { FindPatternsInput, CodePattern } from './types.js';
7
+ import type { Tool } from "@compilr-dev/agents";
8
+ import type { FindPatternsInput, CodePattern } from "./types.js";
9
9
  /**
10
10
  * findPatterns tool
11
11
  */