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.
- package/README.md +668 -20
- package/bin/install.js +0 -0
- package/flows/development-flow.json +452 -0
- package/flows/quick-flow.json +118 -0
- package/package.json +3 -2
- package/references/SYSTEM_INDEX.md +379 -5
- package/references/agent-marketplace.md +2274 -0
- package/references/agent-protocol.md +1126 -0
- package/references/ai-code-suggestions.md +2413 -0
- package/references/checkpointing.md +595 -0
- package/references/collaboration-patterns.md +851 -0
- package/references/collaborative-sessions.md +1081 -0
- package/references/configuration-management.md +1810 -0
- package/references/cost-tracking.md +1095 -0
- package/references/enterprise-sso.md +2001 -0
- package/references/error-contracts-v2.md +968 -0
- package/references/event-driven.md +1031 -0
- package/references/flow-orchestration.md +940 -0
- package/references/flow-visualization.md +1557 -0
- package/references/ide-integrations.md +3513 -0
- package/references/interrupt-system.md +681 -0
- package/references/kubernetes-deployment.md +3099 -0
- package/references/memory-system.md +683 -0
- package/references/mobile-companion.md +3236 -0
- package/references/multi-llm-providers.md +2494 -0
- package/references/multi-project-memory.md +1182 -0
- package/references/observability.md +793 -0
- package/references/output-schemas.md +858 -0
- package/references/performance-profiler.md +955 -0
- package/references/plugin-system.md +1526 -0
- package/references/prompt-management.md +292 -0
- package/references/sandbox-execution.md +303 -0
- package/references/security-system.md +1253 -0
- package/references/streaming.md +696 -0
- package/references/testing-framework.md +1151 -0
- package/references/time-travel.md +802 -0
- package/references/tool-registry.md +886 -0
- package/references/voice-commands.md +3296 -0
- package/templates/agent-marketplace-config.json +220 -0
- package/templates/agent-protocol-config.json +136 -0
- package/templates/ai-suggestions-config.json +100 -0
- package/templates/checkpoint-state.json +61 -0
- package/templates/collaboration-config.json +157 -0
- package/templates/collaborative-sessions-config.json +153 -0
- package/templates/configuration-config.json +245 -0
- package/templates/cost-tracking-config.json +148 -0
- package/templates/enterprise-sso-config.json +438 -0
- package/templates/events-config.json +148 -0
- package/templates/flow-visualization-config.json +196 -0
- package/templates/ide-integrations-config.json +442 -0
- package/templates/kubernetes-config.json +764 -0
- package/templates/memory-state.json +84 -0
- package/templates/mobile-companion-config.json +600 -0
- package/templates/multi-llm-config.json +544 -0
- package/templates/multi-project-memory-config.json +145 -0
- package/templates/observability-config.json +109 -0
- package/templates/performance-profiler-config.json +125 -0
- package/templates/plugin-config.json +170 -0
- package/templates/prompt-management-config.json +86 -0
- package/templates/sandbox-config.json +185 -0
- package/templates/schemas-config.json +65 -0
- package/templates/security-config.json +120 -0
- package/templates/streaming-config.json +72 -0
- package/templates/testing-config.json +81 -0
- package/templates/timetravel-config.json +62 -0
- package/templates/tool-registry-config.json +109 -0
- 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)
|