elsabro 2.3.0 → 3.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 (67) hide show
  1. package/README.md +668 -20
  2. package/bin/install.js +0 -0
  3. package/flows/development-flow.json +452 -0
  4. package/flows/quick-flow.json +118 -0
  5. package/package.json +3 -2
  6. package/references/SYSTEM_INDEX.md +379 -5
  7. package/references/agent-marketplace.md +2274 -0
  8. package/references/agent-protocol.md +1126 -0
  9. package/references/ai-code-suggestions.md +2413 -0
  10. package/references/checkpointing.md +595 -0
  11. package/references/collaboration-patterns.md +851 -0
  12. package/references/collaborative-sessions.md +1081 -0
  13. package/references/configuration-management.md +1810 -0
  14. package/references/cost-tracking.md +1095 -0
  15. package/references/enterprise-sso.md +2001 -0
  16. package/references/error-contracts-v2.md +968 -0
  17. package/references/event-driven.md +1031 -0
  18. package/references/flow-orchestration.md +940 -0
  19. package/references/flow-visualization.md +1557 -0
  20. package/references/ide-integrations.md +3513 -0
  21. package/references/interrupt-system.md +681 -0
  22. package/references/kubernetes-deployment.md +3099 -0
  23. package/references/memory-system.md +683 -0
  24. package/references/mobile-companion.md +3236 -0
  25. package/references/multi-llm-providers.md +2494 -0
  26. package/references/multi-project-memory.md +1182 -0
  27. package/references/observability.md +793 -0
  28. package/references/output-schemas.md +858 -0
  29. package/references/performance-profiler.md +955 -0
  30. package/references/plugin-system.md +1526 -0
  31. package/references/prompt-management.md +292 -0
  32. package/references/sandbox-execution.md +303 -0
  33. package/references/security-system.md +1253 -0
  34. package/references/streaming.md +696 -0
  35. package/references/testing-framework.md +1151 -0
  36. package/references/time-travel.md +802 -0
  37. package/references/tool-registry.md +886 -0
  38. package/references/voice-commands.md +3296 -0
  39. package/templates/agent-marketplace-config.json +220 -0
  40. package/templates/agent-protocol-config.json +136 -0
  41. package/templates/ai-suggestions-config.json +100 -0
  42. package/templates/checkpoint-state.json +61 -0
  43. package/templates/collaboration-config.json +157 -0
  44. package/templates/collaborative-sessions-config.json +153 -0
  45. package/templates/configuration-config.json +245 -0
  46. package/templates/cost-tracking-config.json +148 -0
  47. package/templates/enterprise-sso-config.json +438 -0
  48. package/templates/events-config.json +148 -0
  49. package/templates/flow-visualization-config.json +196 -0
  50. package/templates/ide-integrations-config.json +442 -0
  51. package/templates/kubernetes-config.json +764 -0
  52. package/templates/memory-state.json +84 -0
  53. package/templates/mobile-companion-config.json +600 -0
  54. package/templates/multi-llm-config.json +544 -0
  55. package/templates/multi-project-memory-config.json +145 -0
  56. package/templates/observability-config.json +109 -0
  57. package/templates/performance-profiler-config.json +125 -0
  58. package/templates/plugin-config.json +170 -0
  59. package/templates/prompt-management-config.json +86 -0
  60. package/templates/sandbox-config.json +185 -0
  61. package/templates/schemas-config.json +65 -0
  62. package/templates/security-config.json +120 -0
  63. package/templates/streaming-config.json +72 -0
  64. package/templates/testing-config.json +81 -0
  65. package/templates/timetravel-config.json +62 -0
  66. package/templates/tool-registry-config.json +109 -0
  67. package/templates/voice-commands-config.json +658 -0
