@gotza02/sequential-thinking 10000.1.6 → 10000.1.7
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/CLAUDE.md +31 -6
- package/README.md +20 -1
- package/dist/dashboard/server.js +97 -0
- package/dist/index.js +2 -0
- package/dist/intelligent-code.d.ts +157 -0
- package/dist/intelligent-code.js +779 -0
- package/dist/tools/intelligent-code.d.ts +7 -0
- package/dist/tools/intelligent-code.js +387 -0
- package/dist/tools/sports/tools/match.js +221 -166
- package/package.json +1 -1
|
@@ -0,0 +1,779 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* INTELLIGENT CODE MODULE
|
|
3
|
+
* AI-powered code analysis, semantic search, and smart refactoring
|
|
4
|
+
*/
|
|
5
|
+
import * as fs from 'fs/promises';
|
|
6
|
+
import * as path from 'path';
|
|
7
|
+
import { exec } from 'child_process';
|
|
8
|
+
import { promisify } from 'util';
|
|
9
|
+
import ts from 'typescript';
|
|
10
|
+
import { validatePath } from './utils.js';
|
|
11
|
+
const execAsync = promisify(exec);
|
|
12
|
+
// ============= Intelligent Code Analyzer =============
|
|
13
|
+
export class IntelligentCodeAnalyzer {
|
|
14
|
+
graph;
|
|
15
|
+
program;
|
|
16
|
+
typeChecker;
|
|
17
|
+
constructor(graph) {
|
|
18
|
+
this.graph = graph;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Comprehensive code analysis with metrics and quality scoring
|
|
22
|
+
*/
|
|
23
|
+
async analyzeFile(filePath) {
|
|
24
|
+
const absolutePath = validatePath(filePath);
|
|
25
|
+
const content = await fs.readFile(absolutePath, 'utf-8');
|
|
26
|
+
// Parse AST
|
|
27
|
+
const sourceFile = ts.createSourceFile(absolutePath, content, ts.ScriptTarget.Latest, true);
|
|
28
|
+
// Calculate metrics
|
|
29
|
+
const metrics = this.calculateMetrics(sourceFile, content);
|
|
30
|
+
// Analyze quality
|
|
31
|
+
const quality = await this.analyzeQuality(sourceFile, content, metrics);
|
|
32
|
+
// Extract symbols with analysis
|
|
33
|
+
const symbols = this.analyzeSymbols(sourceFile);
|
|
34
|
+
return { metrics, quality, ast: sourceFile, symbols };
|
|
35
|
+
}
|
|
36
|
+
calculateMetrics(sourceFile, content) {
|
|
37
|
+
let complexity = 0;
|
|
38
|
+
let functionCount = 0;
|
|
39
|
+
let maxNestingDepth = 0;
|
|
40
|
+
let currentDepth = 0;
|
|
41
|
+
const visit = (node) => {
|
|
42
|
+
// Cyclomatic complexity
|
|
43
|
+
if (ts.isIfStatement(node) ||
|
|
44
|
+
ts.isConditionalExpression(node) ||
|
|
45
|
+
ts.isWhileStatement(node) ||
|
|
46
|
+
ts.isForStatement(node) ||
|
|
47
|
+
ts.isForInStatement(node) ||
|
|
48
|
+
ts.isForOfStatement(node) ||
|
|
49
|
+
ts.isCaseClause(node) ||
|
|
50
|
+
(ts.isBinaryExpression(node) &&
|
|
51
|
+
(node.operatorToken.kind === ts.SyntaxKind.AmpersandAmpersandToken ||
|
|
52
|
+
node.operatorToken.kind === ts.SyntaxKind.BarBarToken))) {
|
|
53
|
+
complexity++;
|
|
54
|
+
}
|
|
55
|
+
// Function count
|
|
56
|
+
if (ts.isFunctionDeclaration(node) ||
|
|
57
|
+
ts.isArrowFunction(node) ||
|
|
58
|
+
ts.isMethodDeclaration(node)) {
|
|
59
|
+
functionCount++;
|
|
60
|
+
}
|
|
61
|
+
// Nesting depth
|
|
62
|
+
if (ts.isBlock(node) ||
|
|
63
|
+
ts.isIfStatement(node) ||
|
|
64
|
+
ts.isForStatement(node) ||
|
|
65
|
+
ts.isWhileStatement(node)) {
|
|
66
|
+
currentDepth++;
|
|
67
|
+
maxNestingDepth = Math.max(maxNestingDepth, currentDepth);
|
|
68
|
+
}
|
|
69
|
+
ts.forEachChild(node, visit);
|
|
70
|
+
if (ts.isBlock(node) ||
|
|
71
|
+
ts.isIfStatement(node) ||
|
|
72
|
+
ts.isForStatement(node) ||
|
|
73
|
+
ts.isWhileStatement(node)) {
|
|
74
|
+
currentDepth--;
|
|
75
|
+
}
|
|
76
|
+
};
|
|
77
|
+
visit(sourceFile);
|
|
78
|
+
const lines = content.split('\n');
|
|
79
|
+
const codeLines = lines.filter(l => l.trim().length > 0 && !l.trim().startsWith('//'));
|
|
80
|
+
const commentLines = lines.filter(l => l.trim().startsWith('//') || l.trim().startsWith('/*'));
|
|
81
|
+
return {
|
|
82
|
+
complexity: complexity + 1, // Base complexity is 1
|
|
83
|
+
linesOfCode: codeLines.length,
|
|
84
|
+
commentRatio: codeLines.length > 0 ? commentLines.length / codeLines.length : 0,
|
|
85
|
+
functionCount,
|
|
86
|
+
maxNestingDepth,
|
|
87
|
+
duplicateCode: 0, // Would need cross-file analysis
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
async analyzeQuality(sourceFile, content, metrics) {
|
|
91
|
+
const issues = [];
|
|
92
|
+
// Complexity checks
|
|
93
|
+
if (metrics.complexity > 20) {
|
|
94
|
+
issues.push({
|
|
95
|
+
type: 'warning',
|
|
96
|
+
category: 'complexity',
|
|
97
|
+
message: `High cyclomatic complexity (${metrics.complexity}). Consider refactoring.`,
|
|
98
|
+
severity: 7,
|
|
99
|
+
suggestion: 'Extract smaller functions or simplify conditional logic',
|
|
100
|
+
autoFixable: false,
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
if (metrics.maxNestingDepth > 4) {
|
|
104
|
+
issues.push({
|
|
105
|
+
type: 'warning',
|
|
106
|
+
category: 'maintainability',
|
|
107
|
+
message: `Deep nesting detected (${metrics.maxNestingDepth} levels)`,
|
|
108
|
+
severity: 6,
|
|
109
|
+
suggestion: 'Use early returns or extract nested logic into functions',
|
|
110
|
+
autoFixable: false,
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
// Comment ratio
|
|
114
|
+
if (metrics.commentRatio < 0.05 && metrics.linesOfCode > 50) {
|
|
115
|
+
issues.push({
|
|
116
|
+
type: 'info',
|
|
117
|
+
category: 'maintainability',
|
|
118
|
+
message: 'Low comment ratio. Consider adding documentation.',
|
|
119
|
+
severity: 3,
|
|
120
|
+
suggestion: 'Add JSDoc comments for public APIs',
|
|
121
|
+
autoFixable: false,
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
// Function length
|
|
125
|
+
const visit = (node) => {
|
|
126
|
+
if (ts.isFunctionDeclaration(node) || ts.isMethodDeclaration(node)) {
|
|
127
|
+
const start = node.getStart(sourceFile);
|
|
128
|
+
const end = node.getEnd();
|
|
129
|
+
const functionLines = content.substring(start, end).split('\n').length;
|
|
130
|
+
if (functionLines > 50) {
|
|
131
|
+
issues.push({
|
|
132
|
+
type: 'warning',
|
|
133
|
+
category: 'maintainability',
|
|
134
|
+
message: `Long function detected (${functionLines} lines)`,
|
|
135
|
+
line: ts.getLineAndCharacterOfPosition(sourceFile, node.getStart()).line + 1,
|
|
136
|
+
severity: 6,
|
|
137
|
+
suggestion: 'Extract helper functions to reduce function length',
|
|
138
|
+
autoFixable: false,
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
ts.forEachChild(node, visit);
|
|
143
|
+
};
|
|
144
|
+
visit(sourceFile);
|
|
145
|
+
// Check for any types
|
|
146
|
+
const anyTypeRegex = /:\s*any\b/g;
|
|
147
|
+
const anyMatches = content.match(anyTypeRegex);
|
|
148
|
+
if (anyMatches && anyMatches.length > 5) {
|
|
149
|
+
issues.push({
|
|
150
|
+
type: 'warning',
|
|
151
|
+
category: 'maintainability',
|
|
152
|
+
message: `Excessive use of 'any' type (${anyMatches.length} occurrences)`,
|
|
153
|
+
severity: 5,
|
|
154
|
+
suggestion: 'Replace with proper types or use unknown with type guards',
|
|
155
|
+
autoFixable: false,
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
// Calculate scores
|
|
159
|
+
const maintainability = Math.max(0, 100 - metrics.complexity * 2 - metrics.maxNestingDepth * 5);
|
|
160
|
+
const reliability = Math.max(0, 100 - (anyMatches?.length || 0) * 5);
|
|
161
|
+
const security = 100; // Would need security-specific checks
|
|
162
|
+
const performance = 100; // Would need performance analysis
|
|
163
|
+
const issuePenalty = issues.reduce((sum, i) => sum + i.severity * 2, 0);
|
|
164
|
+
const overall = Math.max(0, Math.min(100, (maintainability + reliability + security + performance) / 4 - issuePenalty));
|
|
165
|
+
return {
|
|
166
|
+
overall: Math.round(overall),
|
|
167
|
+
maintainability: Math.round(maintainability),
|
|
168
|
+
reliability: Math.round(reliability),
|
|
169
|
+
security,
|
|
170
|
+
performance,
|
|
171
|
+
issues: issues.sort((a, b) => b.severity - a.severity),
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
analyzeSymbols(sourceFile) {
|
|
175
|
+
const symbols = [];
|
|
176
|
+
const visit = (node) => {
|
|
177
|
+
if (ts.isFunctionDeclaration(node) && node.name) {
|
|
178
|
+
const symbol = this.analyzeFunction(node, sourceFile);
|
|
179
|
+
if (symbol)
|
|
180
|
+
symbols.push(symbol);
|
|
181
|
+
}
|
|
182
|
+
else if (ts.isClassDeclaration(node) && node.name) {
|
|
183
|
+
const symbol = this.analyzeClass(node, sourceFile);
|
|
184
|
+
if (symbol)
|
|
185
|
+
symbols.push(symbol);
|
|
186
|
+
}
|
|
187
|
+
else if (ts.isInterfaceDeclaration(node)) {
|
|
188
|
+
const symbol = this.analyzeInterface(node, sourceFile);
|
|
189
|
+
if (symbol)
|
|
190
|
+
symbols.push(symbol);
|
|
191
|
+
}
|
|
192
|
+
ts.forEachChild(node, visit);
|
|
193
|
+
};
|
|
194
|
+
visit(sourceFile);
|
|
195
|
+
return symbols;
|
|
196
|
+
}
|
|
197
|
+
analyzeFunction(node, sourceFile) {
|
|
198
|
+
if (!node.name)
|
|
199
|
+
return null;
|
|
200
|
+
const params = node.parameters.map(p => ({
|
|
201
|
+
name: p.name.getText(sourceFile),
|
|
202
|
+
type: p.type?.getText(sourceFile) || 'unknown',
|
|
203
|
+
optional: !!p.questionToken,
|
|
204
|
+
}));
|
|
205
|
+
const returnType = node.type?.getText(sourceFile) || 'inferred';
|
|
206
|
+
// Check for async
|
|
207
|
+
const isAsync = node.modifiers?.some(m => m.kind === ts.SyntaxKind.AsyncKeyword);
|
|
208
|
+
// Calculate function complexity
|
|
209
|
+
let complexity = 1;
|
|
210
|
+
const countComplexity = (n) => {
|
|
211
|
+
if (ts.isIfStatement(n) ||
|
|
212
|
+
ts.isWhileStatement(n) ||
|
|
213
|
+
ts.isForStatement(n) ||
|
|
214
|
+
ts.isConditionalExpression(n)) {
|
|
215
|
+
complexity++;
|
|
216
|
+
}
|
|
217
|
+
ts.forEachChild(n, countComplexity);
|
|
218
|
+
};
|
|
219
|
+
countComplexity(node);
|
|
220
|
+
return {
|
|
221
|
+
name: node.name.text,
|
|
222
|
+
kind: 'function',
|
|
223
|
+
params,
|
|
224
|
+
returnType,
|
|
225
|
+
isAsync,
|
|
226
|
+
complexity,
|
|
227
|
+
isExported: node.modifiers?.some(m => m.kind === ts.SyntaxKind.ExportKeyword) || false,
|
|
228
|
+
documentation: this.extractJSDoc(node, sourceFile),
|
|
229
|
+
};
|
|
230
|
+
}
|
|
231
|
+
analyzeClass(node, sourceFile) {
|
|
232
|
+
if (!node.name)
|
|
233
|
+
return null;
|
|
234
|
+
const methods = [];
|
|
235
|
+
const properties = [];
|
|
236
|
+
node.members.forEach(member => {
|
|
237
|
+
if (ts.isMethodDeclaration(member) && member.name) {
|
|
238
|
+
methods.push({
|
|
239
|
+
name: member.name.getText(sourceFile),
|
|
240
|
+
kind: 'method',
|
|
241
|
+
params: member.parameters.map(p => ({
|
|
242
|
+
name: p.name.getText(sourceFile),
|
|
243
|
+
type: p.type?.getText(sourceFile) || 'unknown',
|
|
244
|
+
})),
|
|
245
|
+
returnType: member.type?.getText(sourceFile) || 'void',
|
|
246
|
+
isAsync: member.modifiers?.some(m => m.kind === ts.SyntaxKind.AsyncKeyword) || false,
|
|
247
|
+
complexity: 1,
|
|
248
|
+
isExported: false,
|
|
249
|
+
});
|
|
250
|
+
}
|
|
251
|
+
else if (ts.isPropertyDeclaration(member) && member.name) {
|
|
252
|
+
properties.push({
|
|
253
|
+
name: member.name.getText(sourceFile),
|
|
254
|
+
type: member.type?.getText(sourceFile) || 'unknown',
|
|
255
|
+
});
|
|
256
|
+
}
|
|
257
|
+
});
|
|
258
|
+
return {
|
|
259
|
+
name: node.name.text,
|
|
260
|
+
kind: 'class',
|
|
261
|
+
methods,
|
|
262
|
+
properties,
|
|
263
|
+
isExported: node.modifiers?.some(m => m.kind === ts.SyntaxKind.ExportKeyword) || false,
|
|
264
|
+
documentation: this.extractJSDoc(node, sourceFile),
|
|
265
|
+
};
|
|
266
|
+
}
|
|
267
|
+
analyzeInterface(node, sourceFile) {
|
|
268
|
+
const properties = node.members.map(member => {
|
|
269
|
+
if (ts.isPropertySignature(member) && member.name) {
|
|
270
|
+
return {
|
|
271
|
+
name: member.name.getText(sourceFile),
|
|
272
|
+
type: member.type?.getText(sourceFile) || 'unknown',
|
|
273
|
+
optional: !!member.questionToken,
|
|
274
|
+
};
|
|
275
|
+
}
|
|
276
|
+
return null;
|
|
277
|
+
}).filter((p) => p !== null);
|
|
278
|
+
return {
|
|
279
|
+
name: node.name.text,
|
|
280
|
+
kind: 'interface',
|
|
281
|
+
properties,
|
|
282
|
+
isExported: node.modifiers?.some(m => m.kind === ts.SyntaxKind.ExportKeyword) || false,
|
|
283
|
+
documentation: this.extractJSDoc(node, sourceFile),
|
|
284
|
+
};
|
|
285
|
+
}
|
|
286
|
+
extractJSDoc(node, sourceFile) {
|
|
287
|
+
const jsDoc = node.jsDoc;
|
|
288
|
+
if (jsDoc && jsDoc.length > 0) {
|
|
289
|
+
return jsDoc[0].comment?.toString();
|
|
290
|
+
}
|
|
291
|
+
return undefined;
|
|
292
|
+
}
|
|
293
|
+
/**
|
|
294
|
+
* Smart impact analysis - predicts what will be affected by changes
|
|
295
|
+
*/
|
|
296
|
+
async analyzeImpact(filePath, changeDescription) {
|
|
297
|
+
const absolutePath = validatePath(filePath);
|
|
298
|
+
// Get graph relationships
|
|
299
|
+
const context = this.graph.getDeepContext(filePath);
|
|
300
|
+
if (!context) {
|
|
301
|
+
await this.graph.build(process.cwd());
|
|
302
|
+
}
|
|
303
|
+
const relationships = this.graph.getRelationships(filePath);
|
|
304
|
+
if (!relationships) {
|
|
305
|
+
return {
|
|
306
|
+
filePath,
|
|
307
|
+
directImpacts: [],
|
|
308
|
+
indirectImpacts: [],
|
|
309
|
+
testFiles: [],
|
|
310
|
+
riskScore: 0,
|
|
311
|
+
breakingChanges: false,
|
|
312
|
+
estimatedReviewTime: 0,
|
|
313
|
+
};
|
|
314
|
+
}
|
|
315
|
+
// Direct impacts: files that import this file
|
|
316
|
+
const directImpacts = relationships.importedBy;
|
|
317
|
+
// Find indirect impacts (files that import the direct impacts)
|
|
318
|
+
const indirectImpacts = [];
|
|
319
|
+
for (const directFile of directImpacts) {
|
|
320
|
+
const directRel = this.graph.getRelationships(directFile);
|
|
321
|
+
if (directRel) {
|
|
322
|
+
for (const indirect of directRel.importedBy) {
|
|
323
|
+
if (indirect !== filePath && !directImpacts.includes(indirect)) {
|
|
324
|
+
indirectImpacts.push(indirect);
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
// Find related test files
|
|
330
|
+
const testFiles = await this.findRelatedTests(filePath);
|
|
331
|
+
// Calculate risk score
|
|
332
|
+
let riskScore = 0;
|
|
333
|
+
// Base risk from number of dependents
|
|
334
|
+
riskScore += directImpacts.length * 10;
|
|
335
|
+
riskScore += indirectImpacts.length * 5;
|
|
336
|
+
// Higher risk if changing public API
|
|
337
|
+
if (relationships.symbols.some(s => s.includes('export'))) {
|
|
338
|
+
riskScore += 20;
|
|
339
|
+
}
|
|
340
|
+
// Check change description for risky keywords
|
|
341
|
+
if (changeDescription) {
|
|
342
|
+
const riskyKeywords = ['delete', 'remove', 'rename', 'breaking', 'api'];
|
|
343
|
+
if (riskyKeywords.some(k => changeDescription.toLowerCase().includes(k))) {
|
|
344
|
+
riskScore += 15;
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
const breakingChanges = riskScore > 50;
|
|
348
|
+
const estimatedReviewTime = Math.min(120, 5 + directImpacts.length * 2 + indirectImpacts.length + testFiles.length * 3);
|
|
349
|
+
return {
|
|
350
|
+
filePath,
|
|
351
|
+
directImpacts,
|
|
352
|
+
indirectImpacts: [...new Set(indirectImpacts)],
|
|
353
|
+
testFiles,
|
|
354
|
+
riskScore: Math.min(100, riskScore),
|
|
355
|
+
breakingChanges,
|
|
356
|
+
estimatedReviewTime,
|
|
357
|
+
};
|
|
358
|
+
}
|
|
359
|
+
async findRelatedTests(filePath) {
|
|
360
|
+
const testFiles = [];
|
|
361
|
+
const baseName = path.basename(filePath, path.extname(filePath));
|
|
362
|
+
const dir = path.dirname(filePath);
|
|
363
|
+
// Common test file patterns
|
|
364
|
+
const patterns = [
|
|
365
|
+
`${baseName}.test.ts`,
|
|
366
|
+
`${baseName}.spec.ts`,
|
|
367
|
+
`${baseName}.test.js`,
|
|
368
|
+
`${baseName}.spec.js`,
|
|
369
|
+
];
|
|
370
|
+
for (const pattern of patterns) {
|
|
371
|
+
const testPath = path.join(dir, pattern);
|
|
372
|
+
try {
|
|
373
|
+
await fs.access(testPath);
|
|
374
|
+
testFiles.push(testPath);
|
|
375
|
+
}
|
|
376
|
+
catch {
|
|
377
|
+
// File doesn't exist
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
// Check for __tests__ directory
|
|
381
|
+
const testsDir = path.join(dir, '__tests__');
|
|
382
|
+
try {
|
|
383
|
+
const files = await fs.readdir(testsDir);
|
|
384
|
+
files.forEach(f => {
|
|
385
|
+
if (f.includes(baseName) && (f.endsWith('.test.ts') || f.endsWith('.spec.ts'))) {
|
|
386
|
+
testFiles.push(path.join(testsDir, f));
|
|
387
|
+
}
|
|
388
|
+
});
|
|
389
|
+
}
|
|
390
|
+
catch {
|
|
391
|
+
// Directory doesn't exist
|
|
392
|
+
}
|
|
393
|
+
return testFiles;
|
|
394
|
+
}
|
|
395
|
+
/**
|
|
396
|
+
* Generate intelligent refactoring suggestions
|
|
397
|
+
*/
|
|
398
|
+
async suggestRefactoring(filePath) {
|
|
399
|
+
const { ast, metrics, quality } = await this.analyzeFile(filePath);
|
|
400
|
+
if (!ast)
|
|
401
|
+
return [];
|
|
402
|
+
const suggestions = [];
|
|
403
|
+
// Suggest extracting long functions
|
|
404
|
+
if (metrics.complexity > 10) {
|
|
405
|
+
const longFunctions = this.findLongFunctions(ast);
|
|
406
|
+
for (const func of longFunctions) {
|
|
407
|
+
suggestions.push({
|
|
408
|
+
type: 'extract-method',
|
|
409
|
+
description: `Extract ${func.name} into smaller functions`,
|
|
410
|
+
currentCode: func.code,
|
|
411
|
+
suggestedCode: `// Suggested: Split into ${Math.ceil(func.lines / 20)} helper functions`,
|
|
412
|
+
confidence: 85,
|
|
413
|
+
impact: 'medium',
|
|
414
|
+
effort: 'moderate',
|
|
415
|
+
benefits: ['Reduced complexity', 'Better testability', 'Easier maintenance'],
|
|
416
|
+
});
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
// Simplify nested conditionals
|
|
420
|
+
const nestedConditionals = this.findNestedConditionals(ast);
|
|
421
|
+
for (const nested of nestedConditionals) {
|
|
422
|
+
suggestions.push({
|
|
423
|
+
type: 'simplify',
|
|
424
|
+
description: 'Simplify deeply nested conditionals',
|
|
425
|
+
currentCode: nested.code,
|
|
426
|
+
suggestedCode: '// Use early returns or extract into guard clauses',
|
|
427
|
+
confidence: 90,
|
|
428
|
+
impact: 'low',
|
|
429
|
+
effort: 'quick',
|
|
430
|
+
benefits: ['Improved readability', 'Reduced cognitive load'],
|
|
431
|
+
});
|
|
432
|
+
}
|
|
433
|
+
// Add types for any
|
|
434
|
+
const anyUsages = this.findAnyUsages(ast);
|
|
435
|
+
if (anyUsages.length > 0) {
|
|
436
|
+
suggestions.push({
|
|
437
|
+
type: 'add-types',
|
|
438
|
+
description: `Replace ${anyUsages.length} 'any' types with proper types`,
|
|
439
|
+
currentCode: anyUsages.map(a => a.code).join('\n'),
|
|
440
|
+
suggestedCode: '// Define proper interfaces or use unknown with type guards',
|
|
441
|
+
confidence: 80,
|
|
442
|
+
impact: 'medium',
|
|
443
|
+
effort: 'moderate',
|
|
444
|
+
benefits: ['Type safety', 'Better IDE support', 'Fewer runtime errors'],
|
|
445
|
+
});
|
|
446
|
+
}
|
|
447
|
+
return suggestions.sort((a, b) => b.confidence - a.confidence);
|
|
448
|
+
}
|
|
449
|
+
findLongFunctions(sourceFile) {
|
|
450
|
+
const functions = [];
|
|
451
|
+
const content = sourceFile.getFullText();
|
|
452
|
+
const visit = (node) => {
|
|
453
|
+
if (ts.isFunctionDeclaration(node) && node.name) {
|
|
454
|
+
const start = node.getStart(sourceFile);
|
|
455
|
+
const end = node.getEnd();
|
|
456
|
+
const code = content.substring(start, end);
|
|
457
|
+
const lines = code.split('\n').length;
|
|
458
|
+
if (lines > 30) {
|
|
459
|
+
functions.push({
|
|
460
|
+
name: node.name.text,
|
|
461
|
+
lines,
|
|
462
|
+
code: code.substring(0, 200) + '...',
|
|
463
|
+
});
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
ts.forEachChild(node, visit);
|
|
467
|
+
};
|
|
468
|
+
visit(sourceFile);
|
|
469
|
+
return functions;
|
|
470
|
+
}
|
|
471
|
+
findNestedConditionals(sourceFile) {
|
|
472
|
+
const nested = [];
|
|
473
|
+
const content = sourceFile.getFullText();
|
|
474
|
+
const visit = (node, depth = 0) => {
|
|
475
|
+
if (ts.isIfStatement(node)) {
|
|
476
|
+
if (depth > 3) {
|
|
477
|
+
const code = content.substring(node.getStart(sourceFile), node.getEnd());
|
|
478
|
+
nested.push({ depth, code: code.substring(0, 150) + '...' });
|
|
479
|
+
}
|
|
480
|
+
ts.forEachChild(node, n => visit(n, depth + 1));
|
|
481
|
+
}
|
|
482
|
+
else {
|
|
483
|
+
ts.forEachChild(node, n => visit(n, depth));
|
|
484
|
+
}
|
|
485
|
+
};
|
|
486
|
+
visit(sourceFile);
|
|
487
|
+
return nested;
|
|
488
|
+
}
|
|
489
|
+
findAnyUsages(sourceFile) {
|
|
490
|
+
const usages = [];
|
|
491
|
+
const content = sourceFile.getFullText();
|
|
492
|
+
const visit = (node) => {
|
|
493
|
+
if (ts.isTypeReferenceNode(node) && node.typeName.getText(sourceFile) === 'any') {
|
|
494
|
+
usages.push({
|
|
495
|
+
code: content.substring(node.getStart(sourceFile), node.getEnd()),
|
|
496
|
+
});
|
|
497
|
+
}
|
|
498
|
+
ts.forEachChild(node, visit);
|
|
499
|
+
};
|
|
500
|
+
visit(sourceFile);
|
|
501
|
+
return usages;
|
|
502
|
+
}
|
|
503
|
+
/**
|
|
504
|
+
* Semantic code search - find code by concept, not just text
|
|
505
|
+
*/
|
|
506
|
+
async semanticSearch(query, options = {}) {
|
|
507
|
+
const { filePattern = '*', maxResults = 10 } = options;
|
|
508
|
+
// Build query concepts
|
|
509
|
+
const concepts = this.extractConcepts(query);
|
|
510
|
+
// Search through codebase
|
|
511
|
+
const files = await this.getMatchingFiles(filePattern);
|
|
512
|
+
const results = [];
|
|
513
|
+
for (const filePath of files) {
|
|
514
|
+
try {
|
|
515
|
+
const content = await fs.readFile(filePath, 'utf-8');
|
|
516
|
+
const relevance = this.calculateRelevance(content, concepts, query);
|
|
517
|
+
if (relevance > 0.3) {
|
|
518
|
+
const snippet = this.extractRelevantSnippet(content, concepts);
|
|
519
|
+
results.push({
|
|
520
|
+
filePath,
|
|
521
|
+
relevance,
|
|
522
|
+
context: this.extractContext(content, snippet),
|
|
523
|
+
matchedConcepts: concepts.filter(c => content.toLowerCase().includes(c.toLowerCase())),
|
|
524
|
+
codeSnippet: snippet,
|
|
525
|
+
});
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
catch {
|
|
529
|
+
// Skip files that can't be read
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
return results
|
|
533
|
+
.sort((a, b) => b.relevance - a.relevance)
|
|
534
|
+
.slice(0, maxResults);
|
|
535
|
+
}
|
|
536
|
+
extractConcepts(query) {
|
|
537
|
+
// Extract key concepts from query
|
|
538
|
+
const stopWords = new Set(['the', 'a', 'an', 'in', 'on', 'at', 'to', 'for', 'of', 'and', 'or']);
|
|
539
|
+
return query
|
|
540
|
+
.toLowerCase()
|
|
541
|
+
.split(/\s+/)
|
|
542
|
+
.filter(w => w.length > 2 && !stopWords.has(w))
|
|
543
|
+
.map(w => w.replace(/[^a-z0-9]/g, ''));
|
|
544
|
+
}
|
|
545
|
+
calculateRelevance(content, concepts, originalQuery) {
|
|
546
|
+
const lowerContent = content.toLowerCase();
|
|
547
|
+
let score = 0;
|
|
548
|
+
// Concept matching
|
|
549
|
+
for (const concept of concepts) {
|
|
550
|
+
const count = (lowerContent.match(new RegExp(concept, 'g')) || []).length;
|
|
551
|
+
score += Math.min(count * 0.1, 0.5);
|
|
552
|
+
}
|
|
553
|
+
// Check for function/variable names matching query
|
|
554
|
+
const camelCaseQuery = originalQuery.replace(/\s+(.)/g, (_, c) => c.toUpperCase());
|
|
555
|
+
const snakeCaseQuery = originalQuery.replace(/\s+/g, '_');
|
|
556
|
+
if (content.includes(camelCaseQuery) || content.includes(snakeCaseQuery)) {
|
|
557
|
+
score += 0.3;
|
|
558
|
+
}
|
|
559
|
+
// Boost for TypeScript/JavaScript files
|
|
560
|
+
if (content.includes('function') || content.includes('const') || content.includes('export')) {
|
|
561
|
+
score += 0.1;
|
|
562
|
+
}
|
|
563
|
+
return Math.min(1, score);
|
|
564
|
+
}
|
|
565
|
+
extractRelevantSnippet(content, concepts) {
|
|
566
|
+
const lines = content.split('\n');
|
|
567
|
+
let bestStart = 0;
|
|
568
|
+
let bestScore = 0;
|
|
569
|
+
// Find window with highest concept density
|
|
570
|
+
for (let i = 0; i < lines.length - 10; i++) {
|
|
571
|
+
const window = lines.slice(i, i + 10).join('\n').toLowerCase();
|
|
572
|
+
const score = concepts.filter(c => window.includes(c)).length;
|
|
573
|
+
if (score > bestScore) {
|
|
574
|
+
bestScore = score;
|
|
575
|
+
bestStart = i;
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
return lines.slice(bestStart, bestStart + 15).join('\n');
|
|
579
|
+
}
|
|
580
|
+
extractContext(content, snippet) {
|
|
581
|
+
const lines = content.split('\n');
|
|
582
|
+
const snippetLines = snippet.split('\n');
|
|
583
|
+
const firstLine = snippetLines[0];
|
|
584
|
+
// Try to find class/function name
|
|
585
|
+
for (let i = 0; i < lines.length; i++) {
|
|
586
|
+
if (lines[i] === firstLine) {
|
|
587
|
+
// Look backwards for function/class declaration
|
|
588
|
+
for (let j = Math.max(0, i - 5); j < i; j++) {
|
|
589
|
+
const match = lines[j].match(/(?:export\s+)?(?:class|function|interface)\s+(\w+)/);
|
|
590
|
+
if (match) {
|
|
591
|
+
return `${match[0]} (line ${j + 1})`;
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
}
|
|
595
|
+
}
|
|
596
|
+
return 'Global scope';
|
|
597
|
+
}
|
|
598
|
+
async getMatchingFiles(pattern) {
|
|
599
|
+
// Simple glob-like matching
|
|
600
|
+
const files = [];
|
|
601
|
+
try {
|
|
602
|
+
const { stdout } = await execAsync(`find . -type f -name "${pattern}" ! -path "*/node_modules/*" ! -path "*/.git/*" | head -100`, { cwd: process.cwd() });
|
|
603
|
+
return stdout.trim().split('\n').filter(Boolean);
|
|
604
|
+
}
|
|
605
|
+
catch {
|
|
606
|
+
return [];
|
|
607
|
+
}
|
|
608
|
+
}
|
|
609
|
+
/**
|
|
610
|
+
* Auto-fix common code issues
|
|
611
|
+
*/
|
|
612
|
+
async autoFix(filePath, options = {}) {
|
|
613
|
+
const absolutePath = validatePath(filePath);
|
|
614
|
+
const content = await fs.readFile(absolutePath, 'utf-8');
|
|
615
|
+
const lines = content.split('\n');
|
|
616
|
+
const fixes = [];
|
|
617
|
+
let modifiedContent = content;
|
|
618
|
+
// Fix 1: Remove trailing whitespace
|
|
619
|
+
if (options.fixTrailingWhitespace !== false) {
|
|
620
|
+
for (let i = 0; i < lines.length; i++) {
|
|
621
|
+
const line = lines[i];
|
|
622
|
+
const trimmed = line.replace(/\s+$/, '');
|
|
623
|
+
if (line !== trimmed) {
|
|
624
|
+
fixes.push({
|
|
625
|
+
type: 'trailing-whitespace',
|
|
626
|
+
line: i + 1,
|
|
627
|
+
original: line,
|
|
628
|
+
fixed: trimmed,
|
|
629
|
+
});
|
|
630
|
+
lines[i] = trimmed;
|
|
631
|
+
}
|
|
632
|
+
}
|
|
633
|
+
modifiedContent = lines.join('\n');
|
|
634
|
+
}
|
|
635
|
+
// Fix 2: Add missing semicolons (simple heuristic)
|
|
636
|
+
if (options.fixMissingSemicolons) {
|
|
637
|
+
const semicolonPattern = /^(\s*)(.+?)(?<!;)(\s*)$/;
|
|
638
|
+
for (let i = 0; i < lines.length; i++) {
|
|
639
|
+
const line = lines[i];
|
|
640
|
+
// Skip comments, strings, imports, exports, function declarations, etc.
|
|
641
|
+
if (line.trim().startsWith('//') ||
|
|
642
|
+
line.trim().startsWith('/*') ||
|
|
643
|
+
line.trim().startsWith('*') ||
|
|
644
|
+
line.trim().startsWith('import') ||
|
|
645
|
+
line.trim().startsWith('export') ||
|
|
646
|
+
line.trim().startsWith('function') ||
|
|
647
|
+
line.trim().startsWith('async function') ||
|
|
648
|
+
line.trim().startsWith('class') ||
|
|
649
|
+
line.trim().startsWith('interface') ||
|
|
650
|
+
line.trim().startsWith('type') ||
|
|
651
|
+
line.trim().startsWith('if') ||
|
|
652
|
+
line.trim().startsWith('for') ||
|
|
653
|
+
line.trim().startsWith('while') ||
|
|
654
|
+
line.trim().startsWith('switch') ||
|
|
655
|
+
line.trim().startsWith('try') ||
|
|
656
|
+
line.trim().startsWith('catch') ||
|
|
657
|
+
line.trim().startsWith('finally') ||
|
|
658
|
+
line.trim().startsWith('{') ||
|
|
659
|
+
line.trim().startsWith('}') ||
|
|
660
|
+
line.trim().endsWith('{') ||
|
|
661
|
+
line.trim().endsWith('}') ||
|
|
662
|
+
line.trim().endsWith(';') ||
|
|
663
|
+
line.trim() === '') {
|
|
664
|
+
continue;
|
|
665
|
+
}
|
|
666
|
+
// Add semicolon if line looks like a statement
|
|
667
|
+
if (/[a-zA-Z0-9_)\]]\s*$/.test(line) && !line.trim().endsWith(';')) {
|
|
668
|
+
fixes.push({
|
|
669
|
+
type: 'missing-semicolon',
|
|
670
|
+
line: i + 1,
|
|
671
|
+
original: line,
|
|
672
|
+
fixed: line + ';',
|
|
673
|
+
});
|
|
674
|
+
lines[i] = line + ';';
|
|
675
|
+
}
|
|
676
|
+
}
|
|
677
|
+
modifiedContent = lines.join('\n');
|
|
678
|
+
}
|
|
679
|
+
// Fix 3: Optimize imports
|
|
680
|
+
if (options.optimizeImports) {
|
|
681
|
+
const importRegex = /^(import\s+(?:{[^}]*}|\*\s+as\s+\w+|\w+)\s+from\s+['"][^'"]+['"];?)$/gm;
|
|
682
|
+
const imports = [];
|
|
683
|
+
let match;
|
|
684
|
+
while ((match = importRegex.exec(content)) !== null) {
|
|
685
|
+
imports.push(match[1]);
|
|
686
|
+
}
|
|
687
|
+
if (imports.length > 0) {
|
|
688
|
+
// Sort imports
|
|
689
|
+
imports.sort((a, b) => {
|
|
690
|
+
const aIsRelative = a.includes("from '.'") || a.includes("from '..");
|
|
691
|
+
const bIsRelative = b.includes("from '.'") || b.includes("from '..");
|
|
692
|
+
if (aIsRelative && !bIsRelative)
|
|
693
|
+
return 1;
|
|
694
|
+
if (!aIsRelative && bIsRelative)
|
|
695
|
+
return -1;
|
|
696
|
+
return a.localeCompare(b);
|
|
697
|
+
});
|
|
698
|
+
// Remove duplicates
|
|
699
|
+
const uniqueImports = [...new Set(imports)];
|
|
700
|
+
if (uniqueImports.length !== imports.length || imports.join('\n') !== uniqueImports.join('\n')) {
|
|
701
|
+
fixes.push({
|
|
702
|
+
type: 'optimize-imports',
|
|
703
|
+
line: 1,
|
|
704
|
+
original: `Found ${imports.length} imports`,
|
|
705
|
+
fixed: `Optimized to ${uniqueImports.length} imports`,
|
|
706
|
+
});
|
|
707
|
+
}
|
|
708
|
+
}
|
|
709
|
+
}
|
|
710
|
+
// Apply fixes if not dry run
|
|
711
|
+
const applied = !options.dryRun && fixes.length > 0;
|
|
712
|
+
if (applied) {
|
|
713
|
+
await fs.writeFile(absolutePath, modifiedContent, 'utf-8');
|
|
714
|
+
}
|
|
715
|
+
return { fixes, applied };
|
|
716
|
+
}
|
|
717
|
+
/**
|
|
718
|
+
* Detect code patterns and anti-patterns
|
|
719
|
+
*/
|
|
720
|
+
detectPatterns(filePath, sourceFile) {
|
|
721
|
+
const patterns = [];
|
|
722
|
+
const content = sourceFile.getFullText();
|
|
723
|
+
// Pattern: Singleton
|
|
724
|
+
const singletonPattern = /class\s+(\w+).*?private\s+static\s+instance|getInstance/s;
|
|
725
|
+
if (singletonPattern.test(content)) {
|
|
726
|
+
patterns.push({
|
|
727
|
+
name: 'Singleton Pattern',
|
|
728
|
+
description: 'Ensures a class has only one instance',
|
|
729
|
+
examples: ['Database connection', 'Logger', 'Configuration'],
|
|
730
|
+
antiPattern: 'Can make testing difficult, consider dependency injection',
|
|
731
|
+
solution: 'Use factory functions or dependency injection instead',
|
|
732
|
+
confidence: 85,
|
|
733
|
+
});
|
|
734
|
+
}
|
|
735
|
+
// Pattern: Factory
|
|
736
|
+
const factoryPattern = /create[A-Z]\w+\s*\(|make[A-Z]\w+\s*\(/;
|
|
737
|
+
if (factoryPattern.test(content)) {
|
|
738
|
+
patterns.push({
|
|
739
|
+
name: 'Factory Pattern',
|
|
740
|
+
description: 'Creates objects without specifying exact class',
|
|
741
|
+
examples: ['createUser()', 'makeRequest()'],
|
|
742
|
+
solution: 'Use dependency injection container or factory pattern',
|
|
743
|
+
confidence: 70,
|
|
744
|
+
});
|
|
745
|
+
}
|
|
746
|
+
// Anti-pattern: Callback hell
|
|
747
|
+
const callbackHell = /\)\s*\{[\s\S]*?\)\s*\{[\s\S]*?\)\s*\{/;
|
|
748
|
+
if (callbackHell.test(content)) {
|
|
749
|
+
patterns.push({
|
|
750
|
+
name: 'Callback Hell (Anti-pattern)',
|
|
751
|
+
description: 'Deeply nested callbacks',
|
|
752
|
+
examples: [],
|
|
753
|
+
antiPattern: 'Makes code hard to read and maintain',
|
|
754
|
+
solution: 'Use async/await or Promise chains',
|
|
755
|
+
confidence: 90,
|
|
756
|
+
});
|
|
757
|
+
}
|
|
758
|
+
// Pattern: Dependency Injection
|
|
759
|
+
const diPattern = /constructor\s*\([^)]*(?:private|public|protected)[^)]*\)/;
|
|
760
|
+
if (diPattern.test(content)) {
|
|
761
|
+
patterns.push({
|
|
762
|
+
name: 'Dependency Injection',
|
|
763
|
+
description: 'Dependencies provided through constructor',
|
|
764
|
+
examples: ['constructor(private db: Database)'],
|
|
765
|
+
solution: 'Good practice for testability and modularity',
|
|
766
|
+
confidence: 80,
|
|
767
|
+
});
|
|
768
|
+
}
|
|
769
|
+
return patterns;
|
|
770
|
+
}
|
|
771
|
+
}
|
|
772
|
+
// ============= Export singleton =============
|
|
773
|
+
let analyzerInstance = null;
|
|
774
|
+
export function getIntelligentAnalyzer(graph) {
|
|
775
|
+
if (!analyzerInstance) {
|
|
776
|
+
analyzerInstance = new IntelligentCodeAnalyzer(graph);
|
|
777
|
+
}
|
|
778
|
+
return analyzerInstance;
|
|
779
|
+
}
|