@nclamvn/vibecode-cli 2.0.0 → 2.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.
Files changed (53) hide show
  1. package/.vibecode/learning/fixes.json +1 -0
  2. package/.vibecode/learning/preferences.json +1 -0
  3. package/README.md +310 -49
  4. package/SESSION_NOTES.md +154 -0
  5. package/bin/vibecode.js +235 -2
  6. package/package.json +5 -2
  7. package/src/agent/decomposition.js +476 -0
  8. package/src/agent/index.js +391 -0
  9. package/src/agent/memory.js +542 -0
  10. package/src/agent/orchestrator.js +917 -0
  11. package/src/agent/self-healing.js +516 -0
  12. package/src/commands/agent.js +349 -0
  13. package/src/commands/ask.js +230 -0
  14. package/src/commands/assist.js +413 -0
  15. package/src/commands/build.js +345 -4
  16. package/src/commands/debug.js +565 -0
  17. package/src/commands/docs.js +167 -0
  18. package/src/commands/git.js +1024 -0
  19. package/src/commands/go.js +635 -0
  20. package/src/commands/learn.js +294 -0
  21. package/src/commands/migrate.js +341 -0
  22. package/src/commands/plan.js +8 -2
  23. package/src/commands/refactor.js +205 -0
  24. package/src/commands/review.js +126 -1
  25. package/src/commands/security.js +229 -0
  26. package/src/commands/shell.js +486 -0
  27. package/src/commands/templates.js +397 -0
  28. package/src/commands/test.js +194 -0
  29. package/src/commands/undo.js +281 -0
  30. package/src/commands/watch.js +556 -0
  31. package/src/commands/wizard.js +322 -0
  32. package/src/config/constants.js +5 -1
  33. package/src/config/templates.js +146 -15
  34. package/src/core/backup.js +325 -0
  35. package/src/core/error-analyzer.js +237 -0
  36. package/src/core/fix-generator.js +195 -0
  37. package/src/core/iteration.js +226 -0
  38. package/src/core/learning.js +295 -0
  39. package/src/core/session.js +18 -2
  40. package/src/core/test-runner.js +281 -0
  41. package/src/debug/analyzer.js +329 -0
  42. package/src/debug/evidence.js +228 -0
  43. package/src/debug/fixer.js +348 -0
  44. package/src/debug/image-analyzer.js +304 -0
  45. package/src/debug/index.js +378 -0
  46. package/src/debug/verifier.js +346 -0
  47. package/src/index.js +102 -0
  48. package/src/providers/claude-code.js +12 -7
  49. package/src/templates/index.js +724 -0
  50. package/src/ui/__tests__/error-translator.test.js +390 -0
  51. package/src/ui/dashboard.js +364 -0
  52. package/src/ui/error-translator.js +775 -0
  53. package/src/utils/image.js +222 -0
