midas-mcp 5.43.2 → 5.43.11
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/analyzer.d.ts +19 -0
- package/dist/analyzer.d.ts.map +1 -1
- package/dist/analyzer.js +95 -1
- package/dist/analyzer.js.map +1 -1
- package/dist/code-discovery.d.ts.map +1 -1
- package/dist/code-discovery.js +39 -2
- package/dist/code-discovery.js.map +1 -1
- package/dist/docs/INGREDIENTS.md +57 -17
- package/dist/file-index.d.ts +123 -0
- package/dist/file-index.d.ts.map +1 -0
- package/dist/file-index.js +430 -0
- package/dist/file-index.js.map +1 -0
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +11 -2
- package/dist/server.js.map +1 -1
- package/dist/state/phase.d.ts +8 -3
- package/dist/state/phase.d.ts.map +1 -1
- package/dist/state/phase.js +85 -7
- package/dist/state/phase.js.map +1 -1
- package/dist/tools/completeness.d.ts.map +1 -1
- package/dist/tools/completeness.js +153 -24
- package/dist/tools/completeness.js.map +1 -1
- package/dist/tools/complexity.d.ts +91 -0
- package/dist/tools/complexity.d.ts.map +1 -0
- package/dist/tools/complexity.js +583 -0
- package/dist/tools/complexity.js.map +1 -0
- package/dist/tools/index.d.ts +2 -0
- package/dist/tools/index.d.ts.map +1 -1
- package/dist/tools/index.js +4 -0
- package/dist/tools/index.js.map +1 -1
- package/dist/tools/vuln-scan.d.ts +74 -0
- package/dist/tools/vuln-scan.d.ts.map +1 -0
- package/dist/tools/vuln-scan.js +493 -0
- package/dist/tools/vuln-scan.js.map +1 -0
- package/dist/tracker.d.ts +22 -0
- package/dist/tracker.d.ts.map +1 -1
- package/dist/tracker.js +93 -16
- package/dist/tracker.js.map +1 -1
- package/dist/tui.d.ts.map +1 -1
- package/dist/tui.js +44 -32
- package/dist/tui.js.map +1 -1
- package/docs/INGREDIENTS.md +57 -17
- package/package.json +1 -1
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Code Complexity Analysis Tools
|
|
3
|
+
*
|
|
4
|
+
* Metrics-based code quality analysis:
|
|
5
|
+
* - Cyclomatic complexity
|
|
6
|
+
* - Nesting depth
|
|
7
|
+
* - Function length
|
|
8
|
+
* - File size
|
|
9
|
+
* - Cognitive complexity indicators
|
|
10
|
+
*/
|
|
11
|
+
import { z } from 'zod';
|
|
12
|
+
export declare const complexitySchema: z.ZodObject<{
|
|
13
|
+
projectPath: z.ZodOptional<z.ZodString>;
|
|
14
|
+
threshold: z.ZodOptional<z.ZodNumber>;
|
|
15
|
+
limit: z.ZodOptional<z.ZodNumber>;
|
|
16
|
+
}, "strip", z.ZodTypeAny, {
|
|
17
|
+
projectPath?: string | undefined;
|
|
18
|
+
limit?: number | undefined;
|
|
19
|
+
threshold?: number | undefined;
|
|
20
|
+
}, {
|
|
21
|
+
projectPath?: string | undefined;
|
|
22
|
+
limit?: number | undefined;
|
|
23
|
+
threshold?: number | undefined;
|
|
24
|
+
}>;
|
|
25
|
+
export type ComplexityInput = z.infer<typeof complexitySchema>;
|
|
26
|
+
export declare const simplifySchema: z.ZodObject<{
|
|
27
|
+
projectPath: z.ZodOptional<z.ZodString>;
|
|
28
|
+
file: z.ZodOptional<z.ZodString>;
|
|
29
|
+
}, "strip", z.ZodTypeAny, {
|
|
30
|
+
projectPath?: string | undefined;
|
|
31
|
+
file?: string | undefined;
|
|
32
|
+
}, {
|
|
33
|
+
projectPath?: string | undefined;
|
|
34
|
+
file?: string | undefined;
|
|
35
|
+
}>;
|
|
36
|
+
export type SimplifyInput = z.infer<typeof simplifySchema>;
|
|
37
|
+
export interface FunctionComplexity {
|
|
38
|
+
file: string;
|
|
39
|
+
name: string;
|
|
40
|
+
line: number;
|
|
41
|
+
metrics: {
|
|
42
|
+
cyclomaticComplexity: number;
|
|
43
|
+
nestingDepth: number;
|
|
44
|
+
lineCount: number;
|
|
45
|
+
parameterCount: number;
|
|
46
|
+
};
|
|
47
|
+
issues: string[];
|
|
48
|
+
severity: 'low' | 'medium' | 'high' | 'critical';
|
|
49
|
+
}
|
|
50
|
+
export interface FileComplexity {
|
|
51
|
+
file: string;
|
|
52
|
+
lineCount: number;
|
|
53
|
+
functionCount: number;
|
|
54
|
+
avgComplexity: number;
|
|
55
|
+
maxComplexity: number;
|
|
56
|
+
issues: string[];
|
|
57
|
+
}
|
|
58
|
+
export interface ComplexityReport {
|
|
59
|
+
summary: {
|
|
60
|
+
filesAnalyzed: number;
|
|
61
|
+
functionsAnalyzed: number;
|
|
62
|
+
avgComplexity: number;
|
|
63
|
+
hotspotCount: number;
|
|
64
|
+
};
|
|
65
|
+
hotspots: FunctionComplexity[];
|
|
66
|
+
fileStats: FileComplexity[];
|
|
67
|
+
suggestedPrompt: string;
|
|
68
|
+
}
|
|
69
|
+
export interface SimplifyReport {
|
|
70
|
+
file: string;
|
|
71
|
+
issues: SimplifyIssue[];
|
|
72
|
+
suggestedPrompt: string;
|
|
73
|
+
estimatedImprovement: string;
|
|
74
|
+
}
|
|
75
|
+
export interface SimplifyIssue {
|
|
76
|
+
type: 'nesting' | 'length' | 'complexity' | 'duplication' | 'abstraction' | 'dead-code';
|
|
77
|
+
location: string;
|
|
78
|
+
line: number;
|
|
79
|
+
description: string;
|
|
80
|
+
suggestion: string;
|
|
81
|
+
priority: 'low' | 'medium' | 'high';
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Analyze code complexity across the project
|
|
85
|
+
*/
|
|
86
|
+
export declare function analyzeComplexity(input: ComplexityInput): ComplexityReport;
|
|
87
|
+
/**
|
|
88
|
+
* Analyze code for simplification opportunities
|
|
89
|
+
*/
|
|
90
|
+
export declare function analyzeSimplify(input: SimplifyInput): SimplifyReport;
|
|
91
|
+
//# sourceMappingURL=complexity.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"complexity.d.ts","sourceRoot":"","sources":["../../src/tools/complexity.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AASxB,eAAO,MAAM,gBAAgB;;;;;;;;;;;;EAI3B,CAAC;AAEH,MAAM,MAAM,eAAe,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,gBAAgB,CAAC,CAAC;AAE/D,eAAO,MAAM,cAAc;;;;;;;;;EAGzB,CAAC;AAEH,MAAM,MAAM,aAAa,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,cAAc,CAAC,CAAC;AAM3D,MAAM,WAAW,kBAAkB;IACjC,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE;QACP,oBAAoB,EAAE,MAAM,CAAC;QAC7B,YAAY,EAAE,MAAM,CAAC;QACrB,SAAS,EAAE,MAAM,CAAC;QAClB,cAAc,EAAE,MAAM,CAAC;KACxB,CAAC;IACF,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,QAAQ,EAAE,KAAK,GAAG,QAAQ,GAAG,MAAM,GAAG,UAAU,CAAC;CAClD;AAED,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,CAAC;IAClB,aAAa,EAAE,MAAM,CAAC;IACtB,aAAa,EAAE,MAAM,CAAC;IACtB,aAAa,EAAE,MAAM,CAAC;IACtB,MAAM,EAAE,MAAM,EAAE,CAAC;CAClB;AAED,MAAM,WAAW,gBAAgB;IAC/B,OAAO,EAAE;QACP,aAAa,EAAE,MAAM,CAAC;QACtB,iBAAiB,EAAE,MAAM,CAAC;QAC1B,aAAa,EAAE,MAAM,CAAC;QACtB,YAAY,EAAE,MAAM,CAAC;KACtB,CAAC;IACF,QAAQ,EAAE,kBAAkB,EAAE,CAAC;IAC/B,SAAS,EAAE,cAAc,EAAE,CAAC;IAC5B,eAAe,EAAE,MAAM,CAAC;CACzB;AAED,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,aAAa,EAAE,CAAC;IACxB,eAAe,EAAE,MAAM,CAAC;IACxB,oBAAoB,EAAE,MAAM,CAAC;CAC9B;AAED,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,SAAS,GAAG,QAAQ,GAAG,YAAY,GAAG,aAAa,GAAG,aAAa,GAAG,WAAW,CAAC;IACxF,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,KAAK,GAAG,QAAQ,GAAG,MAAM,CAAC;CACrC;AAsBD;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,eAAe,GAAG,gBAAgB,CAsE1E;AAmWD;;GAEG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE,aAAa,GAAG,cAAc,CAwCpE"}
|
|
@@ -0,0 +1,583 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Code Complexity Analysis Tools
|
|
3
|
+
*
|
|
4
|
+
* Metrics-based code quality analysis:
|
|
5
|
+
* - Cyclomatic complexity
|
|
6
|
+
* - Nesting depth
|
|
7
|
+
* - Function length
|
|
8
|
+
* - File size
|
|
9
|
+
* - Cognitive complexity indicators
|
|
10
|
+
*/
|
|
11
|
+
import { z } from 'zod';
|
|
12
|
+
import { existsSync, readFileSync, readdirSync } from 'fs';
|
|
13
|
+
import { join, relative, extname } from 'path';
|
|
14
|
+
import { sanitizePath } from '../security.js';
|
|
15
|
+
// ============================================================================
|
|
16
|
+
// SCHEMAS
|
|
17
|
+
// ============================================================================
|
|
18
|
+
export const complexitySchema = z.object({
|
|
19
|
+
projectPath: z.string().optional().describe('Path to project root'),
|
|
20
|
+
threshold: z.number().optional().describe('Complexity threshold (default: 10)'),
|
|
21
|
+
limit: z.number().optional().describe('Max results to return (default: 20)'),
|
|
22
|
+
});
|
|
23
|
+
export const simplifySchema = z.object({
|
|
24
|
+
projectPath: z.string().optional().describe('Path to project root'),
|
|
25
|
+
file: z.string().optional().describe('Specific file to analyze'),
|
|
26
|
+
});
|
|
27
|
+
// ============================================================================
|
|
28
|
+
// CONSTANTS
|
|
29
|
+
// ============================================================================
|
|
30
|
+
const IGNORE_DIRS = ['node_modules', '.git', 'dist', 'build', '.midas', 'coverage', '.next', '__pycache__'];
|
|
31
|
+
const CODE_EXTENSIONS = ['.ts', '.tsx', '.js', '.jsx', '.py', '.rs', '.go'];
|
|
32
|
+
// Complexity thresholds
|
|
33
|
+
const THRESHOLDS = {
|
|
34
|
+
cyclomaticComplexity: { low: 5, medium: 10, high: 15, critical: 20 },
|
|
35
|
+
nestingDepth: { low: 2, medium: 3, high: 4, critical: 5 },
|
|
36
|
+
lineCount: { low: 30, medium: 50, high: 100, critical: 200 },
|
|
37
|
+
fileLineCount: { low: 200, medium: 400, high: 600, critical: 1000 },
|
|
38
|
+
parameterCount: { low: 3, medium: 5, high: 7, critical: 10 },
|
|
39
|
+
};
|
|
40
|
+
// ============================================================================
|
|
41
|
+
// COMPLEXITY ANALYSIS
|
|
42
|
+
// ============================================================================
|
|
43
|
+
/**
|
|
44
|
+
* Analyze code complexity across the project
|
|
45
|
+
*/
|
|
46
|
+
export function analyzeComplexity(input) {
|
|
47
|
+
const projectPath = sanitizePath(input.projectPath);
|
|
48
|
+
const threshold = input.threshold ?? 10;
|
|
49
|
+
const limit = input.limit ?? 20;
|
|
50
|
+
const allFunctions = [];
|
|
51
|
+
const fileStats = [];
|
|
52
|
+
let filesAnalyzed = 0;
|
|
53
|
+
function scanDir(dir, depth = 0) {
|
|
54
|
+
if (depth > 10)
|
|
55
|
+
return;
|
|
56
|
+
try {
|
|
57
|
+
const entries = readdirSync(dir, { withFileTypes: true });
|
|
58
|
+
for (const entry of entries) {
|
|
59
|
+
if (IGNORE_DIRS.includes(entry.name) || entry.name.startsWith('.'))
|
|
60
|
+
continue;
|
|
61
|
+
const fullPath = join(dir, entry.name);
|
|
62
|
+
if (entry.isDirectory()) {
|
|
63
|
+
scanDir(fullPath, depth + 1);
|
|
64
|
+
}
|
|
65
|
+
else if (entry.isFile()) {
|
|
66
|
+
const ext = extname(entry.name);
|
|
67
|
+
if (CODE_EXTENSIONS.includes(ext)) {
|
|
68
|
+
const result = analyzeFile(fullPath, projectPath);
|
|
69
|
+
if (result) {
|
|
70
|
+
filesAnalyzed++;
|
|
71
|
+
allFunctions.push(...result.functions);
|
|
72
|
+
fileStats.push(result.fileStats);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
catch {
|
|
79
|
+
// Skip inaccessible
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
scanDir(projectPath);
|
|
83
|
+
// Filter hotspots above threshold
|
|
84
|
+
const hotspots = allFunctions
|
|
85
|
+
.filter(f => f.metrics.cyclomaticComplexity >= threshold)
|
|
86
|
+
.sort((a, b) => b.metrics.cyclomaticComplexity - a.metrics.cyclomaticComplexity)
|
|
87
|
+
.slice(0, limit);
|
|
88
|
+
// Calculate summary
|
|
89
|
+
const totalComplexity = allFunctions.reduce((sum, f) => sum + f.metrics.cyclomaticComplexity, 0);
|
|
90
|
+
const avgComplexity = allFunctions.length > 0 ? totalComplexity / allFunctions.length : 0;
|
|
91
|
+
// Generate suggested prompt
|
|
92
|
+
let suggestedPrompt = 'Codebase complexity is healthy. No immediate action needed.';
|
|
93
|
+
if (hotspots.length > 0) {
|
|
94
|
+
const top = hotspots[0];
|
|
95
|
+
suggestedPrompt = `Refactor ${top.name} in ${top.file}:${top.line} - complexity ${top.metrics.cyclomaticComplexity}, ` +
|
|
96
|
+
`${top.metrics.nestingDepth} levels deep, ${top.metrics.lineCount} lines. ${top.issues[0] || 'Extract helper functions.'}`;
|
|
97
|
+
}
|
|
98
|
+
return {
|
|
99
|
+
summary: {
|
|
100
|
+
filesAnalyzed,
|
|
101
|
+
functionsAnalyzed: allFunctions.length,
|
|
102
|
+
avgComplexity: Math.round(avgComplexity * 10) / 10,
|
|
103
|
+
hotspotCount: hotspots.length,
|
|
104
|
+
},
|
|
105
|
+
hotspots,
|
|
106
|
+
fileStats: fileStats
|
|
107
|
+
.sort((a, b) => b.maxComplexity - a.maxComplexity)
|
|
108
|
+
.slice(0, 10),
|
|
109
|
+
suggestedPrompt,
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Analyze a single file for complexity
|
|
114
|
+
*/
|
|
115
|
+
function analyzeFile(filePath, projectPath) {
|
|
116
|
+
try {
|
|
117
|
+
const content = readFileSync(filePath, 'utf-8');
|
|
118
|
+
const lines = content.split('\n');
|
|
119
|
+
const relativePath = relative(projectPath, filePath);
|
|
120
|
+
const functions = [];
|
|
121
|
+
const ext = extname(filePath);
|
|
122
|
+
// Extract functions based on language
|
|
123
|
+
if (['.ts', '.tsx', '.js', '.jsx'].includes(ext)) {
|
|
124
|
+
functions.push(...extractJSFunctions(content, relativePath));
|
|
125
|
+
}
|
|
126
|
+
else if (ext === '.py') {
|
|
127
|
+
functions.push(...extractPythonFunctions(content, relativePath));
|
|
128
|
+
}
|
|
129
|
+
else if (ext === '.go') {
|
|
130
|
+
functions.push(...extractGoFunctions(content, relativePath));
|
|
131
|
+
}
|
|
132
|
+
else if (ext === '.rs') {
|
|
133
|
+
functions.push(...extractRustFunctions(content, relativePath));
|
|
134
|
+
}
|
|
135
|
+
// File-level stats
|
|
136
|
+
const fileIssues = [];
|
|
137
|
+
if (lines.length > THRESHOLDS.fileLineCount.high) {
|
|
138
|
+
fileIssues.push(`File has ${lines.length} lines - consider splitting`);
|
|
139
|
+
}
|
|
140
|
+
const avgComplexity = functions.length > 0
|
|
141
|
+
? functions.reduce((sum, f) => sum + f.metrics.cyclomaticComplexity, 0) / functions.length
|
|
142
|
+
: 0;
|
|
143
|
+
const maxComplexity = functions.length > 0
|
|
144
|
+
? Math.max(...functions.map(f => f.metrics.cyclomaticComplexity))
|
|
145
|
+
: 0;
|
|
146
|
+
return {
|
|
147
|
+
functions,
|
|
148
|
+
fileStats: {
|
|
149
|
+
file: relativePath,
|
|
150
|
+
lineCount: lines.length,
|
|
151
|
+
functionCount: functions.length,
|
|
152
|
+
avgComplexity: Math.round(avgComplexity * 10) / 10,
|
|
153
|
+
maxComplexity,
|
|
154
|
+
issues: fileIssues,
|
|
155
|
+
},
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
catch {
|
|
159
|
+
return null;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* Extract and analyze JavaScript/TypeScript functions
|
|
164
|
+
*/
|
|
165
|
+
function extractJSFunctions(content, file) {
|
|
166
|
+
const functions = [];
|
|
167
|
+
const lines = content.split('\n');
|
|
168
|
+
// Match function declarations, arrow functions, methods
|
|
169
|
+
const funcPatterns = [
|
|
170
|
+
/(?:export\s+)?(?:async\s+)?function\s+(\w+)\s*\(([^)]*)\)/g,
|
|
171
|
+
/(?:export\s+)?const\s+(\w+)\s*=\s*(?:async\s+)?\(([^)]*)\)\s*(?::\s*\w+)?\s*=>/g,
|
|
172
|
+
/(?:export\s+)?const\s+(\w+)\s*=\s*(?:async\s+)?function\s*\(([^)]*)\)/g,
|
|
173
|
+
/(\w+)\s*\(([^)]*)\)\s*(?::\s*\w+)?\s*\{/g, // class methods
|
|
174
|
+
];
|
|
175
|
+
for (const pattern of funcPatterns) {
|
|
176
|
+
let match;
|
|
177
|
+
while ((match = pattern.exec(content)) !== null) {
|
|
178
|
+
const name = match[1];
|
|
179
|
+
const params = match[2];
|
|
180
|
+
const startIndex = match.index;
|
|
181
|
+
const lineNum = content.slice(0, startIndex).split('\n').length;
|
|
182
|
+
// Find function body
|
|
183
|
+
const bodyStart = content.indexOf('{', startIndex);
|
|
184
|
+
if (bodyStart === -1)
|
|
185
|
+
continue;
|
|
186
|
+
const body = extractBracedBlock(content, bodyStart);
|
|
187
|
+
if (!body)
|
|
188
|
+
continue;
|
|
189
|
+
const metrics = calculateMetrics(body, params);
|
|
190
|
+
const issues = getIssues(metrics, name);
|
|
191
|
+
const severity = getSeverity(metrics);
|
|
192
|
+
functions.push({
|
|
193
|
+
file,
|
|
194
|
+
name,
|
|
195
|
+
line: lineNum,
|
|
196
|
+
metrics,
|
|
197
|
+
issues,
|
|
198
|
+
severity,
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
return functions;
|
|
203
|
+
}
|
|
204
|
+
/**
|
|
205
|
+
* Extract Python functions
|
|
206
|
+
*/
|
|
207
|
+
function extractPythonFunctions(content, file) {
|
|
208
|
+
const functions = [];
|
|
209
|
+
const lines = content.split('\n');
|
|
210
|
+
const funcPattern = /^(\s*)def\s+(\w+)\s*\(([^)]*)\)/gm;
|
|
211
|
+
let match;
|
|
212
|
+
while ((match = funcPattern.exec(content)) !== null) {
|
|
213
|
+
const indent = match[1].length;
|
|
214
|
+
const name = match[2];
|
|
215
|
+
const params = match[3];
|
|
216
|
+
const startIndex = match.index;
|
|
217
|
+
const lineNum = content.slice(0, startIndex).split('\n').length;
|
|
218
|
+
// Find function body (indented block)
|
|
219
|
+
const bodyLines = [];
|
|
220
|
+
for (let i = lineNum; i < lines.length; i++) {
|
|
221
|
+
const line = lines[i];
|
|
222
|
+
if (line.trim() === '') {
|
|
223
|
+
bodyLines.push(line);
|
|
224
|
+
continue;
|
|
225
|
+
}
|
|
226
|
+
const lineIndent = line.match(/^(\s*)/)?.[1].length ?? 0;
|
|
227
|
+
if (lineIndent <= indent && line.trim() !== '')
|
|
228
|
+
break;
|
|
229
|
+
bodyLines.push(line);
|
|
230
|
+
}
|
|
231
|
+
const body = bodyLines.join('\n');
|
|
232
|
+
const metrics = calculateMetrics(body, params);
|
|
233
|
+
const issues = getIssues(metrics, name);
|
|
234
|
+
const severity = getSeverity(metrics);
|
|
235
|
+
functions.push({
|
|
236
|
+
file,
|
|
237
|
+
name,
|
|
238
|
+
line: lineNum,
|
|
239
|
+
metrics,
|
|
240
|
+
issues,
|
|
241
|
+
severity,
|
|
242
|
+
});
|
|
243
|
+
}
|
|
244
|
+
return functions;
|
|
245
|
+
}
|
|
246
|
+
/**
|
|
247
|
+
* Extract Go functions
|
|
248
|
+
*/
|
|
249
|
+
function extractGoFunctions(content, file) {
|
|
250
|
+
const functions = [];
|
|
251
|
+
const funcPattern = /func\s+(?:\(\w+\s+\*?\w+\)\s+)?(\w+)\s*\(([^)]*)\)/g;
|
|
252
|
+
let match;
|
|
253
|
+
while ((match = funcPattern.exec(content)) !== null) {
|
|
254
|
+
const name = match[1];
|
|
255
|
+
const params = match[2];
|
|
256
|
+
const startIndex = match.index;
|
|
257
|
+
const lineNum = content.slice(0, startIndex).split('\n').length;
|
|
258
|
+
const bodyStart = content.indexOf('{', startIndex);
|
|
259
|
+
if (bodyStart === -1)
|
|
260
|
+
continue;
|
|
261
|
+
const body = extractBracedBlock(content, bodyStart);
|
|
262
|
+
if (!body)
|
|
263
|
+
continue;
|
|
264
|
+
const metrics = calculateMetrics(body, params);
|
|
265
|
+
const issues = getIssues(metrics, name);
|
|
266
|
+
const severity = getSeverity(metrics);
|
|
267
|
+
functions.push({
|
|
268
|
+
file,
|
|
269
|
+
name,
|
|
270
|
+
line: lineNum,
|
|
271
|
+
metrics,
|
|
272
|
+
issues,
|
|
273
|
+
severity,
|
|
274
|
+
});
|
|
275
|
+
}
|
|
276
|
+
return functions;
|
|
277
|
+
}
|
|
278
|
+
/**
|
|
279
|
+
* Extract Rust functions
|
|
280
|
+
*/
|
|
281
|
+
function extractRustFunctions(content, file) {
|
|
282
|
+
const functions = [];
|
|
283
|
+
const funcPattern = /(?:pub\s+)?(?:async\s+)?fn\s+(\w+)\s*(?:<[^>]*>)?\s*\(([^)]*)\)/g;
|
|
284
|
+
let match;
|
|
285
|
+
while ((match = funcPattern.exec(content)) !== null) {
|
|
286
|
+
const name = match[1];
|
|
287
|
+
const params = match[2];
|
|
288
|
+
const startIndex = match.index;
|
|
289
|
+
const lineNum = content.slice(0, startIndex).split('\n').length;
|
|
290
|
+
const bodyStart = content.indexOf('{', startIndex);
|
|
291
|
+
if (bodyStart === -1)
|
|
292
|
+
continue;
|
|
293
|
+
const body = extractBracedBlock(content, bodyStart);
|
|
294
|
+
if (!body)
|
|
295
|
+
continue;
|
|
296
|
+
const metrics = calculateMetrics(body, params);
|
|
297
|
+
const issues = getIssues(metrics, name);
|
|
298
|
+
const severity = getSeverity(metrics);
|
|
299
|
+
functions.push({
|
|
300
|
+
file,
|
|
301
|
+
name,
|
|
302
|
+
line: lineNum,
|
|
303
|
+
metrics,
|
|
304
|
+
issues,
|
|
305
|
+
severity,
|
|
306
|
+
});
|
|
307
|
+
}
|
|
308
|
+
return functions;
|
|
309
|
+
}
|
|
310
|
+
/**
|
|
311
|
+
* Extract a braced block from content starting at openBrace index
|
|
312
|
+
*/
|
|
313
|
+
function extractBracedBlock(content, openBrace) {
|
|
314
|
+
let depth = 0;
|
|
315
|
+
let i = openBrace;
|
|
316
|
+
while (i < content.length) {
|
|
317
|
+
if (content[i] === '{')
|
|
318
|
+
depth++;
|
|
319
|
+
if (content[i] === '}')
|
|
320
|
+
depth--;
|
|
321
|
+
if (depth === 0) {
|
|
322
|
+
return content.slice(openBrace, i + 1);
|
|
323
|
+
}
|
|
324
|
+
i++;
|
|
325
|
+
}
|
|
326
|
+
return null;
|
|
327
|
+
}
|
|
328
|
+
/**
|
|
329
|
+
* Calculate complexity metrics for a function body
|
|
330
|
+
*/
|
|
331
|
+
function calculateMetrics(body, params) {
|
|
332
|
+
const lines = body.split('\n').filter(l => l.trim() !== '');
|
|
333
|
+
// Cyclomatic complexity: count decision points
|
|
334
|
+
const decisionPatterns = [
|
|
335
|
+
/\bif\s*\(/g,
|
|
336
|
+
/\belse\s+if\s*\(/g,
|
|
337
|
+
/\bfor\s*\(/g,
|
|
338
|
+
/\bwhile\s*\(/g,
|
|
339
|
+
/\bcase\s+/g,
|
|
340
|
+
/\bcatch\s*\(/g,
|
|
341
|
+
/\?\s*[^:]/g, // ternary
|
|
342
|
+
/&&/g,
|
|
343
|
+
/\|\|/g,
|
|
344
|
+
];
|
|
345
|
+
let cyclomaticComplexity = 1; // Base complexity
|
|
346
|
+
for (const pattern of decisionPatterns) {
|
|
347
|
+
const matches = body.match(pattern);
|
|
348
|
+
if (matches)
|
|
349
|
+
cyclomaticComplexity += matches.length;
|
|
350
|
+
}
|
|
351
|
+
// Nesting depth: count max indent level
|
|
352
|
+
let maxNesting = 0;
|
|
353
|
+
let currentNesting = 0;
|
|
354
|
+
for (const char of body) {
|
|
355
|
+
if (char === '{') {
|
|
356
|
+
currentNesting++;
|
|
357
|
+
maxNesting = Math.max(maxNesting, currentNesting);
|
|
358
|
+
}
|
|
359
|
+
else if (char === '}') {
|
|
360
|
+
currentNesting--;
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
// Parameter count
|
|
364
|
+
const parameterCount = params.trim() === '' ? 0 : params.split(',').length;
|
|
365
|
+
return {
|
|
366
|
+
cyclomaticComplexity,
|
|
367
|
+
nestingDepth: maxNesting,
|
|
368
|
+
lineCount: lines.length,
|
|
369
|
+
parameterCount,
|
|
370
|
+
};
|
|
371
|
+
}
|
|
372
|
+
/**
|
|
373
|
+
* Generate issues based on metrics
|
|
374
|
+
*/
|
|
375
|
+
function getIssues(metrics, name) {
|
|
376
|
+
const issues = [];
|
|
377
|
+
if (metrics.cyclomaticComplexity >= THRESHOLDS.cyclomaticComplexity.high) {
|
|
378
|
+
issues.push(`High cyclomatic complexity (${metrics.cyclomaticComplexity}) - extract helper functions`);
|
|
379
|
+
}
|
|
380
|
+
if (metrics.nestingDepth >= THRESHOLDS.nestingDepth.high) {
|
|
381
|
+
issues.push(`Deep nesting (${metrics.nestingDepth} levels) - use early returns or guard clauses`);
|
|
382
|
+
}
|
|
383
|
+
if (metrics.lineCount >= THRESHOLDS.lineCount.high) {
|
|
384
|
+
issues.push(`Long function (${metrics.lineCount} lines) - split into smaller functions`);
|
|
385
|
+
}
|
|
386
|
+
if (metrics.parameterCount >= THRESHOLDS.parameterCount.high) {
|
|
387
|
+
issues.push(`Too many parameters (${metrics.parameterCount}) - use options object`);
|
|
388
|
+
}
|
|
389
|
+
return issues;
|
|
390
|
+
}
|
|
391
|
+
/**
|
|
392
|
+
* Determine severity based on metrics
|
|
393
|
+
*/
|
|
394
|
+
function getSeverity(metrics) {
|
|
395
|
+
const { cyclomaticComplexity, nestingDepth, lineCount } = metrics;
|
|
396
|
+
if (cyclomaticComplexity >= THRESHOLDS.cyclomaticComplexity.critical ||
|
|
397
|
+
nestingDepth >= THRESHOLDS.nestingDepth.critical ||
|
|
398
|
+
lineCount >= THRESHOLDS.lineCount.critical) {
|
|
399
|
+
return 'critical';
|
|
400
|
+
}
|
|
401
|
+
if (cyclomaticComplexity >= THRESHOLDS.cyclomaticComplexity.high ||
|
|
402
|
+
nestingDepth >= THRESHOLDS.nestingDepth.high ||
|
|
403
|
+
lineCount >= THRESHOLDS.lineCount.high) {
|
|
404
|
+
return 'high';
|
|
405
|
+
}
|
|
406
|
+
if (cyclomaticComplexity >= THRESHOLDS.cyclomaticComplexity.medium ||
|
|
407
|
+
nestingDepth >= THRESHOLDS.nestingDepth.medium ||
|
|
408
|
+
lineCount >= THRESHOLDS.lineCount.medium) {
|
|
409
|
+
return 'medium';
|
|
410
|
+
}
|
|
411
|
+
return 'low';
|
|
412
|
+
}
|
|
413
|
+
// ============================================================================
|
|
414
|
+
// SIMPLIFY ANALYSIS
|
|
415
|
+
// ============================================================================
|
|
416
|
+
/**
|
|
417
|
+
* Analyze code for simplification opportunities
|
|
418
|
+
*/
|
|
419
|
+
export function analyzeSimplify(input) {
|
|
420
|
+
const projectPath = sanitizePath(input.projectPath);
|
|
421
|
+
const targetFile = input.file;
|
|
422
|
+
// If specific file, analyze it
|
|
423
|
+
if (targetFile) {
|
|
424
|
+
const fullPath = targetFile.startsWith('/') ? targetFile : join(projectPath, targetFile);
|
|
425
|
+
return analyzeFileForSimplification(fullPath, projectPath);
|
|
426
|
+
}
|
|
427
|
+
// Otherwise find the most complex file
|
|
428
|
+
const complexity = analyzeComplexity({ projectPath, threshold: 1, limit: 100 });
|
|
429
|
+
if (complexity.hotspots.length === 0) {
|
|
430
|
+
return {
|
|
431
|
+
file: '',
|
|
432
|
+
issues: [],
|
|
433
|
+
suggestedPrompt: 'No simplification opportunities found. Codebase is clean!',
|
|
434
|
+
estimatedImprovement: 'N/A',
|
|
435
|
+
};
|
|
436
|
+
}
|
|
437
|
+
// Find file with most issues
|
|
438
|
+
const fileIssues = new Map();
|
|
439
|
+
for (const hotspot of complexity.hotspots) {
|
|
440
|
+
const count = fileIssues.get(hotspot.file) ?? 0;
|
|
441
|
+
fileIssues.set(hotspot.file, count + hotspot.issues.length);
|
|
442
|
+
}
|
|
443
|
+
const topFile = [...fileIssues.entries()].sort((a, b) => b[1] - a[1])[0]?.[0];
|
|
444
|
+
if (!topFile) {
|
|
445
|
+
return {
|
|
446
|
+
file: '',
|
|
447
|
+
issues: [],
|
|
448
|
+
suggestedPrompt: 'No simplification opportunities found.',
|
|
449
|
+
estimatedImprovement: 'N/A',
|
|
450
|
+
};
|
|
451
|
+
}
|
|
452
|
+
const fullPath = join(projectPath, topFile);
|
|
453
|
+
return analyzeFileForSimplification(fullPath, projectPath);
|
|
454
|
+
}
|
|
455
|
+
/**
|
|
456
|
+
* Analyze a specific file for simplification
|
|
457
|
+
*/
|
|
458
|
+
function analyzeFileForSimplification(filePath, projectPath) {
|
|
459
|
+
if (!existsSync(filePath)) {
|
|
460
|
+
return {
|
|
461
|
+
file: filePath,
|
|
462
|
+
issues: [],
|
|
463
|
+
suggestedPrompt: `File not found: ${filePath}`,
|
|
464
|
+
estimatedImprovement: 'N/A',
|
|
465
|
+
};
|
|
466
|
+
}
|
|
467
|
+
const content = readFileSync(filePath, 'utf-8');
|
|
468
|
+
const lines = content.split('\n');
|
|
469
|
+
const relativePath = relative(projectPath, filePath);
|
|
470
|
+
const issues = [];
|
|
471
|
+
// 1. Check for deep nesting
|
|
472
|
+
let maxNesting = 0;
|
|
473
|
+
let currentNesting = 0;
|
|
474
|
+
let deepNestingLines = [];
|
|
475
|
+
for (let i = 0; i < lines.length; i++) {
|
|
476
|
+
const line = lines[i];
|
|
477
|
+
const opens = (line.match(/\{/g) || []).length;
|
|
478
|
+
const closes = (line.match(/\}/g) || []).length;
|
|
479
|
+
currentNesting += opens - closes;
|
|
480
|
+
if (currentNesting > 3) {
|
|
481
|
+
deepNestingLines.push(i + 1);
|
|
482
|
+
}
|
|
483
|
+
maxNesting = Math.max(maxNesting, currentNesting);
|
|
484
|
+
}
|
|
485
|
+
if (deepNestingLines.length > 0) {
|
|
486
|
+
issues.push({
|
|
487
|
+
type: 'nesting',
|
|
488
|
+
location: `lines ${deepNestingLines.slice(0, 5).join(', ')}${deepNestingLines.length > 5 ? '...' : ''}`,
|
|
489
|
+
line: deepNestingLines[0],
|
|
490
|
+
description: `${deepNestingLines.length} locations with >3 levels of nesting`,
|
|
491
|
+
suggestion: 'Use early returns, extract helper functions, or flatten conditionals',
|
|
492
|
+
priority: deepNestingLines.length > 10 ? 'high' : 'medium',
|
|
493
|
+
});
|
|
494
|
+
}
|
|
495
|
+
// 2. Check for long functions (already covered by complexity, but add specific lines)
|
|
496
|
+
const longFunctionPattern = /(?:function\s+\w+|const\s+\w+\s*=\s*(?:async\s+)?(?:\([^)]*\)|[^=])\s*=>)\s*\{/g;
|
|
497
|
+
let match;
|
|
498
|
+
while ((match = longFunctionPattern.exec(content)) !== null) {
|
|
499
|
+
const startLine = content.slice(0, match.index).split('\n').length;
|
|
500
|
+
const bodyStart = content.indexOf('{', match.index);
|
|
501
|
+
const body = extractBracedBlock(content, bodyStart);
|
|
502
|
+
if (body && body.split('\n').length > 50) {
|
|
503
|
+
issues.push({
|
|
504
|
+
type: 'length',
|
|
505
|
+
location: `line ${startLine}`,
|
|
506
|
+
line: startLine,
|
|
507
|
+
description: `Function spans ${body.split('\n').length} lines`,
|
|
508
|
+
suggestion: 'Break into smaller, focused functions with single responsibilities',
|
|
509
|
+
priority: 'high',
|
|
510
|
+
});
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
// 3. Check for repeated patterns (simple duplication)
|
|
514
|
+
const lineHashes = new Map();
|
|
515
|
+
for (let i = 0; i < lines.length; i++) {
|
|
516
|
+
const normalized = lines[i].trim().replace(/\s+/g, ' ');
|
|
517
|
+
if (normalized.length > 30) { // Only check substantial lines
|
|
518
|
+
const existing = lineHashes.get(normalized) || [];
|
|
519
|
+
existing.push(i + 1);
|
|
520
|
+
lineHashes.set(normalized, existing);
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
const duplicates = [...lineHashes.entries()].filter(([_, lns]) => lns.length >= 3);
|
|
524
|
+
if (duplicates.length > 0) {
|
|
525
|
+
issues.push({
|
|
526
|
+
type: 'duplication',
|
|
527
|
+
location: `${duplicates.length} patterns repeated 3+ times`,
|
|
528
|
+
line: duplicates[0][1][0],
|
|
529
|
+
description: 'Repeated code patterns detected',
|
|
530
|
+
suggestion: 'Extract repeated logic into reusable functions',
|
|
531
|
+
priority: 'medium',
|
|
532
|
+
});
|
|
533
|
+
}
|
|
534
|
+
// 4. Check for potential dead code (commented out code blocks)
|
|
535
|
+
const commentedCodePattern = /\/\/.*(?:function|const|let|var|if|for|while|return)/g;
|
|
536
|
+
const commentedMatches = content.match(commentedCodePattern);
|
|
537
|
+
if (commentedMatches && commentedMatches.length > 5) {
|
|
538
|
+
issues.push({
|
|
539
|
+
type: 'dead-code',
|
|
540
|
+
location: 'throughout file',
|
|
541
|
+
line: 1,
|
|
542
|
+
description: `${commentedMatches.length} lines of commented-out code`,
|
|
543
|
+
suggestion: 'Remove commented code - use git history instead',
|
|
544
|
+
priority: 'low',
|
|
545
|
+
});
|
|
546
|
+
}
|
|
547
|
+
// 5. Check for overly abstract patterns (too many small functions)
|
|
548
|
+
const functionCount = (content.match(/(?:function\s+\w+|const\s+\w+\s*=\s*(?:async\s+)?(?:\([^)]*\)|[^=])\s*=>)/g) || []).length;
|
|
549
|
+
const avgLinesPerFunction = lines.length / Math.max(functionCount, 1);
|
|
550
|
+
if (avgLinesPerFunction < 5 && functionCount > 20) {
|
|
551
|
+
issues.push({
|
|
552
|
+
type: 'abstraction',
|
|
553
|
+
location: 'file-wide',
|
|
554
|
+
line: 1,
|
|
555
|
+
description: `${functionCount} functions averaging ${Math.round(avgLinesPerFunction)} lines - may be over-abstracted`,
|
|
556
|
+
suggestion: 'Consider inlining trivial one-liner functions',
|
|
557
|
+
priority: 'low',
|
|
558
|
+
});
|
|
559
|
+
}
|
|
560
|
+
// Generate prompt
|
|
561
|
+
const highPriority = issues.filter(i => i.priority === 'high');
|
|
562
|
+
let suggestedPrompt = 'No major simplification opportunities found.';
|
|
563
|
+
if (highPriority.length > 0) {
|
|
564
|
+
const top = highPriority[0];
|
|
565
|
+
suggestedPrompt = `Simplify ${relativePath}: ${top.description}. ${top.suggestion}.`;
|
|
566
|
+
}
|
|
567
|
+
else if (issues.length > 0) {
|
|
568
|
+
const top = issues[0];
|
|
569
|
+
suggestedPrompt = `Minor cleanup in ${relativePath}: ${top.description}. ${top.suggestion}.`;
|
|
570
|
+
}
|
|
571
|
+
// Estimate improvement
|
|
572
|
+
const estimatedImprovement = issues.length === 0 ? 'Already clean'
|
|
573
|
+
: highPriority.length >= 3 ? 'Could reduce 30-50% of complexity'
|
|
574
|
+
: highPriority.length >= 1 ? 'Could reduce 10-30% of complexity'
|
|
575
|
+
: 'Minor improvements possible';
|
|
576
|
+
return {
|
|
577
|
+
file: relativePath,
|
|
578
|
+
issues,
|
|
579
|
+
suggestedPrompt,
|
|
580
|
+
estimatedImprovement,
|
|
581
|
+
};
|
|
582
|
+
}
|
|
583
|
+
//# sourceMappingURL=complexity.js.map
|