@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.
- package/LICENSE +21 -0
- package/dist/index.d.ts +34 -0
- package/dist/index.js +66 -0
- package/dist/parser/index.d.ts +7 -0
- package/dist/parser/index.js +6 -0
- package/dist/parser/typescript-parser.d.ts +22 -0
- package/dist/parser/typescript-parser.js +423 -0
- package/dist/skills/code-health.d.ts +9 -0
- package/dist/skills/code-health.js +167 -0
- package/dist/skills/code-structure.d.ts +9 -0
- package/dist/skills/code-structure.js +97 -0
- package/dist/skills/dependency-audit.d.ts +9 -0
- package/dist/skills/dependency-audit.js +110 -0
- package/dist/skills/index.d.ts +16 -0
- package/dist/skills/index.js +27 -0
- package/dist/skills/refactor-impact.d.ts +9 -0
- package/dist/skills/refactor-impact.js +135 -0
- package/dist/skills/type-analysis.d.ts +9 -0
- package/dist/skills/type-analysis.js +150 -0
- package/dist/tools/find-dead-code.d.ts +20 -0
- package/dist/tools/find-dead-code.js +375 -0
- package/dist/tools/find-duplicates.d.ts +21 -0
- package/dist/tools/find-duplicates.js +274 -0
- package/dist/tools/find-implementations.d.ts +21 -0
- package/dist/tools/find-implementations.js +436 -0
- package/dist/tools/find-patterns.d.ts +21 -0
- package/dist/tools/find-patterns.js +457 -0
- package/dist/tools/find-references.d.ts +23 -0
- package/dist/tools/find-references.js +488 -0
- package/dist/tools/find-symbol.d.ts +21 -0
- package/dist/tools/find-symbol.js +458 -0
- package/dist/tools/get-call-graph.d.ts +23 -0
- package/dist/tools/get-call-graph.js +469 -0
- package/dist/tools/get-complexity.d.ts +21 -0
- package/dist/tools/get-complexity.js +394 -0
- package/dist/tools/get-dependency-graph.d.ts +23 -0
- package/dist/tools/get-dependency-graph.js +482 -0
- package/dist/tools/get-documentation.d.ts +21 -0
- package/dist/tools/get-documentation.js +613 -0
- package/dist/tools/get-exports.d.ts +21 -0
- package/dist/tools/get-exports.js +427 -0
- package/dist/tools/get-file-structure.d.ts +27 -0
- package/dist/tools/get-file-structure.js +120 -0
- package/dist/tools/get-imports.d.ts +23 -0
- package/dist/tools/get-imports.js +350 -0
- package/dist/tools/get-signature.d.ts +20 -0
- package/dist/tools/get-signature.js +758 -0
- package/dist/tools/get-type-hierarchy.d.ts +22 -0
- package/dist/tools/get-type-hierarchy.js +485 -0
- package/dist/tools/index.d.ts +23 -0
- package/dist/tools/index.js +25 -0
- package/dist/tools/types.d.ts +1302 -0
- package/dist/tools/types.js +7 -0
- package/package.json +84 -0
|
@@ -0,0 +1,613 @@
|
|
|
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 * 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 = `Extract JSDoc/TSDoc documentation from source files.
|
|
14
|
+
Returns documented symbols with their signatures and documentation content.
|
|
15
|
+
Calculates documentation coverage metrics and identifies undocumented exports.
|
|
16
|
+
Useful for generating API documentation and measuring doc coverage.`;
|
|
17
|
+
// Tool input schema
|
|
18
|
+
const TOOL_INPUT_SCHEMA = {
|
|
19
|
+
type: 'object',
|
|
20
|
+
properties: {
|
|
21
|
+
path: {
|
|
22
|
+
type: 'string',
|
|
23
|
+
description: 'File or directory to analyze',
|
|
24
|
+
},
|
|
25
|
+
recursive: {
|
|
26
|
+
type: 'boolean',
|
|
27
|
+
description: 'Recursive analysis for directories (default: false)',
|
|
28
|
+
default: false,
|
|
29
|
+
},
|
|
30
|
+
kinds: {
|
|
31
|
+
type: 'array',
|
|
32
|
+
items: { type: 'string' },
|
|
33
|
+
description: 'Filter by symbol kinds: function, class, interface, type, enum, variable, method, property',
|
|
34
|
+
},
|
|
35
|
+
exportedOnly: {
|
|
36
|
+
type: 'boolean',
|
|
37
|
+
description: 'Only include exported symbols (default: false)',
|
|
38
|
+
default: false,
|
|
39
|
+
},
|
|
40
|
+
documentedOnly: {
|
|
41
|
+
type: 'boolean',
|
|
42
|
+
description: 'Only include symbols with documentation (default: true)',
|
|
43
|
+
default: true,
|
|
44
|
+
},
|
|
45
|
+
maxFiles: {
|
|
46
|
+
type: 'number',
|
|
47
|
+
description: 'Maximum files to analyze (default: 50)',
|
|
48
|
+
default: 50,
|
|
49
|
+
},
|
|
50
|
+
includePrivate: {
|
|
51
|
+
type: 'boolean',
|
|
52
|
+
description: 'Include private class members (default: false)',
|
|
53
|
+
default: false,
|
|
54
|
+
},
|
|
55
|
+
},
|
|
56
|
+
required: ['path'],
|
|
57
|
+
};
|
|
58
|
+
// Default exclusions
|
|
59
|
+
const DEFAULT_EXCLUDE = ['node_modules', 'dist', 'build', '.git', 'coverage'];
|
|
60
|
+
/**
|
|
61
|
+
* getDocumentation tool
|
|
62
|
+
*/
|
|
63
|
+
export const getDocumentationTool = defineTool({
|
|
64
|
+
name: 'get_documentation',
|
|
65
|
+
description: TOOL_DESCRIPTION,
|
|
66
|
+
inputSchema: TOOL_INPUT_SCHEMA,
|
|
67
|
+
execute: executeGetDocumentation,
|
|
68
|
+
});
|
|
69
|
+
/**
|
|
70
|
+
* Execute the getDocumentation tool
|
|
71
|
+
*/
|
|
72
|
+
async function executeGetDocumentation(input) {
|
|
73
|
+
const { path: inputPath, recursive = false, kinds, exportedOnly = false, documentedOnly = true, maxFiles = 50, includePrivate = false, } = input;
|
|
74
|
+
try {
|
|
75
|
+
const resolvedPath = path.resolve(inputPath);
|
|
76
|
+
// Check if path exists
|
|
77
|
+
try {
|
|
78
|
+
await fs.access(resolvedPath);
|
|
79
|
+
}
|
|
80
|
+
catch {
|
|
81
|
+
return createErrorResult(`Path not found: ${resolvedPath}`);
|
|
82
|
+
}
|
|
83
|
+
const stats = await fs.stat(resolvedPath);
|
|
84
|
+
const files = [];
|
|
85
|
+
if (stats.isDirectory()) {
|
|
86
|
+
await collectFiles(resolvedPath, files, recursive ? 10 : 0, maxFiles);
|
|
87
|
+
}
|
|
88
|
+
else if (stats.isFile()) {
|
|
89
|
+
files.push(resolvedPath);
|
|
90
|
+
}
|
|
91
|
+
// Analyze each file
|
|
92
|
+
const fileResults = [];
|
|
93
|
+
let totalSymbols = 0;
|
|
94
|
+
let documentedSymbols = 0;
|
|
95
|
+
let undocumentedSymbols = 0;
|
|
96
|
+
const undocumentedExports = [];
|
|
97
|
+
for (const file of files) {
|
|
98
|
+
const fileResult = await analyzeFile(file, {
|
|
99
|
+
kinds,
|
|
100
|
+
exportedOnly,
|
|
101
|
+
documentedOnly,
|
|
102
|
+
includePrivate,
|
|
103
|
+
});
|
|
104
|
+
if (fileResult) {
|
|
105
|
+
fileResults.push(fileResult);
|
|
106
|
+
// Update totals
|
|
107
|
+
totalSymbols += fileResult.stats.totalSymbols;
|
|
108
|
+
documentedSymbols += fileResult.stats.documentedSymbols;
|
|
109
|
+
undocumentedSymbols += fileResult.stats.undocumentedSymbols;
|
|
110
|
+
// Track undocumented exports
|
|
111
|
+
for (const symbol of fileResult.symbols) {
|
|
112
|
+
if (symbol.exported && !hasDocumentation(symbol.documentation)) {
|
|
113
|
+
undocumentedExports.push({
|
|
114
|
+
name: symbol.name,
|
|
115
|
+
kind: symbol.kind,
|
|
116
|
+
path: file,
|
|
117
|
+
line: symbol.line,
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
const coveragePercent = totalSymbols > 0 ? Math.round((documentedSymbols / totalSymbols) * 100) : 100;
|
|
124
|
+
const result = {
|
|
125
|
+
path: resolvedPath,
|
|
126
|
+
files: fileResults,
|
|
127
|
+
summary: {
|
|
128
|
+
totalFiles: fileResults.length,
|
|
129
|
+
totalSymbols,
|
|
130
|
+
documentedSymbols,
|
|
131
|
+
undocumentedSymbols,
|
|
132
|
+
coveragePercent,
|
|
133
|
+
},
|
|
134
|
+
undocumentedExports: undocumentedExports.length > 0 ? undocumentedExports : undefined,
|
|
135
|
+
};
|
|
136
|
+
return createSuccessResult(result);
|
|
137
|
+
}
|
|
138
|
+
catch (error) {
|
|
139
|
+
return createErrorResult(`Failed to extract documentation: ${error instanceof Error ? error.message : String(error)}`);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* Check if documentation has meaningful content
|
|
144
|
+
*/
|
|
145
|
+
function hasDocumentation(doc) {
|
|
146
|
+
return !!(doc.summary ||
|
|
147
|
+
doc.description ||
|
|
148
|
+
doc.params ||
|
|
149
|
+
doc.returns ||
|
|
150
|
+
doc.examples?.length ||
|
|
151
|
+
doc.throws?.length);
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* Collect files to analyze
|
|
155
|
+
*/
|
|
156
|
+
async function collectFiles(dirPath, files, maxDepth, maxFiles, currentDepth = 0) {
|
|
157
|
+
if (currentDepth > maxDepth || files.length >= maxFiles)
|
|
158
|
+
return;
|
|
159
|
+
try {
|
|
160
|
+
const entries = await fs.readdir(dirPath, { withFileTypes: true });
|
|
161
|
+
for (const entry of entries) {
|
|
162
|
+
if (files.length >= maxFiles)
|
|
163
|
+
break;
|
|
164
|
+
const fullPath = path.join(dirPath, entry.name);
|
|
165
|
+
// Skip excluded directories
|
|
166
|
+
if (entry.isDirectory()) {
|
|
167
|
+
if (DEFAULT_EXCLUDE.includes(entry.name))
|
|
168
|
+
continue;
|
|
169
|
+
await collectFiles(fullPath, files, maxDepth, maxFiles, currentDepth + 1);
|
|
170
|
+
}
|
|
171
|
+
else if (entry.isFile()) {
|
|
172
|
+
// Only include TypeScript/JavaScript files
|
|
173
|
+
if (/\.(ts|tsx|js|jsx)$/.test(entry.name) && !entry.name.endsWith('.d.ts')) {
|
|
174
|
+
files.push(fullPath);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
catch {
|
|
180
|
+
// Ignore permission errors
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
/**
|
|
184
|
+
* Analyze documentation of a single file
|
|
185
|
+
*/
|
|
186
|
+
async function analyzeFile(filePath, options) {
|
|
187
|
+
try {
|
|
188
|
+
const content = await fs.readFile(filePath, 'utf-8');
|
|
189
|
+
const detection = detectLanguage(filePath);
|
|
190
|
+
if (!detection.language || !isLanguageSupported(detection.language)) {
|
|
191
|
+
return null;
|
|
192
|
+
}
|
|
193
|
+
const sourceFile = ts.createSourceFile(filePath, content, ts.ScriptTarget.Latest, true, filePath.endsWith('.tsx') ? ts.ScriptKind.TSX : ts.ScriptKind.TS);
|
|
194
|
+
// Extract file-level documentation
|
|
195
|
+
const fileDoc = extractFileDocumentation(sourceFile);
|
|
196
|
+
// Extract symbol documentation
|
|
197
|
+
const allSymbols = [];
|
|
198
|
+
extractSymbols(sourceFile, sourceFile, allSymbols, options);
|
|
199
|
+
// Apply filters
|
|
200
|
+
let symbols = allSymbols;
|
|
201
|
+
if (options.kinds && options.kinds.length > 0) {
|
|
202
|
+
const kinds = options.kinds;
|
|
203
|
+
symbols = symbols.filter((s) => kinds.includes(s.kind));
|
|
204
|
+
}
|
|
205
|
+
if (options.exportedOnly) {
|
|
206
|
+
symbols = symbols.filter((s) => s.exported);
|
|
207
|
+
}
|
|
208
|
+
if (options.documentedOnly) {
|
|
209
|
+
symbols = symbols.filter((s) => hasDocumentation(s.documentation));
|
|
210
|
+
}
|
|
211
|
+
// Calculate stats based on all symbols (before documentedOnly filter)
|
|
212
|
+
const totalSymbols = allSymbols.length;
|
|
213
|
+
const documentedCount = allSymbols.filter((s) => hasDocumentation(s.documentation)).length;
|
|
214
|
+
const undocumentedCount = totalSymbols - documentedCount;
|
|
215
|
+
const coveragePercent = totalSymbols > 0 ? Math.round((documentedCount / totalSymbols) * 100) : 100;
|
|
216
|
+
return {
|
|
217
|
+
path: filePath,
|
|
218
|
+
fileDoc: fileDoc && hasDocumentation(fileDoc) ? fileDoc : undefined,
|
|
219
|
+
symbols,
|
|
220
|
+
stats: {
|
|
221
|
+
totalSymbols,
|
|
222
|
+
documentedSymbols: documentedCount,
|
|
223
|
+
undocumentedSymbols: undocumentedCount,
|
|
224
|
+
coveragePercent,
|
|
225
|
+
},
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
catch {
|
|
229
|
+
return null;
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
/**
|
|
233
|
+
* Extract file-level documentation (module doc comment at top of file)
|
|
234
|
+
*/
|
|
235
|
+
function extractFileDocumentation(sourceFile) {
|
|
236
|
+
// Look for JSDoc at the very beginning of the file
|
|
237
|
+
const text = sourceFile.getFullText();
|
|
238
|
+
const match = text.match(/^\s*\/\*\*([\s\S]*?)\*\//);
|
|
239
|
+
if (!match)
|
|
240
|
+
return undefined;
|
|
241
|
+
const commentText = match[1];
|
|
242
|
+
const lines = commentText.split('\n').map((l) => l.replace(/^\s*\*\s?/, '').trim());
|
|
243
|
+
// Filter out @module and similar file-level tags, and empty lines
|
|
244
|
+
const descLines = [];
|
|
245
|
+
let isFileLevel = false;
|
|
246
|
+
for (const line of lines) {
|
|
247
|
+
if (line.startsWith('@module') ||
|
|
248
|
+
line.startsWith('@fileoverview') ||
|
|
249
|
+
line.startsWith('@file')) {
|
|
250
|
+
isFileLevel = true;
|
|
251
|
+
}
|
|
252
|
+
else if (!line.startsWith('@') && line.length > 0) {
|
|
253
|
+
descLines.push(line);
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
// Only return if this looks like a file-level comment
|
|
257
|
+
if (!isFileLevel && descLines.length === 0)
|
|
258
|
+
return undefined;
|
|
259
|
+
const summary = descLines[0] || '';
|
|
260
|
+
const description = descLines.length > 1 ? descLines.slice(1).join('\n').trim() : undefined;
|
|
261
|
+
return {
|
|
262
|
+
summary,
|
|
263
|
+
description,
|
|
264
|
+
};
|
|
265
|
+
}
|
|
266
|
+
/**
|
|
267
|
+
* Extract documented symbols from a node
|
|
268
|
+
*/
|
|
269
|
+
function extractSymbols(node, sourceFile, symbols, options, _containerName) {
|
|
270
|
+
// Function declarations
|
|
271
|
+
if (ts.isFunctionDeclaration(node) && node.name) {
|
|
272
|
+
symbols.push(extractSymbolInfo(node, sourceFile, 'function', node.name.text));
|
|
273
|
+
}
|
|
274
|
+
// Variable declarations (arrow functions, const values)
|
|
275
|
+
else if (ts.isVariableStatement(node)) {
|
|
276
|
+
for (const decl of node.declarationList.declarations) {
|
|
277
|
+
if (ts.isIdentifier(decl.name)) {
|
|
278
|
+
const kind = decl.initializer &&
|
|
279
|
+
(ts.isArrowFunction(decl.initializer) || ts.isFunctionExpression(decl.initializer))
|
|
280
|
+
? 'function'
|
|
281
|
+
: 'variable';
|
|
282
|
+
symbols.push(extractSymbolInfo(node, sourceFile, kind, decl.name.text));
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
// Class declarations
|
|
287
|
+
else if (ts.isClassDeclaration(node) && node.name) {
|
|
288
|
+
symbols.push(extractSymbolInfo(node, sourceFile, 'class', node.name.text));
|
|
289
|
+
// Extract class members
|
|
290
|
+
for (const member of node.members) {
|
|
291
|
+
// Skip private members if not included
|
|
292
|
+
if (!options.includePrivate && isPrivateMember(member))
|
|
293
|
+
continue;
|
|
294
|
+
if (ts.isMethodDeclaration(member) && ts.isIdentifier(member.name)) {
|
|
295
|
+
symbols.push(extractSymbolInfo(member, sourceFile, 'method', member.name.text, node.name.text));
|
|
296
|
+
}
|
|
297
|
+
else if (ts.isPropertyDeclaration(member) && ts.isIdentifier(member.name)) {
|
|
298
|
+
symbols.push(extractSymbolInfo(member, sourceFile, 'property', member.name.text, node.name.text));
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
// Interface declarations
|
|
303
|
+
else if (ts.isInterfaceDeclaration(node)) {
|
|
304
|
+
symbols.push(extractSymbolInfo(node, sourceFile, 'interface', node.name.text));
|
|
305
|
+
// Extract interface members
|
|
306
|
+
for (const member of node.members) {
|
|
307
|
+
if (ts.isPropertySignature(member) && ts.isIdentifier(member.name)) {
|
|
308
|
+
symbols.push(extractSymbolInfo(member, sourceFile, 'property', member.name.text, node.name.text));
|
|
309
|
+
}
|
|
310
|
+
else if (ts.isMethodSignature(member) && ts.isIdentifier(member.name)) {
|
|
311
|
+
symbols.push(extractSymbolInfo(member, sourceFile, 'method', member.name.text, node.name.text));
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
// Type alias declarations
|
|
316
|
+
else if (ts.isTypeAliasDeclaration(node)) {
|
|
317
|
+
symbols.push(extractSymbolInfo(node, sourceFile, 'type', node.name.text));
|
|
318
|
+
}
|
|
319
|
+
// Enum declarations
|
|
320
|
+
else if (ts.isEnumDeclaration(node)) {
|
|
321
|
+
symbols.push(extractSymbolInfo(node, sourceFile, 'enum', node.name.text));
|
|
322
|
+
}
|
|
323
|
+
// Continue traversing (but skip into class/interface bodies as they're handled above)
|
|
324
|
+
if (!ts.isClassDeclaration(node) && !ts.isInterfaceDeclaration(node)) {
|
|
325
|
+
ts.forEachChild(node, (child) => {
|
|
326
|
+
extractSymbols(child, sourceFile, symbols, options);
|
|
327
|
+
});
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
/**
|
|
331
|
+
* Check if a class member is private
|
|
332
|
+
*/
|
|
333
|
+
function isPrivateMember(node) {
|
|
334
|
+
if (!ts.canHaveModifiers(node))
|
|
335
|
+
return false;
|
|
336
|
+
const modifiers = ts.getModifiers(node);
|
|
337
|
+
return modifiers?.some((m) => m.kind === ts.SyntaxKind.PrivateKeyword) ?? false;
|
|
338
|
+
}
|
|
339
|
+
/**
|
|
340
|
+
* Extract symbol information
|
|
341
|
+
*/
|
|
342
|
+
function extractSymbolInfo(node, sourceFile, kind, name, containerName) {
|
|
343
|
+
const { line } = sourceFile.getLineAndCharacterOfPosition(node.getStart());
|
|
344
|
+
const exported = isExported(node);
|
|
345
|
+
const documentation = extractDocumentation(node, sourceFile);
|
|
346
|
+
const signature = extractSignature(node, sourceFile, kind);
|
|
347
|
+
return {
|
|
348
|
+
name,
|
|
349
|
+
kind,
|
|
350
|
+
line: line + 1,
|
|
351
|
+
exported,
|
|
352
|
+
signature,
|
|
353
|
+
documentation,
|
|
354
|
+
container: containerName,
|
|
355
|
+
};
|
|
356
|
+
}
|
|
357
|
+
/**
|
|
358
|
+
* Check if node is exported
|
|
359
|
+
*/
|
|
360
|
+
function isExported(node) {
|
|
361
|
+
// For variable declarations, check the parent VariableStatement
|
|
362
|
+
if (ts.isVariableDeclaration(node)) {
|
|
363
|
+
const varDeclList = node.parent;
|
|
364
|
+
const varStatement = varDeclList.parent;
|
|
365
|
+
if (ts.canHaveModifiers(varStatement)) {
|
|
366
|
+
const modifiers = ts.getModifiers(varStatement);
|
|
367
|
+
return modifiers?.some((m) => m.kind === ts.SyntaxKind.ExportKeyword) ?? false;
|
|
368
|
+
}
|
|
369
|
+
return false;
|
|
370
|
+
}
|
|
371
|
+
// For property/method in class/interface - inherit from parent
|
|
372
|
+
if (ts.isPropertyDeclaration(node) ||
|
|
373
|
+
ts.isMethodDeclaration(node) ||
|
|
374
|
+
ts.isPropertySignature(node) ||
|
|
375
|
+
ts.isMethodSignature(node)) {
|
|
376
|
+
const parent = node.parent;
|
|
377
|
+
if (ts.isClassDeclaration(parent) || ts.isInterfaceDeclaration(parent)) {
|
|
378
|
+
return isExported(parent);
|
|
379
|
+
}
|
|
380
|
+
return false;
|
|
381
|
+
}
|
|
382
|
+
if (!ts.canHaveModifiers(node))
|
|
383
|
+
return false;
|
|
384
|
+
const modifiers = ts.getModifiers(node);
|
|
385
|
+
return modifiers?.some((m) => m.kind === ts.SyntaxKind.ExportKeyword) ?? false;
|
|
386
|
+
}
|
|
387
|
+
/**
|
|
388
|
+
* Extract signature from node
|
|
389
|
+
*/
|
|
390
|
+
function extractSignature(node, sourceFile, kind) {
|
|
391
|
+
switch (kind) {
|
|
392
|
+
case 'function': {
|
|
393
|
+
if (ts.isFunctionDeclaration(node) && node.name) {
|
|
394
|
+
const params = node.parameters.map((p) => p.getText(sourceFile)).join(', ');
|
|
395
|
+
const returnType = node.type ? `: ${node.type.getText(sourceFile)}` : '';
|
|
396
|
+
const generics = node.typeParameters
|
|
397
|
+
? `<${node.typeParameters.map((t) => t.getText(sourceFile)).join(', ')}>`
|
|
398
|
+
: '';
|
|
399
|
+
return `function ${node.name.text}${generics}(${params})${returnType}`;
|
|
400
|
+
}
|
|
401
|
+
if (ts.isVariableStatement(node)) {
|
|
402
|
+
const decl = node.declarationList.declarations[0];
|
|
403
|
+
if (ts.isIdentifier(decl.name)) {
|
|
404
|
+
if (decl.initializer &&
|
|
405
|
+
(ts.isArrowFunction(decl.initializer) || ts.isFunctionExpression(decl.initializer))) {
|
|
406
|
+
const func = decl.initializer;
|
|
407
|
+
const params = func.parameters.map((p) => p.getText(sourceFile)).join(', ');
|
|
408
|
+
const returnType = func.type ? `: ${func.type.getText(sourceFile)}` : '';
|
|
409
|
+
return `const ${decl.name.text} = (${params})${returnType} => ...`;
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
return node.getText(sourceFile).split('\n')[0];
|
|
414
|
+
}
|
|
415
|
+
case 'class': {
|
|
416
|
+
if (ts.isClassDeclaration(node) && node.name) {
|
|
417
|
+
const generics = node.typeParameters
|
|
418
|
+
? `<${node.typeParameters.map((t) => t.getText(sourceFile)).join(', ')}>`
|
|
419
|
+
: '';
|
|
420
|
+
let heritage = '';
|
|
421
|
+
if (node.heritageClauses) {
|
|
422
|
+
for (const clause of node.heritageClauses) {
|
|
423
|
+
const tokenKind = clause.token;
|
|
424
|
+
if (tokenKind === ts.SyntaxKind.ExtendsKeyword) {
|
|
425
|
+
heritage += ` extends ${clause.types.map((t) => t.getText(sourceFile)).join(', ')}`;
|
|
426
|
+
}
|
|
427
|
+
else if (tokenKind === ts.SyntaxKind.ImplementsKeyword) {
|
|
428
|
+
heritage += ` implements ${clause.types.map((t) => t.getText(sourceFile)).join(', ')}`;
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
return `class ${node.name.text}${generics}${heritage}`;
|
|
433
|
+
}
|
|
434
|
+
return node.getText(sourceFile).split('\n')[0];
|
|
435
|
+
}
|
|
436
|
+
case 'interface': {
|
|
437
|
+
if (ts.isInterfaceDeclaration(node)) {
|
|
438
|
+
const generics = node.typeParameters
|
|
439
|
+
? `<${node.typeParameters.map((t) => t.getText(sourceFile)).join(', ')}>`
|
|
440
|
+
: '';
|
|
441
|
+
let heritage = '';
|
|
442
|
+
if (node.heritageClauses) {
|
|
443
|
+
for (const clause of node.heritageClauses) {
|
|
444
|
+
heritage += ` extends ${clause.types.map((t) => t.getText(sourceFile)).join(', ')}`;
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
return `interface ${node.name.text}${generics}${heritage}`;
|
|
448
|
+
}
|
|
449
|
+
return node.getText(sourceFile).split('\n')[0];
|
|
450
|
+
}
|
|
451
|
+
case 'type': {
|
|
452
|
+
if (ts.isTypeAliasDeclaration(node)) {
|
|
453
|
+
const generics = node.typeParameters
|
|
454
|
+
? `<${node.typeParameters.map((t) => t.getText(sourceFile)).join(', ')}>`
|
|
455
|
+
: '';
|
|
456
|
+
const typeText = node.type.getText(sourceFile);
|
|
457
|
+
// Truncate long types
|
|
458
|
+
const maxLen = 80;
|
|
459
|
+
const truncatedType = typeText.length > maxLen ? typeText.slice(0, maxLen) + '...' : typeText;
|
|
460
|
+
return `type ${node.name.text}${generics} = ${truncatedType}`;
|
|
461
|
+
}
|
|
462
|
+
return node.getText(sourceFile).split('\n')[0];
|
|
463
|
+
}
|
|
464
|
+
case 'enum': {
|
|
465
|
+
if (ts.isEnumDeclaration(node)) {
|
|
466
|
+
const members = node.members.map((m) => m.name.getText(sourceFile)).slice(0, 5);
|
|
467
|
+
const suffix = node.members.length > 5 ? ', ...' : '';
|
|
468
|
+
return `enum ${node.name.text} { ${members.join(', ')}${suffix} }`;
|
|
469
|
+
}
|
|
470
|
+
return node.getText(sourceFile).split('\n')[0];
|
|
471
|
+
}
|
|
472
|
+
case 'variable': {
|
|
473
|
+
if (ts.isVariableStatement(node)) {
|
|
474
|
+
const decl = node.declarationList.declarations[0];
|
|
475
|
+
if (ts.isIdentifier(decl.name)) {
|
|
476
|
+
const typeText = decl.type ? `: ${decl.type.getText(sourceFile)}` : '';
|
|
477
|
+
return `const ${decl.name.text}${typeText}`;
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
return node.getText(sourceFile).split('\n')[0];
|
|
481
|
+
}
|
|
482
|
+
case 'method': {
|
|
483
|
+
if (ts.isMethodDeclaration(node) && ts.isIdentifier(node.name)) {
|
|
484
|
+
const params = node.parameters.map((p) => p.getText(sourceFile)).join(', ');
|
|
485
|
+
const returnType = node.type ? `: ${node.type.getText(sourceFile)}` : '';
|
|
486
|
+
return `${node.name.text}(${params})${returnType}`;
|
|
487
|
+
}
|
|
488
|
+
if (ts.isMethodSignature(node) && ts.isIdentifier(node.name)) {
|
|
489
|
+
const params = node.parameters.map((p) => p.getText(sourceFile)).join(', ');
|
|
490
|
+
const returnType = node.type ? `: ${node.type.getText(sourceFile)}` : '';
|
|
491
|
+
return `${node.name.text}(${params})${returnType}`;
|
|
492
|
+
}
|
|
493
|
+
return node.getText(sourceFile).split('\n')[0];
|
|
494
|
+
}
|
|
495
|
+
case 'property': {
|
|
496
|
+
if (ts.isPropertyDeclaration(node) && ts.isIdentifier(node.name)) {
|
|
497
|
+
const typeText = node.type ? `: ${node.type.getText(sourceFile)}` : '';
|
|
498
|
+
const optional = node.questionToken ? '?' : '';
|
|
499
|
+
return `${node.name.text}${optional}${typeText}`;
|
|
500
|
+
}
|
|
501
|
+
if (ts.isPropertySignature(node) && ts.isIdentifier(node.name)) {
|
|
502
|
+
const typeText = node.type ? `: ${node.type.getText(sourceFile)}` : '';
|
|
503
|
+
const optional = node.questionToken ? '?' : '';
|
|
504
|
+
return `${node.name.text}${optional}${typeText}`;
|
|
505
|
+
}
|
|
506
|
+
return node.getText(sourceFile).split('\n')[0];
|
|
507
|
+
}
|
|
508
|
+
default:
|
|
509
|
+
return node.getText(sourceFile).split('\n')[0];
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
/**
|
|
513
|
+
* Extract JSDoc documentation from a node
|
|
514
|
+
*/
|
|
515
|
+
function extractDocumentation(node, sourceFile) {
|
|
516
|
+
const jsDocTags = ts.getJSDocTags(node);
|
|
517
|
+
const jsDocComments = node.jsDoc;
|
|
518
|
+
const doc = {
|
|
519
|
+
summary: '',
|
|
520
|
+
};
|
|
521
|
+
// Get comment text
|
|
522
|
+
if (jsDocComments?.length) {
|
|
523
|
+
const firstDoc = jsDocComments[0];
|
|
524
|
+
if (typeof firstDoc.comment === 'string') {
|
|
525
|
+
const lines = firstDoc.comment.split('\n');
|
|
526
|
+
doc.summary = lines[0].trim();
|
|
527
|
+
if (lines.length > 1) {
|
|
528
|
+
doc.description = lines.slice(1).join('\n').trim();
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
else if (firstDoc.comment && typeof firstDoc.comment !== 'string') {
|
|
532
|
+
// JSDocComment can be an array of JSDocText | JSDocLink
|
|
533
|
+
const commentParts = firstDoc.comment;
|
|
534
|
+
const text = commentParts
|
|
535
|
+
.map((c) => (c.kind === ts.SyntaxKind.JSDocText ? c.text : ''))
|
|
536
|
+
.join('')
|
|
537
|
+
.trim();
|
|
538
|
+
const lines = text.split('\n');
|
|
539
|
+
doc.summary = lines[0].trim();
|
|
540
|
+
if (lines.length > 1) {
|
|
541
|
+
doc.description = lines.slice(1).join('\n').trim();
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
// Process tags
|
|
546
|
+
const params = {};
|
|
547
|
+
const throws = [];
|
|
548
|
+
const examples = [];
|
|
549
|
+
const see = [];
|
|
550
|
+
for (const tag of jsDocTags) {
|
|
551
|
+
const tagName = tag.tagName.text;
|
|
552
|
+
let tagComment = '';
|
|
553
|
+
if (typeof tag.comment === 'string') {
|
|
554
|
+
tagComment = tag.comment;
|
|
555
|
+
}
|
|
556
|
+
else if (tag.comment && typeof tag.comment !== 'string') {
|
|
557
|
+
const commentParts = tag.comment;
|
|
558
|
+
tagComment = commentParts
|
|
559
|
+
.map((c) => (c.kind === ts.SyntaxKind.JSDocText ? c.text : ''))
|
|
560
|
+
.join('')
|
|
561
|
+
.trim();
|
|
562
|
+
}
|
|
563
|
+
if (tagName === 'param' && ts.isJSDocParameterTag(tag)) {
|
|
564
|
+
const paramName = tag.name.getText(sourceFile);
|
|
565
|
+
params[paramName] = tagComment;
|
|
566
|
+
}
|
|
567
|
+
else if (tagName === 'returns' || tagName === 'return') {
|
|
568
|
+
doc.returns = tagComment;
|
|
569
|
+
}
|
|
570
|
+
else if (tagName === 'throws' || tagName === 'exception') {
|
|
571
|
+
throws.push(tagComment);
|
|
572
|
+
}
|
|
573
|
+
else if (tagName === 'example') {
|
|
574
|
+
examples.push(tagComment);
|
|
575
|
+
}
|
|
576
|
+
else if (tagName === 'see') {
|
|
577
|
+
see.push(tagComment);
|
|
578
|
+
}
|
|
579
|
+
else if (ts.isJSDocDeprecatedTag(tag)) {
|
|
580
|
+
doc.deprecatedMessage = tagComment || 'This API is deprecated';
|
|
581
|
+
}
|
|
582
|
+
else if (tagName === 'since') {
|
|
583
|
+
doc.since = tagComment;
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
if (Object.keys(params).length > 0)
|
|
587
|
+
doc.params = params;
|
|
588
|
+
if (throws.length > 0)
|
|
589
|
+
doc.throws = throws;
|
|
590
|
+
if (examples.length > 0)
|
|
591
|
+
doc.examples = examples;
|
|
592
|
+
if (see.length > 0)
|
|
593
|
+
doc.see = see;
|
|
594
|
+
return doc;
|
|
595
|
+
}
|
|
596
|
+
/**
|
|
597
|
+
* Create customizable getDocumentation tool
|
|
598
|
+
*/
|
|
599
|
+
export function createGetDocumentationTool(options) {
|
|
600
|
+
return defineTool({
|
|
601
|
+
name: options?.name ?? 'get_documentation',
|
|
602
|
+
description: options?.description ?? TOOL_DESCRIPTION,
|
|
603
|
+
inputSchema: TOOL_INPUT_SCHEMA,
|
|
604
|
+
execute: async (input) => {
|
|
605
|
+
const modifiedInput = {
|
|
606
|
+
...input,
|
|
607
|
+
maxFiles: input.maxFiles ?? options?.defaultMaxFiles,
|
|
608
|
+
documentedOnly: input.documentedOnly ?? options?.defaultDocumentedOnly,
|
|
609
|
+
};
|
|
610
|
+
return executeGetDocumentation(modifiedInput);
|
|
611
|
+
},
|
|
612
|
+
});
|
|
613
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* getExports Tool
|
|
3
|
+
*
|
|
4
|
+
* Get all exports from a module.
|
|
5
|
+
* Uses TypeScript Compiler API for accurate AST analysis.
|
|
6
|
+
*/
|
|
7
|
+
import type { Tool } from '@compilr-dev/agents';
|
|
8
|
+
import type { GetExportsInput } from './types.js';
|
|
9
|
+
/**
|
|
10
|
+
* getExports tool - Get all exports from a module
|
|
11
|
+
*/
|
|
12
|
+
export declare const getExportsTool: Tool<GetExportsInput>;
|
|
13
|
+
/**
|
|
14
|
+
* Factory function to create a customized getExports tool
|
|
15
|
+
*/
|
|
16
|
+
export declare function createGetExportsTool(options?: {
|
|
17
|
+
/** Default includeReExports */
|
|
18
|
+
defaultIncludeReExports?: boolean;
|
|
19
|
+
/** Default resolveReExports */
|
|
20
|
+
defaultResolveReExports?: boolean;
|
|
21
|
+
}): Tool<GetExportsInput>;
|