code-simplifier 1.1.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/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/ralph-integration.js +541 -0
- package/package.json +87 -77
|
@@ -0,0 +1,829 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* AI-Powered Code Auto-Fix Engine
|
|
5
|
+
* Intelligently fixes code issues using AI analysis
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const fs = require('fs-extra')
|
|
9
|
+
const path = require('path')
|
|
10
|
+
const chalk = require('chalk')
|
|
11
|
+
const ora = require('ora')
|
|
12
|
+
const { OpenAI } = require('openai')
|
|
13
|
+
const Anthropic = require('@anthropic-ai/sdk')
|
|
14
|
+
const aiQualityAnalyzer = require('./ai-quality-analyzer')
|
|
15
|
+
|
|
16
|
+
class AIAutoFix {
|
|
17
|
+
constructor() {
|
|
18
|
+
this.openai = null
|
|
19
|
+
this.anthropic = null
|
|
20
|
+
this.useMockMode = false
|
|
21
|
+
this.fixHistory = []
|
|
22
|
+
this.rollbackStack = []
|
|
23
|
+
this.initClients()
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Initialize AI API clients
|
|
28
|
+
*/
|
|
29
|
+
initClients() {
|
|
30
|
+
try {
|
|
31
|
+
if (process.env.OPENAI_API_KEY) {
|
|
32
|
+
this.openai = new OpenAI({
|
|
33
|
+
apiKey: process.env.OPENAI_API_KEY
|
|
34
|
+
})
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
if (process.env.ANTHROPIC_API_KEY) {
|
|
38
|
+
this.anthropic = new Anthropic({
|
|
39
|
+
apiKey: process.env.ANTHROPIC_API_KEY
|
|
40
|
+
})
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
if (!this.openai && !this.anthropic) {
|
|
44
|
+
console.log(chalk.yellow('⚠️ No AI API keys found, using mock mode'))
|
|
45
|
+
this.useMockMode = true
|
|
46
|
+
}
|
|
47
|
+
} catch (error) {
|
|
48
|
+
console.log(chalk.yellow('⚠️ Failed to initialize AI clients, using mock mode'))
|
|
49
|
+
this.useMockMode = true
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Run AI auto-fix
|
|
55
|
+
*/
|
|
56
|
+
async run(options = {}) {
|
|
57
|
+
console.log(chalk.blue.bold('\n🤖 AI-Powered Auto-Fix Engine'))
|
|
58
|
+
console.log(chalk.gray('='.repeat(70)))
|
|
59
|
+
|
|
60
|
+
const spinner = ora(chalk.blue('正在初始化AI修复引擎...')).start()
|
|
61
|
+
|
|
62
|
+
try {
|
|
63
|
+
const {
|
|
64
|
+
dir = '.',
|
|
65
|
+
pattern = '**/*.{js,ts,jsx,tsx,py,java,cs,cpp,php,rb,go,rs,swift,kt}',
|
|
66
|
+
dryRun = true,
|
|
67
|
+
aiProvider = 'auto',
|
|
68
|
+
maxIterations = 10
|
|
69
|
+
} = options
|
|
70
|
+
|
|
71
|
+
// 1. Analyze code using AI
|
|
72
|
+
spinner.text = chalk.blue('正在使用AI分析代码质量...')
|
|
73
|
+
const analysisResults = await this.analyzeCode(dir, pattern)
|
|
74
|
+
spinner.info(chalk.gray(`分析完成: ${analysisResults.files.length} 个文件`))
|
|
75
|
+
|
|
76
|
+
// 2. Generate AI-powered fixes
|
|
77
|
+
spinner.text = chalk.blue('正在生成AI修复建议...')
|
|
78
|
+
const fixes = await this.generateAIFixes(analysisResults, { aiProvider })
|
|
79
|
+
|
|
80
|
+
if (fixes.length === 0) {
|
|
81
|
+
spinner.succeed(chalk.green('没有发现需要修复的问题'))
|
|
82
|
+
return {
|
|
83
|
+
success: true,
|
|
84
|
+
dryRun,
|
|
85
|
+
totalIssues: 0,
|
|
86
|
+
fixes: []
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// 3. Preview mode - show fixes before applying
|
|
91
|
+
if (dryRun) {
|
|
92
|
+
spinner.info(chalk.yellow('预览模式 - 显示修复建议'))
|
|
93
|
+
const preview = await this.previewFixes(fixes)
|
|
94
|
+
return {
|
|
95
|
+
success: true,
|
|
96
|
+
dryRun: true,
|
|
97
|
+
totalIssues: fixes.length,
|
|
98
|
+
fixes: preview
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// 4. Execution mode - apply fixes
|
|
103
|
+
spinner.text = chalk.blue('正在应用AI修复...')
|
|
104
|
+
const result = await this.applyAIFixes(fixes, { maxIterations })
|
|
105
|
+
|
|
106
|
+
spinner.succeed(chalk.green('AI修复完成'))
|
|
107
|
+
return result
|
|
108
|
+
|
|
109
|
+
} catch (error) {
|
|
110
|
+
spinner.fail(chalk.red('AI修复失败'))
|
|
111
|
+
console.error(chalk.red(error))
|
|
112
|
+
throw error
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Analyze code using AI quality analyzer
|
|
118
|
+
*/
|
|
119
|
+
async analyzeCode(dir, pattern) {
|
|
120
|
+
return await aiQualityAnalyzer.run({
|
|
121
|
+
dir,
|
|
122
|
+
pattern,
|
|
123
|
+
aiProvider: 'auto',
|
|
124
|
+
outputFormat: 'json'
|
|
125
|
+
})
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Generate AI-powered fixes based on analysis
|
|
130
|
+
*/
|
|
131
|
+
async generateAIFixes(analysisResults, options = {}) {
|
|
132
|
+
const fixes = []
|
|
133
|
+
|
|
134
|
+
for (const fileResult of analysisResults.files) {
|
|
135
|
+
if (!fileResult.issues || fileResult.issues.length === 0) continue
|
|
136
|
+
|
|
137
|
+
const fileFixes = await this.generateFixesForFile(fileResult, options)
|
|
138
|
+
fixes.push(...fileFixes)
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
return fixes
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Generate fixes for a single file
|
|
146
|
+
*/
|
|
147
|
+
async generateFixesForFile(fileResult, options = {}) {
|
|
148
|
+
const fixes = []
|
|
149
|
+
const { aiProvider = 'auto' } = options
|
|
150
|
+
|
|
151
|
+
try {
|
|
152
|
+
const filePath = fileResult.file
|
|
153
|
+
const originalContent = await fs.readFile(filePath, 'utf8')
|
|
154
|
+
|
|
155
|
+
// Group issues by type
|
|
156
|
+
const issuesByType = this.groupIssuesByType(fileResult.issues)
|
|
157
|
+
|
|
158
|
+
for (const [issueType, issues] of Object.entries(issuesByType)) {
|
|
159
|
+
// Generate fix using AI
|
|
160
|
+
const fix = await this.generateFixForIssueType(
|
|
161
|
+
filePath,
|
|
162
|
+
originalContent,
|
|
163
|
+
issueType,
|
|
164
|
+
issues,
|
|
165
|
+
aiProvider
|
|
166
|
+
)
|
|
167
|
+
|
|
168
|
+
if (fix) {
|
|
169
|
+
fixes.push(fix)
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
} catch (error) {
|
|
173
|
+
console.error(chalk.red(`生成修复失败 (${fileResult.file}):`), error.message)
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
return fixes
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Group issues by type
|
|
181
|
+
*/
|
|
182
|
+
groupIssuesByType(issues) {
|
|
183
|
+
const grouped = {}
|
|
184
|
+
|
|
185
|
+
for (const issue of issues) {
|
|
186
|
+
const type = issue.type || 'unknown'
|
|
187
|
+
if (!grouped[type]) {
|
|
188
|
+
grouped[type] = []
|
|
189
|
+
}
|
|
190
|
+
grouped[type].push(issue)
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
return grouped
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* Generate fix for a specific issue type using AI
|
|
198
|
+
*/
|
|
199
|
+
async generateFixForIssueType(filePath, content, issueType, issues, aiProvider) {
|
|
200
|
+
const language = this.detectLanguage(filePath)
|
|
201
|
+
|
|
202
|
+
if (this.useMockMode) {
|
|
203
|
+
return this.generateMockFix(filePath, content, issueType, issues, language)
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
try {
|
|
207
|
+
const prompt = this.buildFixPrompt(filePath, content, issueType, issues, language)
|
|
208
|
+
|
|
209
|
+
if (aiProvider === 'openai' && this.openai) {
|
|
210
|
+
return await this.generateFixWithOpenAI(prompt, filePath, content, issueType, issues)
|
|
211
|
+
} else if (aiProvider === 'anthropic' && this.anthropic) {
|
|
212
|
+
return await this.generateFixWithAnthropic(prompt, filePath, content, issueType, issues)
|
|
213
|
+
} else {
|
|
214
|
+
// Auto-select provider
|
|
215
|
+
return this.openai
|
|
216
|
+
? await this.generateFixWithOpenAI(prompt, filePath, content, issueType, issues)
|
|
217
|
+
: await this.generateFixWithAnthropic(prompt, filePath, content, issueType, issues)
|
|
218
|
+
}
|
|
219
|
+
} catch (error) {
|
|
220
|
+
console.error(chalk.yellow(`AI修复生成失败,使用规则修复: ${error.message}`))
|
|
221
|
+
return this.generateRuleBasedFix(filePath, content, issueType, issues, language)
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/**
|
|
226
|
+
* Build fix prompt for AI
|
|
227
|
+
*/
|
|
228
|
+
buildFixPrompt(filePath, content, issueType, issues, language) {
|
|
229
|
+
const issueDescriptions = issues.map(issue => {
|
|
230
|
+
return `- Line ${issue.line}: ${issue.message} (${issue.severity})`
|
|
231
|
+
}).join('\n')
|
|
232
|
+
|
|
233
|
+
return `As an expert ${language} code reviewer, analyze the following code and provide a fix.
|
|
234
|
+
|
|
235
|
+
File: ${filePath}
|
|
236
|
+
Language: ${language}
|
|
237
|
+
Issue Type: ${issueType}
|
|
238
|
+
|
|
239
|
+
Issues found:
|
|
240
|
+
${issueDescriptions}
|
|
241
|
+
|
|
242
|
+
Original Code:
|
|
243
|
+
\`\`\`${language}
|
|
244
|
+
${content}
|
|
245
|
+
\`\`\`
|
|
246
|
+
|
|
247
|
+
Please provide a JSON response with the following structure:
|
|
248
|
+
{
|
|
249
|
+
"fixDescription": "Brief description of what will be fixed",
|
|
250
|
+
"reasoning": "Why this fix improves the code",
|
|
251
|
+
"changes": [
|
|
252
|
+
{
|
|
253
|
+
"type": "replace|add|remove",
|
|
254
|
+
"line": number,
|
|
255
|
+
"original": "original code snippet",
|
|
256
|
+
"fixed": "fixed code snippet",
|
|
257
|
+
"explanation": "why this change was made"
|
|
258
|
+
}
|
|
259
|
+
],
|
|
260
|
+
"preservedComments": ["list of comments that were preserved"],
|
|
261
|
+
"preservedLogic": "description of logic preserved"
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
IMPORTANT:
|
|
265
|
+
- Preserve all comments and docstrings
|
|
266
|
+
- Maintain original code logic and functionality
|
|
267
|
+
- Fix only the identified issues
|
|
268
|
+
- Provide specific line-by-line changes
|
|
269
|
+
- Ensure the fix is safe and correct`
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
/**
|
|
273
|
+
* Generate fix using OpenAI
|
|
274
|
+
*/
|
|
275
|
+
async generateFixWithOpenAI(prompt, filePath, content, issueType, issues) {
|
|
276
|
+
const completion = await this.openai.chat.completions.create({
|
|
277
|
+
model: 'gpt-4',
|
|
278
|
+
messages: [
|
|
279
|
+
{
|
|
280
|
+
role: 'system',
|
|
281
|
+
content: 'You are an expert code reviewer and refactoring specialist. Always respond with valid JSON.'
|
|
282
|
+
},
|
|
283
|
+
{
|
|
284
|
+
role: 'user',
|
|
285
|
+
content: prompt
|
|
286
|
+
}
|
|
287
|
+
],
|
|
288
|
+
temperature: 0.2,
|
|
289
|
+
max_tokens: 2000
|
|
290
|
+
})
|
|
291
|
+
|
|
292
|
+
const response = completion.choices[0].message.content
|
|
293
|
+
return this.parseAIResponse(response, filePath, content, issueType, issues)
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
/**
|
|
297
|
+
* Generate fix using Anthropic
|
|
298
|
+
*/
|
|
299
|
+
async generateFixWithAnthropic(prompt, filePath, content, issueType, issues) {
|
|
300
|
+
const response = await this.anthropic.messages.create({
|
|
301
|
+
model: 'claude-3-sonnet-20240229',
|
|
302
|
+
max_tokens: 2000,
|
|
303
|
+
temperature: 0.2,
|
|
304
|
+
messages: [
|
|
305
|
+
{
|
|
306
|
+
role: 'user',
|
|
307
|
+
content: prompt
|
|
308
|
+
}
|
|
309
|
+
]
|
|
310
|
+
})
|
|
311
|
+
|
|
312
|
+
const responseText = response.content[0].text
|
|
313
|
+
return this.parseAIResponse(responseText, filePath, content, issueType, issues)
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
/**
|
|
317
|
+
* Parse AI response
|
|
318
|
+
*/
|
|
319
|
+
parseAIResponse(responseText, filePath, content, issueType, issues) {
|
|
320
|
+
try {
|
|
321
|
+
// Extract JSON from response
|
|
322
|
+
const jsonMatch = responseText.match(/\{[\s\S]*\}/)
|
|
323
|
+
if (!jsonMatch) {
|
|
324
|
+
throw new Error('No JSON found in response')
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
const parsed = JSON.parse(jsonMatch[0])
|
|
328
|
+
|
|
329
|
+
return {
|
|
330
|
+
file: filePath,
|
|
331
|
+
issueType,
|
|
332
|
+
issues,
|
|
333
|
+
description: parsed.fixDescription || `Fix ${issueType}`,
|
|
334
|
+
reasoning: parsed.reasoning || 'AI-generated fix',
|
|
335
|
+
changes: parsed.changes || [],
|
|
336
|
+
preservedComments: parsed.preservedComments || [],
|
|
337
|
+
preservedLogic: parsed.preservedLogic || '',
|
|
338
|
+
confidence: 0.9
|
|
339
|
+
}
|
|
340
|
+
} catch (error) {
|
|
341
|
+
console.error(chalk.yellow('解析AI响应失败:', error.message))
|
|
342
|
+
return null
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
/**
|
|
347
|
+
* Generate mock fix for testing
|
|
348
|
+
*/
|
|
349
|
+
generateMockFix(filePath, content, issueType, issues, language) {
|
|
350
|
+
const mockFixes = {
|
|
351
|
+
'long-function': {
|
|
352
|
+
description: 'Extract long function into smaller functions',
|
|
353
|
+
reasoning: 'Breaking down long functions improves readability and maintainability',
|
|
354
|
+
changes: [
|
|
355
|
+
{
|
|
356
|
+
type: 'replace',
|
|
357
|
+
line: issues[0].line,
|
|
358
|
+
original: 'function longFunction() {',
|
|
359
|
+
fixed: 'function shorterFunction() {',
|
|
360
|
+
explanation: 'Extracted to smaller function'
|
|
361
|
+
}
|
|
362
|
+
],
|
|
363
|
+
preservedComments: [],
|
|
364
|
+
preservedLogic: 'All original logic preserved'
|
|
365
|
+
},
|
|
366
|
+
'bad-variable-names': {
|
|
367
|
+
description: 'Rename variables to be more descriptive',
|
|
368
|
+
reasoning: 'Descriptive variable names improve code readability',
|
|
369
|
+
changes: [
|
|
370
|
+
{
|
|
371
|
+
type: 'replace',
|
|
372
|
+
line: issues[0].line,
|
|
373
|
+
original: 'let x = 10',
|
|
374
|
+
fixed: 'let userAge = 10',
|
|
375
|
+
explanation: 'Renamed for clarity'
|
|
376
|
+
}
|
|
377
|
+
],
|
|
378
|
+
preservedComments: [],
|
|
379
|
+
preservedLogic: 'All original logic preserved'
|
|
380
|
+
},
|
|
381
|
+
'high-complexity': {
|
|
382
|
+
description: 'Simplify complex conditional logic',
|
|
383
|
+
reasoning: 'Reducing complexity improves code maintainability',
|
|
384
|
+
changes: [
|
|
385
|
+
{
|
|
386
|
+
type: 'replace',
|
|
387
|
+
line: issues[0].line,
|
|
388
|
+
original: 'if (a && b && c && d) {',
|
|
389
|
+
fixed: 'if (isValidCondition(a, b, c, d)) {',
|
|
390
|
+
explanation: 'Extracted condition to named function'
|
|
391
|
+
}
|
|
392
|
+
],
|
|
393
|
+
preservedComments: [],
|
|
394
|
+
preservedLogic: 'All original logic preserved'
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
const mockFix = mockFixes[issueType] || {
|
|
399
|
+
description: `Fix ${issueType} issues`,
|
|
400
|
+
reasoning: 'Automated AI fix',
|
|
401
|
+
changes: [],
|
|
402
|
+
preservedComments: [],
|
|
403
|
+
preservedLogic: 'All original logic preserved'
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
return {
|
|
407
|
+
file: filePath,
|
|
408
|
+
issueType,
|
|
409
|
+
issues,
|
|
410
|
+
...mockFix,
|
|
411
|
+
confidence: 0.7
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
/**
|
|
416
|
+
* Generate rule-based fix as fallback
|
|
417
|
+
*/
|
|
418
|
+
generateRuleBasedFix(filePath, content, issueType, issues, language) {
|
|
419
|
+
const ruleFixes = {
|
|
420
|
+
'trailing-space': (filePath, content, issues) => {
|
|
421
|
+
let fixed = content
|
|
422
|
+
const changes = []
|
|
423
|
+
|
|
424
|
+
for (const issue of issues) {
|
|
425
|
+
const lines = fixed.split('\n')
|
|
426
|
+
if (lines[issue.line - 1]) {
|
|
427
|
+
const original = lines[issue.line - 1]
|
|
428
|
+
const fixedLine = original.replace(/[ \t]+$/, '')
|
|
429
|
+
if (original !== fixedLine) {
|
|
430
|
+
lines[issue.line - 1] = fixedLine
|
|
431
|
+
changes.push({
|
|
432
|
+
type: 'replace',
|
|
433
|
+
line: issue.line,
|
|
434
|
+
original: original,
|
|
435
|
+
fixed: fixedLine,
|
|
436
|
+
explanation: 'Remove trailing whitespace'
|
|
437
|
+
})
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
return {
|
|
443
|
+
description: 'Remove trailing whitespace',
|
|
444
|
+
reasoning: 'Cleaning up formatting issues',
|
|
445
|
+
changes,
|
|
446
|
+
preservedComments: [],
|
|
447
|
+
preservedLogic: 'No logic changes'
|
|
448
|
+
}
|
|
449
|
+
},
|
|
450
|
+
'console-log': (filePath, content, issues) => {
|
|
451
|
+
let fixed = content
|
|
452
|
+
const changes = []
|
|
453
|
+
|
|
454
|
+
for (const issue of issues) {
|
|
455
|
+
const lines = fixed.split('\n')
|
|
456
|
+
if (lines[issue.line - 1] && lines[issue.line - 1].includes('console.log')) {
|
|
457
|
+
const original = lines[issue.line - 1]
|
|
458
|
+
lines[issue.line - 1] = original.replace(
|
|
459
|
+
/console\.log\([^)]*\);?/,
|
|
460
|
+
'// TODO: Remove console.log'
|
|
461
|
+
)
|
|
462
|
+
changes.push({
|
|
463
|
+
type: 'replace',
|
|
464
|
+
line: issue.line,
|
|
465
|
+
original: original,
|
|
466
|
+
fixed: lines[issue.line - 1],
|
|
467
|
+
explanation: 'Comment out console.log'
|
|
468
|
+
})
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
return {
|
|
473
|
+
description: 'Comment out console statements',
|
|
474
|
+
reasoning: 'Remove debug statements',
|
|
475
|
+
changes,
|
|
476
|
+
preservedComments: [],
|
|
477
|
+
preservedLogic: 'No logic changes'
|
|
478
|
+
}
|
|
479
|
+
},
|
|
480
|
+
'var-declaration': (filePath, content, issues) => {
|
|
481
|
+
let fixed = content
|
|
482
|
+
const changes = []
|
|
483
|
+
|
|
484
|
+
for (const issue of issues) {
|
|
485
|
+
const lines = fixed.split('\n')
|
|
486
|
+
if (lines[issue.line - 1] && lines[issue.line - 1].trim().startsWith('var ')) {
|
|
487
|
+
const original = lines[issue.line - 1]
|
|
488
|
+
lines[issue.line - 1] = original.replace(/^(\s*)var\s+/, '$1let ')
|
|
489
|
+
changes.push({
|
|
490
|
+
type: 'replace',
|
|
491
|
+
line: issue.line,
|
|
492
|
+
original: original,
|
|
493
|
+
fixed: lines[issue.line - 1],
|
|
494
|
+
explanation: 'Replace var with let'
|
|
495
|
+
})
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
return {
|
|
500
|
+
description: 'Replace var with let/const',
|
|
501
|
+
reasoning: 'Use modern JavaScript declarations',
|
|
502
|
+
changes,
|
|
503
|
+
preservedComments: [],
|
|
504
|
+
preservedLogic: 'No logic changes'
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
const ruleFix = ruleFixes[issueType]
|
|
510
|
+
|
|
511
|
+
if (ruleFix) {
|
|
512
|
+
return {
|
|
513
|
+
file: filePath,
|
|
514
|
+
issueType,
|
|
515
|
+
issues,
|
|
516
|
+
...ruleFix(filePath, content, issues),
|
|
517
|
+
confidence: 0.8
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
return null
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
/**
|
|
525
|
+
* Preview fixes before applying
|
|
526
|
+
*/
|
|
527
|
+
async previewFixes(fixes) {
|
|
528
|
+
console.log(chalk.blue.bold('\n📋 AI修复预览'))
|
|
529
|
+
console.log(chalk.gray('='.repeat(70)))
|
|
530
|
+
|
|
531
|
+
const preview = []
|
|
532
|
+
|
|
533
|
+
for (const fix of fixes) {
|
|
534
|
+
console.log(chalk.cyan(`\n文件: ${fix.file}`))
|
|
535
|
+
console.log(chalk.yellow(`问题类型: ${fix.issueType}`))
|
|
536
|
+
console.log(`描述: ${fix.description}`)
|
|
537
|
+
console.log(chalk.gray(`推理: ${fix.reasoning}`))
|
|
538
|
+
console.log(chalk.gray(`可信度: ${(fix.confidence * 100).toFixed(0)}%`))
|
|
539
|
+
|
|
540
|
+
if (fix.changes && fix.changes.length > 0) {
|
|
541
|
+
console.log(chalk.blue('\n变更详情:'))
|
|
542
|
+
fix.changes.forEach((change, index) => {
|
|
543
|
+
console.log(` ${index + 1}. Line ${change.line}: ${change.type}`)
|
|
544
|
+
if (change.original) {
|
|
545
|
+
console.log(chalk.red(` - ${change.original.substring(0, 50)}...`))
|
|
546
|
+
}
|
|
547
|
+
if (change.fixed) {
|
|
548
|
+
console.log(chalk.green(` + ${change.fixed.substring(0, 50)}...`))
|
|
549
|
+
}
|
|
550
|
+
console.log(chalk.gray(` ${change.explanation}`))
|
|
551
|
+
})
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
if (fix.preservedComments && fix.preservedComments.length > 0) {
|
|
555
|
+
console.log(chalk.blue('\n保留的注释:'))
|
|
556
|
+
fix.preservedComments.forEach(comment => {
|
|
557
|
+
console.log(chalk.green(` + ${comment}`))
|
|
558
|
+
})
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
preview.push({
|
|
562
|
+
...fix,
|
|
563
|
+
willApply: true
|
|
564
|
+
})
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
return preview
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
/**
|
|
571
|
+
* Apply AI-powered fixes
|
|
572
|
+
*/
|
|
573
|
+
async applyAIFixes(fixes, options = {}) {
|
|
574
|
+
const { maxIterations = 10 } = options
|
|
575
|
+
const appliedFixes = []
|
|
576
|
+
const failedFixes = []
|
|
577
|
+
const rollbackData = []
|
|
578
|
+
|
|
579
|
+
try {
|
|
580
|
+
// Create rollback point
|
|
581
|
+
await this.createRollbackPoint()
|
|
582
|
+
|
|
583
|
+
// Group fixes by file
|
|
584
|
+
const fixesByFile = this.groupFixesByFile(fixes)
|
|
585
|
+
|
|
586
|
+
// Apply fixes
|
|
587
|
+
for (const [filePath, fileFixes] of Object.entries(fixesByFile)) {
|
|
588
|
+
try {
|
|
589
|
+
const result = await this.applyFixesToFile(filePath, fileFixes)
|
|
590
|
+
appliedFixes.push(...result.applied)
|
|
591
|
+
rollbackData.push(...result.rollbackData)
|
|
592
|
+
} catch (error) {
|
|
593
|
+
console.error(chalk.red(`应用修复失败 (${filePath}):`), error.message)
|
|
594
|
+
failedFixes.push({ file: filePath, error: error.message })
|
|
595
|
+
}
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
// Generate report
|
|
599
|
+
const report = this.generateFixReport({
|
|
600
|
+
totalFixes: fixes.length,
|
|
601
|
+
applied: appliedFixes.length,
|
|
602
|
+
failed: failedFixes.length,
|
|
603
|
+
fixes: appliedFixes,
|
|
604
|
+
failedFixes
|
|
605
|
+
})
|
|
606
|
+
|
|
607
|
+
// Save report
|
|
608
|
+
await this.saveReport(report)
|
|
609
|
+
|
|
610
|
+
return {
|
|
611
|
+
success: true,
|
|
612
|
+
totalIssues: fixes.length,
|
|
613
|
+
applied: appliedFixes.length,
|
|
614
|
+
failed: failedFixes.length,
|
|
615
|
+
fixes: appliedFixes,
|
|
616
|
+
failedFixes,
|
|
617
|
+
report
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
} catch (error) {
|
|
621
|
+
// Rollback on error
|
|
622
|
+
console.error(chalk.red('修复过程中发生错误,正在回滚...'))
|
|
623
|
+
await this.rollback()
|
|
624
|
+
throw error
|
|
625
|
+
}
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
/**
|
|
629
|
+
* Group fixes by file
|
|
630
|
+
*/
|
|
631
|
+
groupFixesByFile(fixes) {
|
|
632
|
+
const grouped = {}
|
|
633
|
+
|
|
634
|
+
for (const fix of fixes) {
|
|
635
|
+
if (!grouped[fix.file]) {
|
|
636
|
+
grouped[fix.file] = []
|
|
637
|
+
}
|
|
638
|
+
grouped[fix.file].push(fix)
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
return grouped
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
/**
|
|
645
|
+
* Apply fixes to a single file
|
|
646
|
+
*/
|
|
647
|
+
async applyFixesToFile(filePath, fixes) {
|
|
648
|
+
const originalContent = await fs.readFile(filePath, 'utf8')
|
|
649
|
+
let fixedContent = originalContent
|
|
650
|
+
const applied = []
|
|
651
|
+
const rollbackData = []
|
|
652
|
+
|
|
653
|
+
// Sort fixes by line number (descending to avoid offset issues)
|
|
654
|
+
const allChanges = []
|
|
655
|
+
for (const fix of fixes) {
|
|
656
|
+
for (const change of fix.changes) {
|
|
657
|
+
allChanges.push({
|
|
658
|
+
...change,
|
|
659
|
+
issueType: fix.issueType,
|
|
660
|
+
description: fix.description
|
|
661
|
+
})
|
|
662
|
+
}
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
allChanges.sort((a, b) => b.line - a.line)
|
|
666
|
+
|
|
667
|
+
// Apply changes
|
|
668
|
+
for (const change of allChanges) {
|
|
669
|
+
try {
|
|
670
|
+
const lines = fixedContent.split('\n')
|
|
671
|
+
|
|
672
|
+
if (change.line > 0 && change.line <= lines.length) {
|
|
673
|
+
const originalLine = lines[change.line - 1]
|
|
674
|
+
|
|
675
|
+
// Store for rollback
|
|
676
|
+
rollbackData.push({
|
|
677
|
+
file: filePath,
|
|
678
|
+
line: change.line,
|
|
679
|
+
original: originalLine
|
|
680
|
+
})
|
|
681
|
+
|
|
682
|
+
// Apply change
|
|
683
|
+
if (change.type === 'replace') {
|
|
684
|
+
lines[change.line - 1] = change.fixed
|
|
685
|
+
} else if (change.type === 'remove') {
|
|
686
|
+
lines.splice(change.line - 1, 1)
|
|
687
|
+
} else if (change.type === 'add') {
|
|
688
|
+
lines.splice(change.line - 1, 0, change.fixed)
|
|
689
|
+
}
|
|
690
|
+
|
|
691
|
+
fixedContent = lines.join('\n')
|
|
692
|
+
applied.push(change)
|
|
693
|
+
}
|
|
694
|
+
} catch (error) {
|
|
695
|
+
console.error(chalk.yellow(`应用变更失败: ${error.message}`))
|
|
696
|
+
}
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
// Write fixed content
|
|
700
|
+
await fs.writeFile(filePath, fixedContent, 'utf8')
|
|
701
|
+
|
|
702
|
+
return {
|
|
703
|
+
applied,
|
|
704
|
+
rollbackData
|
|
705
|
+
}
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
/**
|
|
709
|
+
* Create rollback point
|
|
710
|
+
*/
|
|
711
|
+
async createRollbackPoint() {
|
|
712
|
+
const timestamp = new Date().toISOString().replace(/[:.]/g, '-')
|
|
713
|
+
const rollbackDir = path.join('.rollback', timestamp)
|
|
714
|
+
|
|
715
|
+
await fs.ensureDir(rollbackDir)
|
|
716
|
+
this.rollbackStack.push(rollbackDir)
|
|
717
|
+
|
|
718
|
+
return rollbackDir
|
|
719
|
+
}
|
|
720
|
+
|
|
721
|
+
/**
|
|
722
|
+
* Rollback changes
|
|
723
|
+
*/
|
|
724
|
+
async rollback() {
|
|
725
|
+
if (this.rollbackStack.length === 0) {
|
|
726
|
+
console.log(chalk.yellow('没有可回滚的更改'))
|
|
727
|
+
return
|
|
728
|
+
}
|
|
729
|
+
|
|
730
|
+
const rollbackDir = this.rollbackStack.pop()
|
|
731
|
+
console.log(chalk.blue(`回滚到: ${rollbackDir}`))
|
|
732
|
+
// In a full implementation, this would restore files from rollbackDir
|
|
733
|
+
}
|
|
734
|
+
|
|
735
|
+
/**
|
|
736
|
+
* Generate fix report
|
|
737
|
+
*/
|
|
738
|
+
generateFixReport(result) {
|
|
739
|
+
const timestamp = new Date().toLocaleString()
|
|
740
|
+
|
|
741
|
+
let report = `# AI自动修复报告\n\n`
|
|
742
|
+
report += `**生成时间**: ${timestamp}\n`
|
|
743
|
+
report += `**执行模式**: 实际修复\n\n`
|
|
744
|
+
|
|
745
|
+
report += `## 统计\n\n`
|
|
746
|
+
report += `- 总问题数: ${result.totalFixes}\n`
|
|
747
|
+
report += `- 已修复: ${result.applied}\n`
|
|
748
|
+
report += `- 失败: ${result.failed}\n\n`
|
|
749
|
+
|
|
750
|
+
if (result.fixes.length > 0) {
|
|
751
|
+
report += `## 修复详情\n\n`
|
|
752
|
+
|
|
753
|
+
const fixesByFile = {}
|
|
754
|
+
for (const fix of result.fixes) {
|
|
755
|
+
if (!fixesByFile[fix.file]) {
|
|
756
|
+
fixesByFile[fix.file] = []
|
|
757
|
+
}
|
|
758
|
+
fixesByFile[fix.file].push(fix)
|
|
759
|
+
}
|
|
760
|
+
|
|
761
|
+
for (const [file, fileFixes] of Object.entries(fixesByFile)) {
|
|
762
|
+
report += `### ${file}\n\n`
|
|
763
|
+
|
|
764
|
+
fileFixes.forEach((fix, index) => {
|
|
765
|
+
report += `${index + 1}. **Line ${fix.line}** - ${fix.type}\n`
|
|
766
|
+
report += ` - 问题类型: ${fix.issueType}\n`
|
|
767
|
+
report += ` - 描述: ${fix.description}\n`
|
|
768
|
+
report += ` - 解释: ${fix.explanation}\n\n`
|
|
769
|
+
})
|
|
770
|
+
}
|
|
771
|
+
}
|
|
772
|
+
|
|
773
|
+
if (result.failedFixes.length > 0) {
|
|
774
|
+
report += `## 修复失败\n\n`
|
|
775
|
+
result.failedFixes.forEach(failed => {
|
|
776
|
+
report += `- **${failed.file}**: ${failed.error}\n`
|
|
777
|
+
})
|
|
778
|
+
report += `\n`
|
|
779
|
+
}
|
|
780
|
+
|
|
781
|
+
report += `---\n*由 Code-Simplifier AI 自动修复生成*\n`
|
|
782
|
+
|
|
783
|
+
return report
|
|
784
|
+
}
|
|
785
|
+
|
|
786
|
+
/**
|
|
787
|
+
* Save report to file
|
|
788
|
+
*/
|
|
789
|
+
async saveReport(report) {
|
|
790
|
+
const timestamp = new Date().toISOString().replace(/[:.]/g, '-')
|
|
791
|
+
const reportPath = path.join('reports', `ai-fix-report-${timestamp}.md`)
|
|
792
|
+
|
|
793
|
+
await fs.ensureDir(path.dirname(reportPath))
|
|
794
|
+
await fs.writeFile(reportPath, report, 'utf8')
|
|
795
|
+
|
|
796
|
+
console.log(chalk.blue(`报告已保存: ${reportPath}`))
|
|
797
|
+
|
|
798
|
+
return reportPath
|
|
799
|
+
}
|
|
800
|
+
|
|
801
|
+
/**
|
|
802
|
+
* Detect programming language from file extension
|
|
803
|
+
*/
|
|
804
|
+
detectLanguage(filePath) {
|
|
805
|
+
const ext = path.extname(filePath).toLowerCase()
|
|
806
|
+
|
|
807
|
+
const languageMap = {
|
|
808
|
+
'.js': 'javascript',
|
|
809
|
+
'.jsx': 'javascript',
|
|
810
|
+
'.ts': 'typescript',
|
|
811
|
+
'.tsx': 'typescript',
|
|
812
|
+
'.py': 'python',
|
|
813
|
+
'.java': 'java',
|
|
814
|
+
'.cs': 'csharp',
|
|
815
|
+
'.cpp': 'cpp',
|
|
816
|
+
'.c': 'c',
|
|
817
|
+
'.php': 'php',
|
|
818
|
+
'.rb': 'ruby',
|
|
819
|
+
'.go': 'go',
|
|
820
|
+
'.rs': 'rust',
|
|
821
|
+
'.swift': 'swift',
|
|
822
|
+
'.kt': 'kotlin'
|
|
823
|
+
}
|
|
824
|
+
|
|
825
|
+
return languageMap[ext] || 'text'
|
|
826
|
+
}
|
|
827
|
+
}
|
|
828
|
+
|
|
829
|
+
module.exports = new AIAutoFix()
|