@@ -0,0 +1,1182 @@
1
+ # ELSABRO Multi-Project Memory System
2
+
3
+ > Sistema de memoria compartida entre proyectos para reutilizar conocimiento y aprendizajes.
4
+
5
+ ## Arquitectura General
6
+
7
+ ```
8
+ ┌─────────────────────────────────────────────────────────────────────────┐
9
+ │ Multi-Project Memory System │
10
+ ├─────────────────────────────────────────────────────────────────────────┤
11
+ │ ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ │
12
+ │ │ SharedMemoryHub │ │ ProjectLinker │ │ KnowledgeSync │ │
13
+ │ │ ───────────── │ │ ───────────── │ │ ───────────── │ │
14
+ │ │ • Global store │ │ • Link projects │ │ • Sync changes │ │
15
+ │ │ • Namespaces │ │ • Permissions │ │ • Conflicts │ │
16
+ │ │ • Versioning │ │ • Inheritance │ │ • Webhooks │ │
17
+ │ └─────────────────┘ └─────────────────┘ └─────────────────┘ │
18
+ │ │ │
19
+ │ ┌───────────────────────────┴───────────────────────────────┐ │
20
+ │ │ CrossProjectSearch │ │
21
+ │ │ • Semantic search • Pattern matching • Relevance ranking │ │
22
+ │ └────────────────────────────────────────────────────────────┘ │
23
+ │ │ │
24
+ │ ┌───────────────────────────┴───────────────────────────────┐ │
25
+ │ │ KnowledgeExtractor │ │
26
+ │ │ • Auto-extract patterns • Mistakes • Best practices │ │
27
+ │ └────────────────────────────────────────────────────────────┘ │
28
+ └─────────────────────────────────────────────────────────────────────────┘
29
+ ```
30
+
31
+ ---
32
+
33
+ ## 1. SharedMemoryHub
34
+
35
+ ### Propósito
36
+ Almacén central de conocimiento compartido entre proyectos con namespacing y versionado.
37
+
38
+ ### Interfaz
39
+
40
+ ```typescript
41
+ interface MemoryNamespace {
42
+ id: string;
43
+ name: string;
44
+ description?: string;
45
+ visibility: 'private' | 'team' | 'public';
46
+ owner: string;
47
+ projects: string[];
48
+ createdAt: string;
49
+ updatedAt: string;
50
+ }
51
+
52
+ interface SharedMemoryEntry {
53
+ id: string;
54
+ namespace: string;
55
+ type: 'pattern' | 'mistake' | 'preference' | 'snippet' | 'architecture' | 'dependency';
56
+ title: string;
57
+ content: string;
58
+ metadata: Record<string, unknown>;
59
+ tags: string[];
60
+ sourceProject?: string;
61
+ version: number;
62
+ embedding?: number[]; // For semantic search
63
+ usageCount: number;
64
+ lastUsed?: string;
65
+ createdAt: string;
66
+ updatedAt: string;
67
+ }
68
+
69
+ interface SharedMemoryHub {
70
+ // Namespaces
71
+ createNamespace(config: Omit<MemoryNamespace, 'id' | 'createdAt' | 'updatedAt'>): Promise<MemoryNamespace>;
72
+ getNamespace(id: string): Promise<MemoryNamespace | null>;
73
+ listNamespaces(filter?: { visibility?: string; owner?: string }): Promise<MemoryNamespace[]>;
74
+ updateNamespace(id: string, updates: Partial<MemoryNamespace>): Promise<MemoryNamespace>;
75
+ deleteNamespace(id: string): Promise<void>;
76
+
77
+ // Entries
78
+ store(entry: Omit<SharedMemoryEntry, 'id' | 'version' | 'createdAt' | 'updatedAt'>): Promise<SharedMemoryEntry>;
79
+ get(id: string): Promise<SharedMemoryEntry | null>;
80
+ update(id: string, updates: Partial<SharedMemoryEntry>): Promise<SharedMemoryEntry>;
81
+ delete(id: string): Promise<void>;
82
+
83
+ // Query
84
+ query(options: MemoryQueryOptions): Promise<SharedMemoryEntry[]>;
85
+ search(text: string, options?: SearchOptions): Promise<SearchResult[]>;
86
+ findSimilar(entryId: string, limit?: number): Promise<SharedMemoryEntry[]>;
87
+
88
+ // Sync
89
+ exportNamespace(namespaceId: string): Promise<ExportedMemory>;
90
+ importNamespace(data: ExportedMemory, options?: ImportOptions): Promise<MemoryNamespace>;
91
+
92
+ // Stats
93
+ getStats(namespaceId?: string): Promise<MemoryStats>;
94
+ }
95
+
96
+ interface MemoryQueryOptions {
97
+ namespace?: string;
98
+ type?: SharedMemoryEntry['type'];
99
+ tags?: string[];
100
+ sourceProject?: string;
101
+ minUsageCount?: number;
102
+ limit?: number;
103
+ offset?: number;
104
+ orderBy?: 'createdAt' | 'updatedAt' | 'usageCount' | 'relevance';
105
+ }
106
+
107
+ interface SearchResult {
108
+ entry: SharedMemoryEntry;
109
+ score: number;
110
+ highlights: string[];
111
+ }
112
+ ```
113
+
114
+ ### Implementación
115
+
116
+ ```typescript
117
+ class SharedMemoryHubImpl implements SharedMemoryHub {
118
+ private namespaces: Map<string, MemoryNamespace> = new Map();
119
+ private entries: Map<string, SharedMemoryEntry> = new Map();
120
+ private embeddings: Map<string, number[]> = new Map();
121
+ private storagePath: string;
122
+
123
+ constructor(private config: SharedMemoryConfig) {
124
+ this.storagePath = config.storagePath || '~/.elsabro/shared-memory';
125
+ this.loadFromDisk();
126
+ }
127
+
128
+ private async loadFromDisk(): Promise<void> {
129
+ try {
130
+ const namespacesPath = path.join(this.storagePath, 'namespaces.json');
131
+ const entriesPath = path.join(this.storagePath, 'entries.json');
132
+
133
+ if (await this.fileExists(namespacesPath)) {
134
+ const data = JSON.parse(await fs.readFile(namespacesPath, 'utf-8'));
135
+ for (const ns of data) {
136
+ this.namespaces.set(ns.id, ns);
137
+ }
138
+ }
139
+
140
+ if (await this.fileExists(entriesPath)) {
141
+ const data = JSON.parse(await fs.readFile(entriesPath, 'utf-8'));
142
+ for (const entry of data) {
143
+ this.entries.set(entry.id, entry);
144
+ if (entry.embedding) {
145
+ this.embeddings.set(entry.id, entry.embedding);
146
+ }
147
+ }
148
+ }
149
+ } catch (error) {
150
+ console.warn('Failed to load shared memory from disk:', error);
151
+ }
152
+ }
153
+
154
+ private async saveToDisk(): Promise<void> {
155
+ await fs.mkdir(this.storagePath, { recursive: true });
156
+
157
+ const namespacesPath = path.join(this.storagePath, 'namespaces.json');
158
+ const entriesPath = path.join(this.storagePath, 'entries.json');
159
+
160
+ await fs.writeFile(
161
+ namespacesPath,
162
+ JSON.stringify(Array.from(this.namespaces.values()), null, 2)
163
+ );
164
+
165
+ await fs.writeFile(
166
+ entriesPath,
167
+ JSON.stringify(Array.from(this.entries.values()), null, 2)
168
+ );
169
+ }
170
+
171
+ async createNamespace(
172
+ config: Omit<MemoryNamespace, 'id' | 'createdAt' | 'updatedAt'>
173
+ ): Promise<MemoryNamespace> {
174
+ const namespace: MemoryNamespace = {
175
+ ...config,
176
+ id: `ns_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`,
177
+ createdAt: new Date().toISOString(),
178
+ updatedAt: new Date().toISOString()
179
+ };
180
+
181
+ this.namespaces.set(namespace.id, namespace);
182
+ await this.saveToDisk();
183
+ return namespace;
184
+ }
185
+
186
+ async store(
187
+ entry: Omit<SharedMemoryEntry, 'id' | 'version' | 'createdAt' | 'updatedAt'>
188
+ ): Promise<SharedMemoryEntry> {
189
+ const fullEntry: SharedMemoryEntry = {
190
+ ...entry,
191
+ id: `mem_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`,
192
+ version: 1,
193
+ usageCount: 0,
194
+ createdAt: new Date().toISOString(),
195
+ updatedAt: new Date().toISOString()
196
+ };
197
+
198
+ // Generate embedding for semantic search
199
+ if (this.config.enableSemanticSearch) {
200
+ fullEntry.embedding = await this.generateEmbedding(
201
+ `${entry.title} ${entry.content} ${entry.tags.join(' ')}`
202
+ );
203
+ this.embeddings.set(fullEntry.id, fullEntry.embedding);
204
+ }
205
+
206
+ this.entries.set(fullEntry.id, fullEntry);
207
+ await this.saveToDisk();
208
+ return fullEntry;
209
+ }
210
+
211
+ async get(id: string): Promise<SharedMemoryEntry | null> {
212
+ const entry = this.entries.get(id);
213
+ if (entry) {
214
+ // Update usage stats
215
+ entry.usageCount++;
216
+ entry.lastUsed = new Date().toISOString();
217
+ await this.saveToDisk();
218
+ }
219
+ return entry || null;
220
+ }
221
+
222
+ async update(id: string, updates: Partial<SharedMemoryEntry>): Promise<SharedMemoryEntry> {
223
+ const existing = this.entries.get(id);
224
+ if (!existing) {
225
+ throw new Error(`Entry not found: ${id}`);
226
+ }
227
+
228
+ const updated: SharedMemoryEntry = {
229
+ ...existing,
230
+ ...updates,
231
+ id: existing.id, // Prevent ID change
232
+ version: existing.version + 1,
233
+ updatedAt: new Date().toISOString()
234
+ };
235
+
236
+ // Regenerate embedding if content changed
237
+ if (updates.title || updates.content || updates.tags) {
238
+ if (this.config.enableSemanticSearch) {
239
+ updated.embedding = await this.generateEmbedding(
240
+ `${updated.title} ${updated.content} ${updated.tags.join(' ')}`
241
+ );
242
+ this.embeddings.set(updated.id, updated.embedding);
243
+ }
244
+ }
245
+
246
+ this.entries.set(id, updated);
247
+ await this.saveToDisk();
248
+ return updated;
249
+ }
250
+
251
+ async query(options: MemoryQueryOptions): Promise<SharedMemoryEntry[]> {
252
+ let results = Array.from(this.entries.values());
253
+
254
+ // Apply filters
255
+ if (options.namespace) {
256
+ results = results.filter(e => e.namespace === options.namespace);
257
+ }
258
+ if (options.type) {
259
+ results = results.filter(e => e.type === options.type);
260
+ }
261
+ if (options.tags && options.tags.length > 0) {
262
+ results = results.filter(e =>
263
+ options.tags!.some(tag => e.tags.includes(tag))
264
+ );
265
+ }
266
+ if (options.sourceProject) {
267
+ results = results.filter(e => e.sourceProject === options.sourceProject);
268
+ }
269
+ if (options.minUsageCount !== undefined) {
270
+ results = results.filter(e => e.usageCount >= options.minUsageCount!);
271
+ }
272
+
273
+ // Sort
274
+ switch (options.orderBy) {
275
+ case 'createdAt':
276
+ results.sort((a, b) => b.createdAt.localeCompare(a.createdAt));
277
+ break;
278
+ case 'updatedAt':
279
+ results.sort((a, b) => b.updatedAt.localeCompare(a.updatedAt));
280
+ break;
281
+ case 'usageCount':
282
+ results.sort((a, b) => b.usageCount - a.usageCount);
283
+ break;
284
+ default:
285
+ results.sort((a, b) => b.updatedAt.localeCompare(a.updatedAt));
286
+ }
287
+
288
+ // Pagination
289
+ const offset = options.offset || 0;
290
+ const limit = options.limit || 50;
291
+ return results.slice(offset, offset + limit);
292
+ }
293
+
294
+ async search(text: string, options?: SearchOptions): Promise<SearchResult[]> {
295
+ const results: SearchResult[] = [];
296
+ const searchTerms = text.toLowerCase().split(/\s+/);
297
+
298
+ for (const entry of this.entries.values()) {
299
+ // Apply namespace filter
300
+ if (options?.namespace && entry.namespace !== options.namespace) {
301
+ continue;
302
+ }
303
+
304
+ // Text search
305
+ const searchableText = `${entry.title} ${entry.content} ${entry.tags.join(' ')}`.toLowerCase();
306
+ const matchedTerms = searchTerms.filter(term => searchableText.includes(term));
307
+
308
+ if (matchedTerms.length === 0) continue;
309
+
310
+ // Calculate score
311
+ let score = matchedTerms.length / searchTerms.length;
312
+
313
+ // Boost exact title match
314
+ if (entry.title.toLowerCase().includes(text.toLowerCase())) {
315
+ score += 0.5;
316
+ }
317
+
318
+ // Boost by usage
319
+ score += Math.min(entry.usageCount / 100, 0.2);
320
+
321
+ // Generate highlights
322
+ const highlights = this.generateHighlights(entry.content, searchTerms);
323
+
324
+ results.push({ entry, score, highlights });
325
+ }
326
+
327
+ // Semantic search if enabled
328
+ if (this.config.enableSemanticSearch && options?.useSemanticSearch !== false) {
329
+ const semanticResults = await this.semanticSearch(text, options?.limit || 10);
330
+
331
+ // Merge results
332
+ for (const sr of semanticResults) {
333
+ const existing = results.find(r => r.entry.id === sr.entry.id);
334
+ if (existing) {
335
+ existing.score = Math.max(existing.score, sr.score);
336
+ } else {
337
+ results.push(sr);
338
+ }
339
+ }
340
+ }
341
+
342
+ // Sort by score
343
+ results.sort((a, b) => b.score - a.score);
344
+
345
+ return results.slice(0, options?.limit || 20);
346
+ }
347
+
348
+ private async semanticSearch(text: string, limit: number): Promise<SearchResult[]> {
349
+ const queryEmbedding = await this.generateEmbedding(text);
350
+ const results: SearchResult[] = [];
351
+
352
+ for (const [id, embedding] of this.embeddings) {
353
+ const similarity = this.cosineSimilarity(queryEmbedding, embedding);
354
+ if (similarity > 0.5) { // Threshold
355
+ const entry = this.entries.get(id)!;
356
+ results.push({
357
+ entry,
358
+ score: similarity,
359
+ highlights: []
360
+ });
361
+ }
362
+ }
363
+
364
+ results.sort((a, b) => b.score - a.score);
365
+ return results.slice(0, limit);
366
+ }
367
+
368
+ async findSimilar(entryId: string, limit: number = 5): Promise<SharedMemoryEntry[]> {
369
+ const sourceEntry = this.entries.get(entryId);
370
+ if (!sourceEntry) return [];
371
+
372
+ const sourceEmbedding = this.embeddings.get(entryId);
373
+ if (!sourceEmbedding) {
374
+ // Fallback to tag-based similarity
375
+ return this.findSimilarByTags(sourceEntry, limit);
376
+ }
377
+
378
+ const similarities: Array<{ entry: SharedMemoryEntry; score: number }> = [];
379
+
380
+ for (const [id, embedding] of this.embeddings) {
381
+ if (id === entryId) continue;
382
+
383
+ const similarity = this.cosineSimilarity(sourceEmbedding, embedding);
384
+ const entry = this.entries.get(id)!;
385
+
386
+ // Boost same type
387
+ const typeBoost = entry.type === sourceEntry.type ? 0.1 : 0;
388
+
389
+ similarities.push({
390
+ entry,
391
+ score: similarity + typeBoost
392
+ });
393
+ }
394
+
395
+ similarities.sort((a, b) => b.score - a.score);
396
+ return similarities.slice(0, limit).map(s => s.entry);
397
+ }
398
+
399
+ private findSimilarByTags(entry: SharedMemoryEntry, limit: number): SharedMemoryEntry[] {
400
+ const results: Array<{ entry: SharedMemoryEntry; score: number }> = [];
401
+
402
+ for (const [id, other] of this.entries) {
403
+ if (id === entry.id) continue;
404
+
405
+ const commonTags = entry.tags.filter(t => other.tags.includes(t));
406
+ if (commonTags.length === 0) continue;
407
+
408
+ const score = commonTags.length / Math.max(entry.tags.length, other.tags.length);
409
+ results.push({ entry: other, score });
410
+ }
411
+
412
+ results.sort((a, b) => b.score - a.score);
413
+ return results.slice(0, limit).map(r => r.entry);
414
+ }
415
+
416
+ async exportNamespace(namespaceId: string): Promise<ExportedMemory> {
417
+ const namespace = this.namespaces.get(namespaceId);
418
+ if (!namespace) {
419
+ throw new Error(`Namespace not found: ${namespaceId}`);
420
+ }
421
+
422
+ const entries = Array.from(this.entries.values())
423
+ .filter(e => e.namespace === namespaceId);
424
+
425
+ return {
426
+ version: '1.0.0',
427
+ exportedAt: new Date().toISOString(),
428
+ namespace,
429
+ entries
430
+ };
431
+ }
432
+
433
+ async importNamespace(
434
+ data: ExportedMemory,
435
+ options?: ImportOptions
436
+ ): Promise<MemoryNamespace> {
437
+ // Create or update namespace
438
+ let namespace: MemoryNamespace;
439
+
440
+ if (options?.mergeInto) {
441
+ namespace = this.namespaces.get(options.mergeInto)!;
442
+ if (!namespace) {
443
+ throw new Error(`Target namespace not found: ${options.mergeInto}`);
444
+ }
445
+ } else {
446
+ namespace = await this.createNamespace({
447
+ ...data.namespace,
448
+ name: options?.rename || data.namespace.name
449
+ });
450
+ }
451
+
452
+ // Import entries
453
+ for (const entry of data.entries) {
454
+ // Check for duplicates
455
+ if (options?.skipDuplicates) {
456
+ const exists = Array.from(this.entries.values()).some(
457
+ e => e.namespace === namespace.id &&
458
+ e.title === entry.title &&
459
+ e.type === entry.type
460
+ );
461
+ if (exists) continue;
462
+ }
463
+
464
+ await this.store({
465
+ ...entry,
466
+ namespace: namespace.id,
467
+ sourceProject: entry.sourceProject || data.namespace.name
468
+ });
469
+ }
470
+
471
+ return namespace;
472
+ }
473
+
474
+ async getStats(namespaceId?: string): Promise<MemoryStats> {
475
+ let entries = Array.from(this.entries.values());
476
+
477
+ if (namespaceId) {
478
+ entries = entries.filter(e => e.namespace === namespaceId);
479
+ }
480
+
481
+ const byType = new Map<string, number>();
482
+ let totalUsage = 0;
483
+
484
+ for (const entry of entries) {
485
+ byType.set(entry.type, (byType.get(entry.type) || 0) + 1);
486
+ totalUsage += entry.usageCount;
487
+ }
488
+
489
+ return {
490
+ totalEntries: entries.length,
491
+ byType: Object.fromEntries(byType),
492
+ totalUsage,
493
+ averageUsage: entries.length > 0 ? totalUsage / entries.length : 0,
494
+ namespaceCount: namespaceId ? 1 : this.namespaces.size
495
+ };
496
+ }
497
+
498
+ private async generateEmbedding(text: string): Promise<number[]> {
499
+ // Simple hash-based embedding for demo
500
+ // In production, use a proper embedding model
501
+ const hash = this.simpleHash(text);
502
+ const embedding: number[] = [];
503
+
504
+ for (let i = 0; i < 128; i++) {
505
+ embedding.push(Math.sin(hash * (i + 1)) * 0.5 + 0.5);
506
+ }
507
+
508
+ return embedding;
509
+ }
510
+
511
+ private simpleHash(str: string): number {
512
+ let hash = 0;
513
+ for (let i = 0; i < str.length; i++) {
514
+ const char = str.charCodeAt(i);
515
+ hash = ((hash << 5) - hash) + char;
516
+ hash = hash & hash;
517
+ }
518
+ return hash;
519
+ }
520
+
521
+ private cosineSimilarity(a: number[], b: number[]): number {
522
+ let dotProduct = 0;
523
+ let normA = 0;
524
+ let normB = 0;
525
+
526
+ for (let i = 0; i < a.length; i++) {
527
+ dotProduct += a[i] * b[i];
528
+ normA += a[i] * a[i];
529
+ normB += b[i] * b[i];
530
+ }
531
+
532
+ return dotProduct / (Math.sqrt(normA) * Math.sqrt(normB));
533
+ }
534
+
535
+ private generateHighlights(content: string, terms: string[]): string[] {
536
+ const highlights: string[] = [];
537
+ const sentences = content.split(/[.!?]+/);
538
+
539
+ for (const sentence of sentences) {
540
+ const lower = sentence.toLowerCase();
541
+ if (terms.some(term => lower.includes(term))) {
542
+ highlights.push(sentence.trim());
543
+ if (highlights.length >= 3) break;
544
+ }
545
+ }
546
+
547
+ return highlights;
548
+ }
549
+
550
+ private async fileExists(path: string): Promise<boolean> {
551
+ try {
552
+ await fs.access(path);
553
+ return true;
554
+ } catch {
555
+ return false;
556
+ }
557
+ }
558
+ }
559
+ ```
560
+
561
+ ---
562
+
563
+ ## 2. ProjectLinker
564
+
565
+ ### Propósito
566
+ Gestiona las relaciones entre proyectos para compartir conocimiento.
567
+
568
+ ### Interfaz
569
+
570
+ ```typescript
571
+ interface ProjectLink {
572
+ id: string;
573
+ sourceProject: string;
574
+ targetProject: string;
575
+ direction: 'unidirectional' | 'bidirectional';
576
+ shareTypes: SharedMemoryEntry['type'][];
577
+ autoSync: boolean;
578
+ syncInterval?: number;
579
+ permissions: LinkPermissions;
580
+ createdAt: string;
581
+ }
582
+
583
+ interface LinkPermissions {
584
+ canRead: boolean;
585
+ canWrite: boolean;
586
+ canDelete: boolean;
587
+ canShare: boolean;
588
+ }
589
+
590
+ interface ProjectLinker {
591
+ // Link management
592
+ createLink(config: Omit<ProjectLink, 'id' | 'createdAt'>): Promise<ProjectLink>;
593
+ getLink(id: string): Promise<ProjectLink | null>;
594
+ listLinks(projectId: string): Promise<ProjectLink[]>;
595
+ updateLink(id: string, updates: Partial<ProjectLink>): Promise<ProjectLink>;
596
+ removeLink(id: string): Promise<void>;
597
+
598
+ // Project operations
599
+ registerProject(project: ProjectInfo): Promise<void>;
600
+ unregisterProject(projectId: string): Promise<void>;
601
+ getProjectInfo(projectId: string): Promise<ProjectInfo | null>;
602
+ listProjects(): Promise<ProjectInfo[]>;
603
+
604
+ // Sharing
605
+ shareEntry(entryId: string, targetProjectId: string): Promise<void>;
606
+ unshareEntry(entryId: string, targetProjectId: string): Promise<void>;
607
+ getSharedWith(entryId: string): Promise<string[]>;
608
+
609
+ // Discovery
610
+ discoverRelatedProjects(projectId: string): Promise<ProjectSuggestion[]>;
611
+ }
612
+
613
+ interface ProjectInfo {
614
+ id: string;
615
+ name: string;
616
+ path: string;
617
+ techStack: string[];
618
+ description?: string;
619
+ lastActive: string;
620
+ }
621
+
622
+ interface ProjectSuggestion {
623
+ project: ProjectInfo;
624
+ relevanceScore: number;
625
+ reason: string;
626
+ sharedTags: string[];
627
+ }
628
+ ```
629
+
630
+ ### Implementación
631
+
632
+ ```typescript
633
+ class ProjectLinkerImpl implements ProjectLinker {
634
+ private links: Map<string, ProjectLink> = new Map();
635
+ private projects: Map<string, ProjectInfo> = new Map();
636
+ private entryShares: Map<string, Set<string>> = new Map(); // entryId -> projectIds
637
+
638
+ constructor(
639
+ private hub: SharedMemoryHub,
640
+ private config: ProjectLinkerConfig
641
+ ) {
642
+ this.loadState();
643
+ }
644
+
645
+ async createLink(
646
+ config: Omit<ProjectLink, 'id' | 'createdAt'>
647
+ ): Promise<ProjectLink> {
648
+ // Validate projects exist
649
+ if (!this.projects.has(config.sourceProject)) {
650
+ throw new Error(`Source project not found: ${config.sourceProject}`);
651
+ }
652
+ if (!this.projects.has(config.targetProject)) {
653
+ throw new Error(`Target project not found: ${config.targetProject}`);
654
+ }
655
+
656
+ const link: ProjectLink = {
657
+ ...config,
658
+ id: `link_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`,
659
+ createdAt: new Date().toISOString()
660
+ };
661
+
662
+ this.links.set(link.id, link);
663
+ await this.saveState();
664
+
665
+ // Setup auto-sync if enabled
666
+ if (config.autoSync && config.syncInterval) {
667
+ this.setupAutoSync(link);
668
+ }
669
+
670
+ return link;
671
+ }
672
+
673
+ async listLinks(projectId: string): Promise<ProjectLink[]> {
674
+ return Array.from(this.links.values()).filter(
675
+ link => link.sourceProject === projectId || link.targetProject === projectId
676
+ );
677
+ }
678
+
679
+ async registerProject(project: ProjectInfo): Promise<void> {
680
+ this.projects.set(project.id, {
681
+ ...project,
682
+ lastActive: new Date().toISOString()
683
+ });
684
+ await this.saveState();
685
+ }
686
+
687
+ async shareEntry(entryId: string, targetProjectId: string): Promise<void> {
688
+ if (!this.entryShares.has(entryId)) {
689
+ this.entryShares.set(entryId, new Set());
690
+ }
691
+ this.entryShares.get(entryId)!.add(targetProjectId);
692
+ await this.saveState();
693
+ }
694
+
695
+ async discoverRelatedProjects(projectId: string): Promise<ProjectSuggestion[]> {
696
+ const currentProject = this.projects.get(projectId);
697
+ if (!currentProject) return [];
698
+
699
+ const suggestions: ProjectSuggestion[] = [];
700
+
701
+ for (const [id, project] of this.projects) {
702
+ if (id === projectId) continue;
703
+
704
+ // Calculate relevance
705
+ const sharedTags = currentProject.techStack.filter(
706
+ tech => project.techStack.includes(tech)
707
+ );
708
+
709
+ if (sharedTags.length === 0) continue;
710
+
711
+ const relevanceScore = sharedTags.length /
712
+ Math.max(currentProject.techStack.length, project.techStack.length);
713
+
714
+ suggestions.push({
715
+ project,
716
+ relevanceScore,
717
+ reason: this.generateReason(sharedTags),
718
+ sharedTags
719
+ });
720
+ }
721
+
722
+ // Sort by relevance
723
+ suggestions.sort((a, b) => b.relevanceScore - a.relevanceScore);
724
+ return suggestions.slice(0, 5);
725
+ }
726
+
727
+ private generateReason(sharedTags: string[]): string {
728
+ if (sharedTags.length >= 3) {
729
+ return `Shares ${sharedTags.length} technologies: ${sharedTags.slice(0, 3).join(', ')}`;
730
+ } else if (sharedTags.length > 0) {
731
+ return `Both use ${sharedTags.join(' and ')}`;
732
+ }
733
+ return 'Potentially related project';
734
+ }
735
+
736
+ private setupAutoSync(link: ProjectLink): void {
737
+ setInterval(async () => {
738
+ await this.syncLink(link);
739
+ }, link.syncInterval);
740
+ }
741
+
742
+ private async syncLink(link: ProjectLink): Promise<void> {
743
+ // Get entries from source that match share types
744
+ const entries = await this.hub.query({
745
+ sourceProject: link.sourceProject,
746
+ type: link.shareTypes[0] // Simplified - would iterate all types
747
+ });
748
+
749
+ // Share each to target
750
+ for (const entry of entries) {
751
+ await this.shareEntry(entry.id, link.targetProject);
752
+ }
753
+ }
754
+
755
+ private async loadState(): Promise<void> {
756
+ // Load from disk
757
+ }
758
+
759
+ private async saveState(): Promise<void> {
760
+ // Save to disk
761
+ }
762
+ }
763
+ ```
764
+
765
+ ---
766
+
767
+ ## 3. KnowledgeSync
768
+
769
+ ### Propósito
770
+ Sincroniza conocimiento entre proyectos con manejo de conflictos.
771
+
772
+ ### Interfaz
773
+
774
+ ```typescript
775
+ interface SyncOperation {
776
+ id: string;
777
+ type: 'push' | 'pull' | 'merge';
778
+ sourceProject: string;
779
+ targetProject: string;
780
+ entries: string[];
781
+ status: 'pending' | 'in_progress' | 'completed' | 'failed' | 'conflict';
782
+ conflicts?: SyncConflict[];
783
+ startedAt: string;
784
+ completedAt?: string;
785
+ }
786
+
787
+ interface SyncConflict {
788
+ entryId: string;
789
+ localVersion: SharedMemoryEntry;
790
+ remoteVersion: SharedMemoryEntry;
791
+ resolution?: 'local' | 'remote' | 'merge' | 'skip';
792
+ }
793
+
794
+ interface KnowledgeSync {
795
+ // Sync operations
796
+ push(sourceProject: string, targetProject: string, entryIds?: string[]): Promise<SyncOperation>;
797
+ pull(sourceProject: string, targetProject: string, entryIds?: string[]): Promise<SyncOperation>;
798
+ sync(projectA: string, projectB: string): Promise<SyncOperation>;
799
+
800
+ // Conflict resolution
801
+ getConflicts(operationId: string): Promise<SyncConflict[]>;
802
+ resolveConflict(operationId: string, entryId: string, resolution: SyncConflict['resolution']): Promise<void>;
803
+ resolveAllConflicts(operationId: string, resolution: SyncConflict['resolution']): Promise<void>;
804
+
805
+ // Status
806
+ getOperation(id: string): Promise<SyncOperation | null>;
807
+ listOperations(projectId: string): Promise<SyncOperation[]>;
808
+ cancelOperation(id: string): Promise<void>;
809
+
810
+ // Webhooks
811
+ registerWebhook(projectId: string, url: string, events: string[]): Promise<string>;
812
+ removeWebhook(webhookId: string): Promise<void>;
813
+ }
814
+ ```
815
+
816
+ ### Implementación
817
+
818
+ ```typescript
819
+ class KnowledgeSyncImpl implements KnowledgeSync {
820
+ private operations: Map<string, SyncOperation> = new Map();
821
+ private webhooks: Map<string, WebhookConfig[]> = new Map();
822
+
823
+ constructor(
824
+ private hub: SharedMemoryHub,
825
+ private linker: ProjectLinker
826
+ ) {}
827
+
828
+ async push(
829
+ sourceProject: string,
830
+ targetProject: string,
831
+ entryIds?: string[]
832
+ ): Promise<SyncOperation> {
833
+ const operation: SyncOperation = {
834
+ id: `sync_${Date.now()}`,
835
+ type: 'push',
836
+ sourceProject,
837
+ targetProject,
838
+ entries: entryIds || [],
839
+ status: 'pending',
840
+ startedAt: new Date().toISOString()
841
+ };
842
+
843
+ this.operations.set(operation.id, operation);
844
+
845
+ try {
846
+ operation.status = 'in_progress';
847
+
848
+ // Get entries to sync
849
+ let entries: SharedMemoryEntry[];
850
+ if (entryIds && entryIds.length > 0) {
851
+ entries = await Promise.all(
852
+ entryIds.map(id => this.hub.get(id))
853
+ ).then(results => results.filter((e): e is SharedMemoryEntry => e !== null));
854
+ } else {
855
+ entries = await this.hub.query({ sourceProject });
856
+ }
857
+
858
+ operation.entries = entries.map(e => e.id);
859
+
860
+ // Check for conflicts
861
+ const conflicts: SyncConflict[] = [];
862
+ for (const entry of entries) {
863
+ const existingEntries = await this.hub.query({
864
+ sourceProject: targetProject,
865
+ type: entry.type
866
+ });
867
+
868
+ const duplicate = existingEntries.find(
869
+ e => e.title === entry.title
870
+ );
871
+
872
+ if (duplicate && duplicate.version !== entry.version) {
873
+ conflicts.push({
874
+ entryId: entry.id,
875
+ localVersion: duplicate,
876
+ remoteVersion: entry
877
+ });
878
+ }
879
+ }
880
+
881
+ if (conflicts.length > 0) {
882
+ operation.status = 'conflict';
883
+ operation.conflicts = conflicts;
884
+ } else {
885
+ // No conflicts, proceed with sync
886
+ for (const entry of entries) {
887
+ await this.hub.store({
888
+ ...entry,
889
+ id: undefined as any, // Generate new ID
890
+ sourceProject: targetProject,
891
+ metadata: {
892
+ ...entry.metadata,
893
+ syncedFrom: sourceProject,
894
+ syncedAt: new Date().toISOString()
895
+ }
896
+ });
897
+ }
898
+
899
+ operation.status = 'completed';
900
+ operation.completedAt = new Date().toISOString();
901
+
902
+ // Trigger webhooks
903
+ await this.triggerWebhooks(targetProject, 'sync:completed', operation);
904
+ }
905
+ } catch (error) {
906
+ operation.status = 'failed';
907
+ operation.completedAt = new Date().toISOString();
908
+ }
909
+
910
+ return operation;
911
+ }
912
+
913
+ async resolveConflict(
914
+ operationId: string,
915
+ entryId: string,
916
+ resolution: SyncConflict['resolution']
917
+ ): Promise<void> {
918
+ const operation = this.operations.get(operationId);
919
+ if (!operation || !operation.conflicts) {
920
+ throw new Error('Operation not found or has no conflicts');
921
+ }
922
+
923
+ const conflict = operation.conflicts.find(c => c.entryId === entryId);
924
+ if (!conflict) {
925
+ throw new Error('Conflict not found');
926
+ }
927
+
928
+ conflict.resolution = resolution;
929
+
930
+ // Apply resolution
931
+ switch (resolution) {
932
+ case 'local':
933
+ // Keep local, do nothing
934
+ break;
935
+ case 'remote':
936
+ // Overwrite with remote
937
+ await this.hub.update(conflict.localVersion.id, conflict.remoteVersion);
938
+ break;
939
+ case 'merge':
940
+ // Merge content
941
+ const merged = this.mergeEntries(conflict.localVersion, conflict.remoteVersion);
942
+ await this.hub.update(conflict.localVersion.id, merged);
943
+ break;
944
+ case 'skip':
945
+ // Do nothing
946
+ break;
947
+ }
948
+
949
+ // Check if all conflicts are resolved
950
+ const unresolvedConflicts = operation.conflicts.filter(c => !c.resolution);
951
+ if (unresolvedConflicts.length === 0) {
952
+ operation.status = 'completed';
953
+ operation.completedAt = new Date().toISOString();
954
+ }
955
+ }
956
+
957
+ private mergeEntries(
958
+ local: SharedMemoryEntry,
959
+ remote: SharedMemoryEntry
960
+ ): Partial<SharedMemoryEntry> {
961
+ return {
962
+ content: `${local.content}\n\n---\n\n${remote.content}`,
963
+ tags: [...new Set([...local.tags, ...remote.tags])],
964
+ metadata: {
965
+ ...local.metadata,
966
+ ...remote.metadata,
967
+ merged: true,
968
+ mergedAt: new Date().toISOString()
969
+ }
970
+ };
971
+ }
972
+
973
+ private async triggerWebhooks(
974
+ projectId: string,
975
+ event: string,
976
+ data: unknown
977
+ ): Promise<void> {
978
+ const projectWebhooks = this.webhooks.get(projectId) || [];
979
+
980
+ for (const webhook of projectWebhooks) {
981
+ if (!webhook.events.includes(event)) continue;
982
+
983
+ try {
984
+ await fetch(webhook.url, {
985
+ method: 'POST',
986
+ headers: { 'Content-Type': 'application/json' },
987
+ body: JSON.stringify({ event, data, timestamp: new Date().toISOString() })
988
+ });
989
+ } catch (error) {
990
+ console.warn(`Webhook failed: ${webhook.url}`, error);
991
+ }
992
+ }
993
+ }
994
+ }
995
+ ```
996
+
997
+ ---
998
+
999
+ ## 4. CrossProjectSearch
1000
+
1001
+ ### Propósito
1002
+ Búsqueda semántica y por patrones a través de múltiples proyectos.
1003
+
1004
+ ### Interfaz
1005
+
1006
+ ```typescript
1007
+ interface CrossProjectSearchOptions {
1008
+ query: string;
1009
+ projects?: string[]; // Search specific projects, or all if not specified
1010
+ types?: SharedMemoryEntry['type'][];
1011
+ minRelevance?: number;
1012
+ limit?: number;
1013
+ includePrivate?: boolean;
1014
+ useSemanticSearch?: boolean;
1015
+ }
1016
+
1017
+ interface CrossProjectSearchResult {
1018
+ entry: SharedMemoryEntry;
1019
+ project: ProjectInfo;
1020
+ relevanceScore: number;
1021
+ highlights: string[];
1022
+ matchType: 'exact' | 'partial' | 'semantic';
1023
+ }
1024
+
1025
+ interface CrossProjectSearch {
1026
+ search(options: CrossProjectSearchOptions): Promise<CrossProjectSearchResult[]>;
1027
+ searchPatterns(pattern: string, projects?: string[]): Promise<CrossProjectSearchResult[]>;
1028
+ searchByTags(tags: string[], projects?: string[]): Promise<CrossProjectSearchResult[]>;
1029
+ getSuggestions(partialQuery: string): Promise<string[]>;
1030
+ }
1031
+ ```
1032
+
1033
+ ### Uso
1034
+
1035
+ ```typescript
1036
+ const search = new CrossProjectSearch(hub, linker);
1037
+
1038
+ // Search across all projects
1039
+ const results = await search.search({
1040
+ query: 'authentication JWT refresh token',
1041
+ types: ['pattern', 'snippet'],
1042
+ minRelevance: 0.5,
1043
+ limit: 20
1044
+ });
1045
+
1046
+ // Display results
1047
+ for (const result of results) {
1048
+ console.log(`[${result.project.name}] ${result.entry.title}`);
1049
+ console.log(` Type: ${result.entry.type} | Score: ${result.relevanceScore.toFixed(2)}`);
1050
+ console.log(` ${result.highlights[0]}`);
1051
+ }
1052
+ ```
1053
+
1054
+ ---
1055
+
1056
+ ## 5. KnowledgeExtractor
1057
+
1058
+ ### Propósito
1059
+ Extrae automáticamente patrones, errores y mejores prácticas del código y conversaciones.
1060
+
1061
+ ### Interfaz
1062
+
1063
+ ```typescript
1064
+ interface ExtractionSource {
1065
+ type: 'code' | 'conversation' | 'commit' | 'review';
1066
+ content: string;
1067
+ metadata?: Record<string, unknown>;
1068
+ }
1069
+
1070
+ interface ExtractedKnowledge {
1071
+ type: SharedMemoryEntry['type'];
1072
+ title: string;
1073
+ content: string;
1074
+ confidence: number;
1075
+ tags: string[];
1076
+ source: string;
1077
+ }
1078
+
1079
+ interface KnowledgeExtractor {
1080
+ extract(source: ExtractionSource): Promise<ExtractedKnowledge[]>;
1081
+ extractPatterns(code: string): Promise<ExtractedKnowledge[]>;
1082
+ extractMistakes(conversation: string): Promise<ExtractedKnowledge[]>;
1083
+ extractPreferences(history: string[]): Promise<ExtractedKnowledge[]>;
1084
+ autoStore(knowledge: ExtractedKnowledge, namespace: string): Promise<SharedMemoryEntry>;
1085
+ }
1086
+ ```
1087
+
1088
+ ### Triggers de Extracción
1089
+
1090
+ ```typescript
1091
+ // Auto-extract on user correction
1092
+ const correctionPattern = /no,?\s+(use|do|prefer|always|never)/i;
1093
+
1094
+ // Auto-extract on explicit teaching
1095
+ const teachingPatterns = [
1096
+ /in this project,?\s+we\s+(always|never|prefer)/i,
1097
+ /remember to\s+/i,
1098
+ /don't forget\s+/i,
1099
+ /the pattern here is\s+/i
1100
+ ];
1101
+
1102
+ // Auto-extract from code review
1103
+ const reviewPatterns = [
1104
+ /this could be improved by/i,
1105
+ /better approach would be/i,
1106
+ /avoid doing/i
1107
+ ];
1108
+ ```
1109
+
1110
+ ---
1111
+
1112
+ ## 6. Comandos CLI
1113
+
1114
+ ```bash
1115
+ # Listar proyectos registrados
1116
+ /elsabro:memory projects
1117
+
1118
+ # Registrar proyecto actual
1119
+ /elsabro:memory register
1120
+
1121
+ # Buscar en todos los proyectos
1122
+ /elsabro:memory search "authentication pattern"
1123
+
1124
+ # Compartir conocimiento
1125
+ /elsabro:memory share <entry-id> --to <project>
1126
+
1127
+ # Ver sugerencias de proyectos relacionados
1128
+ /elsabro:memory discover
1129
+
1130
+ # Sincronizar con otro proyecto
1131
+ /elsabro:memory sync <project> --direction bidirectional
1132
+
1133
+ # Exportar namespace
1134
+ /elsabro:memory export <namespace> --output memory-export.json
1135
+
1136
+ # Importar namespace
1137
+ /elsabro:memory import memory-export.json --merge-into default
1138
+ ```
1139
+
1140
+ ---
1141
+
1142
+ ## 7. Dashboard
1143
+
1144
+ ```
1145
+ ╔══════════════════════════════════════════════════════════════════════════════╗
1146
+ ║ Multi-Project Memory Hub ║
1147
+ ╠══════════════════════════════════════════════════════════════════════════════╣
1148
+ ║ Namespaces: 3 │ Entries: 247 │ Projects: 8 │ Links: 12 ║
1149
+ ╠══════════════════════════════════════════════════════════════════════════════╣
1150
+ ║ Recent Activity ║
1151
+ ├──────────────────────────────────────────────────────────────────────────────┤
1152
+ │ [2h ago] Pattern synced: "React Query cache pattern" → webapp-2 │
1153
+ │ [3h ago] Mistake learned: "Don't use any in TypeScript interfaces" │
1154
+ │ [5h ago] Link created: mobile-app ↔ webapp-1 (bidirectional) │
1155
+ │ [1d ago] Preference stored: "Prefer Zod over Yup for validation" │
1156
+ ╠══════════════════════════════════════════════════════════════════════════════╣
1157
+ ║ Top Shared Knowledge │
1158
+ ├──────────────────────────────────────────────────────────────────────────────┤
1159
+ │ 1. [pattern] JWT Authentication Flow Used: 47x Projects: 5 │
1160
+ │ 2. [pattern] Error Boundary Component Used: 38x Projects: 4 │
1161
+ │ 3. [mistake] Forgot to handle loading state Used: 29x Projects: 6 │
1162
+ │ 4. [snippet] API Client with Retry Used: 24x Projects: 3 │
1163
+ │ 5. [preference] Use pnpm over npm Used: 19x Projects: 7 │
1164
+ ╠══════════════════════════════════════════════════════════════════════════════╣
1165
+ ║ Related Projects │
1166
+ ├──────────────────────────────────────────────────────────────────────────────┤
1167
+ │ Current: webapp-1 (Next.js, TypeScript, Prisma) │
1168
+ │ │
1169
+ │ Suggested links: │
1170
+ │ • webapp-2 (95% match) - Shares: Next.js, TypeScript, TailwindCSS │
1171
+ │ • api-service (78% match) - Shares: TypeScript, Prisma │
1172
+ │ • mobile-app (62% match) - Shares: TypeScript, React │
1173
+ ╚══════════════════════════════════════════════════════════════════════════════╝
1174
+ ```
1175
+
1176
+ ---
1177
+
1178
+ ## Referencias
1179
+
1180
+ - **REF-009**: Memory System
1181
+ - **REF-019**: Event-Driven Architecture
1182
+ - **REF-029**: Esta referencia (Multi-Project Memory)