@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.
- package/.vibecode/learning/fixes.json +1 -0
- package/.vibecode/learning/preferences.json +1 -0
- package/bin/vibecode.js +86 -3
- package/docs-site/README.md +41 -0
- package/docs-site/blog/2019-05-28-first-blog-post.md +12 -0
- package/docs-site/blog/2019-05-29-long-blog-post.md +44 -0
- package/docs-site/blog/2021-08-01-mdx-blog-post.mdx +24 -0
- package/docs-site/blog/2021-08-26-welcome/docusaurus-plushie-banner.jpeg +0 -0
- package/docs-site/blog/2021-08-26-welcome/index.md +29 -0
- package/docs-site/blog/authors.yml +25 -0
- package/docs-site/blog/tags.yml +19 -0
- package/docs-site/docs/commands/agent.md +162 -0
- package/docs-site/docs/commands/assist.md +71 -0
- package/docs-site/docs/commands/build.md +53 -0
- package/docs-site/docs/commands/config.md +30 -0
- package/docs-site/docs/commands/debug.md +173 -0
- package/docs-site/docs/commands/doctor.md +34 -0
- package/docs-site/docs/commands/go.md +128 -0
- package/docs-site/docs/commands/index.md +79 -0
- package/docs-site/docs/commands/init.md +42 -0
- package/docs-site/docs/commands/learn.md +82 -0
- package/docs-site/docs/commands/lock.md +33 -0
- package/docs-site/docs/commands/plan.md +29 -0
- package/docs-site/docs/commands/review.md +31 -0
- package/docs-site/docs/commands/snapshot.md +34 -0
- package/docs-site/docs/commands/start.md +32 -0
- package/docs-site/docs/commands/status.md +37 -0
- package/docs-site/docs/commands/undo.md +83 -0
- package/docs-site/docs/configuration.md +72 -0
- package/docs-site/docs/faq.md +83 -0
- package/docs-site/docs/getting-started.md +119 -0
- package/docs-site/docs/guides/agent-mode.md +94 -0
- package/docs-site/docs/guides/debug-mode.md +83 -0
- package/docs-site/docs/guides/magic-mode.md +107 -0
- package/docs-site/docs/installation.md +98 -0
- package/docs-site/docs/intro.md +67 -0
- package/docs-site/docusaurus.config.ts +141 -0
- package/docs-site/package-lock.json +18039 -0
- package/docs-site/package.json +48 -0
- package/docs-site/sidebars.ts +70 -0
- package/docs-site/src/components/HomepageFeatures/index.tsx +72 -0
- package/docs-site/src/components/HomepageFeatures/styles.module.css +16 -0
- package/docs-site/src/css/custom.css +30 -0
- package/docs-site/src/pages/index.module.css +23 -0
- package/docs-site/src/pages/index.tsx +44 -0
- package/docs-site/src/pages/markdown-page.md +7 -0
- package/docs-site/src/theme/Footer/index.tsx +127 -0
- package/docs-site/src/theme/Footer/styles.module.css +285 -0
- package/docs-site/static/.nojekyll +0 -0
- package/docs-site/static/img/docusaurus-social-card.jpg +0 -0
- package/docs-site/static/img/docusaurus.png +0 -0
- package/docs-site/static/img/favicon.ico +0 -0
- package/docs-site/static/img/logo.svg +1 -0
- package/docs-site/static/img/undraw_docusaurus_mountain.svg +171 -0
- package/docs-site/static/img/undraw_docusaurus_react.svg +170 -0
- package/docs-site/static/img/undraw_docusaurus_tree.svg +40 -0
- package/docs-site/tsconfig.json +8 -0
- package/package.json +5 -2
- package/src/agent/orchestrator.js +104 -35
- package/src/commands/build.js +13 -3
- package/src/commands/debug.js +109 -1
- package/src/commands/git.js +923 -0
- package/src/commands/go.js +9 -2
- package/src/commands/learn.js +294 -0
- package/src/commands/shell.js +486 -0
- package/src/commands/undo.js +281 -0
- package/src/commands/watch.js +556 -0
- package/src/commands/wizard.js +322 -0
- package/src/core/backup.js +325 -0
- package/src/core/learning.js +295 -0
- package/src/debug/image-analyzer.js +304 -0
- package/src/debug/index.js +30 -1
- package/src/index.js +50 -0
- package/src/ui/__tests__/error-translator.test.js +390 -0
- package/src/ui/dashboard.js +364 -0
- package/src/ui/error-translator.js +775 -0
- 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;
|
package/src/debug/index.js
CHANGED
|
@@ -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
|
-
|
|
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
|
}
|