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.
@@ -0,0 +1,705 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * AI-Powered Code Review Assistant
5
+ * Performs real-time code review with Git integration
6
+ * Checks security, performance, and maintainability issues
7
+ * Supports Git hooks for automated pre-commit review
8
+ */
9
+
10
+ const fs = require('fs-extra')
11
+ const path = require('path')
12
+ const chalk = require('chalk')
13
+ const { execSync } = require('child_process')
14
+ const AIQualityAnalyzer = require('./ai-quality-analyzer')
15
+ const { OpenAI } = require('openai')
16
+ const Anthropic = require('@anthropic-ai/sdk')
17
+
18
+ /**
19
+ * AI Code Review Assistant
20
+ */
21
+ class AICodeReview {
22
+ constructor(config = {}) {
23
+ this.config = {
24
+ reviewTimeout: config.reviewTimeout || 5000, // 5 seconds per file
25
+ blockOnSevere: config.blockOnSevere !== false,
26
+ severityThreshold: config.severityThreshold || 'medium',
27
+ enableGitHook: config.enableGitHook !== false,
28
+ reviewTypes: config.reviewTypes || [
29
+ 'security',
30
+ 'performance',
31
+ 'maintainability',
32
+ 'style',
33
+ 'best-practices',
34
+ 'documentation',
35
+ 'testing',
36
+ 'complexity',
37
+ 'naming',
38
+ 'architecture',
39
+ 'error-handling',
40
+ 'concurrency',
41
+ 'memory',
42
+ 'logic',
43
+ 'dependencies'
44
+ ],
45
+ openaiApiKey: config.openaiApiKey || process.env.OPENAI_API_KEY,
46
+ anthropicApiKey: config.anthropicApiKey || process.env.ANTHROPIC_API_KEY,
47
+ ...config
48
+ }
49
+
50
+ this.reviewResults = []
51
+ this.issues = []
52
+ this.stats = {
53
+ filesReviewed: 0,
54
+ issuesFound: 0,
55
+ severeIssues: 0,
56
+ reviewTime: 0
57
+ }
58
+
59
+ this.qualityAnalyzer = new AIQualityAnalyzer({
60
+ openaiApiKey: this.config.openaiApiKey,
61
+ anthropicApiKey: this.config.anthropicApiKey
62
+ })
63
+
64
+ this.openai = this.config.openaiApiKey ? new OpenAI({ apiKey: this.config.openaiApiKey }) : null
65
+ this.anthropic = this.config.anthropicApiKey ? new Anthropic({ apiKey: this.config.anthropicApiKey }) : null
66
+ }
67
+
68
+ /**
69
+ * Initialize Code Review Assistant
70
+ */
71
+ async initialize() {
72
+ console.log(chalk.blue('šŸ” Initializing AI Code Review Assistant...'))
73
+
74
+ if (this.config.enableGitHook) {
75
+ await this.setupGitHook()
76
+ console.log(chalk.green('āœ“ Git hook configured'))
77
+ }
78
+
79
+ console.log(chalk.green('āœ“ AI Code Review Assistant initialized'))
80
+ }
81
+
82
+ /**
83
+ * Perform code review on changed files
84
+ */
85
+ async reviewChanges(options = {}) {
86
+ const startTime = Date.now()
87
+
88
+ console.log(chalk.blue('\nšŸ” Starting AI Code Review...'))
89
+
90
+ try {
91
+ // Get changed files
92
+ const changedFiles = await this.getChangedFiles(options.files)
93
+ if (changedFiles.length === 0) {
94
+ console.log(chalk.yellow('No files to review'))
95
+ return this.generateReport()
96
+ }
97
+
98
+ console.log(chalk.blue(`Reviewing ${changedFiles.length} files...`))
99
+
100
+ // Review each file
101
+ for (const file of changedFiles) {
102
+ const fileStartTime = Date.now()
103
+ console.log(chalk.cyan(` šŸ“„ Reviewing: ${file}`))
104
+
105
+ const fileResult = await this.reviewFile(file, options)
106
+
107
+ this.reviewResults.push(fileResult)
108
+ this.stats.filesReviewed++
109
+
110
+ const fileTime = Date.now() - fileStartTime
111
+ if (fileTime > this.config.reviewTimeout) {
112
+ console.log(chalk.yellow(` ⚠ Review timeout exceeded for ${file}`))
113
+ }
114
+ }
115
+
116
+ // Analyze overall results
117
+ this.analyzeResults()
118
+
119
+ this.stats.reviewTime = Date.now() - startTime
120
+
121
+ // Generate report
122
+ const report = this.generateReport()
123
+
124
+ // Display results
125
+ this.displayResults(report)
126
+
127
+ // Return whether to block commit
128
+ return {
129
+ shouldBlock: this.shouldBlock(),
130
+ report: report
131
+ }
132
+
133
+ } catch (error) {
134
+ console.error(chalk.red('āŒ Code review failed:'), error.message)
135
+ throw error
136
+ }
137
+ }
138
+
139
+ /**
140
+ * Review a single file
141
+ */
142
+ async reviewFile(filePath, options = {}) {
143
+ try {
144
+ // Read file content
145
+ const content = await fs.readFile(filePath, 'utf8')
146
+ const language = this.detectLanguage(filePath)
147
+
148
+ // Get Git diff for the file
149
+ const diff = await this.getFileDiff(filePath)
150
+
151
+ // Analyze with quality analyzer
152
+ const analysis = await this.qualityAnalyzer.analyzeCode(filePath, {
153
+ language: language,
154
+ includeTests: options.includeTests || false
155
+ })
156
+
157
+ // Get AI review
158
+ const aiReview = await this.generateAIReview(filePath, content, diff, language)
159
+
160
+ // Combine results
161
+ const fileResult = {
162
+ filePath: filePath,
163
+ language: language,
164
+ timestamp: new Date().toISOString(),
165
+ issues: analysis.files?.[0]?.issues || [],
166
+ aiReview: aiReview,
167
+ summary: this.summarizeFile(filePath, analysis, aiReview)
168
+ }
169
+
170
+ // Update stats
171
+ this.stats.issuesFound += fileResult.issues.length
172
+ this.stats.severeIssues += fileResult.issues.filter(i => i.severity === 'error').length
173
+
174
+ return fileResult
175
+
176
+ } catch (error) {
177
+ console.warn(chalk.yellow(` ⚠ Failed to review ${filePath}: ${error.message}`))
178
+ return {
179
+ filePath: filePath,
180
+ error: error.message,
181
+ timestamp: new Date().toISOString()
182
+ }
183
+ }
184
+ }
185
+
186
+ /**
187
+ * Generate AI-powered review
188
+ */
189
+ async generateAIReview(filePath, content, diff, language) {
190
+ const prompt = `
191
+ As an expert code reviewer, analyze this ${language} code and provide feedback:
192
+
193
+ File: ${filePath}
194
+ Language: ${language}
195
+
196
+ Code:
197
+ ${content.substring(0, 2000)}...
198
+
199
+ Git Diff:
200
+ ${diff || 'No diff available'}
201
+
202
+ Provide a comprehensive review covering:
203
+ 1. Security issues (vulnerabilities, injection risks, etc.)
204
+ 2. Performance problems (inefficiencies, bottlenecks)
205
+ 3. Maintainability issues (complexity, readability)
206
+ 4. Best practices violations
207
+ 5. Architectural concerns
208
+ 6. Testing gaps
209
+
210
+ Rate each issue by severity: critical, high, medium, low
211
+
212
+ Return JSON format:
213
+ {
214
+ "overallScore": 0-100,
215
+ "summary": "Brief summary",
216
+ "issues": [
217
+ {
218
+ "type": "security|performance|maintainability|...",
219
+ "severity": "critical|high|medium|low",
220
+ "line": 0,
221
+ "description": "Issue description",
222
+ "recommendation": "How to fix",
223
+ "impact": "Why this matters"
224
+ }
225
+ ],
226
+ "strengths": ["What the code does well"],
227
+ "recommendations": ["General improvement suggestions"]
228
+ }
229
+ `
230
+
231
+ try {
232
+ const response = await this.generateWithAI(prompt)
233
+ return JSON.parse(response)
234
+ } catch (error) {
235
+ console.warn(chalk.yellow(` ⚠ AI review failed for ${filePath}, using fallback`))
236
+ return this.generateFallbackReview(content, language)
237
+ }
238
+ }
239
+
240
+ /**
241
+ * Generate fallback review when AI is unavailable
242
+ */
243
+ generateFallbackReview(content, language) {
244
+ const issues = []
245
+
246
+ // Basic checks
247
+ if (content.includes('console.log')) {
248
+ issues.push({
249
+ type: 'style',
250
+ severity: 'low',
251
+ line: this.findLine(content, 'console.log'),
252
+ description: 'Console.log statement found',
253
+ recommendation: 'Remove console.log or use proper logging',
254
+ impact: 'Debug statements should not be in production code'
255
+ })
256
+ }
257
+
258
+ if (language === 'javascript' || language === 'typescript') {
259
+ if (content.includes('var ')) {
260
+ issues.push({
261
+ type: 'style',
262
+ severity: 'medium',
263
+ line: this.findLine(content, 'var '),
264
+ description: 'Using var instead of let/const',
265
+ recommendation: 'Use let or const for block-scoped variables',
266
+ impact: 'var has function scope and can cause bugs'
267
+ })
268
+ }
269
+
270
+ // Check for TODO/FIXME
271
+ const todoMatch = content.match(/TODO|FIXME/gi)
272
+ if (todoMatch) {
273
+ issues.push({
274
+ type: 'maintainability',
275
+ severity: 'low',
276
+ line: this.findLine(content, todoMatch[0]),
277
+ description: 'TODO/FIXME comment found',
278
+ recommendation: 'Address or track TODO items',
279
+ impact: 'Incomplete code should be tracked'
280
+ })
281
+ }
282
+ }
283
+
284
+ return {
285
+ overallScore: Math.max(50, 100 - issues.length * 10),
286
+ summary: `Basic review completed. Found ${issues.length} issues.`,
287
+ issues: issues,
288
+ strengths: ['Code structure is present'],
289
+ recommendations: ['Use AI-powered review for detailed analysis']
290
+ }
291
+ }
292
+
293
+ /**
294
+ * Get changed files from Git
295
+ */
296
+ async getChangedFiles(specificFiles) {
297
+ if (specificFiles && specificFiles.length > 0) {
298
+ return specificFiles.filter(f => fs.existsSync(f))
299
+ }
300
+
301
+ try {
302
+ // Get staged files
303
+ const staged = execSync('git diff --cached --name-only', { encoding: 'utf8' }).trim()
304
+ const stagedFiles = staged ? staged.split('\n') : []
305
+
306
+ // Get unstaged changes
307
+ const unstaged = execSync('git diff --name-only', { encoding: 'utf8' }).trim()
308
+ const unstagedFiles = unstaged ? unstaged.split('\n') : []
309
+
310
+ // Get untracked files
311
+ const untracked = execSync('git ls-files --others --exclude-standard', { encoding: 'utf8' }).trim()
312
+ const untrackedFiles = untracked ? untracked.split('\n') : []
313
+
314
+ // Combine all
315
+ const allFiles = [...stagedFiles, ...unstagedFiles, ...untrackedFiles]
316
+
317
+ // Filter for code files
318
+ return allFiles.filter(f => this.isCodeFile(f))
319
+ } catch (error) {
320
+ console.warn(chalk.yellow('⚠ Not a Git repository or Git command failed'))
321
+ return []
322
+ }
323
+ }
324
+
325
+ /**
326
+ * Get file diff from Git
327
+ */
328
+ async getFileDiff(filePath) {
329
+ try {
330
+ const diff = execSync(`git diff ${filePath}`, { encoding: 'utf8' })
331
+ return diff
332
+ } catch (error) {
333
+ return null
334
+ }
335
+ }
336
+
337
+ /**
338
+ * Detect programming language from file extension
339
+ */
340
+ detectLanguage(filePath) {
341
+ const ext = path.extname(filePath).toLowerCase()
342
+ const langMap = {
343
+ '.js': 'javascript',
344
+ '.jsx': 'javascript',
345
+ '.ts': 'typescript',
346
+ '.tsx': 'typescript',
347
+ '.py': 'python',
348
+ '.java': 'java',
349
+ '.cs': 'csharp',
350
+ '.cpp': 'cpp',
351
+ '.c': 'c',
352
+ '.h': 'c',
353
+ '.hpp': 'cpp',
354
+ '.cc': 'cpp',
355
+ '.php': 'php',
356
+ '.rb': 'ruby',
357
+ '.go': 'go',
358
+ '.rs': 'rust',
359
+ '.swift': 'swift',
360
+ '.kt': 'kotlin',
361
+ '.scala': 'scala',
362
+ '.sql': 'sql',
363
+ '.html': 'html',
364
+ '.css': 'css',
365
+ '.scss': 'scss',
366
+ '.vue': 'vue'
367
+ }
368
+ return langMap[ext] || 'unknown'
369
+ }
370
+
371
+ /**
372
+ * Check if file is a code file
373
+ */
374
+ isCodeFile(filePath) {
375
+ if (!filePath || filePath.startsWith('.git')) return false
376
+
377
+ const ext = path.extname(filePath).toLowerCase()
378
+ const codeExtensions = [
379
+ '.js', '.jsx', '.ts', '.tsx',
380
+ '.py', '.java', '.cs',
381
+ '.cpp', '.c', '.h', '.hpp', '.cc',
382
+ '.php', '.rb', '.go', '.rs',
383
+ '.swift', '.kt', '.scala',
384
+ '.vue', '.html', '.css', '.scss',
385
+ '.sql', '.json', '.yaml', '.yml'
386
+ ]
387
+
388
+ return codeExtensions.includes(ext)
389
+ }
390
+
391
+ /**
392
+ * Analyze review results
393
+ */
394
+ analyzeResults() {
395
+ this.issues = this.reviewResults.flatMap(r => r.issues || [])
396
+
397
+ // Calculate overall score
398
+ const totalIssues = this.issues.length
399
+ const severeCount = this.issues.filter(i => i.severity === 'error').length
400
+
401
+ this.overallScore = Math.max(0, 100 - (totalIssues * 2) - (severeCount * 5))
402
+ }
403
+
404
+ /**
405
+ * Summarize file review
406
+ */
407
+ summarizeFile(filePath, analysis, aiReview) {
408
+ const issueCount = analysis.files?.[0]?.issues?.length || 0
409
+ const aiScore = aiReview?.overallScore || 0
410
+ const severeCount = analysis.files?.[0]?.issues?.filter(i => i.severity === 'error').length || 0
411
+
412
+ return {
413
+ issueCount: issueCount,
414
+ aiScore: aiScore,
415
+ status: issueCount === 0 ? 'clean' : issueCount > 10 ? 'needs-work' : 'acceptable',
416
+ recommendation: issueCount === 0 ? 'Ready to commit' :
417
+ severeCount > 0 ? 'Fix severe issues before commit' :
418
+ 'Review and fix issues before commit'
419
+ }
420
+ }
421
+
422
+ /**
423
+ * Determine if commit should be blocked
424
+ */
425
+ shouldBlock() {
426
+ if (!this.config.blockOnSevere) return false
427
+
428
+ // Block if there are critical or high severity issues
429
+ const severeIssues = this.issues.filter(i =>
430
+ i.severity === 'error' || i.severity === 'warning'
431
+ )
432
+
433
+ return severeIssues.length > 0
434
+ }
435
+
436
+ /**
437
+ * Generate comprehensive review report
438
+ */
439
+ generateReport() {
440
+ const report = {
441
+ timestamp: new Date().toISOString(),
442
+ summary: {
443
+ filesReviewed: this.stats.filesReviewed,
444
+ issuesFound: this.stats.issuesFound,
445
+ severeIssues: this.stats.severeIssues,
446
+ reviewTime: this.stats.reviewTime,
447
+ overallScore: this.overallScore,
448
+ shouldBlock: this.shouldBlock()
449
+ },
450
+ issues: this.issues,
451
+ files: this.reviewResults,
452
+ recommendations: this.generateRecommendations()
453
+ }
454
+
455
+ return report
456
+ }
457
+
458
+ /**
459
+ * Generate improvement recommendations
460
+ */
461
+ generateRecommendations() {
462
+ const recommendations = []
463
+
464
+ // Count issues by type
465
+ const issueTypes = {}
466
+ this.issues.forEach(issue => {
467
+ issueTypes[issue.type] = (issueTypes[issue.type] || 0) + 1
468
+ })
469
+
470
+ // Generate type-specific recommendations
471
+ Object.entries(issueTypes).forEach(([type, count]) => {
472
+ switch (type) {
473
+ case 'security':
474
+ recommendations.push(`Security: Found ${count} security issues. Review and fix immediately.`)
475
+ break
476
+ case 'performance':
477
+ recommendations.push(`Performance: Found ${count} performance issues. Optimize for better efficiency.`)
478
+ break
479
+ case 'maintainability':
480
+ recommendations.push(`Maintainability: Found ${count} maintainability issues. Improve code structure.`)
481
+ break
482
+ default:
483
+ recommendations.push(`${type}: Found ${count} issues. Review and address.`)
484
+ }
485
+ })
486
+
487
+ return recommendations
488
+ }
489
+
490
+ /**
491
+ * Display review results in terminal
492
+ */
493
+ displayResults(report) {
494
+ console.log(chalk.blue('\n' + '='.repeat(60)))
495
+ console.log(chalk.blue('šŸ“‹ CODE REVIEW REPORT'))
496
+ console.log(chalk.blue('='.repeat(60)))
497
+
498
+ // Summary
499
+ console.log(chalk.cyan('\nšŸ“Š Summary:'))
500
+ console.log(` Files Reviewed: ${report.summary.filesReviewed}`)
501
+ console.log(` Issues Found: ${report.summary.issuesFound}`)
502
+ console.log(` Severe Issues: ${report.summary.severeIssues}`)
503
+ console.log(` Overall Score: ${report.summary.overallScore}/100`)
504
+ console.log(` Review Time: ${(report.summary.reviewTime / 1000).toFixed(2)}s`)
505
+
506
+ if (report.summary.shouldBlock) {
507
+ console.log(chalk.red('\nā›” COMMIT BLOCKED: Severe issues found'))
508
+ } else {
509
+ console.log(chalk.green('\nāœ… Commit approved (no blocking issues)'))
510
+ }
511
+
512
+ // Issues by severity
513
+ const critical = report.issues.filter(i => i.severity === 'error')
514
+ const warnings = report.issues.filter(i => i.severity === 'warning')
515
+
516
+ if (critical.length > 0) {
517
+ console.log(chalk.red(`\nāŒ Critical Issues (${critical.length}):`))
518
+ critical.slice(0, 10).forEach(issue => {
519
+ console.log(chalk.red(` • ${issue.filePath || ''}:${issue.line || '?'} - ${issue.description}`))
520
+ })
521
+ }
522
+
523
+ if (warnings.length > 0) {
524
+ console.log(chalk.yellow(`\nāš ļø Warnings (${warnings.length}):`))
525
+ warnings.slice(0, 10).forEach(issue => {
526
+ console.log(chalk.yellow(` • ${issue.filePath || ''}:${issue.line || '?'} - ${issue.description}`))
527
+ })
528
+ }
529
+
530
+ // Recommendations
531
+ if (report.recommendations.length > 0) {
532
+ console.log(chalk.cyan('\nšŸ’” Recommendations:'))
533
+ report.recommendations.forEach(rec => {
534
+ console.log(` • ${rec}`)
535
+ })
536
+ }
537
+
538
+ console.log(chalk.blue('\n' + '='.repeat(60)))
539
+ }
540
+
541
+ /**
542
+ * Setup Git pre-commit hook
543
+ */
544
+ async setupGitHook() {
545
+ const hookPath = path.join(process.cwd(), '.git', 'hooks', 'pre-commit')
546
+
547
+ const hookContent = `#!/bin/bash
548
+ # AI Code Review Pre-Commit Hook
549
+ # Generated by Code-Simplifier
550
+
551
+ echo "šŸ” Running AI Code Review..."
552
+
553
+ # Run code review
554
+ node -e "
555
+ const AICodeReview = require('./lib/ai-code-review');
556
+ const review = new AICodeReview({ blockOnSevere: true });
557
+ review.initialize().then(() => {
558
+ return review.reviewChanges();
559
+ }).then(result => {
560
+ if (result.shouldBlock) {
561
+ console.error('\\nā›” Code review failed. Commit blocked.');
562
+ console.error('Fix the issues above and try again.');
563
+ process.exit(1);
564
+ }
565
+ }).catch(err => {
566
+ console.error('Code review error:', err.message);
567
+ process.exit(1);
568
+ });
569
+ "
570
+
571
+ # Check exit code
572
+ if [ $? -ne 0 ]; then
573
+ exit 1
574
+ fi
575
+ `
576
+
577
+ try {
578
+ await fs.writeFile(hookPath, hookContent)
579
+ await fs.chmod(hookPath, 0o755)
580
+ console.log(chalk.green('āœ“ Git pre-commit hook installed'))
581
+ } catch (error) {
582
+ console.warn(chalk.yellow(`⚠ Could not install Git hook: ${error.message}`))
583
+ }
584
+ }
585
+
586
+ /**
587
+ * Export report to file
588
+ */
589
+ async exportReport(report, format = 'json', outputPath) {
590
+ if (!outputPath) {
591
+ const timestamp = new Date().toISOString().replace(/[:.]/g, '-')
592
+ outputPath = path.join(process.cwd(), `code-review-report-${timestamp}.${format}`)
593
+ }
594
+
595
+ if (format === 'json') {
596
+ await fs.writeJson(outputPath, report, { spaces: 2 })
597
+ } else if (format === 'markdown') {
598
+ const markdown = this.generateMarkdownReport(report)
599
+ await fs.writeFile(outputPath, markdown)
600
+ }
601
+
602
+ console.log(chalk.green(`āœ“ Report exported to: ${outputPath}`))
603
+ }
604
+
605
+ /**
606
+ * Generate Markdown report
607
+ */
608
+ generateMarkdownReport(report) {
609
+ let markdown = `# Code Review Report
610
+
611
+ Generated: ${report.timestamp}
612
+
613
+ ## Summary
614
+
615
+ - **Files Reviewed:** ${report.summary.filesReviewed}
616
+ - **Issues Found:** ${report.summary.issuesFound}
617
+ - **Severe Issues:** ${report.summary.severeIssues}
618
+ - **Overall Score:** ${report.summary.overallScore}/100
619
+ - **Review Time:** ${(report.summary.reviewTime / 1000).toFixed(2)}s
620
+
621
+ ## Issues
622
+
623
+ `
624
+
625
+ report.issues.forEach(issue => {
626
+ markdown += `### ${issue.description}\n\n`
627
+ markdown += `- **File:** ${issue.filePath || 'N/A'}\n`
628
+ markdown += `- **Line:** ${issue.line || 'N/A'}\n`
629
+ markdown += `- **Severity:** ${issue.severity}\n`
630
+ markdown += `- **Type:** ${issue.type}\n`
631
+ markdown += `- **Recommendation:** ${issue.recommendation}\n\n`
632
+ })
633
+
634
+ return markdown
635
+ }
636
+
637
+ /**
638
+ * Find line number of a pattern in content
639
+ */
640
+ findLine(content, pattern) {
641
+ const lines = content.split('\n')
642
+ for (let i = 0; i < lines.length; i++) {
643
+ if (lines[i].includes(pattern)) {
644
+ return i + 1
645
+ }
646
+ }
647
+ return 0
648
+ }
649
+
650
+ /**
651
+ * Generate with AI (OpenAI or Anthropic)
652
+ */
653
+ async generateWithAI(prompt) {
654
+ // Try OpenAI first
655
+ if (this.openai) {
656
+ try {
657
+ const response = await this.openai.chat.completions.create({
658
+ model: 'gpt-4',
659
+ messages: [{ role: 'user', content: prompt }],
660
+ temperature: 0.7,
661
+ max_tokens: 2000
662
+ })
663
+ return response.choices[0].message.content
664
+ } catch (error) {
665
+ console.warn(chalk.yellow('OpenAI failed, trying Anthropic...'))
666
+ }
667
+ }
668
+
669
+ // Try Anthropic
670
+ if (this.anthropic) {
671
+ try {
672
+ const response = await this.anthropic.messages.create({
673
+ model: 'claude-3-5-sonnet-20241022',
674
+ max_tokens: 2000,
675
+ messages: [{ role: 'user', content: prompt }]
676
+ })
677
+ return response.content[0].text
678
+ } catch (error) {
679
+ console.warn(chalk.yellow('Anthropic failed, using mock mode'))
680
+ }
681
+ }
682
+
683
+ // Mock mode
684
+ return JSON.stringify({
685
+ overallScore: 85,
686
+ summary: 'Code review completed with no major issues.',
687
+ issues: [],
688
+ strengths: ['Clean code structure', 'Good naming conventions'],
689
+ recommendations: ['Consider adding more tests', 'Review documentation coverage']
690
+ })
691
+ }
692
+
693
+ /**
694
+ * Get review statistics
695
+ */
696
+ getStats() {
697
+ return {
698
+ ...this.stats,
699
+ overallScore: this.overallScore,
700
+ shouldBlock: this.shouldBlock()
701
+ }
702
+ }
703
+ }
704
+
705
+ module.exports = AICodeReview