@nclamvn/vibecode-cli 1.5.0 → 1.7.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.
Files changed (77) hide show
  1. package/.vibecode/learning/fixes.json +1 -0
  2. package/.vibecode/learning/preferences.json +1 -0
  3. package/bin/vibecode.js +86 -3
  4. package/docs-site/README.md +41 -0
  5. package/docs-site/blog/2019-05-28-first-blog-post.md +12 -0
  6. package/docs-site/blog/2019-05-29-long-blog-post.md +44 -0
  7. package/docs-site/blog/2021-08-01-mdx-blog-post.mdx +24 -0
  8. package/docs-site/blog/2021-08-26-welcome/docusaurus-plushie-banner.jpeg +0 -0
  9. package/docs-site/blog/2021-08-26-welcome/index.md +29 -0
  10. package/docs-site/blog/authors.yml +25 -0
  11. package/docs-site/blog/tags.yml +19 -0
  12. package/docs-site/docs/commands/agent.md +162 -0
  13. package/docs-site/docs/commands/assist.md +71 -0
  14. package/docs-site/docs/commands/build.md +53 -0
  15. package/docs-site/docs/commands/config.md +30 -0
  16. package/docs-site/docs/commands/debug.md +173 -0
  17. package/docs-site/docs/commands/doctor.md +34 -0
  18. package/docs-site/docs/commands/go.md +128 -0
  19. package/docs-site/docs/commands/index.md +79 -0
  20. package/docs-site/docs/commands/init.md +42 -0
  21. package/docs-site/docs/commands/learn.md +82 -0
  22. package/docs-site/docs/commands/lock.md +33 -0
  23. package/docs-site/docs/commands/plan.md +29 -0
  24. package/docs-site/docs/commands/review.md +31 -0
  25. package/docs-site/docs/commands/snapshot.md +34 -0
  26. package/docs-site/docs/commands/start.md +32 -0
  27. package/docs-site/docs/commands/status.md +37 -0
  28. package/docs-site/docs/commands/undo.md +83 -0
  29. package/docs-site/docs/configuration.md +72 -0
  30. package/docs-site/docs/faq.md +83 -0
  31. package/docs-site/docs/getting-started.md +119 -0
  32. package/docs-site/docs/guides/agent-mode.md +94 -0
  33. package/docs-site/docs/guides/debug-mode.md +83 -0
  34. package/docs-site/docs/guides/magic-mode.md +107 -0
  35. package/docs-site/docs/installation.md +98 -0
  36. package/docs-site/docs/intro.md +67 -0
  37. package/docs-site/docusaurus.config.ts +141 -0
  38. package/docs-site/package-lock.json +18039 -0
  39. package/docs-site/package.json +48 -0
  40. package/docs-site/sidebars.ts +70 -0
  41. package/docs-site/src/components/HomepageFeatures/index.tsx +72 -0
  42. package/docs-site/src/components/HomepageFeatures/styles.module.css +16 -0
  43. package/docs-site/src/css/custom.css +30 -0
  44. package/docs-site/src/pages/index.module.css +23 -0
  45. package/docs-site/src/pages/index.tsx +44 -0
  46. package/docs-site/src/pages/markdown-page.md +7 -0
  47. package/docs-site/src/theme/Footer/index.tsx +127 -0
  48. package/docs-site/src/theme/Footer/styles.module.css +285 -0
  49. package/docs-site/static/.nojekyll +0 -0
  50. package/docs-site/static/img/docusaurus-social-card.jpg +0 -0
  51. package/docs-site/static/img/docusaurus.png +0 -0
  52. package/docs-site/static/img/favicon.ico +0 -0
  53. package/docs-site/static/img/logo.svg +1 -0
  54. package/docs-site/static/img/undraw_docusaurus_mountain.svg +171 -0
  55. package/docs-site/static/img/undraw_docusaurus_react.svg +170 -0
  56. package/docs-site/static/img/undraw_docusaurus_tree.svg +40 -0
  57. package/docs-site/tsconfig.json +8 -0
  58. package/package.json +5 -2
  59. package/src/agent/orchestrator.js +104 -35
  60. package/src/commands/build.js +13 -3
  61. package/src/commands/debug.js +109 -1
  62. package/src/commands/git.js +923 -0
  63. package/src/commands/go.js +9 -2
  64. package/src/commands/learn.js +294 -0
  65. package/src/commands/shell.js +486 -0
  66. package/src/commands/undo.js +281 -0
  67. package/src/commands/watch.js +556 -0
  68. package/src/commands/wizard.js +322 -0
  69. package/src/core/backup.js +325 -0
  70. package/src/core/learning.js +295 -0
  71. package/src/debug/image-analyzer.js +304 -0
  72. package/src/debug/index.js +30 -1
  73. package/src/index.js +50 -0
  74. package/src/ui/__tests__/error-translator.test.js +390 -0
  75. package/src/ui/dashboard.js +364 -0
  76. package/src/ui/error-translator.js +775 -0
  77. package/src/utils/image.js +222 -0
