@nclamvn/vibecode-cli 1.3.0 → 1.5.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,542 @@
1
+ // ═══════════════════════════════════════════════════════════════════════════════
2
+ // VIBECODE AGENT - Memory Engine
3
+ // Persistent context and learning across module builds
4
+ // ═══════════════════════════════════════════════════════════════════════════════
5
+
6
+ import fs from 'fs-extra';
7
+ import path from 'path';
8
+
9
+ /**
10
+ * Memory categories for organization
11
+ */
12
+ const MEMORY_CATEGORIES = {
13
+ DECISIONS: 'decisions', // Architectural/design decisions made
14
+ PATTERNS: 'patterns', // Code patterns discovered/used
15
+ ERRORS: 'errors', // Errors encountered and how they were fixed
16
+ FILES: 'files', // Files created/modified
17
+ CONTEXT: 'context', // Project context and requirements
18
+ LEARNINGS: 'learnings' // What worked/didn't work
19
+ };
20
+
21
+ /**
22
+ * Memory Engine Class
23
+ * Maintains persistent context across module builds
24
+ */
25
+ export class MemoryEngine {
26
+ constructor(memoryPath) {
27
+ this.memoryPath = memoryPath;
28
+ this.memory = {
29
+ version: '1.0.0',
30
+ created: new Date().toISOString(),
31
+ updated: new Date().toISOString(),
32
+ projectContext: {},
33
+ modules: {},
34
+ decisions: [],
35
+ patterns: [],
36
+ errors: [],
37
+ files: {},
38
+ learnings: [],
39
+ globalContext: ''
40
+ };
41
+ this.loaded = false;
42
+ }
43
+
44
+ /**
45
+ * Initialize memory storage
46
+ */
47
+ async initialize() {
48
+ await fs.ensureDir(path.dirname(this.memoryPath));
49
+
50
+ if (await fs.pathExists(this.memoryPath)) {
51
+ await this.load();
52
+ } else {
53
+ await this.save();
54
+ }
55
+
56
+ this.loaded = true;
57
+ return this;
58
+ }
59
+
60
+ /**
61
+ * Load memory from disk
62
+ */
63
+ async load() {
64
+ try {
65
+ const data = await fs.readJson(this.memoryPath);
66
+ this.memory = { ...this.memory, ...data };
67
+ this.loaded = true;
68
+ } catch (error) {
69
+ console.warn(`Could not load memory: ${error.message}`);
70
+ }
71
+ return this.memory;
72
+ }
73
+
74
+ /**
75
+ * Save memory to disk
76
+ */
77
+ async save() {
78
+ this.memory.updated = new Date().toISOString();
79
+ await fs.writeJson(this.memoryPath, this.memory, { spaces: 2 });
80
+ }
81
+
82
+ // ═══════════════════════════════════════════════════════════════════════════
83
+ // PROJECT CONTEXT
84
+ // ═══════════════════════════════════════════════════════════════════════════
85
+
86
+ /**
87
+ * Set project context
88
+ */
89
+ setProjectContext(context) {
90
+ this.memory.projectContext = {
91
+ ...this.memory.projectContext,
92
+ ...context,
93
+ updatedAt: new Date().toISOString()
94
+ };
95
+ return this.save();
96
+ }
97
+
98
+ /**
99
+ * Get project context
100
+ */
101
+ getProjectContext() {
102
+ return this.memory.projectContext;
103
+ }
104
+
105
+ // ═══════════════════════════════════════════════════════════════════════════
106
+ // MODULE MEMORY
107
+ // ═══════════════════════════════════════════════════════════════════════════
108
+
109
+ /**
110
+ * Start tracking a module
111
+ */
112
+ startModule(moduleId, moduleData) {
113
+ this.memory.modules[moduleId] = {
114
+ ...moduleData,
115
+ startedAt: new Date().toISOString(),
116
+ status: 'in_progress',
117
+ attempts: 0,
118
+ files: [],
119
+ errors: [],
120
+ decisions: []
121
+ };
122
+ return this.save();
123
+ }
124
+
125
+ /**
126
+ * Update module status
127
+ */
128
+ async updateModule(moduleId, updates) {
129
+ if (this.memory.modules[moduleId]) {
130
+ this.memory.modules[moduleId] = {
131
+ ...this.memory.modules[moduleId],
132
+ ...updates,
133
+ updatedAt: new Date().toISOString()
134
+ };
135
+ await this.save();
136
+ }
137
+ return this.memory.modules[moduleId];
138
+ }
139
+
140
+ /**
141
+ * Complete a module
142
+ */
143
+ async completeModule(moduleId, result) {
144
+ return this.updateModule(moduleId, {
145
+ status: 'completed',
146
+ completedAt: new Date().toISOString(),
147
+ result
148
+ });
149
+ }
150
+
151
+ /**
152
+ * Fail a module
153
+ */
154
+ async failModule(moduleId, error) {
155
+ const mod = this.memory.modules[moduleId];
156
+ if (mod) {
157
+ mod.attempts = (mod.attempts || 0) + 1;
158
+ mod.errors.push({
159
+ error: error.message || error,
160
+ timestamp: new Date().toISOString()
161
+ });
162
+ mod.status = 'failed';
163
+ await this.save();
164
+ }
165
+ return mod;
166
+ }
167
+
168
+ /**
169
+ * Get module memory
170
+ */
171
+ getModule(moduleId) {
172
+ return this.memory.modules[moduleId];
173
+ }
174
+
175
+ /**
176
+ * Get all completed modules
177
+ */
178
+ getCompletedModules() {
179
+ return Object.entries(this.memory.modules)
180
+ .filter(([_, mod]) => mod.status === 'completed')
181
+ .map(([id, mod]) => ({ id, ...mod }));
182
+ }
183
+
184
+ // ═══════════════════════════════════════════════════════════════════════════
185
+ // DECISIONS
186
+ // ═══════════════════════════════════════════════════════════════════════════
187
+
188
+ /**
189
+ * Record a decision
190
+ */
191
+ async recordDecision(decision) {
192
+ this.memory.decisions.push({
193
+ ...decision,
194
+ id: `dec_${Date.now()}`,
195
+ timestamp: new Date().toISOString()
196
+ });
197
+ await this.save();
198
+ return decision;
199
+ }
200
+
201
+ /**
202
+ * Get decisions by category
203
+ */
204
+ getDecisions(category = null) {
205
+ if (!category) return this.memory.decisions;
206
+ return this.memory.decisions.filter(d => d.category === category);
207
+ }
208
+
209
+ /**
210
+ * Get decisions for a specific module
211
+ */
212
+ getModuleDecisions(moduleId) {
213
+ return this.memory.decisions.filter(d => d.moduleId === moduleId);
214
+ }
215
+
216
+ // ═══════════════════════════════════════════════════════════════════════════
217
+ // PATTERNS
218
+ // ═══════════════════════════════════════════════════════════════════════════
219
+
220
+ /**
221
+ * Record a code pattern
222
+ */
223
+ async recordPattern(pattern) {
224
+ // Check if similar pattern exists
225
+ const existing = this.memory.patterns.find(p =>
226
+ p.name === pattern.name || p.pattern === pattern.pattern
227
+ );
228
+
229
+ if (existing) {
230
+ existing.usageCount = (existing.usageCount || 1) + 1;
231
+ existing.lastUsed = new Date().toISOString();
232
+ } else {
233
+ this.memory.patterns.push({
234
+ ...pattern,
235
+ id: `pat_${Date.now()}`,
236
+ usageCount: 1,
237
+ discoveredAt: new Date().toISOString(),
238
+ lastUsed: new Date().toISOString()
239
+ });
240
+ }
241
+
242
+ await this.save();
243
+ return pattern;
244
+ }
245
+
246
+ /**
247
+ * Get patterns by type
248
+ */
249
+ getPatterns(type = null) {
250
+ if (!type) return this.memory.patterns;
251
+ return this.memory.patterns.filter(p => p.type === type);
252
+ }
253
+
254
+ /**
255
+ * Get most used patterns
256
+ */
257
+ getPopularPatterns(limit = 5) {
258
+ return [...this.memory.patterns]
259
+ .sort((a, b) => (b.usageCount || 0) - (a.usageCount || 0))
260
+ .slice(0, limit);
261
+ }
262
+
263
+ // ═══════════════════════════════════════════════════════════════════════════
264
+ // ERRORS & FIXES
265
+ // ═══════════════════════════════════════════════════════════════════════════
266
+
267
+ /**
268
+ * Record an error and its fix
269
+ */
270
+ async recordError(error) {
271
+ this.memory.errors.push({
272
+ ...error,
273
+ id: `err_${Date.now()}`,
274
+ timestamp: new Date().toISOString()
275
+ });
276
+ await this.save();
277
+ return error;
278
+ }
279
+
280
+ /**
281
+ * Record a fix for an error
282
+ */
283
+ async recordFix(errorId, fix) {
284
+ const error = this.memory.errors.find(e => e.id === errorId);
285
+ if (error) {
286
+ error.fix = fix;
287
+ error.fixed = true;
288
+ error.fixedAt = new Date().toISOString();
289
+ await this.save();
290
+ }
291
+ return error;
292
+ }
293
+
294
+ /**
295
+ * Find similar errors from history
296
+ */
297
+ findSimilarErrors(errorMessage) {
298
+ const keywords = errorMessage.toLowerCase().split(/\s+/).filter(w => w.length > 3);
299
+
300
+ return this.memory.errors
301
+ .filter(e => e.fixed && e.fix)
302
+ .map(e => {
303
+ const errorText = (e.message || '').toLowerCase();
304
+ const matchCount = keywords.filter(kw => errorText.includes(kw)).length;
305
+ return { error: e, matchScore: matchCount / keywords.length };
306
+ })
307
+ .filter(item => item.matchScore > 0.3)
308
+ .sort((a, b) => b.matchScore - a.matchScore)
309
+ .map(item => item.error);
310
+ }
311
+
312
+ // ═══════════════════════════════════════════════════════════════════════════
313
+ // FILES
314
+ // ═══════════════════════════════════════════════════════════════════════════
315
+
316
+ /**
317
+ * Record a file creation/modification
318
+ */
319
+ async recordFile(filePath, metadata) {
320
+ this.memory.files[filePath] = {
321
+ ...metadata,
322
+ path: filePath,
323
+ updatedAt: new Date().toISOString()
324
+ };
325
+ await this.save();
326
+ return this.memory.files[filePath];
327
+ }
328
+
329
+ /**
330
+ * Get all recorded files
331
+ */
332
+ getFiles() {
333
+ return this.memory.files;
334
+ }
335
+
336
+ /**
337
+ * Get files by module
338
+ */
339
+ getFilesByModule(moduleId) {
340
+ return Object.values(this.memory.files)
341
+ .filter(f => f.moduleId === moduleId);
342
+ }
343
+
344
+ // ═══════════════════════════════════════════════════════════════════════════
345
+ // LEARNINGS
346
+ // ═══════════════════════════════════════════════════════════════════════════
347
+
348
+ /**
349
+ * Record a learning (what worked/didn't work)
350
+ */
351
+ async recordLearning(learning) {
352
+ this.memory.learnings.push({
353
+ ...learning,
354
+ id: `learn_${Date.now()}`,
355
+ timestamp: new Date().toISOString()
356
+ });
357
+ await this.save();
358
+ return learning;
359
+ }
360
+
361
+ /**
362
+ * Get positive learnings (what worked)
363
+ */
364
+ getPositiveLearnings() {
365
+ return this.memory.learnings.filter(l => l.outcome === 'success');
366
+ }
367
+
368
+ /**
369
+ * Get negative learnings (what didn't work)
370
+ */
371
+ getNegativeLearnings() {
372
+ return this.memory.learnings.filter(l => l.outcome === 'failure');
373
+ }
374
+
375
+ // ═══════════════════════════════════════════════════════════════════════════
376
+ // CONTEXT GENERATION
377
+ // ═══════════════════════════════════════════════════════════════════════════
378
+
379
+ /**
380
+ * Generate context summary for Claude Code
381
+ */
382
+ generateContextSummary() {
383
+ const completed = this.getCompletedModules();
384
+ const patterns = this.getPopularPatterns(3);
385
+ const recentDecisions = this.memory.decisions.slice(-5);
386
+
387
+ let summary = `# Agent Memory Context\n\n`;
388
+
389
+ // Project Context
390
+ if (this.memory.projectContext.description) {
391
+ summary += `## Project\n${this.memory.projectContext.description}\n\n`;
392
+ }
393
+
394
+ // Completed Modules
395
+ if (completed.length > 0) {
396
+ summary += `## Completed Modules (${completed.length})\n`;
397
+ for (const mod of completed) {
398
+ summary += `- **${mod.name || mod.id}**: ${mod.files?.length || 0} files\n`;
399
+ }
400
+ summary += '\n';
401
+ }
402
+
403
+ // Key Patterns
404
+ if (patterns.length > 0) {
405
+ summary += `## Established Patterns\n`;
406
+ for (const pat of patterns) {
407
+ summary += `- **${pat.name}**: ${pat.description || pat.pattern}\n`;
408
+ }
409
+ summary += '\n';
410
+ }
411
+
412
+ // Recent Decisions
413
+ if (recentDecisions.length > 0) {
414
+ summary += `## Recent Decisions\n`;
415
+ for (const dec of recentDecisions) {
416
+ summary += `- ${dec.decision}: ${dec.reason || ''}\n`;
417
+ }
418
+ summary += '\n';
419
+ }
420
+
421
+ // File Structure
422
+ const files = Object.keys(this.memory.files);
423
+ if (files.length > 0) {
424
+ summary += `## Created Files (${files.length})\n`;
425
+ summary += '```\n';
426
+ summary += files.slice(0, 20).join('\n');
427
+ if (files.length > 20) {
428
+ summary += `\n... and ${files.length - 20} more`;
429
+ }
430
+ summary += '\n```\n';
431
+ }
432
+
433
+ return summary;
434
+ }
435
+
436
+ /**
437
+ * Get condensed context for prompts (shorter version)
438
+ */
439
+ getCondensedContext() {
440
+ const completed = this.getCompletedModules();
441
+ const files = Object.keys(this.memory.files);
442
+
443
+ return {
444
+ projectType: this.memory.projectContext.type,
445
+ completedModules: completed.map(m => m.id),
446
+ fileCount: files.length,
447
+ recentFiles: files.slice(-10),
448
+ patterns: this.getPopularPatterns(3).map(p => p.name),
449
+ errorCount: this.memory.errors.length,
450
+ fixedCount: this.memory.errors.filter(e => e.fixed).length
451
+ };
452
+ }
453
+
454
+ // ═══════════════════════════════════════════════════════════════════════════
455
+ // UTILITIES
456
+ // ═══════════════════════════════════════════════════════════════════════════
457
+
458
+ /**
459
+ * Get memory statistics
460
+ */
461
+ getStats() {
462
+ return {
463
+ modulesTotal: Object.keys(this.memory.modules).length,
464
+ modulesCompleted: Object.values(this.memory.modules).filter(m => m.status === 'completed').length,
465
+ modulesFailed: Object.values(this.memory.modules).filter(m => m.status === 'failed').length,
466
+ decisionsCount: this.memory.decisions.length,
467
+ patternsCount: this.memory.patterns.length,
468
+ errorsTotal: this.memory.errors.length,
469
+ errorsFixed: this.memory.errors.filter(e => e.fixed).length,
470
+ filesCreated: Object.keys(this.memory.files).length,
471
+ learningsCount: this.memory.learnings.length
472
+ };
473
+ }
474
+
475
+ /**
476
+ * Clear all memory (reset)
477
+ */
478
+ async clear() {
479
+ this.memory = {
480
+ version: '1.0.0',
481
+ created: new Date().toISOString(),
482
+ updated: new Date().toISOString(),
483
+ projectContext: {},
484
+ modules: {},
485
+ decisions: [],
486
+ patterns: [],
487
+ errors: [],
488
+ files: {},
489
+ learnings: [],
490
+ globalContext: ''
491
+ };
492
+ await this.save();
493
+ }
494
+
495
+ /**
496
+ * Export memory to markdown report
497
+ */
498
+ exportToMarkdown() {
499
+ let md = `# Vibecode Agent Memory Report\n\n`;
500
+ md += `Generated: ${new Date().toISOString()}\n\n`;
501
+
502
+ // Stats
503
+ const stats = this.getStats();
504
+ md += `## Statistics\n`;
505
+ md += `| Metric | Value |\n|--------|-------|\n`;
506
+ for (const [key, value] of Object.entries(stats)) {
507
+ md += `| ${key} | ${value} |\n`;
508
+ }
509
+ md += '\n';
510
+
511
+ // Context Summary
512
+ md += this.generateContextSummary();
513
+
514
+ // Errors and Fixes
515
+ if (this.memory.errors.length > 0) {
516
+ md += `## Error History\n\n`;
517
+ for (const err of this.memory.errors.slice(-10)) {
518
+ md += `### ${err.type || 'Error'}\n`;
519
+ md += `- Message: ${err.message}\n`;
520
+ md += `- Module: ${err.moduleId || 'N/A'}\n`;
521
+ if (err.fix) {
522
+ md += `- Fix: ${err.fix}\n`;
523
+ }
524
+ md += '\n';
525
+ }
526
+ }
527
+
528
+ return md;
529
+ }
530
+ }
531
+
532
+ /**
533
+ * Create memory engine instance
534
+ */
535
+ export async function createMemoryEngine(projectPath) {
536
+ const memoryPath = path.join(projectPath, '.vibecode', 'agent', 'memory.json');
537
+ const engine = new MemoryEngine(memoryPath);
538
+ await engine.initialize();
539
+ return engine;
540
+ }
541
+
542
+ export { MEMORY_CATEGORIES };