@compilr-dev/agents-coding-go 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 +40 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +27 -0
- package/dist/parser/go-parser.d.ts +104 -0
- package/dist/parser/go-parser.d.ts.map +1 -0
- package/dist/parser/go-parser.js +492 -0
- package/dist/parser/index.d.ts +6 -0
- package/dist/parser/index.d.ts.map +1 -0
- package/dist/parser/index.js +5 -0
- package/dist/parser/node-types.d.ts +130 -0
- package/dist/parser/node-types.d.ts.map +1 -0
- package/dist/parser/node-types.js +4 -0
- package/dist/skills/go-best-practices.d.ts +7 -0
- package/dist/skills/go-best-practices.d.ts.map +1 -0
- package/dist/skills/go-best-practices.js +78 -0
- package/dist/skills/go-code-health.d.ts +7 -0
- package/dist/skills/go-code-health.d.ts.map +1 -0
- package/dist/skills/go-code-health.js +209 -0
- package/dist/skills/go-code-structure.d.ts +7 -0
- package/dist/skills/go-code-structure.d.ts.map +1 -0
- package/dist/skills/go-code-structure.js +155 -0
- package/dist/skills/go-dependency-audit.d.ts +7 -0
- package/dist/skills/go-dependency-audit.d.ts.map +1 -0
- package/dist/skills/go-dependency-audit.js +246 -0
- package/dist/skills/go-refactor-impact.d.ts +7 -0
- package/dist/skills/go-refactor-impact.d.ts.map +1 -0
- package/dist/skills/go-refactor-impact.js +232 -0
- package/dist/skills/index.d.ts +26 -0
- package/dist/skills/index.d.ts.map +1 -0
- package/dist/skills/index.js +36 -0
- package/dist/tools/extract-docstrings.d.ts +51 -0
- package/dist/tools/extract-docstrings.d.ts.map +1 -0
- package/dist/tools/extract-docstrings.js +292 -0
- package/dist/tools/find-dead-code.d.ts +62 -0
- package/dist/tools/find-dead-code.d.ts.map +1 -0
- package/dist/tools/find-dead-code.js +422 -0
- package/dist/tools/find-duplicates.d.ts +65 -0
- package/dist/tools/find-duplicates.d.ts.map +1 -0
- package/dist/tools/find-duplicates.js +289 -0
- package/dist/tools/find-implementations.d.ts +71 -0
- package/dist/tools/find-implementations.d.ts.map +1 -0
- package/dist/tools/find-implementations.js +342 -0
- package/dist/tools/find-patterns.d.ts +71 -0
- package/dist/tools/find-patterns.d.ts.map +1 -0
- package/dist/tools/find-patterns.js +477 -0
- package/dist/tools/find-references.d.ts +66 -0
- package/dist/tools/find-references.d.ts.map +1 -0
- package/dist/tools/find-references.js +306 -0
- package/dist/tools/find-symbol.d.ts +86 -0
- package/dist/tools/find-symbol.d.ts.map +1 -0
- package/dist/tools/find-symbol.js +380 -0
- package/dist/tools/get-call-graph.d.ts +89 -0
- package/dist/tools/get-call-graph.d.ts.map +1 -0
- package/dist/tools/get-call-graph.js +431 -0
- package/dist/tools/get-class-hierarchy.d.ts +34 -0
- package/dist/tools/get-class-hierarchy.d.ts.map +1 -0
- package/dist/tools/get-class-hierarchy.js +250 -0
- package/dist/tools/get-complexity.d.ts +53 -0
- package/dist/tools/get-complexity.d.ts.map +1 -0
- package/dist/tools/get-complexity.js +357 -0
- package/dist/tools/get-dependency-graph.d.ts +85 -0
- package/dist/tools/get-dependency-graph.d.ts.map +1 -0
- package/dist/tools/get-dependency-graph.js +389 -0
- package/dist/tools/get-exports.d.ts +78 -0
- package/dist/tools/get-exports.d.ts.map +1 -0
- package/dist/tools/get-exports.js +437 -0
- package/dist/tools/get-file-structure.d.ts +28 -0
- package/dist/tools/get-file-structure.d.ts.map +1 -0
- package/dist/tools/get-file-structure.js +172 -0
- package/dist/tools/get-imports.d.ts +30 -0
- package/dist/tools/get-imports.d.ts.map +1 -0
- package/dist/tools/get-imports.js +420 -0
- package/dist/tools/get-signature.d.ts +100 -0
- package/dist/tools/get-signature.d.ts.map +1 -0
- package/dist/tools/get-signature.js +800 -0
- package/dist/tools/index.d.ts +55 -0
- package/dist/tools/index.d.ts.map +1 -0
- package/dist/tools/index.js +75 -0
- package/dist/tools/types.d.ts +408 -0
- package/dist/tools/types.d.ts.map +1 -0
- package/dist/tools/types.js +4 -0
- package/package.json +86 -0
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* getClassHierarchy Tool
|
|
3
|
+
*
|
|
4
|
+
* Analyze type hierarchies in Go code.
|
|
5
|
+
* Go uses composition (embedding) rather than inheritance.
|
|
6
|
+
* This tool finds embedded types and types that embed a given type.
|
|
7
|
+
*/
|
|
8
|
+
import * as fs from "node:fs/promises";
|
|
9
|
+
import * as path from "node:path";
|
|
10
|
+
import { defineTool, createSuccessResult, createErrorResult, } from "@compilr-dev/agents";
|
|
11
|
+
import { parseFile, parseClass, walkTree } from "../parser/go-parser.js";
|
|
12
|
+
// Tool description
|
|
13
|
+
const TOOL_DESCRIPTION = `Analyze type hierarchies in Go code.
|
|
14
|
+
Go uses composition (embedding) rather than inheritance.
|
|
15
|
+
Returns embedded types and types that embed the target type.`;
|
|
16
|
+
// Tool input schema
|
|
17
|
+
const TOOL_INPUT_SCHEMA = {
|
|
18
|
+
type: "object",
|
|
19
|
+
properties: {
|
|
20
|
+
name: {
|
|
21
|
+
type: "string",
|
|
22
|
+
description: "Type name to analyze (struct or interface)",
|
|
23
|
+
},
|
|
24
|
+
path: {
|
|
25
|
+
type: "string",
|
|
26
|
+
description: "File containing the type (optional - will search if not provided)",
|
|
27
|
+
},
|
|
28
|
+
scope: {
|
|
29
|
+
type: "string",
|
|
30
|
+
description: "Directory to search for type relationships",
|
|
31
|
+
},
|
|
32
|
+
direction: {
|
|
33
|
+
type: "string",
|
|
34
|
+
enum: ["embeds", "embeddedBy", "both"],
|
|
35
|
+
description: "Direction of hierarchy (default: both)",
|
|
36
|
+
default: "both",
|
|
37
|
+
},
|
|
38
|
+
maxDepth: {
|
|
39
|
+
type: "number",
|
|
40
|
+
description: "Maximum depth to traverse (default: 3)",
|
|
41
|
+
default: 3,
|
|
42
|
+
},
|
|
43
|
+
},
|
|
44
|
+
required: ["name"],
|
|
45
|
+
};
|
|
46
|
+
/**
|
|
47
|
+
* getClassHierarchy tool - Analyze type embedding in Go
|
|
48
|
+
*/
|
|
49
|
+
export const getClassHierarchyTool = defineTool({
|
|
50
|
+
name: "get_class_hierarchy_go",
|
|
51
|
+
description: TOOL_DESCRIPTION,
|
|
52
|
+
inputSchema: TOOL_INPUT_SCHEMA,
|
|
53
|
+
execute: executeGetClassHierarchy,
|
|
54
|
+
});
|
|
55
|
+
/**
|
|
56
|
+
* Execute the getClassHierarchy tool
|
|
57
|
+
*/
|
|
58
|
+
async function executeGetClassHierarchy(input) {
|
|
59
|
+
const { name: typeName, path: inputPath, scope, direction = "both", maxDepth = 3, } = input;
|
|
60
|
+
if (!typeName) {
|
|
61
|
+
return createErrorResult("Type name is required");
|
|
62
|
+
}
|
|
63
|
+
try {
|
|
64
|
+
// Find the type definition
|
|
65
|
+
let typeFile;
|
|
66
|
+
let typeLine;
|
|
67
|
+
let isInterface = false;
|
|
68
|
+
let isStruct = false;
|
|
69
|
+
let embeds = [];
|
|
70
|
+
if (inputPath) {
|
|
71
|
+
// Check specified file
|
|
72
|
+
const typeInfo = await findTypeInFile(inputPath, typeName);
|
|
73
|
+
if (!typeInfo) {
|
|
74
|
+
return createErrorResult(`Type ${typeName} not found in ${inputPath}`);
|
|
75
|
+
}
|
|
76
|
+
typeFile = inputPath;
|
|
77
|
+
typeLine = typeInfo.line;
|
|
78
|
+
isInterface = typeInfo.isInterface;
|
|
79
|
+
isStruct = typeInfo.isStruct;
|
|
80
|
+
embeds = typeInfo.embeds;
|
|
81
|
+
}
|
|
82
|
+
else {
|
|
83
|
+
// Search in scope or current directory
|
|
84
|
+
const searchDir = scope || process.cwd();
|
|
85
|
+
const found = await findTypeInDirectory(searchDir, typeName);
|
|
86
|
+
if (!found) {
|
|
87
|
+
return createErrorResult(`Type ${typeName} not found in ${searchDir}`);
|
|
88
|
+
}
|
|
89
|
+
typeFile = found.path;
|
|
90
|
+
typeLine = found.line;
|
|
91
|
+
isInterface = found.isInterface;
|
|
92
|
+
isStruct = found.isStruct;
|
|
93
|
+
embeds = found.embeds;
|
|
94
|
+
}
|
|
95
|
+
// Find types that embed this type
|
|
96
|
+
const embeddedBy = [];
|
|
97
|
+
if (direction === "embeddedBy" || direction === "both") {
|
|
98
|
+
const searchDir = scope || path.dirname(typeFile);
|
|
99
|
+
const embedders = await findEmbedders(searchDir, typeName, maxDepth);
|
|
100
|
+
embeddedBy.push(...embedders);
|
|
101
|
+
}
|
|
102
|
+
const result = {
|
|
103
|
+
typeName,
|
|
104
|
+
path: typeFile,
|
|
105
|
+
line: typeLine,
|
|
106
|
+
isInterface,
|
|
107
|
+
isStruct,
|
|
108
|
+
embeds,
|
|
109
|
+
embeddedBy,
|
|
110
|
+
};
|
|
111
|
+
return createSuccessResult(result);
|
|
112
|
+
}
|
|
113
|
+
catch (error) {
|
|
114
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
115
|
+
return createErrorResult(`Failed to analyze type hierarchy: ${message}`);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Find a type in a specific file
|
|
120
|
+
*/
|
|
121
|
+
async function findTypeInFile(filePath, typeName) {
|
|
122
|
+
try {
|
|
123
|
+
const parseResult = await parseFile(filePath);
|
|
124
|
+
const { tree, source } = parseResult;
|
|
125
|
+
for (const node of walkTree(tree.rootNode, ["type_declaration"])) {
|
|
126
|
+
const typeInfo = parseClass(node, source);
|
|
127
|
+
if (typeInfo.name === typeName) {
|
|
128
|
+
return {
|
|
129
|
+
line: typeInfo.line,
|
|
130
|
+
isInterface: typeInfo.isInterface ?? false,
|
|
131
|
+
isStruct: typeInfo.isStruct ?? false,
|
|
132
|
+
embeds: typeInfo.bases.map((name) => ({
|
|
133
|
+
name,
|
|
134
|
+
isExternal: name.includes("."),
|
|
135
|
+
isPointer: false, // Would need more parsing to detect
|
|
136
|
+
})),
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
catch {
|
|
142
|
+
// Skip files that can't be parsed
|
|
143
|
+
}
|
|
144
|
+
return null;
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* Find a type in a directory
|
|
148
|
+
*/
|
|
149
|
+
async function findTypeInDirectory(dir, typeName) {
|
|
150
|
+
async function search(currentDir) {
|
|
151
|
+
const entries = await fs.readdir(currentDir, { withFileTypes: true });
|
|
152
|
+
for (const entry of entries) {
|
|
153
|
+
const fullPath = path.join(currentDir, entry.name);
|
|
154
|
+
if (entry.isDirectory()) {
|
|
155
|
+
// Skip vendor and hidden directories
|
|
156
|
+
if (entry.name === "vendor" ||
|
|
157
|
+
entry.name.startsWith(".") ||
|
|
158
|
+
entry.name === "testdata") {
|
|
159
|
+
continue;
|
|
160
|
+
}
|
|
161
|
+
const found = await search(fullPath);
|
|
162
|
+
if (found)
|
|
163
|
+
return found;
|
|
164
|
+
}
|
|
165
|
+
else if (entry.isFile() && entry.name.endsWith(".go")) {
|
|
166
|
+
const info = await findTypeInFile(fullPath, typeName);
|
|
167
|
+
if (info) {
|
|
168
|
+
return { ...info, path: fullPath };
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
return null;
|
|
173
|
+
}
|
|
174
|
+
return search(dir);
|
|
175
|
+
}
|
|
176
|
+
/**
|
|
177
|
+
* Find types that embed a given type
|
|
178
|
+
*/
|
|
179
|
+
async function findEmbedders(dir, typeName, maxDepth) {
|
|
180
|
+
const results = [];
|
|
181
|
+
let depth = 0;
|
|
182
|
+
async function search(currentDir) {
|
|
183
|
+
if (depth >= maxDepth)
|
|
184
|
+
return;
|
|
185
|
+
depth++;
|
|
186
|
+
try {
|
|
187
|
+
const entries = await fs.readdir(currentDir, { withFileTypes: true });
|
|
188
|
+
for (const entry of entries) {
|
|
189
|
+
const fullPath = path.join(currentDir, entry.name);
|
|
190
|
+
if (entry.isDirectory()) {
|
|
191
|
+
if (entry.name === "vendor" ||
|
|
192
|
+
entry.name.startsWith(".") ||
|
|
193
|
+
entry.name === "testdata") {
|
|
194
|
+
continue;
|
|
195
|
+
}
|
|
196
|
+
await search(fullPath);
|
|
197
|
+
}
|
|
198
|
+
else if (entry.isFile() && entry.name.endsWith(".go")) {
|
|
199
|
+
await searchFileForEmbedders(fullPath, typeName, results);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
catch {
|
|
204
|
+
// Skip directories that can't be read
|
|
205
|
+
}
|
|
206
|
+
depth--;
|
|
207
|
+
}
|
|
208
|
+
await search(dir);
|
|
209
|
+
return results;
|
|
210
|
+
}
|
|
211
|
+
/**
|
|
212
|
+
* Search a file for types that embed the target type
|
|
213
|
+
*/
|
|
214
|
+
async function searchFileForEmbedders(filePath, typeName, results) {
|
|
215
|
+
try {
|
|
216
|
+
const parseResult = await parseFile(filePath);
|
|
217
|
+
const { tree, source } = parseResult;
|
|
218
|
+
for (const node of walkTree(tree.rootNode, ["type_declaration"])) {
|
|
219
|
+
const typeInfo = parseClass(node, source);
|
|
220
|
+
// Check if this type embeds the target
|
|
221
|
+
if (typeInfo.bases.includes(typeName)) {
|
|
222
|
+
results.push({
|
|
223
|
+
name: typeInfo.name,
|
|
224
|
+
path: filePath,
|
|
225
|
+
line: typeInfo.line,
|
|
226
|
+
});
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
catch {
|
|
231
|
+
// Skip files that can't be parsed
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
/**
|
|
235
|
+
* Factory function to create a customized getClassHierarchy tool
|
|
236
|
+
*/
|
|
237
|
+
export function createGetClassHierarchyTool(options) {
|
|
238
|
+
const { defaultMaxDepth = 3 } = options ?? {};
|
|
239
|
+
return defineTool({
|
|
240
|
+
name: "get_class_hierarchy_go",
|
|
241
|
+
description: TOOL_DESCRIPTION,
|
|
242
|
+
inputSchema: TOOL_INPUT_SCHEMA,
|
|
243
|
+
execute: async (input) => {
|
|
244
|
+
return executeGetClassHierarchy({
|
|
245
|
+
...input,
|
|
246
|
+
maxDepth: input.maxDepth ?? defaultMaxDepth,
|
|
247
|
+
});
|
|
248
|
+
},
|
|
249
|
+
});
|
|
250
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* getComplexity Tool
|
|
3
|
+
*
|
|
4
|
+
* Calculate cyclomatic and cognitive complexity for Go functions.
|
|
5
|
+
* Identifies complex code that may need refactoring.
|
|
6
|
+
*/
|
|
7
|
+
import type { Tool } from "@compilr-dev/agents";
|
|
8
|
+
import type { FileComplexityResult } from "./types.js";
|
|
9
|
+
/**
|
|
10
|
+
* Input for getComplexity tool
|
|
11
|
+
*/
|
|
12
|
+
export interface GetComplexityInput {
|
|
13
|
+
/** File or directory to analyze */
|
|
14
|
+
path: string;
|
|
15
|
+
/** Recursive analysis for directories (default: false) */
|
|
16
|
+
recursive?: boolean;
|
|
17
|
+
/** Complexity threshold for warnings (default: 10) */
|
|
18
|
+
threshold?: number;
|
|
19
|
+
/** Only return items above threshold (default: false) */
|
|
20
|
+
onlyAboveThreshold?: boolean;
|
|
21
|
+
/** Maximum files to analyze (default: 50) */
|
|
22
|
+
maxFiles?: number;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Complexity result for multiple files
|
|
26
|
+
*/
|
|
27
|
+
export interface GetComplexityResult {
|
|
28
|
+
/** Analyzed path */
|
|
29
|
+
path: string;
|
|
30
|
+
/** Results per file */
|
|
31
|
+
files: FileComplexityResult[];
|
|
32
|
+
/** Overall stats */
|
|
33
|
+
stats: {
|
|
34
|
+
totalFiles: number;
|
|
35
|
+
totalFunctions: number;
|
|
36
|
+
complexFunctions: number;
|
|
37
|
+
averageComplexity: number;
|
|
38
|
+
maxComplexity: number;
|
|
39
|
+
threshold: number;
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* getComplexity tool - Calculate code complexity
|
|
44
|
+
*/
|
|
45
|
+
export declare const getComplexityTool: Tool<GetComplexityInput>;
|
|
46
|
+
/**
|
|
47
|
+
* Factory function to create a customized getComplexity tool
|
|
48
|
+
*/
|
|
49
|
+
export declare function createGetComplexityTool(options?: {
|
|
50
|
+
defaultThreshold?: number;
|
|
51
|
+
defaultMaxFiles?: number;
|
|
52
|
+
}): Tool<GetComplexityInput>;
|
|
53
|
+
//# sourceMappingURL=get-complexity.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"get-complexity.d.ts","sourceRoot":"","sources":["../../src/tools/get-complexity.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AASH,OAAO,KAAK,EAAE,IAAI,EAAuB,MAAM,qBAAqB,CAAC;AAQrE,OAAO,KAAK,EACV,oBAAoB,EAGrB,MAAM,YAAY,CAAC;AAEpB;;GAEG;AACH,MAAM,WAAW,kBAAkB;IACjC,mCAAmC;IACnC,IAAI,EAAE,MAAM,CAAC;IACb,0DAA0D;IAC1D,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,sDAAsD;IACtD,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,yDAAyD;IACzD,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAC7B,6CAA6C;IAC7C,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED;;GAEG;AACH,MAAM,WAAW,mBAAmB;IAClC,oBAAoB;IACpB,IAAI,EAAE,MAAM,CAAC;IACb,uBAAuB;IACvB,KAAK,EAAE,oBAAoB,EAAE,CAAC;IAC9B,oBAAoB;IACpB,KAAK,EAAE;QACL,UAAU,EAAE,MAAM,CAAC;QACnB,cAAc,EAAE,MAAM,CAAC;QACvB,gBAAgB,EAAE,MAAM,CAAC;QACzB,iBAAiB,EAAE,MAAM,CAAC;QAC1B,aAAa,EAAE,MAAM,CAAC;QACtB,SAAS,EAAE,MAAM,CAAC;KACnB,CAAC;CACH;AAuCD;;GAEG;AACH,eAAO,MAAM,iBAAiB,0BAK5B,CAAC;AAuWH;;GAEG;AACH,wBAAgB,uBAAuB,CAAC,OAAO,CAAC,EAAE;IAChD,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B,GAAG,IAAI,CAAC,kBAAkB,CAAC,CAiB3B"}
|
|
@@ -0,0 +1,357 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* getComplexity Tool
|
|
3
|
+
*
|
|
4
|
+
* Calculate cyclomatic and cognitive complexity for Go functions.
|
|
5
|
+
* Identifies complex code that may need refactoring.
|
|
6
|
+
*/
|
|
7
|
+
import * as fs from "node:fs/promises";
|
|
8
|
+
import * as path from "node:path";
|
|
9
|
+
import { defineTool, createSuccessResult, createErrorResult, } from "@compilr-dev/agents";
|
|
10
|
+
import { parseFile, parseFunction, parseMethod, walkTree, } from "../parser/go-parser.js";
|
|
11
|
+
// Tool description
|
|
12
|
+
const TOOL_DESCRIPTION = `Calculate cyclomatic and cognitive complexity for Go functions.
|
|
13
|
+
Returns complexity metrics and identifies functions that may need refactoring.
|
|
14
|
+
Helps find overly complex code that's hard to test and maintain.`;
|
|
15
|
+
// Tool input schema
|
|
16
|
+
const TOOL_INPUT_SCHEMA = {
|
|
17
|
+
type: "object",
|
|
18
|
+
properties: {
|
|
19
|
+
path: {
|
|
20
|
+
type: "string",
|
|
21
|
+
description: "Go file or directory to analyze",
|
|
22
|
+
},
|
|
23
|
+
recursive: {
|
|
24
|
+
type: "boolean",
|
|
25
|
+
description: "Recursively analyze directories (default: false)",
|
|
26
|
+
default: false,
|
|
27
|
+
},
|
|
28
|
+
threshold: {
|
|
29
|
+
type: "number",
|
|
30
|
+
description: "Cyclomatic complexity threshold for warnings (default: 10)",
|
|
31
|
+
default: 10,
|
|
32
|
+
},
|
|
33
|
+
onlyAboveThreshold: {
|
|
34
|
+
type: "boolean",
|
|
35
|
+
description: "Only return functions above threshold (default: false)",
|
|
36
|
+
default: false,
|
|
37
|
+
},
|
|
38
|
+
maxFiles: {
|
|
39
|
+
type: "number",
|
|
40
|
+
description: "Maximum files to analyze (default: 50)",
|
|
41
|
+
default: 50,
|
|
42
|
+
},
|
|
43
|
+
},
|
|
44
|
+
required: ["path"],
|
|
45
|
+
};
|
|
46
|
+
/**
|
|
47
|
+
* getComplexity tool - Calculate code complexity
|
|
48
|
+
*/
|
|
49
|
+
export const getComplexityTool = defineTool({
|
|
50
|
+
name: "get_complexity_go",
|
|
51
|
+
description: TOOL_DESCRIPTION,
|
|
52
|
+
inputSchema: TOOL_INPUT_SCHEMA,
|
|
53
|
+
execute: executeGetComplexity,
|
|
54
|
+
});
|
|
55
|
+
/**
|
|
56
|
+
* Execute the getComplexity tool
|
|
57
|
+
*/
|
|
58
|
+
async function executeGetComplexity(input) {
|
|
59
|
+
const { path: inputPath, recursive = false, threshold = 10, onlyAboveThreshold = false, maxFiles = 50, } = input;
|
|
60
|
+
if (!inputPath) {
|
|
61
|
+
return createErrorResult("Path is required");
|
|
62
|
+
}
|
|
63
|
+
try {
|
|
64
|
+
const stats = await fs.stat(inputPath);
|
|
65
|
+
const files = [];
|
|
66
|
+
if (stats.isFile()) {
|
|
67
|
+
if (inputPath.endsWith(".go")) {
|
|
68
|
+
const result = await analyzeFileComplexity(inputPath, threshold, onlyAboveThreshold);
|
|
69
|
+
files.push(result);
|
|
70
|
+
}
|
|
71
|
+
else {
|
|
72
|
+
return createErrorResult("File must be a .go file");
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
else if (stats.isDirectory()) {
|
|
76
|
+
const goFiles = await collectGoFiles(inputPath, recursive, maxFiles);
|
|
77
|
+
for (const filePath of goFiles) {
|
|
78
|
+
const result = await analyzeFileComplexity(filePath, threshold, onlyAboveThreshold);
|
|
79
|
+
files.push(result);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
else {
|
|
83
|
+
return createErrorResult("Path must be a file or directory");
|
|
84
|
+
}
|
|
85
|
+
// Calculate overall stats
|
|
86
|
+
const allFunctions = files.flatMap((f) => f.functions);
|
|
87
|
+
const complexFunctions = allFunctions.filter((f) => f.isComplex);
|
|
88
|
+
const avgComplexity = allFunctions.length > 0
|
|
89
|
+
? allFunctions.reduce((sum, f) => sum + f.metrics.cyclomatic, 0) /
|
|
90
|
+
allFunctions.length
|
|
91
|
+
: 0;
|
|
92
|
+
const maxComplexity = allFunctions.length > 0
|
|
93
|
+
? Math.max(...allFunctions.map((f) => f.metrics.cyclomatic))
|
|
94
|
+
: 0;
|
|
95
|
+
const result = {
|
|
96
|
+
path: inputPath,
|
|
97
|
+
files,
|
|
98
|
+
stats: {
|
|
99
|
+
totalFiles: files.length,
|
|
100
|
+
totalFunctions: allFunctions.length,
|
|
101
|
+
complexFunctions: complexFunctions.length,
|
|
102
|
+
averageComplexity: Math.round(avgComplexity * 100) / 100,
|
|
103
|
+
maxComplexity,
|
|
104
|
+
threshold,
|
|
105
|
+
},
|
|
106
|
+
};
|
|
107
|
+
return createSuccessResult(result);
|
|
108
|
+
}
|
|
109
|
+
catch (error) {
|
|
110
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
111
|
+
return createErrorResult(`Failed to analyze complexity: ${message}`);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Collect Go files from a directory
|
|
116
|
+
*/
|
|
117
|
+
async function collectGoFiles(dir, recursive, maxFiles) {
|
|
118
|
+
const files = [];
|
|
119
|
+
async function walk(currentDir) {
|
|
120
|
+
if (files.length >= maxFiles)
|
|
121
|
+
return;
|
|
122
|
+
const entries = await fs.readdir(currentDir, { withFileTypes: true });
|
|
123
|
+
for (const entry of entries) {
|
|
124
|
+
if (files.length >= maxFiles)
|
|
125
|
+
break;
|
|
126
|
+
const fullPath = path.join(currentDir, entry.name);
|
|
127
|
+
if (entry.isDirectory()) {
|
|
128
|
+
if (recursive &&
|
|
129
|
+
!entry.name.startsWith(".") &&
|
|
130
|
+
entry.name !== "vendor" &&
|
|
131
|
+
entry.name !== "testdata") {
|
|
132
|
+
await walk(fullPath);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
else if (entry.isFile() && entry.name.endsWith(".go")) {
|
|
136
|
+
files.push(fullPath);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
await walk(dir);
|
|
141
|
+
return files;
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* Analyze complexity for a single file
|
|
145
|
+
*/
|
|
146
|
+
async function analyzeFileComplexity(filePath, threshold, onlyAboveThreshold) {
|
|
147
|
+
const parseResult = await parseFile(filePath);
|
|
148
|
+
const { tree, source } = parseResult;
|
|
149
|
+
const rootNode = tree.rootNode;
|
|
150
|
+
const functions = [];
|
|
151
|
+
// Find all functions and methods
|
|
152
|
+
for (const node of walkTree(rootNode)) {
|
|
153
|
+
// Regular functions
|
|
154
|
+
if (node.type === "function_declaration") {
|
|
155
|
+
const funcInfo = parseFunction(node, source);
|
|
156
|
+
const metrics = calculateComplexity(node, source);
|
|
157
|
+
const isComplex = metrics.cyclomatic >= threshold;
|
|
158
|
+
if (!onlyAboveThreshold || isComplex) {
|
|
159
|
+
functions.push({
|
|
160
|
+
name: funcInfo.name,
|
|
161
|
+
path: filePath,
|
|
162
|
+
line: funcInfo.line,
|
|
163
|
+
metrics,
|
|
164
|
+
isComplex,
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
// Methods (functions with receivers)
|
|
169
|
+
if (node.type === "method_declaration") {
|
|
170
|
+
const methodInfo = parseMethod(node, source);
|
|
171
|
+
const metrics = calculateComplexity(node, source);
|
|
172
|
+
const isComplex = metrics.cyclomatic >= threshold;
|
|
173
|
+
if (!onlyAboveThreshold || isComplex) {
|
|
174
|
+
const name = methodInfo.receiverType
|
|
175
|
+
? `${methodInfo.receiverType}.${methodInfo.name}`
|
|
176
|
+
: methodInfo.name;
|
|
177
|
+
functions.push({
|
|
178
|
+
name,
|
|
179
|
+
path: filePath,
|
|
180
|
+
line: methodInfo.line,
|
|
181
|
+
metrics,
|
|
182
|
+
isComplex,
|
|
183
|
+
receiverType: methodInfo.receiverType,
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
// Calculate file-level averages
|
|
189
|
+
const totalCyclomatic = functions.reduce((sum, f) => sum + f.metrics.cyclomatic, 0);
|
|
190
|
+
const avgComplexity = functions.length > 0 ? totalCyclomatic / functions.length : 0;
|
|
191
|
+
const maxComplexity = functions.length > 0
|
|
192
|
+
? Math.max(...functions.map((f) => f.metrics.cyclomatic))
|
|
193
|
+
: 0;
|
|
194
|
+
return {
|
|
195
|
+
path: filePath,
|
|
196
|
+
functions,
|
|
197
|
+
averageComplexity: Math.round(avgComplexity * 100) / 100,
|
|
198
|
+
maxComplexity,
|
|
199
|
+
stats: {
|
|
200
|
+
totalFunctions: functions.length,
|
|
201
|
+
complexFunctions: functions.filter((f) => f.isComplex).length,
|
|
202
|
+
simpleAverage: avgComplexity,
|
|
203
|
+
},
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
/**
|
|
207
|
+
* Calculate complexity metrics for a function/method
|
|
208
|
+
*/
|
|
209
|
+
function calculateComplexity(node, _source) {
|
|
210
|
+
let cyclomatic = 1; // Start with 1 for the function itself
|
|
211
|
+
let cognitive = 0;
|
|
212
|
+
let maxNesting = 0;
|
|
213
|
+
let parameters = 0;
|
|
214
|
+
// Count parameters
|
|
215
|
+
const params = node.childForFieldName("parameters");
|
|
216
|
+
if (params) {
|
|
217
|
+
for (const child of params.children) {
|
|
218
|
+
if (child.type === "parameter_declaration") {
|
|
219
|
+
// Count identifiers in parameter declaration
|
|
220
|
+
for (const c of child.children) {
|
|
221
|
+
if (c.type === "identifier") {
|
|
222
|
+
parameters++;
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
// Walk the function body to count complexity
|
|
229
|
+
function walk(n, nesting) {
|
|
230
|
+
maxNesting = Math.max(maxNesting, nesting);
|
|
231
|
+
switch (n.type) {
|
|
232
|
+
// Control flow - add to cyclomatic
|
|
233
|
+
case "if_statement":
|
|
234
|
+
cyclomatic++;
|
|
235
|
+
cognitive += 1 + nesting; // Cognitive adds nesting penalty
|
|
236
|
+
for (const child of n.children) {
|
|
237
|
+
walk(child, nesting + 1);
|
|
238
|
+
}
|
|
239
|
+
return;
|
|
240
|
+
case "else_clause":
|
|
241
|
+
// "else" doesn't add to cyclomatic but adds to cognitive
|
|
242
|
+
cognitive += 1;
|
|
243
|
+
for (const child of n.children) {
|
|
244
|
+
walk(child, nesting);
|
|
245
|
+
}
|
|
246
|
+
return;
|
|
247
|
+
case "for_statement":
|
|
248
|
+
case "range_clause":
|
|
249
|
+
cyclomatic++;
|
|
250
|
+
cognitive += 1 + nesting;
|
|
251
|
+
for (const child of n.children) {
|
|
252
|
+
walk(child, nesting + 1);
|
|
253
|
+
}
|
|
254
|
+
return;
|
|
255
|
+
case "switch_statement":
|
|
256
|
+
case "type_switch_statement":
|
|
257
|
+
cyclomatic++;
|
|
258
|
+
cognitive += 1 + nesting;
|
|
259
|
+
for (const child of n.children) {
|
|
260
|
+
walk(child, nesting + 1);
|
|
261
|
+
}
|
|
262
|
+
return;
|
|
263
|
+
case "case_clause":
|
|
264
|
+
case "default_case":
|
|
265
|
+
cyclomatic++;
|
|
266
|
+
for (const child of n.children) {
|
|
267
|
+
walk(child, nesting);
|
|
268
|
+
}
|
|
269
|
+
return;
|
|
270
|
+
case "select_statement":
|
|
271
|
+
cyclomatic++;
|
|
272
|
+
cognitive += 1 + nesting;
|
|
273
|
+
for (const child of n.children) {
|
|
274
|
+
walk(child, nesting + 1);
|
|
275
|
+
}
|
|
276
|
+
return;
|
|
277
|
+
case "communication_case":
|
|
278
|
+
cyclomatic++;
|
|
279
|
+
for (const child of n.children) {
|
|
280
|
+
walk(child, nesting);
|
|
281
|
+
}
|
|
282
|
+
return;
|
|
283
|
+
// Boolean operators
|
|
284
|
+
case "binary_expression": {
|
|
285
|
+
const operator = n.children.find((c) => c.type === "&&" || c.type === "||");
|
|
286
|
+
if (operator) {
|
|
287
|
+
cyclomatic++;
|
|
288
|
+
cognitive++;
|
|
289
|
+
}
|
|
290
|
+
for (const child of n.children) {
|
|
291
|
+
walk(child, nesting);
|
|
292
|
+
}
|
|
293
|
+
return;
|
|
294
|
+
}
|
|
295
|
+
// Go keyword (goroutine)
|
|
296
|
+
case "go_statement":
|
|
297
|
+
cognitive += 1 + nesting; // Concurrency adds cognitive load
|
|
298
|
+
for (const child of n.children) {
|
|
299
|
+
walk(child, nesting);
|
|
300
|
+
}
|
|
301
|
+
return;
|
|
302
|
+
// Defer
|
|
303
|
+
case "defer_statement":
|
|
304
|
+
cognitive++; // Defer adds cognitive load (execution order)
|
|
305
|
+
for (const child of n.children) {
|
|
306
|
+
walk(child, nesting);
|
|
307
|
+
}
|
|
308
|
+
return;
|
|
309
|
+
// Recover (error handling)
|
|
310
|
+
case "call_expression": {
|
|
311
|
+
const funcName = n.childForFieldName("function");
|
|
312
|
+
if (funcName?.text === "recover") {
|
|
313
|
+
cognitive++; // Panic/recover adds cognitive load
|
|
314
|
+
}
|
|
315
|
+
for (const child of n.children) {
|
|
316
|
+
walk(child, nesting);
|
|
317
|
+
}
|
|
318
|
+
return;
|
|
319
|
+
}
|
|
320
|
+
default:
|
|
321
|
+
for (const child of n.children) {
|
|
322
|
+
walk(child, nesting);
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
const body = node.childForFieldName("body");
|
|
327
|
+
if (body) {
|
|
328
|
+
walk(body, 0);
|
|
329
|
+
}
|
|
330
|
+
// Count lines of code
|
|
331
|
+
const linesOfCode = node.endPosition.row - node.startPosition.row + 1;
|
|
332
|
+
return {
|
|
333
|
+
cyclomatic,
|
|
334
|
+
cognitive,
|
|
335
|
+
linesOfCode,
|
|
336
|
+
parameters,
|
|
337
|
+
nestingDepth: maxNesting,
|
|
338
|
+
};
|
|
339
|
+
}
|
|
340
|
+
/**
|
|
341
|
+
* Factory function to create a customized getComplexity tool
|
|
342
|
+
*/
|
|
343
|
+
export function createGetComplexityTool(options) {
|
|
344
|
+
const { defaultThreshold = 10, defaultMaxFiles = 50 } = options ?? {};
|
|
345
|
+
return defineTool({
|
|
346
|
+
name: "get_complexity_go",
|
|
347
|
+
description: TOOL_DESCRIPTION,
|
|
348
|
+
inputSchema: TOOL_INPUT_SCHEMA,
|
|
349
|
+
execute: async (input) => {
|
|
350
|
+
return executeGetComplexity({
|
|
351
|
+
...input,
|
|
352
|
+
threshold: input.threshold ?? defaultThreshold,
|
|
353
|
+
maxFiles: input.maxFiles ?? defaultMaxFiles,
|
|
354
|
+
});
|
|
355
|
+
},
|
|
356
|
+
});
|
|
357
|
+
}
|