opencode-swarm-plugin 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.beads/issues.jsonl +308 -0
- package/README.md +157 -11
- package/dist/index.js +804 -23
- package/dist/plugin.js +443 -23
- package/package.json +7 -3
- package/src/agent-mail.ts +46 -3
- package/src/index.ts +60 -0
- package/src/learning.integration.test.ts +326 -1
- package/src/storage.integration.test.ts +341 -0
- package/src/storage.ts +679 -0
- package/src/swarm.integration.test.ts +194 -3
- package/src/swarm.ts +185 -32
- package/src/tool-availability.ts +389 -0
package/src/storage.ts
ADDED
|
@@ -0,0 +1,679 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Storage Module - Pluggable persistence for learning data
|
|
3
|
+
*
|
|
4
|
+
* Provides a unified storage interface with multiple backends:
|
|
5
|
+
* - semantic-memory (default) - Persistent with semantic search
|
|
6
|
+
* - in-memory - For testing and ephemeral sessions
|
|
7
|
+
*
|
|
8
|
+
* The semantic-memory backend uses collections:
|
|
9
|
+
* - `swarm-feedback` - Criterion feedback events
|
|
10
|
+
* - `swarm-patterns` - Decomposition patterns and anti-patterns
|
|
11
|
+
* - `swarm-maturity` - Pattern maturity tracking
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* ```typescript
|
|
15
|
+
* // Use default semantic-memory storage
|
|
16
|
+
* const storage = createStorage();
|
|
17
|
+
*
|
|
18
|
+
* // Or configure explicitly
|
|
19
|
+
* const storage = createStorage({
|
|
20
|
+
* backend: "semantic-memory",
|
|
21
|
+
* collections: {
|
|
22
|
+
* feedback: "my-feedback",
|
|
23
|
+
* patterns: "my-patterns",
|
|
24
|
+
* maturity: "my-maturity",
|
|
25
|
+
* },
|
|
26
|
+
* });
|
|
27
|
+
*
|
|
28
|
+
* // Or use in-memory for testing
|
|
29
|
+
* const storage = createStorage({ backend: "memory" });
|
|
30
|
+
* ```
|
|
31
|
+
*/
|
|
32
|
+
|
|
33
|
+
import type { FeedbackEvent } from "./learning";
|
|
34
|
+
import type { DecompositionPattern } from "./anti-patterns";
|
|
35
|
+
import type { PatternMaturity, MaturityFeedback } from "./pattern-maturity";
|
|
36
|
+
import { InMemoryFeedbackStorage } from "./learning";
|
|
37
|
+
import { InMemoryPatternStorage } from "./anti-patterns";
|
|
38
|
+
import { InMemoryMaturityStorage } from "./pattern-maturity";
|
|
39
|
+
|
|
40
|
+
// ============================================================================
|
|
41
|
+
// Command Resolution
|
|
42
|
+
// ============================================================================
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Cached semantic-memory command (native or bunx fallback)
|
|
46
|
+
*/
|
|
47
|
+
let cachedCommand: string[] | null = null;
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Resolve the semantic-memory command
|
|
51
|
+
*
|
|
52
|
+
* Checks for native install first, falls back to bunx.
|
|
53
|
+
* Result is cached for the session.
|
|
54
|
+
*/
|
|
55
|
+
async function resolveSemanticMemoryCommand(): Promise<string[]> {
|
|
56
|
+
if (cachedCommand) return cachedCommand;
|
|
57
|
+
|
|
58
|
+
// Try native install first
|
|
59
|
+
const nativeResult = await Bun.$`which semantic-memory`.quiet().nothrow();
|
|
60
|
+
if (nativeResult.exitCode === 0) {
|
|
61
|
+
cachedCommand = ["semantic-memory"];
|
|
62
|
+
return cachedCommand;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Fall back to bunx
|
|
66
|
+
cachedCommand = ["bunx", "semantic-memory"];
|
|
67
|
+
return cachedCommand;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Execute semantic-memory command with args
|
|
72
|
+
*/
|
|
73
|
+
async function execSemanticMemory(
|
|
74
|
+
args: string[],
|
|
75
|
+
): Promise<{ exitCode: number; stdout: Buffer; stderr: Buffer }> {
|
|
76
|
+
const cmd = await resolveSemanticMemoryCommand();
|
|
77
|
+
const fullCmd = [...cmd, ...args];
|
|
78
|
+
|
|
79
|
+
// Use Bun.spawn for dynamic command arrays
|
|
80
|
+
const proc = Bun.spawn(fullCmd, {
|
|
81
|
+
stdout: "pipe",
|
|
82
|
+
stderr: "pipe",
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
const stdout = Buffer.from(await new Response(proc.stdout).arrayBuffer());
|
|
86
|
+
const stderr = Buffer.from(await new Response(proc.stderr).arrayBuffer());
|
|
87
|
+
const exitCode = await proc.exited;
|
|
88
|
+
|
|
89
|
+
return { exitCode, stdout, stderr };
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Reset the cached command (for testing)
|
|
94
|
+
*/
|
|
95
|
+
export function resetCommandCache(): void {
|
|
96
|
+
cachedCommand = null;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// ============================================================================
|
|
100
|
+
// Configuration
|
|
101
|
+
// ============================================================================
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Storage backend type
|
|
105
|
+
*/
|
|
106
|
+
export type StorageBackend = "semantic-memory" | "memory";
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Collection names for semantic-memory
|
|
110
|
+
*/
|
|
111
|
+
export interface StorageCollections {
|
|
112
|
+
feedback: string;
|
|
113
|
+
patterns: string;
|
|
114
|
+
maturity: string;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Storage configuration
|
|
119
|
+
*/
|
|
120
|
+
export interface StorageConfig {
|
|
121
|
+
/** Backend to use (default: "semantic-memory") */
|
|
122
|
+
backend: StorageBackend;
|
|
123
|
+
/** Collection names for semantic-memory backend */
|
|
124
|
+
collections: StorageCollections;
|
|
125
|
+
/** Whether to use semantic search for queries (default: true) */
|
|
126
|
+
useSemanticSearch: boolean;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
export const DEFAULT_STORAGE_CONFIG: StorageConfig = {
|
|
130
|
+
backend: "semantic-memory",
|
|
131
|
+
collections: {
|
|
132
|
+
feedback: "swarm-feedback",
|
|
133
|
+
patterns: "swarm-patterns",
|
|
134
|
+
maturity: "swarm-maturity",
|
|
135
|
+
},
|
|
136
|
+
useSemanticSearch: true,
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
// ============================================================================
|
|
140
|
+
// Unified Storage Interface
|
|
141
|
+
// ============================================================================
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Unified storage interface for all learning data
|
|
145
|
+
*/
|
|
146
|
+
export interface LearningStorage {
|
|
147
|
+
// Feedback operations
|
|
148
|
+
storeFeedback(event: FeedbackEvent): Promise<void>;
|
|
149
|
+
getFeedbackByCriterion(criterion: string): Promise<FeedbackEvent[]>;
|
|
150
|
+
getFeedbackByBead(beadId: string): Promise<FeedbackEvent[]>;
|
|
151
|
+
getAllFeedback(): Promise<FeedbackEvent[]>;
|
|
152
|
+
findSimilarFeedback(query: string, limit?: number): Promise<FeedbackEvent[]>;
|
|
153
|
+
|
|
154
|
+
// Pattern operations
|
|
155
|
+
storePattern(pattern: DecompositionPattern): Promise<void>;
|
|
156
|
+
getPattern(id: string): Promise<DecompositionPattern | null>;
|
|
157
|
+
getAllPatterns(): Promise<DecompositionPattern[]>;
|
|
158
|
+
getAntiPatterns(): Promise<DecompositionPattern[]>;
|
|
159
|
+
getPatternsByTag(tag: string): Promise<DecompositionPattern[]>;
|
|
160
|
+
findSimilarPatterns(
|
|
161
|
+
query: string,
|
|
162
|
+
limit?: number,
|
|
163
|
+
): Promise<DecompositionPattern[]>;
|
|
164
|
+
|
|
165
|
+
// Maturity operations
|
|
166
|
+
storeMaturity(maturity: PatternMaturity): Promise<void>;
|
|
167
|
+
getMaturity(patternId: string): Promise<PatternMaturity | null>;
|
|
168
|
+
getAllMaturity(): Promise<PatternMaturity[]>;
|
|
169
|
+
getMaturityByState(state: string): Promise<PatternMaturity[]>;
|
|
170
|
+
storeMaturityFeedback(feedback: MaturityFeedback): Promise<void>;
|
|
171
|
+
getMaturityFeedback(patternId: string): Promise<MaturityFeedback[]>;
|
|
172
|
+
|
|
173
|
+
// Lifecycle
|
|
174
|
+
close(): Promise<void>;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// ============================================================================
|
|
178
|
+
// Semantic Memory Storage Implementation
|
|
179
|
+
// ============================================================================
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Semantic-memory backed storage
|
|
183
|
+
*
|
|
184
|
+
* Uses the semantic-memory CLI for persistence with semantic search.
|
|
185
|
+
* Data survives across sessions and can be searched by meaning.
|
|
186
|
+
*/
|
|
187
|
+
export class SemanticMemoryStorage implements LearningStorage {
|
|
188
|
+
private config: StorageConfig;
|
|
189
|
+
|
|
190
|
+
constructor(config: Partial<StorageConfig> = {}) {
|
|
191
|
+
this.config = { ...DEFAULT_STORAGE_CONFIG, ...config };
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// -------------------------------------------------------------------------
|
|
195
|
+
// Helpers
|
|
196
|
+
// -------------------------------------------------------------------------
|
|
197
|
+
|
|
198
|
+
private async store(
|
|
199
|
+
collection: string,
|
|
200
|
+
data: unknown,
|
|
201
|
+
metadata?: Record<string, unknown>,
|
|
202
|
+
): Promise<void> {
|
|
203
|
+
const content = typeof data === "string" ? data : JSON.stringify(data);
|
|
204
|
+
const args = ["store", content, "--collection", collection];
|
|
205
|
+
|
|
206
|
+
if (metadata) {
|
|
207
|
+
args.push("--metadata", JSON.stringify(metadata));
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
await execSemanticMemory(args);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
private async find<T>(
|
|
214
|
+
collection: string,
|
|
215
|
+
query: string,
|
|
216
|
+
limit: number = 10,
|
|
217
|
+
useFts: boolean = false,
|
|
218
|
+
): Promise<T[]> {
|
|
219
|
+
const args = [
|
|
220
|
+
"find",
|
|
221
|
+
query,
|
|
222
|
+
"--collection",
|
|
223
|
+
collection,
|
|
224
|
+
"--limit",
|
|
225
|
+
String(limit),
|
|
226
|
+
"--json",
|
|
227
|
+
];
|
|
228
|
+
|
|
229
|
+
if (useFts) {
|
|
230
|
+
args.push("--fts");
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
const result = await execSemanticMemory(args);
|
|
234
|
+
|
|
235
|
+
if (result.exitCode !== 0) {
|
|
236
|
+
return [];
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
try {
|
|
240
|
+
const output = result.stdout.toString().trim();
|
|
241
|
+
if (!output) return [];
|
|
242
|
+
|
|
243
|
+
const parsed = JSON.parse(output);
|
|
244
|
+
// semantic-memory returns { results: [...] } or just [...]
|
|
245
|
+
const results = Array.isArray(parsed) ? parsed : parsed.results || [];
|
|
246
|
+
|
|
247
|
+
// Extract the stored content from each result
|
|
248
|
+
return results.map((r: { content?: string; information?: string }) => {
|
|
249
|
+
const content = r.content || r.information || "";
|
|
250
|
+
try {
|
|
251
|
+
return JSON.parse(content);
|
|
252
|
+
} catch {
|
|
253
|
+
return content;
|
|
254
|
+
}
|
|
255
|
+
});
|
|
256
|
+
} catch {
|
|
257
|
+
return [];
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
private async list<T>(collection: string): Promise<T[]> {
|
|
262
|
+
const result = await execSemanticMemory([
|
|
263
|
+
"list",
|
|
264
|
+
"--collection",
|
|
265
|
+
collection,
|
|
266
|
+
"--json",
|
|
267
|
+
]);
|
|
268
|
+
|
|
269
|
+
if (result.exitCode !== 0) {
|
|
270
|
+
return [];
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
try {
|
|
274
|
+
const output = result.stdout.toString().trim();
|
|
275
|
+
if (!output) return [];
|
|
276
|
+
|
|
277
|
+
const parsed = JSON.parse(output);
|
|
278
|
+
const items = Array.isArray(parsed) ? parsed : parsed.items || [];
|
|
279
|
+
|
|
280
|
+
return items.map((item: { content?: string; information?: string }) => {
|
|
281
|
+
const content = item.content || item.information || "";
|
|
282
|
+
try {
|
|
283
|
+
return JSON.parse(content);
|
|
284
|
+
} catch {
|
|
285
|
+
return content;
|
|
286
|
+
}
|
|
287
|
+
});
|
|
288
|
+
} catch {
|
|
289
|
+
return [];
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
// -------------------------------------------------------------------------
|
|
294
|
+
// Feedback Operations
|
|
295
|
+
// -------------------------------------------------------------------------
|
|
296
|
+
|
|
297
|
+
async storeFeedback(event: FeedbackEvent): Promise<void> {
|
|
298
|
+
await this.store(this.config.collections.feedback, event, {
|
|
299
|
+
criterion: event.criterion,
|
|
300
|
+
type: event.type,
|
|
301
|
+
bead_id: event.bead_id || "",
|
|
302
|
+
timestamp: event.timestamp,
|
|
303
|
+
});
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
async getFeedbackByCriterion(criterion: string): Promise<FeedbackEvent[]> {
|
|
307
|
+
// Use FTS for exact criterion match
|
|
308
|
+
return this.find<FeedbackEvent>(
|
|
309
|
+
this.config.collections.feedback,
|
|
310
|
+
criterion,
|
|
311
|
+
100,
|
|
312
|
+
true, // FTS for exact match
|
|
313
|
+
);
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
async getFeedbackByBead(beadId: string): Promise<FeedbackEvent[]> {
|
|
317
|
+
return this.find<FeedbackEvent>(
|
|
318
|
+
this.config.collections.feedback,
|
|
319
|
+
beadId,
|
|
320
|
+
100,
|
|
321
|
+
true,
|
|
322
|
+
);
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
async getAllFeedback(): Promise<FeedbackEvent[]> {
|
|
326
|
+
return this.list<FeedbackEvent>(this.config.collections.feedback);
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
async findSimilarFeedback(
|
|
330
|
+
query: string,
|
|
331
|
+
limit: number = 10,
|
|
332
|
+
): Promise<FeedbackEvent[]> {
|
|
333
|
+
return this.find<FeedbackEvent>(
|
|
334
|
+
this.config.collections.feedback,
|
|
335
|
+
query,
|
|
336
|
+
limit,
|
|
337
|
+
!this.config.useSemanticSearch,
|
|
338
|
+
);
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
// -------------------------------------------------------------------------
|
|
342
|
+
// Pattern Operations
|
|
343
|
+
// -------------------------------------------------------------------------
|
|
344
|
+
|
|
345
|
+
async storePattern(pattern: DecompositionPattern): Promise<void> {
|
|
346
|
+
await this.store(this.config.collections.patterns, pattern, {
|
|
347
|
+
id: pattern.id,
|
|
348
|
+
kind: pattern.kind,
|
|
349
|
+
is_negative: pattern.is_negative,
|
|
350
|
+
tags: pattern.tags.join(","),
|
|
351
|
+
});
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
async getPattern(id: string): Promise<DecompositionPattern | null> {
|
|
355
|
+
// List all and filter by ID - FTS search by ID is unreliable
|
|
356
|
+
const all = await this.list<DecompositionPattern>(
|
|
357
|
+
this.config.collections.patterns,
|
|
358
|
+
);
|
|
359
|
+
return all.find((p) => p.id === id) || null;
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
async getAllPatterns(): Promise<DecompositionPattern[]> {
|
|
363
|
+
return this.list<DecompositionPattern>(this.config.collections.patterns);
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
async getAntiPatterns(): Promise<DecompositionPattern[]> {
|
|
367
|
+
const all = await this.getAllPatterns();
|
|
368
|
+
return all.filter((p) => p.kind === "anti_pattern");
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
async getPatternsByTag(tag: string): Promise<DecompositionPattern[]> {
|
|
372
|
+
const results = await this.find<DecompositionPattern>(
|
|
373
|
+
this.config.collections.patterns,
|
|
374
|
+
tag,
|
|
375
|
+
100,
|
|
376
|
+
true,
|
|
377
|
+
);
|
|
378
|
+
return results.filter((p) => p.tags.includes(tag));
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
async findSimilarPatterns(
|
|
382
|
+
query: string,
|
|
383
|
+
limit: number = 10,
|
|
384
|
+
): Promise<DecompositionPattern[]> {
|
|
385
|
+
return this.find<DecompositionPattern>(
|
|
386
|
+
this.config.collections.patterns,
|
|
387
|
+
query,
|
|
388
|
+
limit,
|
|
389
|
+
!this.config.useSemanticSearch,
|
|
390
|
+
);
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
// -------------------------------------------------------------------------
|
|
394
|
+
// Maturity Operations
|
|
395
|
+
// -------------------------------------------------------------------------
|
|
396
|
+
|
|
397
|
+
async storeMaturity(maturity: PatternMaturity): Promise<void> {
|
|
398
|
+
await this.store(this.config.collections.maturity, maturity, {
|
|
399
|
+
pattern_id: maturity.pattern_id,
|
|
400
|
+
state: maturity.state,
|
|
401
|
+
});
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
async getMaturity(patternId: string): Promise<PatternMaturity | null> {
|
|
405
|
+
// List all and filter by pattern_id - FTS search by ID is unreliable
|
|
406
|
+
const all = await this.list<PatternMaturity>(
|
|
407
|
+
this.config.collections.maturity,
|
|
408
|
+
);
|
|
409
|
+
return all.find((m) => m.pattern_id === patternId) || null;
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
async getAllMaturity(): Promise<PatternMaturity[]> {
|
|
413
|
+
return this.list<PatternMaturity>(this.config.collections.maturity);
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
async getMaturityByState(state: string): Promise<PatternMaturity[]> {
|
|
417
|
+
const all = await this.getAllMaturity();
|
|
418
|
+
return all.filter((m) => m.state === state);
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
async storeMaturityFeedback(feedback: MaturityFeedback): Promise<void> {
|
|
422
|
+
await this.store(this.config.collections.maturity + "-feedback", feedback, {
|
|
423
|
+
pattern_id: feedback.pattern_id,
|
|
424
|
+
type: feedback.type,
|
|
425
|
+
timestamp: feedback.timestamp,
|
|
426
|
+
});
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
async getMaturityFeedback(patternId: string): Promise<MaturityFeedback[]> {
|
|
430
|
+
// List all and filter by pattern_id - FTS search by ID is unreliable
|
|
431
|
+
const all = await this.list<MaturityFeedback>(
|
|
432
|
+
this.config.collections.maturity + "-feedback",
|
|
433
|
+
);
|
|
434
|
+
return all.filter((f) => f.pattern_id === patternId);
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
async close(): Promise<void> {
|
|
438
|
+
// No cleanup needed for CLI-based storage
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
// ============================================================================
|
|
443
|
+
// In-Memory Storage Implementation
|
|
444
|
+
// ============================================================================
|
|
445
|
+
|
|
446
|
+
/**
|
|
447
|
+
* In-memory storage adapter
|
|
448
|
+
*
|
|
449
|
+
* Wraps the existing in-memory implementations into the unified interface.
|
|
450
|
+
* Useful for testing and ephemeral sessions.
|
|
451
|
+
*/
|
|
452
|
+
export class InMemoryStorage implements LearningStorage {
|
|
453
|
+
private feedback: InMemoryFeedbackStorage;
|
|
454
|
+
private patterns: InMemoryPatternStorage;
|
|
455
|
+
private maturity: InMemoryMaturityStorage;
|
|
456
|
+
|
|
457
|
+
constructor() {
|
|
458
|
+
this.feedback = new InMemoryFeedbackStorage();
|
|
459
|
+
this.patterns = new InMemoryPatternStorage();
|
|
460
|
+
this.maturity = new InMemoryMaturityStorage();
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
// Feedback
|
|
464
|
+
async storeFeedback(event: FeedbackEvent): Promise<void> {
|
|
465
|
+
return this.feedback.store(event);
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
async getFeedbackByCriterion(criterion: string): Promise<FeedbackEvent[]> {
|
|
469
|
+
return this.feedback.getByCriterion(criterion);
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
async getFeedbackByBead(beadId: string): Promise<FeedbackEvent[]> {
|
|
473
|
+
return this.feedback.getByBead(beadId);
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
async getAllFeedback(): Promise<FeedbackEvent[]> {
|
|
477
|
+
return this.feedback.getAll();
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
async findSimilarFeedback(
|
|
481
|
+
query: string,
|
|
482
|
+
limit: number = 10,
|
|
483
|
+
): Promise<FeedbackEvent[]> {
|
|
484
|
+
// In-memory doesn't support semantic search, filter by query string match
|
|
485
|
+
const all = await this.feedback.getAll();
|
|
486
|
+
const lowerQuery = query.toLowerCase();
|
|
487
|
+
const filtered = all.filter(
|
|
488
|
+
(event) =>
|
|
489
|
+
event.criterion.toLowerCase().includes(lowerQuery) ||
|
|
490
|
+
(event.bead_id && event.bead_id.toLowerCase().includes(lowerQuery)) ||
|
|
491
|
+
(event.context && event.context.toLowerCase().includes(lowerQuery)),
|
|
492
|
+
);
|
|
493
|
+
return filtered.slice(0, limit);
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
// Patterns
|
|
497
|
+
async storePattern(pattern: DecompositionPattern): Promise<void> {
|
|
498
|
+
return this.patterns.store(pattern);
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
async getPattern(id: string): Promise<DecompositionPattern | null> {
|
|
502
|
+
return this.patterns.get(id);
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
async getAllPatterns(): Promise<DecompositionPattern[]> {
|
|
506
|
+
return this.patterns.getAll();
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
async getAntiPatterns(): Promise<DecompositionPattern[]> {
|
|
510
|
+
return this.patterns.getAntiPatterns();
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
async getPatternsByTag(tag: string): Promise<DecompositionPattern[]> {
|
|
514
|
+
return this.patterns.getByTag(tag);
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
async findSimilarPatterns(
|
|
518
|
+
query: string,
|
|
519
|
+
limit: number = 10,
|
|
520
|
+
): Promise<DecompositionPattern[]> {
|
|
521
|
+
const results = await this.patterns.findByContent(query);
|
|
522
|
+
return results.slice(0, limit);
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
// Maturity
|
|
526
|
+
async storeMaturity(maturity: PatternMaturity): Promise<void> {
|
|
527
|
+
return this.maturity.store(maturity);
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
async getMaturity(patternId: string): Promise<PatternMaturity | null> {
|
|
531
|
+
return this.maturity.get(patternId);
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
async getAllMaturity(): Promise<PatternMaturity[]> {
|
|
535
|
+
return this.maturity.getAll();
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
async getMaturityByState(state: string): Promise<PatternMaturity[]> {
|
|
539
|
+
return this.maturity.getByState(state as any);
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
async storeMaturityFeedback(feedback: MaturityFeedback): Promise<void> {
|
|
543
|
+
return this.maturity.storeFeedback(feedback);
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
async getMaturityFeedback(patternId: string): Promise<MaturityFeedback[]> {
|
|
547
|
+
return this.maturity.getFeedback(patternId);
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
async close(): Promise<void> {
|
|
551
|
+
// No cleanup needed
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
// ============================================================================
|
|
556
|
+
// Factory
|
|
557
|
+
// ============================================================================
|
|
558
|
+
|
|
559
|
+
/**
|
|
560
|
+
* Create a storage instance
|
|
561
|
+
*
|
|
562
|
+
* @param config - Storage configuration (default: semantic-memory)
|
|
563
|
+
* @returns Configured storage instance
|
|
564
|
+
*
|
|
565
|
+
* @example
|
|
566
|
+
* ```typescript
|
|
567
|
+
* // Default semantic-memory storage
|
|
568
|
+
* const storage = createStorage();
|
|
569
|
+
*
|
|
570
|
+
* // In-memory for testing
|
|
571
|
+
* const storage = createStorage({ backend: "memory" });
|
|
572
|
+
*
|
|
573
|
+
* // Custom collections
|
|
574
|
+
* const storage = createStorage({
|
|
575
|
+
* backend: "semantic-memory",
|
|
576
|
+
* collections: {
|
|
577
|
+
* feedback: "my-project-feedback",
|
|
578
|
+
* patterns: "my-project-patterns",
|
|
579
|
+
* maturity: "my-project-maturity",
|
|
580
|
+
* },
|
|
581
|
+
* });
|
|
582
|
+
* ```
|
|
583
|
+
*/
|
|
584
|
+
export function createStorage(
|
|
585
|
+
config: Partial<StorageConfig> = {},
|
|
586
|
+
): LearningStorage {
|
|
587
|
+
const fullConfig = { ...DEFAULT_STORAGE_CONFIG, ...config };
|
|
588
|
+
|
|
589
|
+
switch (fullConfig.backend) {
|
|
590
|
+
case "semantic-memory":
|
|
591
|
+
return new SemanticMemoryStorage(fullConfig);
|
|
592
|
+
case "memory":
|
|
593
|
+
return new InMemoryStorage();
|
|
594
|
+
default:
|
|
595
|
+
throw new Error(`Unknown storage backend: ${fullConfig.backend}`);
|
|
596
|
+
}
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
/**
|
|
600
|
+
* Check if semantic-memory is available (native or via bunx)
|
|
601
|
+
*/
|
|
602
|
+
export async function isSemanticMemoryAvailable(): Promise<boolean> {
|
|
603
|
+
try {
|
|
604
|
+
const result = await execSemanticMemory(["stats"]);
|
|
605
|
+
return result.exitCode === 0;
|
|
606
|
+
} catch {
|
|
607
|
+
return false;
|
|
608
|
+
}
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
/**
|
|
612
|
+
* Get the resolved semantic-memory command (for debugging/logging)
|
|
613
|
+
*/
|
|
614
|
+
export async function getResolvedCommand(): Promise<string[]> {
|
|
615
|
+
return resolveSemanticMemoryCommand();
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
/**
|
|
619
|
+
* Create storage with automatic fallback
|
|
620
|
+
*
|
|
621
|
+
* Uses semantic-memory if available, otherwise falls back to in-memory.
|
|
622
|
+
*
|
|
623
|
+
* @param config - Storage configuration
|
|
624
|
+
* @returns Storage instance
|
|
625
|
+
*/
|
|
626
|
+
export async function createStorageWithFallback(
|
|
627
|
+
config: Partial<StorageConfig> = {},
|
|
628
|
+
): Promise<LearningStorage> {
|
|
629
|
+
if (config.backend === "memory") {
|
|
630
|
+
return new InMemoryStorage();
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
const available = await isSemanticMemoryAvailable();
|
|
634
|
+
if (available) {
|
|
635
|
+
return new SemanticMemoryStorage(config);
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
console.warn(
|
|
639
|
+
"semantic-memory not available, falling back to in-memory storage",
|
|
640
|
+
);
|
|
641
|
+
return new InMemoryStorage();
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
// ============================================================================
|
|
645
|
+
// Global Storage Instance
|
|
646
|
+
// ============================================================================
|
|
647
|
+
|
|
648
|
+
let globalStorage: LearningStorage | null = null;
|
|
649
|
+
|
|
650
|
+
/**
|
|
651
|
+
* Get or create the global storage instance
|
|
652
|
+
*
|
|
653
|
+
* Uses semantic-memory by default, with automatic fallback to in-memory.
|
|
654
|
+
*/
|
|
655
|
+
export async function getStorage(): Promise<LearningStorage> {
|
|
656
|
+
if (!globalStorage) {
|
|
657
|
+
globalStorage = await createStorageWithFallback();
|
|
658
|
+
}
|
|
659
|
+
return globalStorage;
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
/**
|
|
663
|
+
* Set the global storage instance
|
|
664
|
+
*
|
|
665
|
+
* Useful for testing or custom configurations.
|
|
666
|
+
*/
|
|
667
|
+
export function setStorage(storage: LearningStorage): void {
|
|
668
|
+
globalStorage = storage;
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
/**
|
|
672
|
+
* Reset the global storage instance
|
|
673
|
+
*/
|
|
674
|
+
export async function resetStorage(): Promise<void> {
|
|
675
|
+
if (globalStorage) {
|
|
676
|
+
await globalStorage.close();
|
|
677
|
+
globalStorage = null;
|
|
678
|
+
}
|
|
679
|
+
}
|