@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,482 @@
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 * as fs from 'node:fs/promises';
8
+ import * as fsSync from 'node:fs';
9
+ import * as path from 'node:path';
10
+ import * as ts from 'typescript';
11
+ import { defineTool, createSuccessResult, createErrorResult } from '@compilr-dev/agents';
12
+ import { detectLanguage, isLanguageSupported } from '../parser/typescript-parser.js';
13
+ // Tool description
14
+ const TOOL_DESCRIPTION = `Analyze module-level dependencies across a directory.
15
+ Returns a dependency graph showing which modules import which other modules.
16
+ Detects circular dependencies and provides dependency statistics.`;
17
+ // Tool input schema
18
+ const TOOL_INPUT_SCHEMA = {
19
+ type: 'object',
20
+ properties: {
21
+ path: {
22
+ type: 'string',
23
+ description: 'Directory or file to analyze',
24
+ },
25
+ includeExternal: {
26
+ type: 'boolean',
27
+ description: 'Include external package dependencies (default: true)',
28
+ default: true,
29
+ },
30
+ includeTypeOnly: {
31
+ type: 'boolean',
32
+ description: 'Include type-only imports (default: true)',
33
+ default: true,
34
+ },
35
+ maxDepth: {
36
+ type: 'number',
37
+ description: 'Maximum depth for directory traversal (default: 10)',
38
+ default: 10,
39
+ },
40
+ },
41
+ required: ['path'],
42
+ };
43
+ // Default file patterns
44
+ const DEFAULT_INCLUDE = ['**/*.ts', '**/*.tsx', '**/*.js', '**/*.jsx', '**/*.mts', '**/*.mjs'];
45
+ const DEFAULT_EXCLUDE = ['**/node_modules/**', '**/dist/**', '**/build/**', '**/.git/**'];
46
+ /**
47
+ * getDependencyGraph tool - Analyze module dependencies
48
+ */
49
+ export const getDependencyGraphTool = defineTool({
50
+ name: 'get_dependency_graph',
51
+ description: TOOL_DESCRIPTION,
52
+ inputSchema: TOOL_INPUT_SCHEMA,
53
+ execute: executeGetDependencyGraph,
54
+ });
55
+ /**
56
+ * Execute the getDependencyGraph tool
57
+ */
58
+ async function executeGetDependencyGraph(input) {
59
+ const { path: inputPath, includeExternal = true, includeTypeOnly = true, maxDepth = 10, include = DEFAULT_INCLUDE, exclude = DEFAULT_EXCLUDE, } = input;
60
+ // Validate input
61
+ if (!inputPath || inputPath.trim().length === 0) {
62
+ return createErrorResult('Path is required');
63
+ }
64
+ try {
65
+ // Check if path exists
66
+ try {
67
+ await fs.access(inputPath);
68
+ }
69
+ catch {
70
+ return createErrorResult(`Path not found: ${inputPath}`);
71
+ }
72
+ const stats = await fs.stat(inputPath);
73
+ const rootPath = stats.isDirectory() ? inputPath : path.dirname(inputPath);
74
+ // Collect files to analyze
75
+ const files = stats.isDirectory()
76
+ ? await collectFiles(inputPath, include, exclude, maxDepth)
77
+ : [inputPath];
78
+ if (files.length === 0) {
79
+ return createSuccessResult({
80
+ root: rootPath,
81
+ modules: [],
82
+ edges: [],
83
+ circularDependencies: [],
84
+ stats: {
85
+ totalModules: 0,
86
+ internalModules: 0,
87
+ externalPackages: 0,
88
+ totalEdges: 0,
89
+ circularDependencies: 0,
90
+ externalPackageList: [],
91
+ },
92
+ });
93
+ }
94
+ // Analyze dependencies
95
+ const result = await analyzeDependencies(files, rootPath, includeExternal, includeTypeOnly);
96
+ return createSuccessResult(result);
97
+ }
98
+ catch (error) {
99
+ const message = error instanceof Error ? error.message : String(error);
100
+ return createErrorResult(`Failed to analyze dependencies: ${message}`);
101
+ }
102
+ }
103
+ /**
104
+ * Collect files matching patterns
105
+ */
106
+ async function collectFiles(dir, include, exclude, maxDepth, currentDepth = 0) {
107
+ if (currentDepth >= maxDepth) {
108
+ return [];
109
+ }
110
+ const files = [];
111
+ try {
112
+ const entries = await fs.readdir(dir, { withFileTypes: true });
113
+ for (const entry of entries) {
114
+ const fullPath = path.join(dir, entry.name);
115
+ const relativePath = fullPath;
116
+ // Check exclusions
117
+ if (matchesPattern(relativePath, exclude)) {
118
+ continue;
119
+ }
120
+ if (entry.isDirectory()) {
121
+ const subFiles = await collectFiles(fullPath, include, exclude, maxDepth, currentDepth + 1);
122
+ files.push(...subFiles);
123
+ }
124
+ else if (entry.isFile()) {
125
+ // Check if file matches include patterns
126
+ if (matchesPattern(relativePath, include) && isSupportedFile(fullPath)) {
127
+ files.push(fullPath);
128
+ }
129
+ }
130
+ }
131
+ }
132
+ catch {
133
+ // Silently skip directories we can't read
134
+ }
135
+ return files;
136
+ }
137
+ /**
138
+ * Check if a path matches any of the patterns
139
+ */
140
+ function matchesPattern(filePath, patterns) {
141
+ const fileName = path.basename(filePath);
142
+ for (const pattern of patterns) {
143
+ // Handle simple glob patterns
144
+ if (pattern.includes('**')) {
145
+ // Check if any directory in path matches
146
+ const patternDir = pattern.replace('**/', '').replace('/**', '');
147
+ if (filePath.includes(patternDir.replace('*', ''))) {
148
+ return true;
149
+ }
150
+ }
151
+ else if (pattern.startsWith('*.')) {
152
+ // Extension pattern
153
+ const ext = pattern.substring(1);
154
+ if (fileName.endsWith(ext)) {
155
+ return true;
156
+ }
157
+ }
158
+ else if (filePath.includes(pattern) || fileName === pattern) {
159
+ return true;
160
+ }
161
+ }
162
+ return false;
163
+ }
164
+ /**
165
+ * Check if file is supported
166
+ */
167
+ function isSupportedFile(filePath) {
168
+ const detection = detectLanguage(filePath);
169
+ if (!detection.language) {
170
+ return false;
171
+ }
172
+ return isLanguageSupported(detection.language);
173
+ }
174
+ /**
175
+ * Analyze dependencies across files
176
+ */
177
+ async function analyzeDependencies(files, rootPath, includeExternal, includeTypeOnly) {
178
+ const modules = new Map();
179
+ const edges = [];
180
+ const externalPackages = new Set();
181
+ // Track adjacency for cycle detection
182
+ const adjacency = new Map();
183
+ for (const file of files) {
184
+ const relativePath = path.relative(rootPath, file);
185
+ const imports = await extractImports(file);
186
+ // Add this module
187
+ if (!modules.has(relativePath)) {
188
+ modules.set(relativePath, {
189
+ path: relativePath,
190
+ absolutePath: file,
191
+ isExternal: false,
192
+ });
193
+ }
194
+ const moduleDeps = [];
195
+ for (const imp of imports) {
196
+ // Skip type-only if not requested
197
+ if (imp.typeOnly && !includeTypeOnly) {
198
+ continue;
199
+ }
200
+ // Determine if external
201
+ const isExternal = !imp.source.startsWith('.') && !imp.source.startsWith('/');
202
+ if (isExternal) {
203
+ if (!includeExternal) {
204
+ continue;
205
+ }
206
+ // Extract package name (handle scoped packages)
207
+ const packageName = imp.source.startsWith('@')
208
+ ? imp.source.split('/').slice(0, 2).join('/')
209
+ : imp.source.split('/')[0];
210
+ externalPackages.add(packageName);
211
+ // Add external module node
212
+ if (!modules.has(packageName)) {
213
+ modules.set(packageName, {
214
+ path: packageName,
215
+ absolutePath: packageName,
216
+ isExternal: true,
217
+ packageName,
218
+ });
219
+ }
220
+ edges.push({
221
+ from: relativePath,
222
+ to: packageName,
223
+ importType: imp.importType,
224
+ typeOnly: imp.typeOnly,
225
+ symbolCount: imp.symbols.length,
226
+ symbols: imp.symbols.slice(0, 10),
227
+ });
228
+ moduleDeps.push(packageName);
229
+ }
230
+ else {
231
+ // Internal import - resolve path
232
+ const resolvedPath = resolveImportPath(file, imp.source, rootPath);
233
+ if (resolvedPath) {
234
+ const relativeResolved = path.relative(rootPath, resolvedPath);
235
+ // Add target module if not exists
236
+ if (!modules.has(relativeResolved)) {
237
+ modules.set(relativeResolved, {
238
+ path: relativeResolved,
239
+ absolutePath: resolvedPath,
240
+ isExternal: false,
241
+ });
242
+ }
243
+ edges.push({
244
+ from: relativePath,
245
+ to: relativeResolved,
246
+ importType: imp.importType,
247
+ typeOnly: imp.typeOnly,
248
+ symbolCount: imp.symbols.length,
249
+ symbols: imp.symbols.slice(0, 10),
250
+ });
251
+ moduleDeps.push(relativeResolved);
252
+ }
253
+ }
254
+ }
255
+ adjacency.set(relativePath, moduleDeps);
256
+ }
257
+ // Detect circular dependencies
258
+ const circularDependencies = detectCircularDependencies(adjacency);
259
+ // Calculate statistics
260
+ const internalModules = Array.from(modules.values()).filter((m) => !m.isExternal);
261
+ const dependencyCounts = new Map();
262
+ const dependentCounts = new Map();
263
+ for (const edge of edges) {
264
+ dependencyCounts.set(edge.from, (dependencyCounts.get(edge.from) ?? 0) + 1);
265
+ dependentCounts.set(edge.to, (dependentCounts.get(edge.to) ?? 0) + 1);
266
+ }
267
+ let mostDependencies;
268
+ let mostDependents;
269
+ for (const [module, count] of dependencyCounts) {
270
+ if (!mostDependencies || count > mostDependencies.count) {
271
+ mostDependencies = { module, count };
272
+ }
273
+ }
274
+ for (const [module, count] of dependentCounts) {
275
+ if (!mostDependents || count > mostDependents.count) {
276
+ mostDependents = { module, count };
277
+ }
278
+ }
279
+ const stats = {
280
+ totalModules: modules.size,
281
+ internalModules: internalModules.length,
282
+ externalPackages: externalPackages.size,
283
+ totalEdges: edges.length,
284
+ circularDependencies: circularDependencies.length,
285
+ externalPackageList: Array.from(externalPackages).sort(),
286
+ mostDependencies,
287
+ mostDependents,
288
+ };
289
+ return {
290
+ root: rootPath,
291
+ modules: Array.from(modules.values()),
292
+ edges,
293
+ circularDependencies,
294
+ stats,
295
+ };
296
+ }
297
+ /**
298
+ * Extract imports from a file
299
+ */
300
+ async function extractImports(filePath) {
301
+ const imports = [];
302
+ try {
303
+ const sourceCode = await fs.readFile(filePath, 'utf-8');
304
+ const scriptKind = getScriptKind(filePath);
305
+ const sourceFile = ts.createSourceFile(filePath, sourceCode, ts.ScriptTarget.Latest, true, scriptKind);
306
+ ts.forEachChild(sourceFile, (node) => {
307
+ // Import declarations
308
+ if (ts.isImportDeclaration(node)) {
309
+ if (ts.isStringLiteral(node.moduleSpecifier)) {
310
+ const source = node.moduleSpecifier.text;
311
+ // Check for type-only import by looking at the import text
312
+ const importText = node.getText(sourceFile);
313
+ const typeOnly = importText.includes('import type');
314
+ const symbols = [];
315
+ let importType = 'side-effect';
316
+ if (node.importClause) {
317
+ // Default import
318
+ if (node.importClause.name) {
319
+ importType = 'default';
320
+ symbols.push(node.importClause.name.text);
321
+ }
322
+ // Named or namespace imports
323
+ if (node.importClause.namedBindings) {
324
+ if (ts.isNamespaceImport(node.importClause.namedBindings)) {
325
+ importType = 'namespace';
326
+ symbols.push(node.importClause.namedBindings.name.text);
327
+ }
328
+ else if (ts.isNamedImports(node.importClause.namedBindings)) {
329
+ importType = importType === 'default' ? 'default' : 'named';
330
+ for (const element of node.importClause.namedBindings.elements) {
331
+ symbols.push(element.name.text);
332
+ }
333
+ }
334
+ }
335
+ }
336
+ imports.push({ source, importType, typeOnly, symbols });
337
+ }
338
+ }
339
+ // Export declarations with 'from' (re-exports count as dependencies)
340
+ if (ts.isExportDeclaration(node)) {
341
+ if (node.moduleSpecifier && ts.isStringLiteral(node.moduleSpecifier)) {
342
+ const source = node.moduleSpecifier.text;
343
+ const typeOnly = node.isTypeOnly;
344
+ const symbols = [];
345
+ let importType = 'namespace';
346
+ if (node.exportClause && ts.isNamedExports(node.exportClause)) {
347
+ importType = 'named';
348
+ for (const element of node.exportClause.elements) {
349
+ symbols.push(element.name.text);
350
+ }
351
+ }
352
+ imports.push({ source, importType, typeOnly, symbols });
353
+ }
354
+ }
355
+ });
356
+ }
357
+ catch {
358
+ // Silently skip files that can't be parsed
359
+ }
360
+ return imports;
361
+ }
362
+ /**
363
+ * Resolve import path to absolute file path
364
+ */
365
+ function resolveImportPath(fromFile, importSource, _rootPath) {
366
+ const fromDir = path.dirname(fromFile);
367
+ const basePath = path.resolve(fromDir, importSource);
368
+ // Try various extensions
369
+ const extensions = ['.ts', '.tsx', '.js', '.jsx', '.mts', '.mjs', ''];
370
+ for (const ext of extensions) {
371
+ const candidate = basePath + ext;
372
+ try {
373
+ // Check if file exists synchronously for simplicity
374
+ // (we're already in an async context and this is a quick check)
375
+ fsSync.accessSync(candidate);
376
+ return candidate;
377
+ }
378
+ catch {
379
+ // Try index file
380
+ const indexCandidate = path.join(basePath, `index${ext || '.ts'}`);
381
+ try {
382
+ fsSync.accessSync(indexCandidate);
383
+ return indexCandidate;
384
+ }
385
+ catch {
386
+ // Continue to next extension
387
+ }
388
+ }
389
+ }
390
+ return undefined;
391
+ }
392
+ /**
393
+ * Detect circular dependencies using DFS
394
+ */
395
+ function detectCircularDependencies(adjacency) {
396
+ const cycles = [];
397
+ const visited = new Set();
398
+ const recursionStack = new Set();
399
+ const path = [];
400
+ function dfs(node) {
401
+ if (recursionStack.has(node)) {
402
+ // Found a cycle
403
+ const cycleStart = path.indexOf(node);
404
+ if (cycleStart !== -1) {
405
+ const cycle = [...path.slice(cycleStart), node];
406
+ // Avoid duplicate cycles
407
+ const cycleKey = [...cycle].sort().join('->');
408
+ if (!cycles.some((c) => [...c.cycle].sort().join('->') === cycleKey)) {
409
+ cycles.push({
410
+ cycle,
411
+ length: cycle.length - 1,
412
+ });
413
+ }
414
+ }
415
+ return;
416
+ }
417
+ if (visited.has(node)) {
418
+ return;
419
+ }
420
+ visited.add(node);
421
+ recursionStack.add(node);
422
+ path.push(node);
423
+ const deps = adjacency.get(node) ?? [];
424
+ for (const dep of deps) {
425
+ // Only check internal dependencies
426
+ if (adjacency.has(dep)) {
427
+ dfs(dep);
428
+ }
429
+ }
430
+ path.pop();
431
+ recursionStack.delete(node);
432
+ }
433
+ for (const node of adjacency.keys()) {
434
+ if (!visited.has(node)) {
435
+ dfs(node);
436
+ }
437
+ }
438
+ return cycles;
439
+ }
440
+ /**
441
+ * Get TypeScript script kind from file extension
442
+ */
443
+ function getScriptKind(filePath) {
444
+ const ext = filePath.toLowerCase().split('.').pop();
445
+ switch (ext) {
446
+ case 'ts':
447
+ return ts.ScriptKind.TS;
448
+ case 'tsx':
449
+ return ts.ScriptKind.TSX;
450
+ case 'js':
451
+ return ts.ScriptKind.JS;
452
+ case 'jsx':
453
+ return ts.ScriptKind.JSX;
454
+ case 'mts':
455
+ case 'cts':
456
+ return ts.ScriptKind.TS;
457
+ case 'mjs':
458
+ case 'cjs':
459
+ return ts.ScriptKind.JS;
460
+ default:
461
+ return ts.ScriptKind.TS;
462
+ }
463
+ }
464
+ /**
465
+ * Factory function to create a customized getDependencyGraph tool
466
+ */
467
+ export function createGetDependencyGraphTool(options) {
468
+ const { defaultIncludeExternal = true, defaultIncludeTypeOnly = true, defaultMaxDepth = 10, } = options ?? {};
469
+ return defineTool({
470
+ name: 'get_dependency_graph',
471
+ description: TOOL_DESCRIPTION,
472
+ inputSchema: TOOL_INPUT_SCHEMA,
473
+ execute: async (input) => {
474
+ return executeGetDependencyGraph({
475
+ ...input,
476
+ includeExternal: input.includeExternal ?? defaultIncludeExternal,
477
+ includeTypeOnly: input.includeTypeOnly ?? defaultIncludeTypeOnly,
478
+ maxDepth: input.maxDepth ?? defaultMaxDepth,
479
+ });
480
+ },
481
+ });
482
+ }
@@ -0,0 +1,21 @@
1
+ /**
2
+ * getDocumentation Tool
3
+ *
4
+ * Extract JSDoc/TSDoc documentation from source files.
5
+ * Provides documentation coverage metrics and identifies undocumented exports.
6
+ */
7
+ import type { Tool } from '@compilr-dev/agents';
8
+ import type { GetDocumentationInput } from './types.js';
9
+ /**
10
+ * getDocumentation tool
11
+ */
12
+ export declare const getDocumentationTool: Tool<GetDocumentationInput>;
13
+ /**
14
+ * Create customizable getDocumentation tool
15
+ */
16
+ export declare function createGetDocumentationTool(options?: {
17
+ name?: string;
18
+ description?: string;
19
+ defaultMaxFiles?: number;
20
+ defaultDocumentedOnly?: boolean;
21
+ }): Tool<GetDocumentationInput>;