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.
@@ -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()