@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,394 @@
1
+ /**
2
+ * getComplexity Tool
3
+ *
4
+ * Calculate code complexity metrics including cyclomatic complexity,
5
+ * cognitive complexity, nesting depth, and identify complexity hotspots.
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 = `Calculate code complexity metrics for files or directories.
14
+ Returns cyclomatic complexity, cognitive complexity, nesting depth, and identifies hotspots.
15
+ Useful for finding functions that may need refactoring.`;
16
+ // Tool input schema
17
+ const TOOL_INPUT_SCHEMA = {
18
+ type: 'object',
19
+ properties: {
20
+ path: {
21
+ type: 'string',
22
+ description: 'File or directory to analyze',
23
+ },
24
+ recursive: {
25
+ type: 'boolean',
26
+ description: 'Recursive analysis for directories (default: false)',
27
+ default: false,
28
+ },
29
+ threshold: {
30
+ type: 'number',
31
+ description: 'Complexity threshold for warnings (default: 10)',
32
+ default: 10,
33
+ },
34
+ onlyAboveThreshold: {
35
+ type: 'boolean',
36
+ description: 'Only return items above threshold (default: false)',
37
+ default: false,
38
+ },
39
+ maxFiles: {
40
+ type: 'number',
41
+ description: 'Maximum files to analyze (default: 50)',
42
+ default: 50,
43
+ },
44
+ },
45
+ required: ['path'],
46
+ };
47
+ // Default exclusions
48
+ const DEFAULT_EXCLUDE = ['node_modules', 'dist', 'build', '.git', 'coverage'];
49
+ /**
50
+ * getComplexity tool
51
+ */
52
+ export const getComplexityTool = defineTool({
53
+ name: 'get_complexity',
54
+ description: TOOL_DESCRIPTION,
55
+ inputSchema: TOOL_INPUT_SCHEMA,
56
+ execute: executeGetComplexity,
57
+ });
58
+ /**
59
+ * Execute the getComplexity tool
60
+ */
61
+ async function executeGetComplexity(input) {
62
+ const { path: inputPath, recursive = false, threshold = 10, onlyAboveThreshold = false, maxFiles = 50, } = input;
63
+ try {
64
+ const resolvedPath = path.resolve(inputPath);
65
+ // Check if path exists
66
+ try {
67
+ await fs.access(resolvedPath);
68
+ }
69
+ catch {
70
+ return createErrorResult(`Path not found: ${resolvedPath}`);
71
+ }
72
+ const stats = await fs.stat(resolvedPath);
73
+ const files = [];
74
+ if (stats.isDirectory()) {
75
+ await collectFiles(resolvedPath, files, recursive ? 10 : 1, maxFiles);
76
+ }
77
+ else if (stats.isFile()) {
78
+ files.push(resolvedPath);
79
+ }
80
+ // Analyze each file
81
+ const fileResults = [];
82
+ let totalFunctions = 0;
83
+ let totalComplexity = 0;
84
+ let maxComplexity = 0;
85
+ let aboveThresholdCount = 0;
86
+ const hotspots = [];
87
+ for (const file of files) {
88
+ const fileResult = await analyzeFile(file, threshold);
89
+ if (fileResult) {
90
+ // Filter if onlyAboveThreshold
91
+ if (onlyAboveThreshold) {
92
+ fileResult.functions = fileResult.functions.filter((f) => f.aboveThreshold);
93
+ if (fileResult.functions.length === 0)
94
+ continue;
95
+ }
96
+ fileResults.push(fileResult);
97
+ // Update stats
98
+ totalFunctions += fileResult.functions.length;
99
+ for (const func of fileResult.functions) {
100
+ totalComplexity += func.cyclomatic;
101
+ if (func.cyclomatic > maxComplexity) {
102
+ maxComplexity = func.cyclomatic;
103
+ }
104
+ if (func.aboveThreshold) {
105
+ aboveThresholdCount++;
106
+ }
107
+ // Track hotspots
108
+ hotspots.push({
109
+ name: func.name,
110
+ path: file,
111
+ line: func.line,
112
+ cyclomatic: func.cyclomatic,
113
+ cognitive: func.cognitive,
114
+ });
115
+ }
116
+ }
117
+ }
118
+ // Sort hotspots by complexity and take top 5
119
+ hotspots.sort((a, b) => b.cyclomatic - a.cyclomatic);
120
+ const topHotspots = hotspots.slice(0, 5);
121
+ const result = {
122
+ path: resolvedPath,
123
+ files: fileResults,
124
+ summary: {
125
+ totalFiles: fileResults.length,
126
+ totalFunctions,
127
+ averageComplexity: totalFunctions > 0 ? Math.round((totalComplexity / totalFunctions) * 100) / 100 : 0,
128
+ maxComplexity,
129
+ aboveThreshold: aboveThresholdCount,
130
+ threshold,
131
+ },
132
+ hotspots: topHotspots,
133
+ };
134
+ return createSuccessResult(result);
135
+ }
136
+ catch (error) {
137
+ return createErrorResult(`Failed to analyze complexity: ${error instanceof Error ? error.message : String(error)}`);
138
+ }
139
+ }
140
+ /**
141
+ * Collect files to analyze
142
+ */
143
+ async function collectFiles(dirPath, files, maxDepth, maxFiles, currentDepth = 0) {
144
+ if (currentDepth > maxDepth || files.length >= maxFiles)
145
+ return;
146
+ try {
147
+ const entries = await fs.readdir(dirPath, { withFileTypes: true });
148
+ for (const entry of entries) {
149
+ if (files.length >= maxFiles)
150
+ break;
151
+ const fullPath = path.join(dirPath, entry.name);
152
+ // Skip excluded directories
153
+ if (entry.isDirectory()) {
154
+ if (DEFAULT_EXCLUDE.includes(entry.name))
155
+ continue;
156
+ await collectFiles(fullPath, files, maxDepth, maxFiles, currentDepth + 1);
157
+ }
158
+ else if (entry.isFile()) {
159
+ // Only include TypeScript/JavaScript files
160
+ if (/\.(ts|tsx|js|jsx)$/.test(entry.name) && !entry.name.endsWith('.d.ts')) {
161
+ files.push(fullPath);
162
+ }
163
+ }
164
+ }
165
+ }
166
+ catch {
167
+ // Ignore permission errors
168
+ }
169
+ }
170
+ /**
171
+ * Analyze complexity of a single file
172
+ */
173
+ async function analyzeFile(filePath, threshold) {
174
+ try {
175
+ const content = await fs.readFile(filePath, 'utf-8');
176
+ const detection = detectLanguage(filePath);
177
+ if (!detection.language || !isLanguageSupported(detection.language)) {
178
+ return null;
179
+ }
180
+ const sourceFile = ts.createSourceFile(filePath, content, ts.ScriptTarget.Latest, true, filePath.endsWith('.tsx') ? ts.ScriptKind.TSX : ts.ScriptKind.TS);
181
+ const functions = [];
182
+ let classCount = 0;
183
+ // Visit all nodes
184
+ const visit = (node) => {
185
+ // Track classes
186
+ if (ts.isClassDeclaration(node)) {
187
+ classCount++;
188
+ }
189
+ // Analyze functions
190
+ if (ts.isFunctionDeclaration(node) ||
191
+ ts.isMethodDeclaration(node) ||
192
+ ts.isFunctionExpression(node) ||
193
+ ts.isArrowFunction(node)) {
194
+ const funcComplexity = analyzeFunctionComplexity(node, sourceFile, threshold);
195
+ if (funcComplexity) {
196
+ functions.push(funcComplexity);
197
+ }
198
+ }
199
+ ts.forEachChild(node, visit);
200
+ };
201
+ visit(sourceFile);
202
+ // Calculate file metrics
203
+ const lines = content.split('\n');
204
+ const totalLines = lines.length;
205
+ const blankLines = lines.filter((l) => l.trim() === '').length;
206
+ const linesOfCode = totalLines - blankLines;
207
+ const avgComplexity = functions.length > 0
208
+ ? Math.round((functions.reduce((sum, f) => sum + f.cyclomatic, 0) / functions.length) * 100) / 100
209
+ : 0;
210
+ const maxComplexity = functions.length > 0 ? Math.max(...functions.map((f) => f.cyclomatic)) : 0;
211
+ return {
212
+ path: filePath,
213
+ metrics: {
214
+ lines: totalLines,
215
+ linesOfCode,
216
+ functions: functions.length,
217
+ classes: classCount,
218
+ avgComplexity,
219
+ maxComplexity,
220
+ },
221
+ functions,
222
+ };
223
+ }
224
+ catch {
225
+ return null;
226
+ }
227
+ }
228
+ /**
229
+ * Analyze complexity of a single function
230
+ */
231
+ function analyzeFunctionComplexity(node, sourceFile, threshold) {
232
+ // Get function name
233
+ let name = '<anonymous>';
234
+ if (ts.isFunctionDeclaration(node) && node.name) {
235
+ name = node.name.text;
236
+ }
237
+ else if (ts.isMethodDeclaration(node)) {
238
+ name = ts.isIdentifier(node.name) ? node.name.text : '<computed>';
239
+ }
240
+ else if (ts.isFunctionExpression(node) && node.name) {
241
+ name = node.name.text;
242
+ }
243
+ else if (ts.isArrowFunction(node)) {
244
+ // Try to get name from parent variable declaration
245
+ const parent = node.parent;
246
+ if (ts.isVariableDeclaration(parent) && ts.isIdentifier(parent.name)) {
247
+ name = parent.name.text;
248
+ }
249
+ else if (ts.isPropertyAssignment(parent) && ts.isIdentifier(parent.name)) {
250
+ name = parent.name.text;
251
+ }
252
+ }
253
+ // Skip anonymous functions in certain contexts
254
+ if (name === '<anonymous>') {
255
+ // Only include top-level anonymous functions or named expressions
256
+ const parent = node.parent;
257
+ if (!ts.isVariableDeclaration(parent) && !ts.isPropertyAssignment(parent)) {
258
+ return null;
259
+ }
260
+ }
261
+ const body = ts.isFunctionDeclaration(node) || ts.isMethodDeclaration(node) || ts.isFunctionExpression(node)
262
+ ? node.body
263
+ : node.body;
264
+ if (!body)
265
+ return null;
266
+ // Get position info
267
+ const { line: startLine } = sourceFile.getLineAndCharacterOfPosition(node.getStart());
268
+ const { line: endLine } = sourceFile.getLineAndCharacterOfPosition(node.getEnd());
269
+ // Calculate complexities
270
+ const contributors = [];
271
+ let cyclomatic = 1; // Start at 1
272
+ let cognitive = 0;
273
+ let maxNesting = 0;
274
+ const analyzeNode = (n, nestingLevel) => {
275
+ const { line } = sourceFile.getLineAndCharacterOfPosition(n.getStart());
276
+ // Track max nesting
277
+ if (nestingLevel > maxNesting) {
278
+ maxNesting = nestingLevel;
279
+ }
280
+ // Cyclomatic complexity contributors
281
+ if (ts.isIfStatement(n)) {
282
+ cyclomatic++;
283
+ cognitive += 1 + nestingLevel; // Cognitive adds nesting penalty
284
+ contributors.push({ type: 'if', line: line + 1, contribution: 1 + nestingLevel });
285
+ }
286
+ else if (ts.isConditionalExpression(n)) {
287
+ cyclomatic++;
288
+ cognitive += 1 + nestingLevel;
289
+ contributors.push({ type: 'ternary', line: line + 1, contribution: 1 + nestingLevel });
290
+ }
291
+ else if (ts.isForStatement(n) || ts.isForInStatement(n) || ts.isForOfStatement(n)) {
292
+ cyclomatic++;
293
+ cognitive += 1 + nestingLevel;
294
+ contributors.push({ type: 'for', line: line + 1, contribution: 1 + nestingLevel });
295
+ }
296
+ else if (ts.isWhileStatement(n)) {
297
+ cyclomatic++;
298
+ cognitive += 1 + nestingLevel;
299
+ contributors.push({ type: 'while', line: line + 1, contribution: 1 + nestingLevel });
300
+ }
301
+ else if (ts.isDoStatement(n)) {
302
+ cyclomatic++;
303
+ cognitive += 1 + nestingLevel;
304
+ contributors.push({ type: 'do', line: line + 1, contribution: 1 + nestingLevel });
305
+ }
306
+ else if (ts.isSwitchStatement(n)) {
307
+ cyclomatic++;
308
+ cognitive += 1 + nestingLevel;
309
+ contributors.push({ type: 'switch', line: line + 1, contribution: 1 + nestingLevel });
310
+ }
311
+ else if (ts.isCaseClause(n)) {
312
+ cyclomatic++;
313
+ contributors.push({ type: 'case', line: line + 1, contribution: 1 });
314
+ }
315
+ else if (ts.isCatchClause(n)) {
316
+ cyclomatic++;
317
+ cognitive += 1 + nestingLevel;
318
+ contributors.push({ type: 'catch', line: line + 1, contribution: 1 + nestingLevel });
319
+ }
320
+ else if (ts.isBinaryExpression(n)) {
321
+ if (n.operatorToken.kind === ts.SyntaxKind.AmpersandAmpersandToken) {
322
+ cyclomatic++;
323
+ cognitive++;
324
+ contributors.push({ type: 'logical_and', line: line + 1, contribution: 1 });
325
+ }
326
+ else if (n.operatorToken.kind === ts.SyntaxKind.BarBarToken) {
327
+ cyclomatic++;
328
+ cognitive++;
329
+ contributors.push({ type: 'logical_or', line: line + 1, contribution: 1 });
330
+ }
331
+ else if (n.operatorToken.kind === ts.SyntaxKind.QuestionQuestionToken) {
332
+ cyclomatic++;
333
+ cognitive++;
334
+ contributors.push({ type: 'nullish', line: line + 1, contribution: 1 });
335
+ }
336
+ }
337
+ // Increase nesting for blocks
338
+ const newNesting = isNestingNode(n) ? nestingLevel + 1 : nestingLevel;
339
+ ts.forEachChild(n, (child) => {
340
+ analyzeNode(child, newNesting);
341
+ });
342
+ };
343
+ analyzeNode(body, 0);
344
+ // Calculate LOC
345
+ const bodyText = body.getText(sourceFile);
346
+ const bodyLines = bodyText.split('\n').filter((l) => l.trim() !== '').length;
347
+ // Parameter count
348
+ const parameterCount = node.parameters.length;
349
+ const aboveThreshold = cyclomatic >= threshold;
350
+ return {
351
+ name,
352
+ line: startLine + 1,
353
+ endLine: endLine + 1,
354
+ cyclomatic,
355
+ cognitive,
356
+ maxNesting,
357
+ loc: bodyLines,
358
+ parameterCount,
359
+ aboveThreshold,
360
+ contributors: aboveThreshold ? contributors : undefined,
361
+ };
362
+ }
363
+ /**
364
+ * Check if a node increases nesting level
365
+ */
366
+ function isNestingNode(node) {
367
+ return (ts.isIfStatement(node) ||
368
+ ts.isForStatement(node) ||
369
+ ts.isForInStatement(node) ||
370
+ ts.isForOfStatement(node) ||
371
+ ts.isWhileStatement(node) ||
372
+ ts.isDoStatement(node) ||
373
+ ts.isSwitchStatement(node) ||
374
+ ts.isTryStatement(node) ||
375
+ ts.isCatchClause(node));
376
+ }
377
+ /**
378
+ * Create customizable getComplexity tool
379
+ */
380
+ export function createGetComplexityTool(options) {
381
+ return defineTool({
382
+ name: options?.name ?? 'get_complexity',
383
+ description: options?.description ?? TOOL_DESCRIPTION,
384
+ inputSchema: TOOL_INPUT_SCHEMA,
385
+ execute: async (input) => {
386
+ const modifiedInput = {
387
+ ...input,
388
+ threshold: input.threshold ?? options?.defaultThreshold,
389
+ maxFiles: input.maxFiles ?? options?.defaultMaxFiles,
390
+ };
391
+ return executeGetComplexity(modifiedInput);
392
+ },
393
+ });
394
+ }
@@ -0,0 +1,23 @@
1
+ /**
2
+ * getDependencyGraph Tool
3
+ *
4
+ * Analyze module-level dependencies across a directory.
5
+ * Builds a dependency graph and detects circular dependencies.
6
+ */
7
+ import type { Tool } from '@compilr-dev/agents';
8
+ import type { GetDependencyGraphInput } from './types.js';
9
+ /**
10
+ * getDependencyGraph tool - Analyze module dependencies
11
+ */
12
+ export declare const getDependencyGraphTool: Tool<GetDependencyGraphInput>;
13
+ /**
14
+ * Factory function to create a customized getDependencyGraph tool
15
+ */
16
+ export declare function createGetDependencyGraphTool(options?: {
17
+ /** Default includeExternal */
18
+ defaultIncludeExternal?: boolean;
19
+ /** Default includeTypeOnly */
20
+ defaultIncludeTypeOnly?: boolean;
21
+ /** Default maxDepth */
22
+ defaultMaxDepth?: number;
23
+ }): Tool<GetDependencyGraphInput>;