@@ -0,0 +1,295 @@
1
+ // ═══════════════════════════════════════════════════════════════════════════════
2
+ // VIBECODE CLI - Learning Engine
3
+ // Phase H5: AI learns from user feedback
4
+ // ═══════════════════════════════════════════════════════════════════════════════
5
+
6
+ import fs from 'fs/promises';
7
+ import path from 'path';
8
+ import os from 'os';
9
+
10
+ const LEARNING_DIR = '.vibecode/learning';
11
+ const GLOBAL_LEARNING_DIR = path.join(os.homedir(), '.vibecode/learning');
12
+
13
+ /**
14
+ * Learning Engine - Records and retrieves learning data
15
+ *
16
+ * Features:
17
+ * - Records fix attempts and outcomes
18
+ * - Stores user preferences
19
+ * - Provides suggestions based on past successes
20
+ * - Anonymizes data for global storage
21
+ */
22
+ export class LearningEngine {
23
+ constructor(projectPath = process.cwd()) {
24
+ this.projectPath = projectPath;
25
+ this.localPath = path.join(projectPath, LEARNING_DIR);
26
+ this.globalPath = GLOBAL_LEARNING_DIR;
27
+ }
28
+
29
+ /**
30
+ * Initialize learning directories
31
+ */
32
+ async init() {
33
+ await fs.mkdir(this.localPath, { recursive: true });
34
+ await fs.mkdir(this.globalPath, { recursive: true });
35
+ }
36
+
37
+ /**
38
+ * Record a fix attempt and its outcome
39
+ */
40
+ async recordFix(fixData) {
41
+ await this.init();
42
+
43
+ const record = {
44
+ id: Date.now().toString(36),
45
+ timestamp: new Date().toISOString(),
46
+ errorType: fixData.errorType,
47
+ errorMessage: fixData.errorMessage?.substring(0, 200),
48
+ errorCategory: fixData.errorCategory,
49
+ fixApplied: fixData.fixApplied?.substring(0, 500),
50
+ success: fixData.success,
51
+ userFeedback: fixData.userFeedback,
52
+ userCorrection: fixData.userCorrection,
53
+ projectType: await this.detectProjectType(),
54
+ tags: fixData.tags || []
55
+ };
56
+
57
+ // Save to local project
58
+ const localFile = path.join(this.localPath, 'fixes.json');
59
+ const localFixes = await this.loadJson(localFile, []);
60
+ localFixes.push(record);
61
+ await this.saveJson(localFile, localFixes.slice(-100)); // Keep last 100
62
+
63
+ // Save to global (anonymized)
64
+ const globalFile = path.join(this.globalPath, 'fixes.json');
65
+ const globalFixes = await this.loadJson(globalFile, []);
66
+ globalFixes.push({
67
+ ...record,
68
+ errorMessage: this.anonymize(record.errorMessage),
69
+ fixApplied: this.anonymize(record.fixApplied)
70
+ });
71
+ await this.saveJson(globalFile, globalFixes.slice(-500)); // Keep last 500
72
+
73
+ return record.id;
74
+ }
75
+
76
+ /**
77
+ * Record user preference
78
+ */
79
+ async recordPreference(key, value, context = {}) {
80
+ await this.init();
81
+
82
+ const prefsFile = path.join(this.localPath, 'preferences.json');
83
+ const prefs = await this.loadJson(prefsFile, {});
84
+
85
+ if (!prefs[key]) {
86
+ prefs[key] = { values: [], contexts: [] };
87
+ }
88
+
89
+ prefs[key].values.push(value);
90
+ prefs[key].contexts.push(context);
91
+ prefs[key].lastUsed = new Date().toISOString();
92
+
93
+ // Keep only recent values
94
+ prefs[key].values = prefs[key].values.slice(-20);
95
+ prefs[key].contexts = prefs[key].contexts.slice(-20);
96
+
97
+ await this.saveJson(prefsFile, prefs);
98
+ }
99
+
100
+ /**
101
+ * Get suggestion based on learnings
102
+ */
103
+ async getSuggestion(errorType, errorCategory) {
104
+ const localFixes = await this.loadJson(
105
+ path.join(this.localPath, 'fixes.json'),
106
+ []
107
+ );
108
+ const globalFixes = await this.loadJson(
109
+ path.join(this.globalPath, 'fixes.json'),
110
+ []
111
+ );
112
+
113
+ // Find similar successful fixes
114
+ const allFixes = [...localFixes, ...globalFixes];
115
+ const similarFixes = allFixes.filter(f =>
116
+ f.success &&
117
+ (f.errorType === errorType || f.errorCategory === errorCategory)
118
+ );
119
+
120
+ if (similarFixes.length === 0) {
121
+ return null;
122
+ }
123
+
124
+ // Calculate confidence based on success rate
125
+ const totalSimilar = allFixes.filter(f =>
126
+ f.errorType === errorType || f.errorCategory === errorCategory
127
+ ).length;
128
+
129
+ const successRate = similarFixes.length / totalSimilar;
130
+
131
+ // Get most recent successful fix
132
+ const recentFix = similarFixes.sort((a, b) =>
133
+ new Date(b.timestamp) - new Date(a.timestamp)
134
+ )[0];
135
+
136
+ return {
137
+ suggestion: recentFix.fixApplied,
138
+ confidence: successRate,
139
+ basedOn: similarFixes.length,
140
+ lastUsed: recentFix.timestamp
141
+ };
142
+ }
143
+
144
+ /**
145
+ * Get user preference
146
+ */
147
+ async getPreference(key, defaultValue = null) {
148
+ const prefsFile = path.join(this.localPath, 'preferences.json');
149
+ const prefs = await this.loadJson(prefsFile, {});
150
+
151
+ if (!prefs[key] || prefs[key].values.length === 0) {
152
+ return defaultValue;
153
+ }
154
+
155
+ // Return most common value
156
+ const counts = {};
157
+ for (const v of prefs[key].values) {
158
+ counts[v] = (counts[v] || 0) + 1;
159
+ }
160
+
161
+ const sorted = Object.entries(counts).sort((a, b) => b[1] - a[1]);
162
+ return sorted[0][0];
163
+ }
164
+
165
+ /**
166
+ * Get learning statistics
167
+ */
168
+ async getStats() {
169
+ const localFixes = await this.loadJson(
170
+ path.join(this.localPath, 'fixes.json'),
171
+ []
172
+ );
173
+ const globalFixes = await this.loadJson(
174
+ path.join(this.globalPath, 'fixes.json'),
175
+ []
176
+ );
177
+ const prefs = await this.loadJson(
178
+ path.join(this.localPath, 'preferences.json'),
179
+ {}
180
+ );
181
+
182
+ const localSuccess = localFixes.filter(f => f.success).length;
183
+ const globalSuccess = globalFixes.filter(f => f.success).length;
184
+
185
+ // Group by error category
186
+ const byCategory = {};
187
+ for (const fix of localFixes) {
188
+ const cat = fix.errorCategory || 'unknown';
189
+ if (!byCategory[cat]) {
190
+ byCategory[cat] = { total: 0, success: 0 };
191
+ }
192
+ byCategory[cat].total++;
193
+ if (fix.success) byCategory[cat].success++;
194
+ }
195
+
196
+ return {
197
+ local: {
198
+ total: localFixes.length,
199
+ success: localSuccess,
200
+ rate: localFixes.length > 0 ? (localSuccess / localFixes.length * 100).toFixed(1) : '0'
201
+ },
202
+ global: {
203
+ total: globalFixes.length,
204
+ success: globalSuccess,
205
+ rate: globalFixes.length > 0 ? (globalSuccess / globalFixes.length * 100).toFixed(1) : '0'
206
+ },
207
+ byCategory,
208
+ preferences: Object.keys(prefs).length,
209
+ lastLearning: localFixes[localFixes.length - 1]?.timestamp || null
210
+ };
211
+ }
212
+
213
+ /**
214
+ * Detect project type
215
+ */
216
+ async detectProjectType() {
217
+ try {
218
+ const pkgPath = path.join(this.projectPath, 'package.json');
219
+ const pkg = JSON.parse(await fs.readFile(pkgPath, 'utf-8'));
220
+
221
+ if (pkg.dependencies?.next) return 'nextjs';
222
+ if (pkg.dependencies?.react) return 'react';
223
+ if (pkg.dependencies?.vue) return 'vue';
224
+ if (pkg.dependencies?.express) return 'express';
225
+ if (pkg.dependencies?.['@prisma/client']) return 'prisma';
226
+
227
+ return 'node';
228
+ } catch {
229
+ return 'unknown';
230
+ }
231
+ }
232
+
233
+ /**
234
+ * Anonymize sensitive data for global storage
235
+ */
236
+ anonymize(text) {
237
+ if (!text) return text;
238
+ return text
239
+ .replace(/\/Users\/[^\/\s]+/g, '/Users/***')
240
+ .replace(/\/home\/[^\/\s]+/g, '/home/***')
241
+ .replace(/C:\\Users\\[^\\]+/g, 'C:\\Users\\***')
242
+ .replace(/[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}/g, '***@***.***')
243
+ .replace(/\b\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\b/g, '***.***.***.***')
244
+ .replace(/api[_-]?key[=:]\s*["']?[\w-]+["']?/gi, 'api_key=***')
245
+ .replace(/token[=:]\s*["']?[\w-]+["']?/gi, 'token=***')
246
+ .replace(/password[=:]\s*["']?[^"'\s]+["']?/gi, 'password=***');
247
+ }
248
+
249
+ /**
250
+ * Clear all local learnings
251
+ */
252
+ async clearLocal() {
253
+ await this.saveJson(path.join(this.localPath, 'fixes.json'), []);
254
+ await this.saveJson(path.join(this.localPath, 'preferences.json'), {});
255
+ }
256
+
257
+ /**
258
+ * Load JSON file
259
+ */
260
+ async loadJson(filePath, defaultValue) {
261
+ try {
262
+ const content = await fs.readFile(filePath, 'utf-8');
263
+ return JSON.parse(content);
264
+ } catch {
265
+ return defaultValue;
266
+ }
267
+ }
268
+
269
+ /**
270
+ * Save JSON file
271
+ */
272
+ async saveJson(filePath, data) {
273
+ await fs.writeFile(filePath, JSON.stringify(data, null, 2));
274
+ }
275
+ }
276
+
277
+ // Singleton instance
278
+ let learningEngine = null;
279
+
280
+ /**
281
+ * Get or create LearningEngine instance
282
+ */
283
+ export function getLearningEngine(projectPath = process.cwd()) {
284
+ if (!learningEngine || learningEngine.projectPath !== projectPath) {
285
+ learningEngine = new LearningEngine(projectPath);
286
+ }
287
+ return learningEngine;
288
+ }
289
+
290
+ /**
291
+ * Create a new LearningEngine instance
292
+ */
293
+ export function createLearningEngine(projectPath = process.cwd()) {
294
+ return new LearningEngine(projectPath);
295
+ }
@@ -0,0 +1,304 @@
1
+ /**
2
+ * Image Analyzer for Vibecode CLI
3
+ * Analyze screenshots of errors using AI vision
4
+ */
5
+
6
+ import chalk from 'chalk';
7
+ import fs from 'fs/promises';
8
+ import path from 'path';
9
+ import { spawn } from 'child_process';
10
+ import { imageToBase64, saveClipboardImage, formatFileSize } from '../utils/image.js';
11
+
12
+ /**
13
+ * ImageAnalyzer class for screenshot error analysis
14
+ */
15
+ export class ImageAnalyzer {
16
+ constructor(projectPath = process.cwd()) {
17
+ this.projectPath = projectPath;
18
+ }
19
+
20
+ /**
21
+ * Analyze image file for errors
22
+ */
23
+ async analyzeImage(imagePath) {
24
+ console.log(chalk.cyan('\n Analyzing screenshot...\n'));
25
+
26
+ try {
27
+ // Read and validate image
28
+ const imageInfo = await imageToBase64(imagePath);
29
+
30
+ console.log(chalk.gray(` File: ${path.basename(imageInfo.path)}`));
31
+ console.log(chalk.gray(` Size: ${formatFileSize(imageInfo.size)}\n`));
32
+
33
+ // Run AI analysis
34
+ const analysis = await this.runImageAnalysis(imageInfo);
35
+
36
+ return analysis;
37
+ } catch (error) {
38
+ throw new Error(`Failed to analyze image: ${error.message}`);
39
+ }
40
+ }
41
+
42
+ /**
43
+ * Analyze image from clipboard
44
+ */
45
+ async analyzeClipboard() {
46
+ console.log(chalk.cyan('\n Getting image from clipboard...\n'));
47
+
48
+ try {
49
+ const tempFile = await saveClipboardImage();
50
+ console.log(chalk.green(` Image captured\n`));
51
+
52
+ const result = await this.analyzeImage(tempFile);
53
+
54
+ // Cleanup temp file
55
+ try {
56
+ await fs.unlink(tempFile);
57
+ } catch {
58
+ // Ignore cleanup errors
59
+ }
60
+
61
+ return result;
62
+ } catch (error) {
63
+ throw new Error(`Clipboard error: ${error.message}`);
64
+ }
65
+ }
66
+
67
+ /**
68
+ * Run AI analysis on image
69
+ */
70
+ async runImageAnalysis(imageInfo) {
71
+ // Create analysis prompt
72
+ const prompt = this.buildAnalysisPrompt(imageInfo);
73
+
74
+ // Save prompt to temp file
75
+ const tempDir = path.join(this.projectPath, '.vibecode', 'debug');
76
+ await fs.mkdir(tempDir, { recursive: true });
77
+
78
+ const promptFile = path.join(tempDir, 'image-analysis.md');
79
+ await fs.writeFile(promptFile, prompt);
80
+
81
+ // Also save image reference
82
+ const imageRef = path.join(tempDir, 'screenshot-ref.txt');
83
+ await fs.writeFile(imageRef, imageInfo.path);
84
+
85
+ // Run Claude Code with image
86
+ const result = await this.runClaudeWithImage(imageInfo, promptFile);
87
+
88
+ return this.parseAnalysisResult(result);
89
+ }
90
+
91
+ /**
92
+ * Build analysis prompt
93
+ */
94
+ buildAnalysisPrompt(imageInfo) {
95
+ return `# Screenshot Error Analysis
96
+
97
+ Analyze this screenshot and extract any error information.
98
+
99
+ ## Instructions
100
+ 1. Look for error messages, stack traces, console errors, or warnings
101
+ 2. Identify the error type (TypeError, SyntaxError, Build Error, etc.)
102
+ 3. Extract file names and line numbers if visible
103
+ 4. Note any relevant context visible in the screenshot
104
+ 5. Provide specific fix suggestions
105
+
106
+ ## Response Format
107
+ Respond in this exact format:
108
+
109
+ **Error Type**: [type of error or "None detected"]
110
+ **Error Message**: [main error text]
111
+ **Location**: [file:line if visible]
112
+ **Root Cause**: [explanation of what's wrong]
113
+ **Suggested Fix**: [how to fix it]
114
+ **Confidence**: [High/Medium/Low]
115
+
116
+ If no error is visible, respond with:
117
+ **Error Type**: None detected
118
+ **Error Message**: No error visible in screenshot
119
+ `;
120
+ }
121
+
122
+ /**
123
+ * Run Claude Code with image input
124
+ */
125
+ async runClaudeWithImage(imageInfo, promptFile) {
126
+ return new Promise((resolve) => {
127
+ let output = '';
128
+ let errorOutput = '';
129
+
130
+ // Use claude with image path
131
+ // Claude Code can read images directly
132
+ const args = [
133
+ '--print',
134
+ '-p', `Analyze this image for errors: ${imageInfo.path}
135
+
136
+ Look for:
137
+ - Error messages
138
+ - Stack traces
139
+ - Console errors
140
+ - Type errors
141
+ - Build failures
142
+
143
+ Respond with:
144
+ **Error Type**:
145
+ **Error Message**:
146
+ **Location**:
147
+ **Root Cause**:
148
+ **Suggested Fix**:
149
+ **Confidence**:
150
+
151
+ If no error found, say "No error detected"`
152
+ ];
153
+
154
+ const child = spawn('claude', args, {
155
+ cwd: this.projectPath,
156
+ stdio: ['pipe', 'pipe', 'pipe']
157
+ });
158
+
159
+ child.stdout.on('data', (data) => {
160
+ output += data.toString();
161
+ });
162
+
163
+ child.stderr.on('data', (data) => {
164
+ errorOutput += data.toString();
165
+ });
166
+
167
+ child.on('close', (code) => {
168
+ resolve({
169
+ success: code === 0,
170
+ output: output.trim(),
171
+ error: errorOutput.trim()
172
+ });
173
+ });
174
+
175
+ child.on('error', (error) => {
176
+ resolve({
177
+ success: false,
178
+ output: '',
179
+ error: error.message
180
+ });
181
+ });
182
+
183
+ // Timeout after 60 seconds
184
+ setTimeout(() => {
185
+ child.kill();
186
+ resolve({
187
+ success: false,
188
+ output: '',
189
+ error: 'Analysis timed out'
190
+ });
191
+ }, 60000);
192
+ });
193
+ }
194
+
195
+ /**
196
+ * Parse AI analysis result
197
+ */
198
+ parseAnalysisResult(result) {
199
+ const analysis = {
200
+ errorType: null,
201
+ errorMessage: null,
202
+ location: null,
203
+ rootCause: null,
204
+ suggestedFix: null,
205
+ confidence: 'Low',
206
+ raw: result?.output || '',
207
+ success: result?.success || false
208
+ };
209
+
210
+ const output = result?.output || '';
211
+
212
+ // Extract fields using regex
213
+ const patterns = {
214
+ errorType: /\*\*Error Type\*\*:\s*(.+?)(?:\n|$)/i,
215
+ errorMessage: /\*\*Error Message\*\*:\s*(.+?)(?:\n|$)/i,
216
+ location: /\*\*Location\*\*:\s*(.+?)(?:\n|$)/i,
217
+ rootCause: /\*\*Root Cause\*\*:\s*(.+?)(?:\n|$)/i,
218
+ suggestedFix: /\*\*Suggested Fix\*\*:\s*(.+?)(?:\n|$)/i,
219
+ confidence: /\*\*Confidence\*\*:\s*(.+?)(?:\n|$)/i
220
+ };
221
+
222
+ for (const [key, pattern] of Object.entries(patterns)) {
223
+ const match = output.match(pattern);
224
+ if (match && match[1]) {
225
+ const value = match[1].trim();
226
+ if (value && value.toLowerCase() !== 'none' && value.toLowerCase() !== 'n/a') {
227
+ analysis[key] = value;
228
+ }
229
+ }
230
+ }
231
+
232
+ // Check if error was actually detected
233
+ if (analysis.errorType?.toLowerCase().includes('none detected') ||
234
+ analysis.errorMessage?.toLowerCase().includes('no error')) {
235
+ analysis.errorType = null;
236
+ analysis.errorMessage = null;
237
+ }
238
+
239
+ return analysis;
240
+ }
241
+
242
+ /**
243
+ * Format analysis for display
244
+ */
245
+ formatAnalysis(analysis) {
246
+ // No error case
247
+ if (!analysis.errorType && !analysis.errorMessage) {
248
+ return chalk.yellow('\n No error detected in screenshot.\n');
249
+ }
250
+
251
+ let output = chalk.cyan(`
252
+ +----------------------------------------------------------------------+
253
+ | SCREENSHOT ANALYSIS |
254
+ +----------------------------------------------------------------------+
255
+ `);
256
+
257
+ if (analysis.errorType) {
258
+ output += `\n ${chalk.white('Error Type:')} ${chalk.red(analysis.errorType)}`;
259
+ }
260
+
261
+ if (analysis.errorMessage) {
262
+ output += `\n ${chalk.white('Message:')} ${chalk.yellow(analysis.errorMessage)}`;
263
+ }
264
+
265
+ if (analysis.location) {
266
+ output += `\n ${chalk.white('Location:')} ${chalk.gray(analysis.location)}`;
267
+ }
268
+
269
+ if (analysis.confidence) {
270
+ const confColor = analysis.confidence === 'High' ? chalk.green :
271
+ analysis.confidence === 'Medium' ? chalk.yellow : chalk.gray;
272
+ output += `\n ${chalk.white('Confidence:')} ${confColor(analysis.confidence)}`;
273
+ }
274
+
275
+ if (analysis.rootCause) {
276
+ output += `\n\n ${chalk.cyan('Root Cause:')}`;
277
+ output += `\n ${chalk.gray(analysis.rootCause)}`;
278
+ }
279
+
280
+ if (analysis.suggestedFix) {
281
+ output += `\n\n ${chalk.green('Suggested Fix:')}`;
282
+ output += `\n ${chalk.white(analysis.suggestedFix)}`;
283
+ }
284
+
285
+ output += '\n';
286
+
287
+ return output;
288
+ }
289
+ }
290
+
291
+ /**
292
+ * Helper function for direct use
293
+ */
294
+ export async function analyzeScreenshot(imagePathOrClipboard, options = {}) {
295
+ const analyzer = new ImageAnalyzer(options.projectPath || process.cwd());
296
+
297
+ if (imagePathOrClipboard === 'clipboard') {
298
+ return await analyzer.analyzeClipboard();
299
+ } else {
300
+ return await analyzer.analyzeImage(imagePathOrClipboard);
301
+ }
302
+ }
303
+
304
+ export default ImageAnalyzer;
@@ -27,6 +27,9 @@ import { RootCauseAnalyzer } from './analyzer.js';
27
27
  import { FixGenerator } from './fixer.js';
28
28
  import { FixVerifier } from './verifier.js';
29
29
  import { spawnClaudeCode, isClaudeCodeAvailable } from '../providers/index.js';
30
+ import { translateError, formatTranslatedError } from '../ui/error-translator.js';
31
+ import { getLearningEngine } from '../core/learning.js';
32
+ import { askFeedback, showLearningSuggestion } from '../commands/learn.js';
30
33
 
31
34
  const execAsync = promisify(exec);
32
35
 
@@ -103,6 +106,9 @@ export class DebugEngine {
103
106
  return this.createResult('no_hypothesis', 'Could not generate fix hypotheses');
104
107
  }
105
108
 
109
+ // Check for learning-based suggestions
110
+ await showLearningSuggestion(evidence.type, evidence.category);
111
+
106
112
  // Step 5-7: TEST, FIX, VERIFY - Attempt fixes
107
113
  let fixResult = null;
108
114
  for (let attempt = 0; attempt < this.options.maxAttempts && !this.session.resolved; attempt++) {
@@ -145,6 +151,16 @@ export class DebugEngine {
145
151
  await this.fixer.updateClaudeMd(fixAttempt);
146
152
 
147
153
  console.log(chalk.green.bold('\n✅ Bug fixed and documented!\n'));
154
+
155
+ // Ask for feedback to improve future suggestions
156
+ if (this.options.interactive) {
157
+ await askFeedback({
158
+ errorType: evidence.type,
159
+ errorMessage: evidence.message,
160
+ errorCategory: evidence.category,
161
+ fixApplied: fixAttempt.description || hypothesis.description
162
+ });
163
+ }
148
164
  } else {
149
165
  console.log(chalk.yellow(' ⚠ Verification failed, trying next approach...'));
150
166
  }
@@ -275,9 +291,22 @@ export class DebugEngine {
275
291
  logEvidence(evidence) {
276
292
  console.log(chalk.gray(` Type: ${evidence.type || 'Unknown'}`));
277
293
  console.log(chalk.gray(` Category: ${evidence.category}`));
294
+
278
295
  if (evidence.message) {
279
- console.log(chalk.gray(` Message: ${evidence.message.substring(0, 100)}${evidence.message.length > 100 ? '...' : ''}`));
296
+ // Translate error for human-friendly display
297
+ const translated = translateError(evidence.message);
298
+ console.log(chalk.yellow(` Error: ${translated.title}`));
299
+ console.log(chalk.gray(` → ${translated.description.substring(0, 80)}${translated.description.length > 80 ? '...' : ''}`));
300
+
301
+ // Show suggestions
302
+ if (translated.suggestions && translated.suggestions.length > 0) {
303
+ console.log(chalk.gray(` Suggestions:`));
304
+ for (const s of translated.suggestions.slice(0, 2)) {
305
+ console.log(chalk.gray(` • ${s}`));
306
+ }
307
+ }
280
308
  }
309
+
281
310
  if (evidence.files.length > 0) {
282
311
  console.log(chalk.gray(` Files: ${evidence.files.slice(0, 3).join(', ')}${evidence.files.length > 3 ? '...' : ''}`));
283
312
  }