code-simplifier 1.0.0 → 1.2.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/README.md +56 -1
- package/bin/code-simplifier.js +73 -1
- package/cypress.config.js +45 -0
- package/jest.config.js +46 -0
- package/lib/ai-auto-fix.js +829 -0
- package/lib/ai-code-review.js +705 -0
- package/lib/ai-knowledge-base.js +757 -0
- package/lib/ai-quality-analyzer.js +1095 -0
- package/lib/ai-refactor-advisor.js +853 -0
- package/lib/ai-trend-analyzer.js +674 -0
- package/lib/auto-fix.js +382 -0
- package/lib/eslint-integration.js +328 -0
- package/lib/git-hooks.js +353 -0
- package/lib/master.js +117 -0
- package/lib/multi-language-analyzer.js +397 -0
- package/lib/ralph-integration.js +541 -0
- package/package.json +87 -61
|
@@ -0,0 +1,853 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* AI-Powered Refactor Advisor
|
|
5
|
+
* Provides intelligent refactoring suggestions with impact assessment
|
|
6
|
+
* Generates step-by-step refactoring plans and before/after comparisons
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
const fs = require('fs-extra')
|
|
10
|
+
const path = require('path')
|
|
11
|
+
const chalk = require('chalk')
|
|
12
|
+
const AIQualityAnalyzer = require('./ai-quality-analyzer')
|
|
13
|
+
const { OpenAI } = require('openai')
|
|
14
|
+
const Anthropic = require('@anthropic-ai/sdk')
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* AI Refactor Advisor
|
|
18
|
+
*/
|
|
19
|
+
class AIRefactorAdvisor {
|
|
20
|
+
constructor(config = {}) {
|
|
21
|
+
this.config = {
|
|
22
|
+
refactorTypes: config.refactorTypes || [
|
|
23
|
+
'extract-method',
|
|
24
|
+
'inline-method',
|
|
25
|
+
'rename-variable',
|
|
26
|
+
'rename-class',
|
|
27
|
+
'extract-class',
|
|
28
|
+
'move-method',
|
|
29
|
+
'replace-loop-with-pipeline',
|
|
30
|
+
'simplify-conditional',
|
|
31
|
+
'remove-duplicate-code',
|
|
32
|
+
'decompose-conditional',
|
|
33
|
+
'replace-temp-with-query',
|
|
34
|
+
'introduce-parameter-object',
|
|
35
|
+
'replace-magic-number',
|
|
36
|
+
'consolidate-duplicate-conditional',
|
|
37
|
+
'extract-interface',
|
|
38
|
+
'move-class',
|
|
39
|
+
'change-function-signature',
|
|
40
|
+
'extract-superclass',
|
|
41
|
+
'push-down-method',
|
|
42
|
+
'pull-up-method'
|
|
43
|
+
],
|
|
44
|
+
confidenceThreshold: config.confidenceThreshold || 0.8,
|
|
45
|
+
maxRefactorSuggestions: config.maxRefactorSuggestions || 50,
|
|
46
|
+
impactThreshold: config.impactThreshold || 0.8,
|
|
47
|
+
openaiApiKey: config.openaiApiKey || process.env.OPENAI_API_KEY,
|
|
48
|
+
anthropicApiKey: config.anthropicApiKey || process.env.ANTHROPIC_API_KEY,
|
|
49
|
+
...config
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
this.refactorSuggestions = []
|
|
53
|
+
this.analysisResults = {}
|
|
54
|
+
this.stats = {
|
|
55
|
+
opportunitiesIdentified: 0,
|
|
56
|
+
suggestionsGenerated: 0,
|
|
57
|
+
highImpactRefactors: 0,
|
|
58
|
+
avgConfidence: 0
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
this.qualityAnalyzer = new AIQualityAnalyzer({
|
|
62
|
+
openaiApiKey: this.config.openaiApiKey,
|
|
63
|
+
anthropicApiKey: this.config.anthropicApiKey
|
|
64
|
+
})
|
|
65
|
+
|
|
66
|
+
this.openai = this.config.openaiApiKey ? new OpenAI({ apiKey: this.config.openaiApiKey }) : null
|
|
67
|
+
this.anthropic = this.config.anthropicApiKey ? new Anthropic({ apiKey: this.config.anthropicApiKey }) : null
|
|
68
|
+
|
|
69
|
+
// Refactoring patterns database
|
|
70
|
+
this.refactorPatterns = {
|
|
71
|
+
'extract-method': {
|
|
72
|
+
description: 'Extract a method from a large function',
|
|
73
|
+
indicators: ['long function', 'duplicate code', 'multiple responsibilities'],
|
|
74
|
+
impact: 'high',
|
|
75
|
+
complexity: 'medium',
|
|
76
|
+
timeEstimate: '1-2 hours'
|
|
77
|
+
},
|
|
78
|
+
'rename-variable': {
|
|
79
|
+
description: 'Rename variables to be more descriptive',
|
|
80
|
+
indicators: ['bad variable names', 'single letter variables', 'unclear names'],
|
|
81
|
+
impact: 'medium',
|
|
82
|
+
complexity: 'low',
|
|
83
|
+
timeEstimate: '15-30 minutes'
|
|
84
|
+
},
|
|
85
|
+
'extract-class': {
|
|
86
|
+
description: 'Extract a class from a large class or module',
|
|
87
|
+
indicators: ['god class', 'too many methods', '分散责任'],
|
|
88
|
+
impact: 'high',
|
|
89
|
+
complexity: 'high',
|
|
90
|
+
timeEstimate: '4-8 hours'
|
|
91
|
+
},
|
|
92
|
+
'move-method': {
|
|
93
|
+
description: 'Move a method to a more appropriate class',
|
|
94
|
+
indicators: ['feature envy', 'inappropriate intimacy'],
|
|
95
|
+
impact: 'medium',
|
|
96
|
+
complexity: 'medium',
|
|
97
|
+
timeEstimate: '1-3 hours'
|
|
98
|
+
},
|
|
99
|
+
'simplify-conditional': {
|
|
100
|
+
description: 'Simplify complex conditional logic',
|
|
101
|
+
indicators: ['complex conditionals', 'deeply nested ifs'],
|
|
102
|
+
impact: 'high',
|
|
103
|
+
complexity: 'medium',
|
|
104
|
+
timeEstimate: '2-4 hours'
|
|
105
|
+
},
|
|
106
|
+
'replace-loop-with-pipeline': {
|
|
107
|
+
description: 'Replace loops with functional programming patterns',
|
|
108
|
+
indicators: ['imperative loops', 'manual iteration'],
|
|
109
|
+
impact: 'medium',
|
|
110
|
+
complexity: 'medium',
|
|
111
|
+
timeEstimate: '1-2 hours'
|
|
112
|
+
},
|
|
113
|
+
'remove-duplicate-code': {
|
|
114
|
+
description: 'Remove duplicate code by creating shared methods',
|
|
115
|
+
indicators: ['duplicate code', 'code clones'],
|
|
116
|
+
impact: 'high',
|
|
117
|
+
complexity: 'low',
|
|
118
|
+
timeEstimate: '30 minutes - 2 hours'
|
|
119
|
+
},
|
|
120
|
+
'extract-interface': {
|
|
121
|
+
description: 'Extract interface from class for better abstraction',
|
|
122
|
+
indicators: ['concrete implementation only', 'multiple implementations'],
|
|
123
|
+
impact: 'medium',
|
|
124
|
+
complexity: 'medium',
|
|
125
|
+
timeEstimate: '1-3 hours'
|
|
126
|
+
},
|
|
127
|
+
'introduce-parameter-object': {
|
|
128
|
+
description: 'Replace long parameter lists with parameter object',
|
|
129
|
+
indicators: ['long parameter list', 'parameter groups'],
|
|
130
|
+
impact: 'medium',
|
|
131
|
+
complexity: 'medium',
|
|
132
|
+
timeEstimate: '1-2 hours'
|
|
133
|
+
},
|
|
134
|
+
'replace-magic-number': {
|
|
135
|
+
description: 'Replace magic numbers with named constants',
|
|
136
|
+
indicators: ['magic numbers', 'hardcoded values'],
|
|
137
|
+
impact: 'low',
|
|
138
|
+
complexity: 'low',
|
|
139
|
+
timeEstimate: '15-30 minutes'
|
|
140
|
+
},
|
|
141
|
+
'decompose-conditional': {
|
|
142
|
+
description: 'Break down complex conditionals into smaller functions',
|
|
143
|
+
indicators: ['complex conditionals', 'long boolean expressions'],
|
|
144
|
+
impact: 'medium',
|
|
145
|
+
complexity: 'medium',
|
|
146
|
+
timeEstimate: '1-2 hours'
|
|
147
|
+
},
|
|
148
|
+
'push-down-method': {
|
|
149
|
+
description: 'Move method from parent class to child class',
|
|
150
|
+
indicators: ['method not used by all children'],
|
|
151
|
+
impact: 'medium',
|
|
152
|
+
complexity: 'medium',
|
|
153
|
+
timeEstimate: '2-4 hours'
|
|
154
|
+
},
|
|
155
|
+
'pull-up-method': {
|
|
156
|
+
description: 'Move method from child class to parent class',
|
|
157
|
+
indicators: ['duplicate methods in children'],
|
|
158
|
+
impact: 'medium',
|
|
159
|
+
complexity: 'medium',
|
|
160
|
+
timeEstimate: '2-4 hours'
|
|
161
|
+
},
|
|
162
|
+
'change-function-signature': {
|
|
163
|
+
description: 'Change function parameters or return type',
|
|
164
|
+
indicators: ['function needs different params', 'api change needed'],
|
|
165
|
+
impact: 'high',
|
|
166
|
+
complexity: 'high',
|
|
167
|
+
timeEstimate: '2-6 hours'
|
|
168
|
+
},
|
|
169
|
+
'extract-superclass': {
|
|
170
|
+
description: 'Extract common functionality into superclass',
|
|
171
|
+
indicators: ['duplicate code in siblings', 'shared behavior'],
|
|
172
|
+
impact: 'medium',
|
|
173
|
+
complexity: 'high',
|
|
174
|
+
timeEstimate: '4-8 hours'
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Initialize Refactor Advisor
|
|
181
|
+
*/
|
|
182
|
+
async initialize() {
|
|
183
|
+
console.log(chalk.blue('🔧 Initializing AI Refactor Advisor...'))
|
|
184
|
+
console.log(chalk.green(`✓ Loaded ${Object.keys(this.refactorPatterns).length} refactoring patterns`))
|
|
185
|
+
console.log(chalk.green('✓ AI Refactor Advisor initialized'))
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Analyze code and identify refactoring opportunities
|
|
190
|
+
*/
|
|
191
|
+
async analyzeCode(codePath, options = {}) {
|
|
192
|
+
console.log(chalk.blue('\n🔧 Analyzing code for refactoring opportunities...'))
|
|
193
|
+
|
|
194
|
+
try {
|
|
195
|
+
// Analyze code quality
|
|
196
|
+
const analysis = await this.qualityAnalyzer.analyzeCode(codePath, {
|
|
197
|
+
languages: options.languages || ['javascript', 'typescript', 'python', 'java'],
|
|
198
|
+
includeTests: options.includeTests || false
|
|
199
|
+
})
|
|
200
|
+
|
|
201
|
+
// Identify refactoring opportunities
|
|
202
|
+
const opportunities = this.identifyOpportunities(analysis)
|
|
203
|
+
|
|
204
|
+
// Generate suggestions
|
|
205
|
+
this.refactorSuggestions = await this.generateSuggestions(opportunities, analysis)
|
|
206
|
+
|
|
207
|
+
// Calculate statistics
|
|
208
|
+
this.stats.opportunitiesIdentified = opportunities.length
|
|
209
|
+
this.stats.suggestionsGenerated = this.refactorSuggestions.length
|
|
210
|
+
this.stats.highImpactRefactors = this.refactorSuggestions.filter(s => s.impact === 'high').length
|
|
211
|
+
this.stats.avgConfidence = this.calculateAverageConfidence()
|
|
212
|
+
|
|
213
|
+
console.log(chalk.green(`✓ Found ${opportunities.length} refactoring opportunities`))
|
|
214
|
+
console.log(chalk.green(`✓ Generated ${this.refactorSuggestions.length} suggestions`))
|
|
215
|
+
|
|
216
|
+
return this.generateReport()
|
|
217
|
+
|
|
218
|
+
} catch (error) {
|
|
219
|
+
console.error(chalk.red('❌ Refactoring analysis failed:'), error.message)
|
|
220
|
+
throw error
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* Identify refactoring opportunities from code analysis
|
|
226
|
+
*/
|
|
227
|
+
identifyOpportunities(analysis) {
|
|
228
|
+
const opportunities = []
|
|
229
|
+
|
|
230
|
+
for (const fileResult of analysis.files || []) {
|
|
231
|
+
const issues = fileResult.issues || []
|
|
232
|
+
|
|
233
|
+
// Map issues to refactoring opportunities
|
|
234
|
+
issues.forEach(issue => {
|
|
235
|
+
const opportunity = this.mapIssueToRefactor(issue, fileResult)
|
|
236
|
+
if (opportunity) {
|
|
237
|
+
opportunities.push(opportunity)
|
|
238
|
+
}
|
|
239
|
+
})
|
|
240
|
+
|
|
241
|
+
// Additional opportunity detection
|
|
242
|
+
this.detectCodeSmells(fileResult).forEach(opp => {
|
|
243
|
+
opportunities.push(opp)
|
|
244
|
+
})
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
return opportunities
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
/**
|
|
251
|
+
* Map code issues to refactoring opportunities
|
|
252
|
+
*/
|
|
253
|
+
mapIssueToRefactor(issue, fileResult) {
|
|
254
|
+
const type = issue.type
|
|
255
|
+
const severity = issue.severity
|
|
256
|
+
|
|
257
|
+
// Map based on issue type
|
|
258
|
+
const refactorMap = {
|
|
259
|
+
'longFunction': {
|
|
260
|
+
refactorType: 'extract-method',
|
|
261
|
+
confidence: 0.9,
|
|
262
|
+
reason: 'Function is too long and should be split'
|
|
263
|
+
},
|
|
264
|
+
'badVariableNames': {
|
|
265
|
+
refactorType: 'rename-variable',
|
|
266
|
+
confidence: 0.85,
|
|
267
|
+
reason: 'Variable names are unclear'
|
|
268
|
+
},
|
|
269
|
+
'duplicateCode': {
|
|
270
|
+
refactorType: 'remove-duplicate-code',
|
|
271
|
+
confidence: 0.95,
|
|
272
|
+
reason: 'Duplicate code found'
|
|
273
|
+
},
|
|
274
|
+
'highComplexity': {
|
|
275
|
+
refactorType: 'simplify-conditional',
|
|
276
|
+
confidence: 0.8,
|
|
277
|
+
reason: 'High cyclomatic complexity'
|
|
278
|
+
},
|
|
279
|
+
'magicNumbers': {
|
|
280
|
+
refactorType: 'replace-magic-number',
|
|
281
|
+
confidence: 0.9,
|
|
282
|
+
reason: 'Magic numbers should be replaced with constants'
|
|
283
|
+
},
|
|
284
|
+
'nestedConditionals': {
|
|
285
|
+
refactorType: 'decompose-conditional',
|
|
286
|
+
confidence: 0.85,
|
|
287
|
+
reason: 'Nested conditionals are hard to understand'
|
|
288
|
+
},
|
|
289
|
+
'longParameterList': {
|
|
290
|
+
refactorType: 'introduce-parameter-object',
|
|
291
|
+
confidence: 0.8,
|
|
292
|
+
reason: 'Parameter list is too long'
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
const refactorInfo = refactorMap[type]
|
|
297
|
+
if (!refactorInfo) return null
|
|
298
|
+
|
|
299
|
+
return {
|
|
300
|
+
filePath: fileResult.filePath,
|
|
301
|
+
language: fileResult.language,
|
|
302
|
+
issue: issue,
|
|
303
|
+
refactorType: refactorInfo.refactorType,
|
|
304
|
+
confidence: refactorInfo.confidence,
|
|
305
|
+
reason: refactorInfo.reason,
|
|
306
|
+
line: issue.line,
|
|
307
|
+
severity: severity
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
/**
|
|
312
|
+
* Detect code smells not covered by quality analyzer
|
|
313
|
+
*/
|
|
314
|
+
detectCodeSmells(fileResult) {
|
|
315
|
+
const opportunities = []
|
|
316
|
+
|
|
317
|
+
// Read file content to analyze
|
|
318
|
+
// This would be implemented with actual file parsing
|
|
319
|
+
|
|
320
|
+
return opportunities
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
/**
|
|
324
|
+
* Generate refactoring suggestions
|
|
325
|
+
*/
|
|
326
|
+
async generateSuggestions(opportunities, analysis) {
|
|
327
|
+
const suggestions = []
|
|
328
|
+
|
|
329
|
+
for (const opportunity of opportunities) {
|
|
330
|
+
// Get AI-powered suggestion
|
|
331
|
+
const aiSuggestion = await this.generateAISuggestion(opportunity, analysis)
|
|
332
|
+
|
|
333
|
+
// Enhance with pattern information
|
|
334
|
+
const pattern = this.refactorPatterns[opportunity.refactorType]
|
|
335
|
+
const suggestion = {
|
|
336
|
+
id: this.generateSuggestionId(),
|
|
337
|
+
...opportunity,
|
|
338
|
+
title: this.generateTitle(opportunity, pattern),
|
|
339
|
+
description: pattern.description,
|
|
340
|
+
impact: pattern.impact,
|
|
341
|
+
complexity: pattern.complexity,
|
|
342
|
+
timeEstimate: pattern.timeEstimate,
|
|
343
|
+
steps: aiSuggestion.steps || this.generateDefaultSteps(opportunity.refactorType),
|
|
344
|
+
beforeCode: aiSuggestion.beforeCode,
|
|
345
|
+
afterCode: aiSuggestion.afterCode,
|
|
346
|
+
benefits: aiSuggestion.benefits || this.generateDefaultBenefits(opportunity.refactorType),
|
|
347
|
+
risks: aiSuggestion.risks || this.generateDefaultRisks(opportunity.refactorType),
|
|
348
|
+
testStrategy: aiSuggestion.testStrategy || this.generateDefaultTestStrategy(opportunity.refactorType),
|
|
349
|
+
dependencies: aiSuggestion.dependencies || [],
|
|
350
|
+
estimatedRisk: this.calculateRisk(opportunity, pattern),
|
|
351
|
+
aiConfidence: aiSuggestion.confidence || opportunity.confidence,
|
|
352
|
+
finalConfidence: (opportunity.confidence + (aiSuggestion.confidence || 0)) / 2
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
suggestions.push(suggestion)
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
// Sort by confidence and impact
|
|
359
|
+
suggestions.sort((a, b) => {
|
|
360
|
+
if (a.impact !== b.impact) {
|
|
361
|
+
const impactOrder = { high: 3, medium: 2, low: 1 }
|
|
362
|
+
return impactOrder[b.impact] - impactOrder[a.impact]
|
|
363
|
+
}
|
|
364
|
+
return b.finalConfidence - a.finalConfidence
|
|
365
|
+
})
|
|
366
|
+
|
|
367
|
+
return suggestions.slice(0, this.config.maxRefactorSuggestions)
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
/**
|
|
371
|
+
* Generate AI-powered suggestion
|
|
372
|
+
*/
|
|
373
|
+
async generateAISuggestion(opportunity, analysis) {
|
|
374
|
+
const prompt = `
|
|
375
|
+
As an expert refactoring advisor, provide a detailed refactoring plan:
|
|
376
|
+
|
|
377
|
+
File: ${opportunity.filePath}
|
|
378
|
+
Language: ${opportunity.language}
|
|
379
|
+
Refactoring Type: ${opportunity.refactorType}
|
|
380
|
+
Issue: ${opportunity.reason}
|
|
381
|
+
Line: ${opportunity.line}
|
|
382
|
+
|
|
383
|
+
Provide a detailed refactoring suggestion in JSON format:
|
|
384
|
+
{
|
|
385
|
+
"steps": ["Step 1", "Step 2", "..."],
|
|
386
|
+
"beforeCode": "Code before refactoring",
|
|
387
|
+
"afterCode": "Code after refactoring",
|
|
388
|
+
"benefits": ["Benefit 1", "Benefit 2", "..."],
|
|
389
|
+
"risks": ["Risk 1", "Risk 2", "..."],
|
|
390
|
+
"testStrategy": "How to test this refactoring",
|
|
391
|
+
"confidence": 0.0-1.0
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
Make the steps actionable and specific.
|
|
395
|
+
`
|
|
396
|
+
|
|
397
|
+
try {
|
|
398
|
+
const response = await this.generateWithAI(prompt)
|
|
399
|
+
return JSON.parse(response)
|
|
400
|
+
} catch (error) {
|
|
401
|
+
console.warn(chalk.yellow(`⚠ AI suggestion failed, using template`))
|
|
402
|
+
return this.generateTemplateSuggestion(opportunity)
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
/**
|
|
407
|
+
* Generate template suggestion
|
|
408
|
+
*/
|
|
409
|
+
generateTemplateSuggestion(opportunity) {
|
|
410
|
+
const pattern = this.refactorPatterns[opportunity.refactorType]
|
|
411
|
+
|
|
412
|
+
return {
|
|
413
|
+
steps: this.generateDefaultSteps(opportunity.refactorType),
|
|
414
|
+
benefits: this.generateDefaultBenefits(opportunity.refactorType),
|
|
415
|
+
risks: this.generateDefaultRisks(opportunity.refactorType),
|
|
416
|
+
testStrategy: this.generateDefaultTestStrategy(opportunity.refactorType),
|
|
417
|
+
confidence: 0.7
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
/**
|
|
422
|
+
* Generate default steps for refactoring type
|
|
423
|
+
*/
|
|
424
|
+
generateDefaultSteps(refactorType) {
|
|
425
|
+
const stepMap = {
|
|
426
|
+
'extract-method': [
|
|
427
|
+
'Identify the section of code to extract',
|
|
428
|
+
'Create a new method with a descriptive name',
|
|
429
|
+
'Move the code to the new method',
|
|
430
|
+
'Replace the original code with a call to the new method',
|
|
431
|
+
'Pass parameters as needed',
|
|
432
|
+
'Update tests to cover the new method'
|
|
433
|
+
],
|
|
434
|
+
'rename-variable': [
|
|
435
|
+
'Choose a more descriptive name',
|
|
436
|
+
'Find all occurrences of the variable',
|
|
437
|
+
'Rename all occurrences consistently',
|
|
438
|
+
'Update comments that reference the old name',
|
|
439
|
+
'Run tests to ensure no breakage'
|
|
440
|
+
],
|
|
441
|
+
'remove-duplicate-code': [
|
|
442
|
+
'Identify the duplicate code section',
|
|
443
|
+
'Create a shared method or constant',
|
|
444
|
+
'Replace all instances with calls to the shared code',
|
|
445
|
+
'Ensure the shared code handles all use cases',
|
|
446
|
+
'Update tests accordingly'
|
|
447
|
+
],
|
|
448
|
+
'simplify-conditional': [
|
|
449
|
+
'Analyze the complex conditional logic',
|
|
450
|
+
'Extract complex conditions into well-named methods',
|
|
451
|
+
'Use early returns to reduce nesting',
|
|
452
|
+
'Simplify boolean expressions',
|
|
453
|
+
'Add comments to explain the logic',
|
|
454
|
+
'Test all code paths'
|
|
455
|
+
]
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
return stepMap[refactorType] || [
|
|
459
|
+
'Analyze the code to be refactored',
|
|
460
|
+
'Plan the refactoring approach',
|
|
461
|
+
'Implement the refactoring',
|
|
462
|
+
'Test thoroughly',
|
|
463
|
+
'Review the changes'
|
|
464
|
+
]
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
/**
|
|
468
|
+
* Generate default benefits
|
|
469
|
+
*/
|
|
470
|
+
generateDefaultBenefits(refactorType) {
|
|
471
|
+
const benefitMap = {
|
|
472
|
+
'extract-method': [
|
|
473
|
+
'Improved readability',
|
|
474
|
+
'Reduced code duplication',
|
|
475
|
+
'Easier to test individual methods',
|
|
476
|
+
'Better adherence to Single Responsibility Principle'
|
|
477
|
+
],
|
|
478
|
+
'rename-variable': [
|
|
479
|
+
'Improved code readability',
|
|
480
|
+
'Reduced cognitive load',
|
|
481
|
+
'Easier maintenance',
|
|
482
|
+
'Self-documenting code'
|
|
483
|
+
],
|
|
484
|
+
'remove-duplicate-code': [
|
|
485
|
+
'Reduced maintenance burden',
|
|
486
|
+
'Single source of truth',
|
|
487
|
+
'Easier bug fixes',
|
|
488
|
+
'Reduced code size'
|
|
489
|
+
]
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
return benefitMap[refactorType] || [
|
|
493
|
+
'Improved code quality',
|
|
494
|
+
'Better maintainability',
|
|
495
|
+
'Enhanced readability'
|
|
496
|
+
]
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
/**
|
|
500
|
+
* Generate default risks
|
|
501
|
+
*/
|
|
502
|
+
generateDefaultRisks(refactorType) {
|
|
503
|
+
return [
|
|
504
|
+
'Potential to introduce bugs if not tested thoroughly',
|
|
505
|
+
'May affect existing code that depends on the current structure',
|
|
506
|
+
'Requires comprehensive testing',
|
|
507
|
+
'May need to update documentation'
|
|
508
|
+
]
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
/**
|
|
512
|
+
* Generate default test strategy
|
|
513
|
+
*/
|
|
514
|
+
generateDefaultTestStrategy(refactorType) {
|
|
515
|
+
return [
|
|
516
|
+
'Run existing test suite before refactoring',
|
|
517
|
+
'Write tests for the refactored code if not present',
|
|
518
|
+
'Run tests after each step of refactoring',
|
|
519
|
+
'Perform integration testing',
|
|
520
|
+
'Review test coverage'
|
|
521
|
+
]
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
/**
|
|
525
|
+
* Generate suggestion title
|
|
526
|
+
*/
|
|
527
|
+
generateTitle(opportunity, pattern) {
|
|
528
|
+
return `${this.titleCase(opportunity.refactorType)} in ${path.basename(opportunity.filePath)}`
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
/**
|
|
532
|
+
* Calculate risk for refactoring
|
|
533
|
+
*/
|
|
534
|
+
calculateRisk(opportunity, pattern) {
|
|
535
|
+
let riskScore = 0
|
|
536
|
+
|
|
537
|
+
// Higher risk for high impact
|
|
538
|
+
if (pattern.impact === 'high') riskScore += 3
|
|
539
|
+
else if (pattern.impact === 'medium') riskScore += 2
|
|
540
|
+
else riskScore += 1
|
|
541
|
+
|
|
542
|
+
// Higher risk for high complexity
|
|
543
|
+
if (pattern.complexity === 'high') riskScore += 3
|
|
544
|
+
else if (pattern.complexity === 'medium') riskScore += 2
|
|
545
|
+
else riskScore += 1
|
|
546
|
+
|
|
547
|
+
// Adjust based on confidence (inverse relationship)
|
|
548
|
+
riskScore = riskScore * (1 - opportunity.confidence)
|
|
549
|
+
|
|
550
|
+
if (riskScore >= 5) return 'high'
|
|
551
|
+
if (riskScore >= 3) return 'medium'
|
|
552
|
+
return 'low'
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
/**
|
|
556
|
+
* Calculate average confidence
|
|
557
|
+
*/
|
|
558
|
+
calculateAverageConfidence() {
|
|
559
|
+
if (this.refactorSuggestions.length === 0) return 0
|
|
560
|
+
|
|
561
|
+
const total = this.refactorSuggestions.reduce((sum, s) => sum + s.finalConfidence, 0)
|
|
562
|
+
return (total / this.refactorSuggestions.length).toFixed(2)
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
/**
|
|
566
|
+
* Generate comprehensive refactoring report
|
|
567
|
+
*/
|
|
568
|
+
generateReport() {
|
|
569
|
+
const report = {
|
|
570
|
+
timestamp: new Date().toISOString(),
|
|
571
|
+
summary: {
|
|
572
|
+
totalOpportunities: this.stats.opportunitiesIdentified,
|
|
573
|
+
totalSuggestions: this.stats.suggestionsGenerated,
|
|
574
|
+
highImpactRefactors: this.stats.highImpactRefactors,
|
|
575
|
+
averageConfidence: this.stats.avgConfidence,
|
|
576
|
+
refactoringTypes: [...new Set(this.refactorSuggestions.map(s => s.refactorType))].length
|
|
577
|
+
},
|
|
578
|
+
suggestions: this.refactorSuggestions,
|
|
579
|
+
groupedByImpact: this.groupByImpact(),
|
|
580
|
+
phasedPlan: this.generatePhasedPlan(),
|
|
581
|
+
statistics: this.stats
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
return report
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
/**
|
|
588
|
+
* Group suggestions by impact
|
|
589
|
+
*/
|
|
590
|
+
groupByImpact() {
|
|
591
|
+
const grouped = {
|
|
592
|
+
high: [],
|
|
593
|
+
medium: [],
|
|
594
|
+
low: []
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
this.refactorSuggestions.forEach(suggestion => {
|
|
598
|
+
grouped[suggestion.impact].push(suggestion)
|
|
599
|
+
})
|
|
600
|
+
|
|
601
|
+
return grouped
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
/**
|
|
605
|
+
* Generate phased refactoring plan
|
|
606
|
+
*/
|
|
607
|
+
generatePhasedPlan() {
|
|
608
|
+
// Phase 1: Low risk, high confidence
|
|
609
|
+
const phase1 = this.refactorSuggestions.filter(s =>
|
|
610
|
+
s.estimatedRisk === 'low' && s.finalConfidence >= this.config.confidenceThreshold
|
|
611
|
+
)
|
|
612
|
+
|
|
613
|
+
// Phase 2: Medium risk or medium confidence
|
|
614
|
+
const phase2 = this.refactorSuggestions.filter(s =>
|
|
615
|
+
s.estimatedRisk === 'medium' && s.finalConfidence >= 0.7
|
|
616
|
+
)
|
|
617
|
+
|
|
618
|
+
// Phase 3: High risk or lower confidence
|
|
619
|
+
const phase3 = this.refactorSuggestions.filter(s =>
|
|
620
|
+
s.estimatedRisk === 'high' || s.finalConfidence < 0.7
|
|
621
|
+
)
|
|
622
|
+
|
|
623
|
+
return {
|
|
624
|
+
phase1: {
|
|
625
|
+
name: 'Quick Wins',
|
|
626
|
+
description: 'Low risk, high confidence refactoring',
|
|
627
|
+
suggestions: phase1,
|
|
628
|
+
estimatedTime: this.calculatePhaseTime(phase1)
|
|
629
|
+
},
|
|
630
|
+
phase2: {
|
|
631
|
+
name: 'Moderate Changes',
|
|
632
|
+
description: 'Medium risk, moderate impact refactoring',
|
|
633
|
+
suggestions: phase2,
|
|
634
|
+
estimatedTime: this.calculatePhaseTime(phase2)
|
|
635
|
+
},
|
|
636
|
+
phase3: {
|
|
637
|
+
name: 'Major Refactoring',
|
|
638
|
+
description: 'High risk, complex refactoring',
|
|
639
|
+
suggestions: phase3,
|
|
640
|
+
estimatedTime: this.calculatePhaseTime(phase3)
|
|
641
|
+
}
|
|
642
|
+
}
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
/**
|
|
646
|
+
* Calculate time for a phase
|
|
647
|
+
*/
|
|
648
|
+
calculatePhaseTime(suggestions) {
|
|
649
|
+
const totalHours = suggestions.reduce((sum, s) => {
|
|
650
|
+
const hours = this.parseTimeEstimate(s.timeEstimate)
|
|
651
|
+
return sum + hours
|
|
652
|
+
}, 0)
|
|
653
|
+
|
|
654
|
+
if (totalHours < 1) return 'Less than 1 hour'
|
|
655
|
+
if (totalHours < 8) return `${Math.ceil(totalHours)} hours`
|
|
656
|
+
return `${Math.ceil(totalHours / 8)} days`
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
/**
|
|
660
|
+
* Parse time estimate string to hours
|
|
661
|
+
*/
|
|
662
|
+
parseTimeEstimate(estimate) {
|
|
663
|
+
if (estimate.includes('minutes')) {
|
|
664
|
+
const match = estimate.match(/(\d+)/)
|
|
665
|
+
return match ? parseInt(match[1]) / 60 : 0.5
|
|
666
|
+
}
|
|
667
|
+
if (estimate.includes('hour')) {
|
|
668
|
+
const match = estimate.match(/(\d+)/)
|
|
669
|
+
return match ? parseInt(match[1]) : 1
|
|
670
|
+
}
|
|
671
|
+
if (estimate.includes('day')) {
|
|
672
|
+
const match = estimate.match(/(\d+)/)
|
|
673
|
+
return match ? parseInt(match[1]) * 8 : 8
|
|
674
|
+
}
|
|
675
|
+
return 1 // Default
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
/**
|
|
679
|
+
* Display refactoring suggestions
|
|
680
|
+
*/
|
|
681
|
+
displaySuggestions(report) {
|
|
682
|
+
console.log(chalk.blue('\n' + '='.repeat(70)))
|
|
683
|
+
console.log(chalk.blue('🔧 REFACTORING ADVISOR REPORT'))
|
|
684
|
+
console.log(chalk.blue('='.repeat(70)))
|
|
685
|
+
|
|
686
|
+
// Summary
|
|
687
|
+
console.log(chalk.cyan('\n📊 Summary:'))
|
|
688
|
+
console.log(` Opportunities Found: ${report.summary.totalOpportunities}`)
|
|
689
|
+
console.log(` Suggestions Generated: ${report.summary.totalSuggestions}`)
|
|
690
|
+
console.log(` High Impact Refactors: ${report.summary.highImpactRefactors}`)
|
|
691
|
+
console.log(` Average Confidence: ${report.summary.averageConfidence}`)
|
|
692
|
+
console.log(` Refactoring Types: ${report.summary.refactoringTypes}`)
|
|
693
|
+
|
|
694
|
+
// Phased plan
|
|
695
|
+
console.log(chalk.cyan('\n📋 Phased Refactoring Plan:'))
|
|
696
|
+
Object.entries(report.phasedPlan).forEach(([phaseKey, phase]) => {
|
|
697
|
+
if (phase.suggestions.length === 0) return
|
|
698
|
+
|
|
699
|
+
console.log(chalk.green(`\n ${phase.name}:`))
|
|
700
|
+
console.log(` ${phase.description}`)
|
|
701
|
+
console.log(` Estimated Time: ${phase.estimatedTime}`)
|
|
702
|
+
console.log(` Suggestions: ${phase.suggestions.length}`)
|
|
703
|
+
|
|
704
|
+
// Show top 3 suggestions
|
|
705
|
+
phase.suggestions.slice(0, 3).forEach(s => {
|
|
706
|
+
console.log(chalk.yellow(` • ${s.title}`))
|
|
707
|
+
})
|
|
708
|
+
|
|
709
|
+
if (phase.suggestions.length > 3) {
|
|
710
|
+
console.log(` ... and ${phase.suggestions.length - 3} more`)
|
|
711
|
+
}
|
|
712
|
+
})
|
|
713
|
+
|
|
714
|
+
// Top suggestions
|
|
715
|
+
console.log(chalk.cyan('\n⭐ Top Suggestions:'))
|
|
716
|
+
report.suggestions.slice(0, 5).forEach((s, i) => {
|
|
717
|
+
console.log(chalk.green(`\n ${i + 1}. ${s.title}`))
|
|
718
|
+
console.log(` Type: ${s.refactorType}`)
|
|
719
|
+
console.log(` Impact: ${s.impact}, Risk: ${s.estimatedRisk}`)
|
|
720
|
+
console.log(` Confidence: ${(s.finalConfidence * 100).toFixed(0)}%`)
|
|
721
|
+
console.log(` Time: ${s.timeEstimate}`)
|
|
722
|
+
console.log(` Steps: ${s.steps.length} steps`)
|
|
723
|
+
})
|
|
724
|
+
|
|
725
|
+
console.log(chalk.blue('\n' + '='.repeat(70)))
|
|
726
|
+
}
|
|
727
|
+
|
|
728
|
+
/**
|
|
729
|
+
* Export report
|
|
730
|
+
*/
|
|
731
|
+
async exportReport(report, format = 'json', outputPath) {
|
|
732
|
+
if (!outputPath) {
|
|
733
|
+
const timestamp = new Date().toISOString().replace(/[:.]/g, '-')
|
|
734
|
+
outputPath = path.join(process.cwd(), `refactor-report-${timestamp}.${format}`)
|
|
735
|
+
}
|
|
736
|
+
|
|
737
|
+
if (format === 'json') {
|
|
738
|
+
await fs.writeJson(outputPath, report, { spaces: 2 })
|
|
739
|
+
} else if (format === 'markdown') {
|
|
740
|
+
const markdown = this.generateMarkdownReport(report)
|
|
741
|
+
await fs.writeFile(outputPath, markdown)
|
|
742
|
+
}
|
|
743
|
+
|
|
744
|
+
console.log(chalk.green(`✓ Report exported to: ${outputPath}`))
|
|
745
|
+
}
|
|
746
|
+
|
|
747
|
+
/**
|
|
748
|
+
* Generate Markdown report
|
|
749
|
+
*/
|
|
750
|
+
generateMarkdownReport(report) {
|
|
751
|
+
let markdown = `# Refactoring Advisor Report
|
|
752
|
+
|
|
753
|
+
Generated: ${report.timestamp}
|
|
754
|
+
|
|
755
|
+
## Summary
|
|
756
|
+
|
|
757
|
+
- **Total Opportunities:** ${report.summary.totalOpportunities}
|
|
758
|
+
- **Suggestions Generated:** ${report.summary.totalSuggestions}
|
|
759
|
+
- **High Impact Refactors:** ${report.summary.highImpactRefactors}
|
|
760
|
+
- **Average Confidence:** ${report.summary.averageConfidence}
|
|
761
|
+
- **Refactoring Types:** ${report.summary.refactoringTypes}
|
|
762
|
+
|
|
763
|
+
## Phased Plan
|
|
764
|
+
|
|
765
|
+
`
|
|
766
|
+
|
|
767
|
+
Object.entries(report.phasedPlan).forEach(([phaseKey, phase]) => {
|
|
768
|
+
if (phase.suggestions.length === 0) return
|
|
769
|
+
|
|
770
|
+
markdown += `### ${phase.name}\n\n`
|
|
771
|
+
markdown += `${phase.description}\n\n`
|
|
772
|
+
markdown += `**Estimated Time:** ${phase.estimatedTime}\n\n`
|
|
773
|
+
markdown += `**Suggestions:** ${phase.suggestions.length}\n\n`
|
|
774
|
+
|
|
775
|
+
phase.suggestions.forEach(s => {
|
|
776
|
+
markdown += `#### ${s.title}\n\n`
|
|
777
|
+
markdown += `- **Type:** ${s.refactorType}\n`
|
|
778
|
+
markdown += `- **Impact:** ${s.impact}\n`
|
|
779
|
+
markdown += `- **Risk:** ${s.estimatedRisk}\n`
|
|
780
|
+
markdown += `- **Confidence:** ${(s.finalConfidence * 100).toFixed(0)}%\n`
|
|
781
|
+
markdown += `- **Time Estimate:** ${s.timeEstimate}\n\n`
|
|
782
|
+
markdown += `**Description:** ${s.description}\n\n`
|
|
783
|
+
markdown += `**Steps:**\n`
|
|
784
|
+
s.steps.forEach(step => markdown += `1. ${step}\n`)
|
|
785
|
+
markdown += `\n`
|
|
786
|
+
})
|
|
787
|
+
})
|
|
788
|
+
|
|
789
|
+
return markdown
|
|
790
|
+
}
|
|
791
|
+
|
|
792
|
+
/**
|
|
793
|
+
* Generate suggestion ID
|
|
794
|
+
*/
|
|
795
|
+
generateSuggestionId() {
|
|
796
|
+
return `REF-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`
|
|
797
|
+
}
|
|
798
|
+
|
|
799
|
+
/**
|
|
800
|
+
* Title case helper
|
|
801
|
+
*/
|
|
802
|
+
titleCase(str) {
|
|
803
|
+
return str.replace(/-/g, ' ')
|
|
804
|
+
.split(' ')
|
|
805
|
+
.map(word => word.charAt(0).toUpperCase() + word.slice(1))
|
|
806
|
+
.join(' ')
|
|
807
|
+
}
|
|
808
|
+
|
|
809
|
+
/**
|
|
810
|
+
* Generate with AI
|
|
811
|
+
*/
|
|
812
|
+
async generateWithAI(prompt) {
|
|
813
|
+
// Try OpenAI first
|
|
814
|
+
if (this.openai) {
|
|
815
|
+
try {
|
|
816
|
+
const response = await this.openai.chat.completions.create({
|
|
817
|
+
model: 'gpt-4',
|
|
818
|
+
messages: [{ role: 'user', content: prompt }],
|
|
819
|
+
temperature: 0.7,
|
|
820
|
+
max_tokens: 2000
|
|
821
|
+
})
|
|
822
|
+
return response.choices[0].message.content
|
|
823
|
+
} catch (error) {
|
|
824
|
+
console.warn(chalk.yellow('OpenAI failed, trying Anthropic...'))
|
|
825
|
+
}
|
|
826
|
+
}
|
|
827
|
+
|
|
828
|
+
// Try Anthropic
|
|
829
|
+
if (this.anthropic) {
|
|
830
|
+
try {
|
|
831
|
+
const response = await this.anthropic.messages.create({
|
|
832
|
+
model: 'claude-3-5-sonnet-20241022',
|
|
833
|
+
max_tokens: 2000,
|
|
834
|
+
messages: [{ role: 'user', content: prompt }]
|
|
835
|
+
})
|
|
836
|
+
return response.content[0].text
|
|
837
|
+
} catch (error) {
|
|
838
|
+
console.warn(chalk.yellow('Anthropic failed, using mock mode'))
|
|
839
|
+
}
|
|
840
|
+
}
|
|
841
|
+
|
|
842
|
+
// Mock mode
|
|
843
|
+
return JSON.stringify({
|
|
844
|
+
steps: ['Step 1', 'Step 2', 'Step 3'],
|
|
845
|
+
benefits: ['Improved code quality'],
|
|
846
|
+
risks: ['May need comprehensive testing'],
|
|
847
|
+
testStrategy: 'Run all tests before and after',
|
|
848
|
+
confidence: 0.8
|
|
849
|
+
})
|
|
850
|
+
}
|
|
851
|
+
}
|
|
852
|
+
|
|
853
|
+
module.exports = AIRefactorAdvisor
|