@compilr-dev/agents-coding-ts 0.1.3 → 0.1.4
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/dist/index.d.ts +8 -5
- package/dist/index.js +14 -4
- package/dist/parser/index.d.ts +2 -2
- package/dist/parser/index.js +1 -1
- package/dist/parser/typescript-parser.d.ts +2 -2
- package/dist/parser/typescript-parser.js +77 -54
- package/dist/skills/code-health.js +5 -5
- package/dist/skills/code-structure.js +5 -5
- package/dist/skills/dependency-audit.js +5 -5
- package/dist/skills/index.d.ts +7 -7
- package/dist/skills/index.js +11 -11
- package/dist/skills/refactor-impact.js +5 -5
- package/dist/skills/type-analysis.js +5 -5
- package/dist/tools/find-dead-code.d.ts +2 -2
- package/dist/tools/find-dead-code.js +82 -58
- package/dist/tools/find-duplicates.d.ts +2 -2
- package/dist/tools/find-duplicates.js +41 -38
- package/dist/tools/find-implementations.d.ts +2 -2
- package/dist/tools/find-implementations.js +44 -36
- package/dist/tools/find-patterns.d.ts +2 -2
- package/dist/tools/find-patterns.js +154 -148
- package/dist/tools/find-references.d.ts +2 -2
- package/dist/tools/find-references.js +76 -72
- package/dist/tools/find-symbol.d.ts +2 -2
- package/dist/tools/find-symbol.js +106 -96
- package/dist/tools/get-call-graph.d.ts +2 -2
- package/dist/tools/get-call-graph.js +52 -47
- package/dist/tools/get-complexity.d.ts +2 -2
- package/dist/tools/get-complexity.js +94 -46
- package/dist/tools/get-dependency-graph.d.ts +2 -2
- package/dist/tools/get-dependency-graph.js +66 -52
- package/dist/tools/get-documentation.d.ts +2 -2
- package/dist/tools/get-documentation.js +154 -122
- package/dist/tools/get-exports.d.ts +2 -2
- package/dist/tools/get-exports.js +73 -61
- package/dist/tools/get-file-structure.d.ts +2 -2
- package/dist/tools/get-file-structure.js +16 -16
- package/dist/tools/get-imports.d.ts +2 -2
- package/dist/tools/get-imports.js +46 -46
- package/dist/tools/get-signature.d.ts +2 -2
- package/dist/tools/get-signature.js +168 -124
- package/dist/tools/get-type-hierarchy.d.ts +2 -2
- package/dist/tools/get-type-hierarchy.js +53 -44
- package/dist/tools/index.d.ts +18 -16
- package/dist/tools/index.js +17 -15
- package/dist/tools/read-symbol.d.ts +62 -0
- package/dist/tools/read-symbol.js +464 -0
- package/dist/tools/types.d.ts +27 -27
- package/package.json +1 -1
|
@@ -4,21 +4,21 @@
|
|
|
4
4
|
* Find the definition location of a symbol across the codebase.
|
|
5
5
|
* Uses TypeScript Compiler API for accurate AST analysis.
|
|
6
6
|
*/
|
|
7
|
-
import * as fs from
|
|
8
|
-
import * as path from
|
|
9
|
-
import * as ts from
|
|
10
|
-
import { defineTool, createSuccessResult, createErrorResult } from
|
|
11
|
-
import { detectLanguage, isLanguageSupported } from
|
|
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
|
// Supported file extensions for searching
|
|
13
13
|
const SUPPORTED_EXTENSIONS = new Set([
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
14
|
+
".ts",
|
|
15
|
+
".tsx",
|
|
16
|
+
".js",
|
|
17
|
+
".jsx",
|
|
18
|
+
".mts",
|
|
19
|
+
".mjs",
|
|
20
|
+
".cts",
|
|
21
|
+
".cjs",
|
|
22
22
|
]);
|
|
23
23
|
// Helper to check for a specific modifier kind
|
|
24
24
|
function hasModifierKind(modifiers, kind) {
|
|
@@ -30,50 +30,50 @@ Returns structured JSON with file path, line number, symbol kind, and other meta
|
|
|
30
30
|
Use this to locate where functions, classes, variables, types, etc. are defined.`;
|
|
31
31
|
// Tool input schema
|
|
32
32
|
const TOOL_INPUT_SCHEMA = {
|
|
33
|
-
type:
|
|
33
|
+
type: "object",
|
|
34
34
|
properties: {
|
|
35
35
|
symbol: {
|
|
36
|
-
type:
|
|
37
|
-
description:
|
|
36
|
+
type: "string",
|
|
37
|
+
description: "Symbol name to find",
|
|
38
38
|
},
|
|
39
39
|
scope: {
|
|
40
|
-
type:
|
|
41
|
-
description:
|
|
40
|
+
type: "string",
|
|
41
|
+
description: "Scope the search to a specific file or directory",
|
|
42
42
|
},
|
|
43
43
|
kind: {
|
|
44
|
-
type:
|
|
44
|
+
type: "string",
|
|
45
45
|
enum: [
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
46
|
+
"function",
|
|
47
|
+
"class",
|
|
48
|
+
"variable",
|
|
49
|
+
"type",
|
|
50
|
+
"interface",
|
|
51
|
+
"enum",
|
|
52
|
+
"method",
|
|
53
|
+
"property",
|
|
54
|
+
"any",
|
|
55
55
|
],
|
|
56
|
-
description:
|
|
57
|
-
default:
|
|
56
|
+
description: "Filter by symbol kind (default: any)",
|
|
57
|
+
default: "any",
|
|
58
58
|
},
|
|
59
59
|
limit: {
|
|
60
|
-
type:
|
|
61
|
-
description:
|
|
60
|
+
type: "number",
|
|
61
|
+
description: "Maximum results to return (default: 10)",
|
|
62
62
|
default: 10,
|
|
63
63
|
},
|
|
64
64
|
includeNodeModules: {
|
|
65
|
-
type:
|
|
66
|
-
description:
|
|
65
|
+
type: "boolean",
|
|
66
|
+
description: "Include symbols from node_modules (default: false)",
|
|
67
67
|
default: false,
|
|
68
68
|
},
|
|
69
69
|
},
|
|
70
|
-
required: [
|
|
70
|
+
required: ["symbol"],
|
|
71
71
|
};
|
|
72
72
|
/**
|
|
73
73
|
* findSymbol tool - Find symbol definitions
|
|
74
74
|
*/
|
|
75
75
|
export const findSymbolTool = defineTool({
|
|
76
|
-
name:
|
|
76
|
+
name: "find_symbol",
|
|
77
77
|
description: TOOL_DESCRIPTION,
|
|
78
78
|
inputSchema: TOOL_INPUT_SCHEMA,
|
|
79
79
|
execute: executeFindSymbol,
|
|
@@ -82,10 +82,10 @@ export const findSymbolTool = defineTool({
|
|
|
82
82
|
* Execute the findSymbol tool
|
|
83
83
|
*/
|
|
84
84
|
async function executeFindSymbol(input) {
|
|
85
|
-
const { symbol, scope, kind =
|
|
85
|
+
const { symbol, scope, kind = "any", limit = 10, includeNodeModules = false, } = input;
|
|
86
86
|
// Validate input
|
|
87
87
|
if (!symbol || symbol.trim().length === 0) {
|
|
88
|
-
return createErrorResult(
|
|
88
|
+
return createErrorResult("Symbol name is required");
|
|
89
89
|
}
|
|
90
90
|
const startTime = Date.now();
|
|
91
91
|
const definitions = [];
|
|
@@ -152,14 +152,14 @@ async function collectFiles(dir, includeNodeModules) {
|
|
|
152
152
|
const fullPath = path.join(currentDir, entry.name);
|
|
153
153
|
if (entry.isDirectory()) {
|
|
154
154
|
// Skip node_modules unless explicitly included
|
|
155
|
-
if (entry.name ===
|
|
155
|
+
if (entry.name === "node_modules" && !includeNodeModules) {
|
|
156
156
|
continue;
|
|
157
157
|
}
|
|
158
158
|
// Skip common non-source directories
|
|
159
|
-
if (entry.name.startsWith(
|
|
160
|
-
entry.name ===
|
|
161
|
-
entry.name ===
|
|
162
|
-
entry.name ===
|
|
159
|
+
if (entry.name.startsWith(".") ||
|
|
160
|
+
entry.name === "dist" ||
|
|
161
|
+
entry.name === "build" ||
|
|
162
|
+
entry.name === "coverage") {
|
|
163
163
|
continue;
|
|
164
164
|
}
|
|
165
165
|
await walk(fullPath);
|
|
@@ -181,7 +181,7 @@ async function collectFiles(dir, includeNodeModules) {
|
|
|
181
181
|
async function searchFileForSymbol(filePath, symbolName, kindFilter) {
|
|
182
182
|
const definitions = [];
|
|
183
183
|
try {
|
|
184
|
-
const sourceCode = await fs.readFile(filePath,
|
|
184
|
+
const sourceCode = await fs.readFile(filePath, "utf-8");
|
|
185
185
|
const scriptKind = getScriptKind(filePath);
|
|
186
186
|
const sourceFile = ts.createSourceFile(filePath, sourceCode, ts.ScriptTarget.Latest, true, scriptKind);
|
|
187
187
|
// Search for symbols
|
|
@@ -196,21 +196,21 @@ async function searchFileForSymbol(filePath, symbolName, kindFilter) {
|
|
|
196
196
|
* Get TypeScript script kind from file extension
|
|
197
197
|
*/
|
|
198
198
|
function getScriptKind(filePath) {
|
|
199
|
-
const ext = filePath.toLowerCase().split(
|
|
199
|
+
const ext = filePath.toLowerCase().split(".").pop();
|
|
200
200
|
switch (ext) {
|
|
201
|
-
case
|
|
201
|
+
case "ts":
|
|
202
202
|
return ts.ScriptKind.TS;
|
|
203
|
-
case
|
|
203
|
+
case "tsx":
|
|
204
204
|
return ts.ScriptKind.TSX;
|
|
205
|
-
case
|
|
205
|
+
case "js":
|
|
206
206
|
return ts.ScriptKind.JS;
|
|
207
|
-
case
|
|
207
|
+
case "jsx":
|
|
208
208
|
return ts.ScriptKind.JSX;
|
|
209
|
-
case
|
|
210
|
-
case
|
|
209
|
+
case "mts":
|
|
210
|
+
case "cts":
|
|
211
211
|
return ts.ScriptKind.TS;
|
|
212
|
-
case
|
|
213
|
-
case
|
|
212
|
+
case "mjs":
|
|
213
|
+
case "cjs":
|
|
214
214
|
return ts.ScriptKind.JS;
|
|
215
215
|
default:
|
|
216
216
|
return ts.ScriptKind.TS;
|
|
@@ -244,40 +244,42 @@ function extractSymbolDefinition(node, sourceFile, symbolName, kindFilter, fileP
|
|
|
244
244
|
let exported = false;
|
|
245
245
|
let signature;
|
|
246
246
|
let docSummary;
|
|
247
|
-
const modifiers = ts.canHaveModifiers(node)
|
|
247
|
+
const modifiers = ts.canHaveModifiers(node)
|
|
248
|
+
? ts.getModifiers(node)
|
|
249
|
+
: undefined;
|
|
248
250
|
exported = hasModifierKind(modifiers, ts.SyntaxKind.ExportKeyword);
|
|
249
251
|
// Function declarations
|
|
250
252
|
if (ts.isFunctionDeclaration(node) && node.name) {
|
|
251
253
|
name = node.name.text;
|
|
252
|
-
kind =
|
|
254
|
+
kind = "function";
|
|
253
255
|
signature = buildFunctionSignature(node);
|
|
254
256
|
docSummary = getJsDocSummary(node, sourceFile);
|
|
255
257
|
}
|
|
256
258
|
// Class declarations
|
|
257
259
|
else if (ts.isClassDeclaration(node) && node.name) {
|
|
258
260
|
name = node.name.text;
|
|
259
|
-
kind =
|
|
261
|
+
kind = "class";
|
|
260
262
|
signature = `class ${name}`;
|
|
261
263
|
docSummary = getJsDocSummary(node, sourceFile);
|
|
262
264
|
}
|
|
263
265
|
// Interface declarations
|
|
264
266
|
else if (ts.isInterfaceDeclaration(node)) {
|
|
265
267
|
name = node.name.text;
|
|
266
|
-
kind =
|
|
268
|
+
kind = "interface";
|
|
267
269
|
signature = `interface ${name}`;
|
|
268
270
|
docSummary = getJsDocSummary(node, sourceFile);
|
|
269
271
|
}
|
|
270
272
|
// Type alias declarations
|
|
271
273
|
else if (ts.isTypeAliasDeclaration(node)) {
|
|
272
274
|
name = node.name.text;
|
|
273
|
-
kind =
|
|
275
|
+
kind = "type";
|
|
274
276
|
signature = `type ${name}`;
|
|
275
277
|
docSummary = getJsDocSummary(node, sourceFile);
|
|
276
278
|
}
|
|
277
279
|
// Enum declarations
|
|
278
280
|
else if (ts.isEnumDeclaration(node)) {
|
|
279
281
|
name = node.name.text;
|
|
280
|
-
kind =
|
|
282
|
+
kind = "enum";
|
|
281
283
|
signature = `enum ${name}`;
|
|
282
284
|
docSummary = getJsDocSummary(node, sourceFile);
|
|
283
285
|
}
|
|
@@ -285,39 +287,42 @@ function extractSymbolDefinition(node, sourceFile, symbolName, kindFilter, fileP
|
|
|
285
287
|
else if (ts.isVariableDeclaration(node) && ts.isIdentifier(node.name)) {
|
|
286
288
|
// Skip if this is a function (arrow function or function expression)
|
|
287
289
|
if (node.initializer &&
|
|
288
|
-
(ts.isArrowFunction(node.initializer) ||
|
|
290
|
+
(ts.isArrowFunction(node.initializer) ||
|
|
291
|
+
ts.isFunctionExpression(node.initializer))) {
|
|
289
292
|
name = node.name.text;
|
|
290
|
-
kind =
|
|
293
|
+
kind = "function";
|
|
291
294
|
signature = buildArrowFunctionSignature(node);
|
|
292
295
|
}
|
|
293
296
|
else {
|
|
294
297
|
name = node.name.text;
|
|
295
|
-
kind =
|
|
298
|
+
kind = "variable";
|
|
296
299
|
signature = `${getVariableKind(node)} ${name}`;
|
|
297
300
|
}
|
|
298
301
|
// Check parent for export status
|
|
299
302
|
const parent = node.parent.parent;
|
|
300
303
|
if (ts.isVariableStatement(parent)) {
|
|
301
|
-
const parentMods = ts.canHaveModifiers(parent)
|
|
304
|
+
const parentMods = ts.canHaveModifiers(parent)
|
|
305
|
+
? ts.getModifiers(parent)
|
|
306
|
+
: undefined;
|
|
302
307
|
exported = hasModifierKind(parentMods, ts.SyntaxKind.ExportKeyword);
|
|
303
308
|
docSummary = getJsDocSummary(parent, sourceFile);
|
|
304
309
|
}
|
|
305
310
|
}
|
|
306
311
|
// Method declarations (inside classes)
|
|
307
|
-
|
|
308
|
-
else if (ts.isMethodDeclaration(node) && node.name && ts.isIdentifier(node.name)) {
|
|
312
|
+
else if (ts.isMethodDeclaration(node) && ts.isIdentifier(node.name)) {
|
|
309
313
|
name = node.name.text;
|
|
310
|
-
kind =
|
|
314
|
+
kind = "method";
|
|
311
315
|
signature = buildMethodSignature(node);
|
|
312
316
|
docSummary = getJsDocSummary(node, sourceFile);
|
|
313
317
|
// Methods are exported if their containing class is exported
|
|
314
318
|
}
|
|
315
319
|
// Property declarations (inside classes)
|
|
316
|
-
|
|
317
|
-
else if (ts.isPropertyDeclaration(node) && node.name && ts.isIdentifier(node.name)) {
|
|
320
|
+
else if (ts.isPropertyDeclaration(node) && ts.isIdentifier(node.name)) {
|
|
318
321
|
name = node.name.text;
|
|
319
|
-
kind =
|
|
320
|
-
const typeAnnotation = node.type
|
|
322
|
+
kind = "property";
|
|
323
|
+
const typeAnnotation = node.type
|
|
324
|
+
? `: ${node.type.getText(sourceFile)}`
|
|
325
|
+
: "";
|
|
321
326
|
signature = `${name}${typeAnnotation}`;
|
|
322
327
|
docSummary = getJsDocSummary(node, sourceFile);
|
|
323
328
|
}
|
|
@@ -329,9 +334,9 @@ function extractSymbolDefinition(node, sourceFile, symbolName, kindFilter, fileP
|
|
|
329
334
|
return null;
|
|
330
335
|
}
|
|
331
336
|
// Apply kind filter
|
|
332
|
-
if (kindFilter !==
|
|
337
|
+
if (kindFilter !== "any" && kind !== kindFilter) {
|
|
333
338
|
// Handle type filter matching both 'type' and 'interface'
|
|
334
|
-
if (kindFilter ===
|
|
339
|
+
if (kindFilter === "type" && kind === "interface") {
|
|
335
340
|
// Allow interface to match type filter
|
|
336
341
|
}
|
|
337
342
|
else {
|
|
@@ -360,64 +365,69 @@ function getVariableKind(node) {
|
|
|
360
365
|
const parent = node.parent;
|
|
361
366
|
if (ts.isVariableDeclarationList(parent)) {
|
|
362
367
|
if (parent.flags & ts.NodeFlags.Const)
|
|
363
|
-
return
|
|
368
|
+
return "const";
|
|
364
369
|
if (parent.flags & ts.NodeFlags.Let)
|
|
365
|
-
return
|
|
370
|
+
return "let";
|
|
366
371
|
}
|
|
367
|
-
return
|
|
372
|
+
return "var";
|
|
368
373
|
}
|
|
369
374
|
/**
|
|
370
375
|
* Build a function signature string
|
|
371
376
|
*/
|
|
372
377
|
function buildFunctionSignature(node) {
|
|
373
|
-
const name = node.name?.text ??
|
|
378
|
+
const name = node.name?.text ?? "anonymous";
|
|
374
379
|
const params = node.parameters
|
|
375
380
|
.map((p) => {
|
|
376
|
-
const paramName = ts.isIdentifier(p.name) ? p.name.text :
|
|
377
|
-
const paramType = p.type ? p.type.getText() :
|
|
381
|
+
const paramName = ts.isIdentifier(p.name) ? p.name.text : "param";
|
|
382
|
+
const paramType = p.type ? p.type.getText() : "";
|
|
378
383
|
return paramType ? `${paramName}: ${paramType}` : paramName;
|
|
379
384
|
})
|
|
380
|
-
.join(
|
|
381
|
-
const returnType = node.type ? `: ${node.type.getText()}` :
|
|
385
|
+
.join(", ");
|
|
386
|
+
const returnType = node.type ? `: ${node.type.getText()}` : "";
|
|
382
387
|
const asyncPrefix = hasModifierKind(ts.getModifiers(node), ts.SyntaxKind.AsyncKeyword)
|
|
383
|
-
?
|
|
384
|
-
:
|
|
388
|
+
? "async "
|
|
389
|
+
: "";
|
|
385
390
|
return `${asyncPrefix}function ${name}(${params})${returnType}`;
|
|
386
391
|
}
|
|
387
392
|
/**
|
|
388
393
|
* Build a method signature string
|
|
389
394
|
*/
|
|
390
395
|
function buildMethodSignature(node) {
|
|
391
|
-
const name = ts.isIdentifier(node.name) ? node.name.text :
|
|
396
|
+
const name = ts.isIdentifier(node.name) ? node.name.text : "method";
|
|
392
397
|
const params = node.parameters
|
|
393
398
|
.map((p) => {
|
|
394
|
-
const paramName = ts.isIdentifier(p.name) ? p.name.text :
|
|
395
|
-
const paramType = p.type ? p.type.getText() :
|
|
399
|
+
const paramName = ts.isIdentifier(p.name) ? p.name.text : "param";
|
|
400
|
+
const paramType = p.type ? p.type.getText() : "";
|
|
396
401
|
return paramType ? `${paramName}: ${paramType}` : paramName;
|
|
397
402
|
})
|
|
398
|
-
.join(
|
|
399
|
-
const returnType = node.type ? `: ${node.type.getText()}` :
|
|
403
|
+
.join(", ");
|
|
404
|
+
const returnType = node.type ? `: ${node.type.getText()}` : "";
|
|
400
405
|
const modifiers = ts.getModifiers(node);
|
|
401
|
-
const asyncPrefix = hasModifierKind(modifiers, ts.SyntaxKind.AsyncKeyword)
|
|
402
|
-
|
|
406
|
+
const asyncPrefix = hasModifierKind(modifiers, ts.SyntaxKind.AsyncKeyword)
|
|
407
|
+
? "async "
|
|
408
|
+
: "";
|
|
409
|
+
const staticPrefix = hasModifierKind(modifiers, ts.SyntaxKind.StaticKeyword)
|
|
410
|
+
? "static "
|
|
411
|
+
: "";
|
|
403
412
|
return `${staticPrefix}${asyncPrefix}${name}(${params})${returnType}`;
|
|
404
413
|
}
|
|
405
414
|
/**
|
|
406
415
|
* Build an arrow function signature string from variable declaration
|
|
407
416
|
*/
|
|
408
417
|
function buildArrowFunctionSignature(node) {
|
|
409
|
-
const name = ts.isIdentifier(node.name) ? node.name.text :
|
|
418
|
+
const name = ts.isIdentifier(node.name) ? node.name.text : "anonymous";
|
|
410
419
|
if (node.initializer &&
|
|
411
|
-
(ts.isArrowFunction(node.initializer) ||
|
|
420
|
+
(ts.isArrowFunction(node.initializer) ||
|
|
421
|
+
ts.isFunctionExpression(node.initializer))) {
|
|
412
422
|
const func = node.initializer;
|
|
413
423
|
const params = func.parameters
|
|
414
424
|
.map((p) => {
|
|
415
|
-
const paramName = ts.isIdentifier(p.name) ? p.name.text :
|
|
416
|
-
const paramType = p.type ? p.type.getText() :
|
|
425
|
+
const paramName = ts.isIdentifier(p.name) ? p.name.text : "param";
|
|
426
|
+
const paramType = p.type ? p.type.getText() : "";
|
|
417
427
|
return paramType ? `${paramName}: ${paramType}` : paramName;
|
|
418
428
|
})
|
|
419
|
-
.join(
|
|
420
|
-
const returnType = func.type ? `: ${func.type.getText()}` :
|
|
429
|
+
.join(", ");
|
|
430
|
+
const returnType = func.type ? `: ${func.type.getText()}` : "";
|
|
421
431
|
return `const ${name} = (${params})${returnType}`;
|
|
422
432
|
}
|
|
423
433
|
return `const ${name}`;
|
|
@@ -444,7 +454,7 @@ function getJsDocSummary(node, sourceFile) {
|
|
|
444
454
|
export function createFindSymbolTool(options) {
|
|
445
455
|
const { defaultLimit = 10, defaultIncludeNodeModules = false } = options ?? {};
|
|
446
456
|
return defineTool({
|
|
447
|
-
name:
|
|
457
|
+
name: "find_symbol",
|
|
448
458
|
description: TOOL_DESCRIPTION,
|
|
449
459
|
inputSchema: TOOL_INPUT_SCHEMA,
|
|
450
460
|
execute: async (input) => {
|
|
@@ -4,8 +4,8 @@
|
|
|
4
4
|
* Analyze function call relationships using TypeScript Compiler API.
|
|
5
5
|
* Returns a call graph showing what functions call what.
|
|
6
6
|
*/
|
|
7
|
-
import type { Tool } from
|
|
8
|
-
import type { GetCallGraphInput, CallGraphDirection } from
|
|
7
|
+
import type { Tool } from "@compilr-dev/agents";
|
|
8
|
+
import type { GetCallGraphInput, CallGraphDirection } from "./types.js";
|
|
9
9
|
/**
|
|
10
10
|
* getCallGraph tool - Analyze function call relationships
|
|
11
11
|
*/
|
|
@@ -4,54 +4,54 @@
|
|
|
4
4
|
* Analyze function call relationships using TypeScript Compiler API.
|
|
5
5
|
* Returns a call graph showing what functions call what.
|
|
6
6
|
*/
|
|
7
|
-
import * as fs from
|
|
8
|
-
import * as path from
|
|
9
|
-
import * as ts from
|
|
10
|
-
import { defineTool, createSuccessResult, createErrorResult } from
|
|
11
|
-
import { detectLanguage, isLanguageSupported } from
|
|
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 = `Analyze function call relationships in source code.
|
|
14
14
|
Returns a call graph showing which functions call which other functions.
|
|
15
15
|
Use this to understand code flow, dependencies, and refactoring impact.`;
|
|
16
16
|
// Tool input schema
|
|
17
17
|
const TOOL_INPUT_SCHEMA = {
|
|
18
|
-
type:
|
|
18
|
+
type: "object",
|
|
19
19
|
properties: {
|
|
20
20
|
path: {
|
|
21
|
-
type:
|
|
22
|
-
description:
|
|
21
|
+
type: "string",
|
|
22
|
+
description: "File to analyze",
|
|
23
23
|
},
|
|
24
24
|
functionName: {
|
|
25
|
-
type:
|
|
26
|
-
description:
|
|
25
|
+
type: "string",
|
|
26
|
+
description: "Specific function to analyze (optional)",
|
|
27
27
|
},
|
|
28
28
|
direction: {
|
|
29
|
-
type:
|
|
30
|
-
enum: [
|
|
29
|
+
type: "string",
|
|
30
|
+
enum: ["callers", "callees", "both"],
|
|
31
31
|
description: "Direction: 'callers' | 'callees' | 'both' (default: 'both')",
|
|
32
32
|
},
|
|
33
33
|
maxDepth: {
|
|
34
|
-
type:
|
|
35
|
-
description:
|
|
34
|
+
type: "number",
|
|
35
|
+
description: "Maximum depth to traverse (default: 2)",
|
|
36
36
|
default: 2,
|
|
37
37
|
},
|
|
38
38
|
includeExternal: {
|
|
39
|
-
type:
|
|
40
|
-
description:
|
|
39
|
+
type: "boolean",
|
|
40
|
+
description: "Include external package calls (default: false)",
|
|
41
41
|
default: false,
|
|
42
42
|
},
|
|
43
43
|
scope: {
|
|
44
|
-
type:
|
|
44
|
+
type: "string",
|
|
45
45
|
description: "Scope directory for finding callers (default: file's directory)",
|
|
46
46
|
},
|
|
47
47
|
},
|
|
48
|
-
required: [
|
|
48
|
+
required: ["path"],
|
|
49
49
|
};
|
|
50
50
|
/**
|
|
51
51
|
* getCallGraph tool - Analyze function call relationships
|
|
52
52
|
*/
|
|
53
53
|
export const getCallGraphTool = defineTool({
|
|
54
|
-
name:
|
|
54
|
+
name: "get_call_graph",
|
|
55
55
|
description: TOOL_DESCRIPTION,
|
|
56
56
|
inputSchema: TOOL_INPUT_SCHEMA,
|
|
57
57
|
execute: executeGetCallGraph,
|
|
@@ -60,10 +60,10 @@ export const getCallGraphTool = defineTool({
|
|
|
60
60
|
* Execute the getCallGraph tool
|
|
61
61
|
*/
|
|
62
62
|
async function executeGetCallGraph(input) {
|
|
63
|
-
const { path: inputPath, functionName, direction =
|
|
63
|
+
const { path: inputPath, functionName, direction = "both", maxDepth = 2, includeExternal = false, scope, } = input;
|
|
64
64
|
// Validate input
|
|
65
65
|
if (!inputPath || inputPath.trim().length === 0) {
|
|
66
|
-
return createErrorResult(
|
|
66
|
+
return createErrorResult("Path is required");
|
|
67
67
|
}
|
|
68
68
|
try {
|
|
69
69
|
// Check if path exists
|
|
@@ -96,7 +96,7 @@ async function executeGetCallGraph(input) {
|
|
|
96
96
|
* Analyze call graph for a file
|
|
97
97
|
*/
|
|
98
98
|
async function analyzeCallGraph(filePath, functionName, direction, maxDepth, includeExternal, _scopeDir) {
|
|
99
|
-
const sourceCode = await fs.readFile(filePath,
|
|
99
|
+
const sourceCode = await fs.readFile(filePath, "utf-8");
|
|
100
100
|
const scriptKind = getScriptKind(filePath);
|
|
101
101
|
const sourceFile = ts.createSourceFile(filePath, sourceCode, ts.ScriptTarget.Latest, true, scriptKind);
|
|
102
102
|
// Find all functions in the file
|
|
@@ -122,7 +122,7 @@ async function analyzeCallGraph(filePath, functionName, direction, maxDepth, inc
|
|
|
122
122
|
rootNode = targetFunc.node;
|
|
123
123
|
}
|
|
124
124
|
// Analyze callees (what each function calls)
|
|
125
|
-
if (direction ===
|
|
125
|
+
if (direction === "callees" || direction === "both") {
|
|
126
126
|
const functionsToAnalyze = functionName
|
|
127
127
|
? functions.filter((f) => f.node.name === functionName)
|
|
128
128
|
: functions;
|
|
@@ -140,12 +140,12 @@ async function analyzeCallGraph(filePath, functionName, direction, maxDepth, inc
|
|
|
140
140
|
graphNode.calls = callees;
|
|
141
141
|
// Count external calls
|
|
142
142
|
if (includeExternal) {
|
|
143
|
-
externalCallCount += callees.filter((c) => c.function.path ===
|
|
143
|
+
externalCallCount += callees.filter((c) => c.function.path === "external").length;
|
|
144
144
|
}
|
|
145
145
|
}
|
|
146
146
|
}
|
|
147
147
|
// Analyze callers (what calls each function)
|
|
148
|
-
if (direction ===
|
|
148
|
+
if (direction === "callers" || direction === "both") {
|
|
149
149
|
const functionsToAnalyze = functionName
|
|
150
150
|
? functions.filter((f) => f.node.name === functionName)
|
|
151
151
|
: functions;
|
|
@@ -208,7 +208,8 @@ function extractFunctions(sourceFile, filePath) {
|
|
|
208
208
|
const decls = node.declarationList.declarations;
|
|
209
209
|
for (const decl of decls) {
|
|
210
210
|
if (ts.isIdentifier(decl.name) && decl.initializer) {
|
|
211
|
-
if (ts.isArrowFunction(decl.initializer) ||
|
|
211
|
+
if (ts.isArrowFunction(decl.initializer) ||
|
|
212
|
+
ts.isFunctionExpression(decl.initializer)) {
|
|
212
213
|
const { line, character } = sourceFile.getLineAndCharacterOfPosition(decl.getStart(sourceFile));
|
|
213
214
|
const isExported = hasExportModifier(node);
|
|
214
215
|
const isAsync = hasAsyncModifier(decl.initializer);
|
|
@@ -295,7 +296,7 @@ function extractCallees(sourceFile, func, allFunctions, includeExternal) {
|
|
|
295
296
|
function extractCallInfo(node, sourceFile, allFunctions, includeExternal) {
|
|
296
297
|
const { line, character } = sourceFile.getLineAndCharacterOfPosition(node.getStart(sourceFile));
|
|
297
298
|
let funcName;
|
|
298
|
-
let callType =
|
|
299
|
+
let callType = "direct";
|
|
299
300
|
let className;
|
|
300
301
|
// Simple function call: funcName()
|
|
301
302
|
if (ts.isIdentifier(node.expression)) {
|
|
@@ -306,14 +307,14 @@ function extractCallInfo(node, sourceFile, allFunctions, includeExternal) {
|
|
|
306
307
|
funcName = node.expression.name.text;
|
|
307
308
|
const propName = node.expression.name.text;
|
|
308
309
|
// Check for promise chain
|
|
309
|
-
if (propName ===
|
|
310
|
-
callType =
|
|
310
|
+
if (propName === "then" || propName === "catch" || propName === "finally") {
|
|
311
|
+
callType = "promise";
|
|
311
312
|
}
|
|
312
313
|
else {
|
|
313
|
-
callType =
|
|
314
|
+
callType = "method";
|
|
314
315
|
if (ts.isIdentifier(node.expression.expression)) {
|
|
315
316
|
const objName = node.expression.expression.text;
|
|
316
|
-
if (objName !==
|
|
317
|
+
if (objName !== "this") {
|
|
317
318
|
// Could be instance.method or ClassName.staticMethod
|
|
318
319
|
className = objName;
|
|
319
320
|
}
|
|
@@ -339,7 +340,7 @@ function extractCallInfo(node, sourceFile, allFunctions, includeExternal) {
|
|
|
339
340
|
return {
|
|
340
341
|
function: {
|
|
341
342
|
name: funcName,
|
|
342
|
-
path:
|
|
343
|
+
path: "external",
|
|
343
344
|
line: 0,
|
|
344
345
|
column: 0,
|
|
345
346
|
className,
|
|
@@ -403,7 +404,7 @@ function findCallersInFile(sourceFile, targetFuncName, allFunctions, _filePath)
|
|
|
403
404
|
function: func.node,
|
|
404
405
|
callLine,
|
|
405
406
|
callColumn,
|
|
406
|
-
type:
|
|
407
|
+
type: "direct",
|
|
407
408
|
count: callCount,
|
|
408
409
|
});
|
|
409
410
|
}
|
|
@@ -414,35 +415,39 @@ function findCallersInFile(sourceFile, targetFuncName, allFunctions, _filePath)
|
|
|
414
415
|
* Check if a node has export modifier
|
|
415
416
|
*/
|
|
416
417
|
function hasExportModifier(node) {
|
|
417
|
-
const modifiers = ts.canHaveModifiers(node)
|
|
418
|
-
|
|
418
|
+
const modifiers = ts.canHaveModifiers(node)
|
|
419
|
+
? ts.getModifiers(node)
|
|
420
|
+
: undefined;
|
|
421
|
+
return (modifiers?.some((m) => m.kind === ts.SyntaxKind.ExportKeyword) ?? false);
|
|
419
422
|
}
|
|
420
423
|
/**
|
|
421
424
|
* Check if a node has async modifier
|
|
422
425
|
*/
|
|
423
426
|
function hasAsyncModifier(node) {
|
|
424
|
-
const modifiers = ts.canHaveModifiers(node)
|
|
427
|
+
const modifiers = ts.canHaveModifiers(node)
|
|
428
|
+
? ts.getModifiers(node)
|
|
429
|
+
: undefined;
|
|
425
430
|
return modifiers?.some((m) => m.kind === ts.SyntaxKind.AsyncKeyword) ?? false;
|
|
426
431
|
}
|
|
427
432
|
/**
|
|
428
433
|
* Get TypeScript script kind from file extension
|
|
429
434
|
*/
|
|
430
435
|
function getScriptKind(filePath) {
|
|
431
|
-
const ext = filePath.toLowerCase().split(
|
|
436
|
+
const ext = filePath.toLowerCase().split(".").pop();
|
|
432
437
|
switch (ext) {
|
|
433
|
-
case
|
|
438
|
+
case "ts":
|
|
434
439
|
return ts.ScriptKind.TS;
|
|
435
|
-
case
|
|
440
|
+
case "tsx":
|
|
436
441
|
return ts.ScriptKind.TSX;
|
|
437
|
-
case
|
|
442
|
+
case "js":
|
|
438
443
|
return ts.ScriptKind.JS;
|
|
439
|
-
case
|
|
444
|
+
case "jsx":
|
|
440
445
|
return ts.ScriptKind.JSX;
|
|
441
|
-
case
|
|
442
|
-
case
|
|
446
|
+
case "mts":
|
|
447
|
+
case "cts":
|
|
443
448
|
return ts.ScriptKind.TS;
|
|
444
|
-
case
|
|
445
|
-
case
|
|
449
|
+
case "mjs":
|
|
450
|
+
case "cjs":
|
|
446
451
|
return ts.ScriptKind.JS;
|
|
447
452
|
default:
|
|
448
453
|
return ts.ScriptKind.TS;
|
|
@@ -452,9 +457,9 @@ function getScriptKind(filePath) {
|
|
|
452
457
|
* Factory function to create a customized getCallGraph tool
|
|
453
458
|
*/
|
|
454
459
|
export function createGetCallGraphTool(options) {
|
|
455
|
-
const { defaultDirection =
|
|
460
|
+
const { defaultDirection = "both", defaultMaxDepth = 2, defaultIncludeExternal = false, } = options ?? {};
|
|
456
461
|
return defineTool({
|
|
457
|
-
name:
|
|
462
|
+
name: "get_call_graph",
|
|
458
463
|
description: TOOL_DESCRIPTION,
|
|
459
464
|
inputSchema: TOOL_INPUT_SCHEMA,
|
|
460
465
|
execute: async (input) => {
|
|
@@ -4,8 +4,8 @@
|
|
|
4
4
|
* Calculate code complexity metrics including cyclomatic complexity,
|
|
5
5
|
* cognitive complexity, nesting depth, and identify complexity hotspots.
|
|
6
6
|
*/
|
|
7
|
-
import type { Tool } from
|
|
8
|
-
import type { GetComplexityInput } from
|
|
7
|
+
import type { Tool } from "@compilr-dev/agents";
|
|
8
|
+
import type { GetComplexityInput } from "./types.js";
|
|
9
9
|
/**
|
|
10
10
|
* getComplexity tool
|
|
11
11
|
*/
|