@@ -0,0 +1,325 @@
1
+ // ═══════════════════════════════════════════════════════════════════════════════
2
+ // VIBECODE CLI - Backup Manager
3
+ // Phase H4: Undo/Rollback - Auto-backup before every action
4
+ // ═══════════════════════════════════════════════════════════════════════════════
5
+
6
+ import fs from 'fs-extra';
7
+ import path from 'path';
8
+ import { createHash } from 'crypto';
9
+
10
+ const BACKUP_DIR = '.vibecode/backups';
11
+ const MAX_BACKUPS = 10;
12
+
13
+ /**
14
+ * BackupManager - Creates and manages backups for undo functionality
15
+ *
16
+ * Usage:
17
+ * const backup = new BackupManager();
18
+ * const id = await backup.createBackup('build');
19
+ * // ... do stuff ...
20
+ * await backup.restore(id); // Undo!
21
+ */
22
+ export class BackupManager {
23
+ constructor(projectPath = process.cwd()) {
24
+ this.projectPath = projectPath;
25
+ this.backupPath = path.join(projectPath, BACKUP_DIR);
26
+ }
27
+
28
+ /**
29
+ * Initialize backup directory
30
+ */
31
+ async init() {
32
+ await fs.ensureDir(this.backupPath);
33
+ }
34
+
35
+ /**
36
+ * Create backup before action
37
+ * @param {string} actionName - Name of action (build, agent, go, etc.)
38
+ * @param {string[]} files - Specific files to backup (optional)
39
+ * @returns {string} Backup ID
40
+ */
41
+ async createBackup(actionName, files = null) {
42
+ await this.init();
43
+
44
+ const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
45
+ const backupId = `${timestamp}_${actionName}`;
46
+ const backupDir = path.join(this.backupPath, backupId);
47
+
48
+ await fs.ensureDir(backupDir);
49
+
50
+ // If specific files provided, backup only those
51
+ // Otherwise, backup common source files
52
+ const filesToBackup = files || await this.getSourceFiles();
53
+
54
+ const manifest = {
55
+ id: backupId,
56
+ action: actionName,
57
+ timestamp: new Date().toISOString(),
58
+ files: []
59
+ };
60
+
61
+ for (const file of filesToBackup) {
62
+ try {
63
+ const sourcePath = path.join(this.projectPath, file);
64
+ const stats = await fs.stat(sourcePath).catch(() => null);
65
+
66
+ if (stats && stats.isFile()) {
67
+ const content = await fs.readFile(sourcePath);
68
+ const hash = createHash('md5').update(content).digest('hex');
69
+
70
+ // Replace slashes with __ for flat storage
71
+ const backupFileName = file.replace(/[/\\]/g, '__');
72
+ const backupFilePath = path.join(backupDir, backupFileName);
73
+
74
+ await fs.writeFile(backupFilePath, content);
75
+
76
+ manifest.files.push({
77
+ path: file,
78
+ hash,
79
+ size: stats.size,
80
+ backupName: backupFileName
81
+ });
82
+ }
83
+ } catch (error) {
84
+ // Skip files that can't be backed up
85
+ }
86
+ }
87
+
88
+ // Save manifest
89
+ await fs.writeFile(
90
+ path.join(backupDir, 'manifest.json'),
91
+ JSON.stringify(manifest, null, 2)
92
+ );
93
+
94
+ // Cleanup old backups
95
+ await this.cleanupOldBackups();
96
+
97
+ return backupId;
98
+ }
99
+
100
+ /**
101
+ * Get list of source files to backup
102
+ * @returns {string[]} File paths relative to project
103
+ */
104
+ async getSourceFiles() {
105
+ const files = [];
106
+ const ignoreDirs = [
107
+ 'node_modules', '.git', '.next', 'dist', 'build',
108
+ '.vibecode/backups', 'coverage', '.cache', '__pycache__'
109
+ ];
110
+ const extensions = [
111
+ '.js', '.ts', '.tsx', '.jsx', '.json', '.css', '.scss',
112
+ '.html', '.md', '.vue', '.svelte', '.prisma', '.env'
113
+ ];
114
+
115
+ const scan = async (dir, prefix = '') => {
116
+ try {
117
+ const entries = await fs.readdir(dir, { withFileTypes: true });
118
+
119
+ for (const entry of entries) {
120
+ const fullPath = path.join(dir, entry.name);
121
+ const relativePath = prefix ? `${prefix}/${entry.name}` : entry.name;
122
+
123
+ if (entry.isDirectory()) {
124
+ if (!ignoreDirs.includes(entry.name) && !entry.name.startsWith('.')) {
125
+ await scan(fullPath, relativePath);
126
+ }
127
+ } else if (entry.isFile()) {
128
+ const ext = path.extname(entry.name);
129
+ if (extensions.includes(ext)) {
130
+ files.push(relativePath);
131
+ }
132
+ }
133
+ }
134
+ } catch (error) {
135
+ // Skip directories that can't be read
136
+ }
137
+ };
138
+
139
+ await scan(this.projectPath);
140
+ return files.slice(0, 100); // Limit to 100 files
141
+ }
142
+
143
+ /**
144
+ * List available backups
145
+ * @returns {Object[]} Array of backup manifests
146
+ */
147
+ async listBackups() {
148
+ await this.init();
149
+
150
+ try {
151
+ const entries = await fs.readdir(this.backupPath, { withFileTypes: true });
152
+ const backups = [];
153
+
154
+ for (const entry of entries) {
155
+ if (entry.isDirectory()) {
156
+ const manifestPath = path.join(this.backupPath, entry.name, 'manifest.json');
157
+ try {
158
+ const manifest = await fs.readJson(manifestPath);
159
+ backups.push(manifest);
160
+ } catch {
161
+ // Skip invalid backups
162
+ }
163
+ }
164
+ }
165
+
166
+ // Sort by timestamp, newest first
167
+ return backups.sort((a, b) => new Date(b.timestamp) - new Date(a.timestamp));
168
+ } catch {
169
+ return [];
170
+ }
171
+ }
172
+
173
+ /**
174
+ * Restore from backup
175
+ * @param {string} backupId - Backup ID to restore
176
+ * @returns {Object} Restore result
177
+ */
178
+ async restore(backupId) {
179
+ const backupDir = path.join(this.backupPath, backupId);
180
+ const manifestPath = path.join(backupDir, 'manifest.json');
181
+
182
+ try {
183
+ const manifest = await fs.readJson(manifestPath);
184
+ const restored = [];
185
+
186
+ for (const file of manifest.files) {
187
+ const backupFilePath = path.join(backupDir, file.backupName);
188
+ const targetPath = path.join(this.projectPath, file.path);
189
+
190
+ // Ensure directory exists
191
+ await fs.ensureDir(path.dirname(targetPath));
192
+
193
+ // Restore file
194
+ const content = await fs.readFile(backupFilePath);
195
+ await fs.writeFile(targetPath, content);
196
+
197
+ restored.push(file.path);
198
+ }
199
+
200
+ return {
201
+ success: true,
202
+ backupId,
203
+ action: manifest.action,
204
+ timestamp: manifest.timestamp,
205
+ filesRestored: restored.length,
206
+ files: restored
207
+ };
208
+ } catch (error) {
209
+ return {
210
+ success: false,
211
+ error: error.message
212
+ };
213
+ }
214
+ }
215
+
216
+ /**
217
+ * Restore to N steps ago
218
+ * @param {number} steps - Number of steps back
219
+ * @returns {Object} Restore result
220
+ */
221
+ async restoreSteps(steps = 1) {
222
+ const backups = await this.listBackups();
223
+
224
+ if (steps > backups.length) {
225
+ return {
226
+ success: false,
227
+ error: `Only ${backups.length} backups available`
228
+ };
229
+ }
230
+
231
+ const backup = backups[steps - 1];
232
+ return await this.restore(backup.id);
233
+ }
234
+
235
+ /**
236
+ * Get the most recent backup
237
+ * @returns {Object|null} Latest backup manifest or null
238
+ */
239
+ async getLatestBackup() {
240
+ const backups = await this.listBackups();
241
+ return backups.length > 0 ? backups[0] : null;
242
+ }
243
+
244
+ /**
245
+ * Cleanup old backups to maintain MAX_BACKUPS limit
246
+ */
247
+ async cleanupOldBackups() {
248
+ const backups = await this.listBackups();
249
+
250
+ if (backups.length > MAX_BACKUPS) {
251
+ const toDelete = backups.slice(MAX_BACKUPS);
252
+
253
+ for (const backup of toDelete) {
254
+ const backupDir = path.join(this.backupPath, backup.id);
255
+ await fs.remove(backupDir);
256
+ }
257
+ }
258
+ }
259
+
260
+ /**
261
+ * Delete specific backup
262
+ * @param {string} backupId - Backup ID to delete
263
+ */
264
+ async deleteBackup(backupId) {
265
+ const backupDir = path.join(this.backupPath, backupId);
266
+ await fs.remove(backupDir);
267
+ }
268
+
269
+ /**
270
+ * Clear all backups
271
+ */
272
+ async clearAllBackups() {
273
+ await fs.remove(this.backupPath);
274
+ await this.init();
275
+ }
276
+
277
+ /**
278
+ * Get backup size in bytes
279
+ * @param {string} backupId - Backup ID
280
+ * @returns {number} Size in bytes
281
+ */
282
+ async getBackupSize(backupId) {
283
+ const backupDir = path.join(this.backupPath, backupId);
284
+ let totalSize = 0;
285
+
286
+ try {
287
+ const files = await fs.readdir(backupDir);
288
+ for (const file of files) {
289
+ const stats = await fs.stat(path.join(backupDir, file));
290
+ totalSize += stats.size;
291
+ }
292
+ } catch {
293
+ // Ignore errors
294
+ }
295
+
296
+ return totalSize;
297
+ }
298
+ }
299
+
300
+ /**
301
+ * Helper to wrap action with auto-backup
302
+ * @param {string} actionName - Name of action
303
+ * @param {Function} fn - Async function to execute
304
+ * @returns {*} Result of fn()
305
+ */
306
+ export async function withBackup(actionName, fn) {
307
+ const backup = new BackupManager();
308
+ const backupId = await backup.createBackup(actionName);
309
+
310
+ try {
311
+ return await fn();
312
+ } catch (error) {
313
+ // Error occurred - backup is available for restore
314
+ console.log(`\n💾 Backup created: ${backupId}`);
315
+ console.log(` Run 'vibecode undo' to restore previous state.\n`);
316
+ throw error;
317
+ }
318
+ }
319
+
320
+ /**
321
+ * Create backup manager instance
322
+ */
323
+ export function createBackupManager(projectPath) {
324
+ return new BackupManager(projectPath);
325
+ }
@@ -0,0 +1,237 @@
1
+ // ═══════════════════════════════════════════════════════════════════════════════
2
+ // VIBECODE CLI - Error Analyzer
3
+ // Intelligent error analysis for iterative builds
4
+ // ═══════════════════════════════════════════════════════════════════════════════
5
+
6
+ /**
7
+ * Analyze test results and extract actionable errors
8
+ * @param {TestResults} testResult - Results from test runner
9
+ * @returns {AnalyzedError[]}
10
+ */
11
+ export function analyzeErrors(testResult) {
12
+ const errors = [];
13
+
14
+ for (const test of testResult.tests) {
15
+ if (!test.passed) {
16
+ const testErrors = test.errors || [];
17
+
18
+ for (const error of testErrors) {
19
+ errors.push({
20
+ source: test.name,
21
+ type: categorizeError(error),
22
+ file: error.file || null,
23
+ line: error.line || null,
24
+ column: error.column || null,
25
+ message: error.message,
26
+ suggestion: generateSuggestion(error),
27
+ priority: calculatePriority(error),
28
+ raw: error.raw
29
+ });
30
+ }
31
+
32
+ // If no specific errors but test failed, add generic error
33
+ if (testErrors.length === 0) {
34
+ errors.push({
35
+ source: test.name,
36
+ type: 'unknown',
37
+ message: test.error || `${test.name} failed`,
38
+ suggestion: `Check ${test.name} output for details`,
39
+ priority: 'medium',
40
+ raw: test.output
41
+ });
42
+ }
43
+ }
44
+ }
45
+
46
+ // Sort by priority
47
+ return errors.sort((a, b) => {
48
+ const priorityOrder = { critical: 0, high: 1, medium: 2, low: 3 };
49
+ return (priorityOrder[a.priority] || 2) - (priorityOrder[b.priority] || 2);
50
+ });
51
+ }
52
+
53
+ /**
54
+ * Categorize error type
55
+ */
56
+ function categorizeError(error) {
57
+ const message = (error.message || '').toLowerCase();
58
+ const raw = (error.raw || '').toLowerCase();
59
+ const combined = message + ' ' + raw;
60
+
61
+ if (combined.includes('syntaxerror') || combined.includes('unexpected token')) {
62
+ return 'syntax';
63
+ }
64
+ if (combined.includes('typeerror') || combined.includes('type error') || combined.includes('is not a function')) {
65
+ return 'type';
66
+ }
67
+ if (combined.includes('referenceerror') || combined.includes('is not defined')) {
68
+ return 'reference';
69
+ }
70
+ if (combined.includes('import') || combined.includes('require') || combined.includes('module not found')) {
71
+ return 'import';
72
+ }
73
+ if (combined.includes('eslint') || combined.includes('lint')) {
74
+ return 'lint';
75
+ }
76
+ if (combined.includes('test') || combined.includes('expect') || combined.includes('assert')) {
77
+ return 'test';
78
+ }
79
+ if (combined.includes('typescript') || combined.includes('ts(')) {
80
+ return 'typescript';
81
+ }
82
+ if (combined.includes('build') || combined.includes('compile')) {
83
+ return 'build';
84
+ }
85
+
86
+ return 'unknown';
87
+ }
88
+
89
+ /**
90
+ * Generate fix suggestion based on error type
91
+ */
92
+ function generateSuggestion(error) {
93
+ const type = categorizeError(error);
94
+ const message = error.message || '';
95
+
96
+ switch (type) {
97
+ case 'syntax':
98
+ return 'Check for missing brackets, semicolons, or typos near the error location';
99
+
100
+ case 'type':
101
+ if (message.includes('undefined')) {
102
+ return 'Check if the variable/property is properly initialized';
103
+ }
104
+ if (message.includes('is not a function')) {
105
+ return 'Verify the function exists and is properly imported';
106
+ }
107
+ return 'Check type compatibility and ensure proper type handling';
108
+
109
+ case 'reference':
110
+ return 'Ensure the variable/function is defined or imported before use';
111
+
112
+ case 'import':
113
+ if (message.includes('module not found')) {
114
+ return 'Install missing package with npm install or fix import path';
115
+ }
116
+ return 'Check import path and ensure the module exports correctly';
117
+
118
+ case 'lint':
119
+ return 'Fix the linting issue as specified in the error message';
120
+
121
+ case 'test':
122
+ return 'Update the implementation to match expected behavior, or fix the test assertion';
123
+
124
+ case 'typescript':
125
+ return 'Fix type errors by adding proper types or type guards';
126
+
127
+ case 'build':
128
+ return 'Check build configuration and dependencies';
129
+
130
+ default:
131
+ return 'Review the error message and fix accordingly';
132
+ }
133
+ }
134
+
135
+ /**
136
+ * Calculate error priority
137
+ */
138
+ function calculatePriority(error) {
139
+ const type = categorizeError(error);
140
+
141
+ // Critical - blocks everything
142
+ if (type === 'syntax' || type === 'import') {
143
+ return 'critical';
144
+ }
145
+
146
+ // High - likely causes cascading failures
147
+ if (type === 'reference' || type === 'type') {
148
+ return 'high';
149
+ }
150
+
151
+ // Medium - should be fixed
152
+ if (type === 'typescript' || type === 'build' || type === 'test') {
153
+ return 'medium';
154
+ }
155
+
156
+ // Low - nice to fix
157
+ if (type === 'lint') {
158
+ return 'low';
159
+ }
160
+
161
+ return 'medium';
162
+ }
163
+
164
+ /**
165
+ * Group errors by file for better organization
166
+ */
167
+ export function groupErrorsByFile(errors) {
168
+ const grouped = {};
169
+
170
+ for (const error of errors) {
171
+ const file = error.file || 'unknown';
172
+ if (!grouped[file]) {
173
+ grouped[file] = [];
174
+ }
175
+ grouped[file].push(error);
176
+ }
177
+
178
+ return grouped;
179
+ }
180
+
181
+ /**
182
+ * Get unique files with errors
183
+ */
184
+ export function getAffectedFiles(errors) {
185
+ const files = new Set();
186
+ for (const error of errors) {
187
+ if (error.file) {
188
+ files.add(error.file);
189
+ }
190
+ }
191
+ return Array.from(files);
192
+ }
193
+
194
+ /**
195
+ * Format errors for display
196
+ */
197
+ export function formatErrors(errors) {
198
+ const lines = [];
199
+
200
+ lines.push(`Found ${errors.length} error(s):`);
201
+ lines.push('');
202
+
203
+ const grouped = groupErrorsByFile(errors);
204
+
205
+ for (const [file, fileErrors] of Object.entries(grouped)) {
206
+ lines.push(`📄 ${file}`);
207
+ for (const error of fileErrors) {
208
+ const loc = error.line ? `:${error.line}` : '';
209
+ const priority = error.priority === 'critical' ? '🔴' :
210
+ error.priority === 'high' ? '🟠' :
211
+ error.priority === 'medium' ? '🟡' : '🟢';
212
+ lines.push(` ${priority} ${error.type}: ${error.message?.substring(0, 60) || 'Unknown error'}`);
213
+ }
214
+ }
215
+
216
+ return lines.join('\n');
217
+ }
218
+
219
+ /**
220
+ * Create a summary of errors for logging
221
+ */
222
+ export function createErrorSummary(errors) {
223
+ const byType = {};
224
+ const byPriority = { critical: 0, high: 0, medium: 0, low: 0 };
225
+
226
+ for (const error of errors) {
227
+ byType[error.type] = (byType[error.type] || 0) + 1;
228
+ byPriority[error.priority] = (byPriority[error.priority] || 0) + 1;
229
+ }
230
+
231
+ return {
232
+ total: errors.length,
233
+ byType,
234
+ byPriority,
235
+ files: getAffectedFiles(errors)
236
+ };
237
+ }