moflo 4.7.7 → 4.8.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 (61) hide show
  1. package/.claude/helpers/statusline.cjs +34 -26
  2. package/.claude/settings.json +2 -2
  3. package/README.md +1 -1
  4. package/bin/hooks.mjs +33 -3
  5. package/bin/session-start-launcher.mjs +88 -3
  6. package/package.json +3 -5
  7. package/src/@claude-flow/cli/README.md +1 -1
  8. package/src/@claude-flow/cli/dist/src/commands/daemon.js +42 -95
  9. package/src/@claude-flow/cli/dist/src/commands/doctor.js +11 -5
  10. package/src/@claude-flow/cli/dist/src/commands/init.js +0 -145
  11. package/src/@claude-flow/cli/dist/src/config/moflo-config.d.ts +5 -0
  12. package/src/@claude-flow/cli/dist/src/config/moflo-config.js +16 -0
  13. package/src/@claude-flow/cli/dist/src/config-adapter.d.ts +1 -1
  14. package/src/@claude-flow/cli/dist/src/init/executor.js +74 -7
  15. package/src/@claude-flow/cli/dist/src/init/mcp-generator.d.ts +3 -4
  16. package/src/@claude-flow/cli/dist/src/init/mcp-generator.js +65 -22
  17. package/src/@claude-flow/cli/dist/src/init/types.d.ts +0 -4
  18. package/src/@claude-flow/cli/dist/src/init/types.js +0 -5
  19. package/src/@claude-flow/cli/dist/src/mcp-server.js +36 -0
  20. package/src/@claude-flow/cli/dist/src/memory/memory-bridge.d.ts +6 -0
  21. package/src/@claude-flow/cli/dist/src/memory/memory-bridge.js +66 -0
  22. package/src/@claude-flow/cli/dist/src/memory/memory-initializer.js +52 -1
  23. package/src/@claude-flow/cli/dist/src/services/daemon-lock.d.ts +39 -0
  24. package/src/@claude-flow/cli/dist/src/services/daemon-lock.js +213 -0
  25. package/src/@claude-flow/cli/package.json +2 -6
  26. package/.claude/helpers/README.md +0 -97
  27. package/.claude/helpers/adr-compliance.sh +0 -186
  28. package/.claude/helpers/aggressive-microcompact.mjs +0 -36
  29. package/.claude/helpers/auto-commit.sh +0 -178
  30. package/.claude/helpers/checkpoint-manager.sh +0 -251
  31. package/.claude/helpers/context-persistence-hook.mjs +0 -1979
  32. package/.claude/helpers/daemon-manager.sh +0 -252
  33. package/.claude/helpers/ddd-tracker.sh +0 -144
  34. package/.claude/helpers/github-safe.js +0 -106
  35. package/.claude/helpers/github-setup.sh +0 -28
  36. package/.claude/helpers/guidance-hook.sh +0 -13
  37. package/.claude/helpers/guidance-hooks.sh +0 -102
  38. package/.claude/helpers/health-monitor.sh +0 -108
  39. package/.claude/helpers/learning-hooks.sh +0 -329
  40. package/.claude/helpers/learning-optimizer.sh +0 -127
  41. package/.claude/helpers/learning-service.mjs +0 -1211
  42. package/.claude/helpers/memory.cjs +0 -84
  43. package/.claude/helpers/metrics-db.mjs +0 -492
  44. package/.claude/helpers/patch-aggressive-prune.mjs +0 -184
  45. package/.claude/helpers/pattern-consolidator.sh +0 -86
  46. package/.claude/helpers/perf-worker.sh +0 -160
  47. package/.claude/helpers/quick-start.sh +0 -19
  48. package/.claude/helpers/router.cjs +0 -62
  49. package/.claude/helpers/security-scanner.sh +0 -127
  50. package/.claude/helpers/session.cjs +0 -125
  51. package/.claude/helpers/setup-mcp.sh +0 -18
  52. package/.claude/helpers/standard-checkpoint-hooks.sh +0 -189
  53. package/.claude/helpers/swarm-comms.sh +0 -353
  54. package/.claude/helpers/swarm-hooks.sh +0 -761
  55. package/.claude/helpers/swarm-monitor.sh +0 -211
  56. package/.claude/helpers/sync-v3-metrics.sh +0 -245
  57. package/.claude/helpers/update-v3-progress.sh +0 -166
  58. package/.claude/helpers/v3-quick-status.sh +0 -58
  59. package/.claude/helpers/v3.sh +0 -111
  60. package/.claude/helpers/validate-v3-config.sh +0 -216
  61. package/.claude/helpers/worker-manager.sh +0 -170
@@ -1,1211 +0,0 @@
1
- #!/usr/bin/env node
2
- /**
3
- * MoFlo V3 - Persistent Learning Service
4
- *
5
- * Connects ReasoningBank to AgentDB with HNSW indexing and ONNX embeddings.
6
- *
7
- * Features:
8
- * - Persistent pattern storage via sql.js (WASM SQLite)
9
- * - HNSW indexing for 150x-12,500x faster search
10
- * - ONNX embeddings via agentic-flow@alpha
11
- * - Session-level pattern loading and consolidation
12
- * - Short-term → Long-term pattern promotion
13
- *
14
- * Performance Targets:
15
- * - Pattern search: <1ms (HNSW)
16
- * - Embedding generation: <10ms (ONNX)
17
- * - Pattern storage: <5ms
18
- */
19
-
20
- import { createRequire } from 'module';
21
- import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'fs';
22
- import { join, dirname } from 'path';
23
- import { fileURLToPath } from 'url';
24
- import { execSync, spawn } from 'child_process';
25
- import initSqlJs from 'sql.js';
26
-
27
- const __filename = fileURLToPath(import.meta.url);
28
- const __dirname = dirname(__filename);
29
- const PROJECT_ROOT = join(__dirname, '../..');
30
- const DATA_DIR = join(PROJECT_ROOT, '.claude-flow/learning');
31
- const DB_PATH = join(DATA_DIR, 'patterns.db');
32
- const METRICS_PATH = join(DATA_DIR, 'learning-metrics.json');
33
-
34
- // Ensure data directory exists
35
- if (!existsSync(DATA_DIR)) {
36
- mkdirSync(DATA_DIR, { recursive: true });
37
- }
38
-
39
- // =============================================================================
40
- // Configuration
41
- // =============================================================================
42
-
43
- const CONFIG = {
44
- // HNSW parameters
45
- hnsw: {
46
- M: 16, // Max connections per layer
47
- efConstruction: 200, // Construction time accuracy
48
- efSearch: 100, // Search time accuracy
49
- metric: 'cosine', // Distance metric
50
- },
51
-
52
- // Pattern management
53
- patterns: {
54
- shortTermMaxAge: 24 * 60 * 60 * 1000, // 24 hours
55
- promotionThreshold: 3, // Uses before promotion to long-term
56
- qualityThreshold: 0.6, // Min quality for storage
57
- maxShortTerm: 500, // Max short-term patterns
58
- maxLongTerm: 2000, // Max long-term patterns
59
- dedupThreshold: 0.95, // Similarity for dedup
60
- },
61
-
62
- // Embedding
63
- embedding: {
64
- dimension: 384, // MiniLM-L6 dimension
65
- model: 'all-MiniLM-L6-v2', // ONNX model
66
- batchSize: 32, // Batch size for embedding
67
- },
68
-
69
- // Consolidation
70
- consolidation: {
71
- interval: 30 * 60 * 1000, // 30 minutes
72
- pruneAge: 30 * 24 * 60 * 60 * 1000, // 30 days
73
- minUsageForKeep: 2, // Min uses to keep old pattern
74
- },
75
- };
76
-
77
- // =============================================================================
78
- // sql.js Database Helpers
79
- // =============================================================================
80
-
81
- let _sqlJs = null;
82
-
83
- async function getSqlJs() {
84
- if (!_sqlJs) {
85
- _sqlJs = await initSqlJs();
86
- }
87
- return _sqlJs;
88
- }
89
-
90
- async function openDb() {
91
- const SQL = await getSqlJs();
92
- if (existsSync(DB_PATH)) {
93
- const buffer = readFileSync(DB_PATH);
94
- return new SQL.Database(buffer);
95
- }
96
- return new SQL.Database();
97
- }
98
-
99
- function saveDb(db) {
100
- const data = db.export();
101
- writeFileSync(DB_PATH, Buffer.from(data));
102
- }
103
-
104
- // Helper: run a SELECT that returns multiple rows
105
- function dbAll(db, sql, params = []) {
106
- const stmt = db.prepare(sql);
107
- if (params.length) stmt.bind(params);
108
- const rows = [];
109
- while (stmt.step()) {
110
- rows.push(stmt.getAsObject());
111
- }
112
- stmt.free();
113
- return rows;
114
- }
115
-
116
- // Helper: run a SELECT that returns one row
117
- function dbGet(db, sql, params = []) {
118
- const stmt = db.prepare(sql);
119
- if (params.length) stmt.bind(params);
120
- let row = null;
121
- if (stmt.step()) {
122
- row = stmt.getAsObject();
123
- }
124
- stmt.free();
125
- return row;
126
- }
127
-
128
- // Helper: run INSERT/UPDATE/DELETE, returns { changes }
129
- function dbRun(db, sql, params = []) {
130
- db.run(sql, params);
131
- const changes = db.getRowsModified();
132
- return { changes };
133
- }
134
-
135
- // =============================================================================
136
- // Database Schema
137
- // =============================================================================
138
-
139
- function initializeDatabase(db) {
140
- db.run(`
141
- -- Short-term patterns (session-level)
142
- CREATE TABLE IF NOT EXISTS short_term_patterns (
143
- id TEXT PRIMARY KEY,
144
- strategy TEXT NOT NULL,
145
- domain TEXT DEFAULT 'general',
146
- embedding BLOB NOT NULL,
147
- quality REAL DEFAULT 0.5,
148
- usage_count INTEGER DEFAULT 0,
149
- success_count INTEGER DEFAULT 0,
150
- created_at INTEGER NOT NULL,
151
- updated_at INTEGER NOT NULL,
152
- session_id TEXT,
153
- trajectory_id TEXT,
154
- metadata TEXT
155
- );
156
-
157
- -- Long-term patterns (promoted from short-term)
158
- CREATE TABLE IF NOT EXISTS long_term_patterns (
159
- id TEXT PRIMARY KEY,
160
- strategy TEXT NOT NULL,
161
- domain TEXT DEFAULT 'general',
162
- embedding BLOB NOT NULL,
163
- quality REAL DEFAULT 0.5,
164
- usage_count INTEGER DEFAULT 0,
165
- success_count INTEGER DEFAULT 0,
166
- created_at INTEGER NOT NULL,
167
- updated_at INTEGER NOT NULL,
168
- promoted_at INTEGER,
169
- source_pattern_id TEXT,
170
- quality_history TEXT,
171
- metadata TEXT
172
- );
173
-
174
- -- HNSW index metadata
175
- CREATE TABLE IF NOT EXISTS hnsw_index (
176
- id INTEGER PRIMARY KEY,
177
- pattern_type TEXT NOT NULL,
178
- pattern_id TEXT NOT NULL,
179
- vector_id INTEGER NOT NULL,
180
- created_at INTEGER NOT NULL,
181
- UNIQUE(pattern_type, pattern_id)
182
- );
183
-
184
- -- Learning trajectories
185
- CREATE TABLE IF NOT EXISTS trajectories (
186
- id TEXT PRIMARY KEY,
187
- session_id TEXT NOT NULL,
188
- domain TEXT DEFAULT 'general',
189
- steps TEXT NOT NULL,
190
- quality_score REAL,
191
- verdict TEXT,
192
- started_at INTEGER NOT NULL,
193
- ended_at INTEGER,
194
- distilled_pattern_id TEXT
195
- );
196
-
197
- -- Learning metrics
198
- CREATE TABLE IF NOT EXISTS learning_metrics (
199
- id INTEGER PRIMARY KEY AUTOINCREMENT,
200
- timestamp INTEGER NOT NULL,
201
- metric_type TEXT NOT NULL,
202
- metric_name TEXT NOT NULL,
203
- metric_value REAL NOT NULL,
204
- metadata TEXT
205
- );
206
-
207
- -- Session state
208
- CREATE TABLE IF NOT EXISTS session_state (
209
- key TEXT PRIMARY KEY,
210
- value TEXT NOT NULL,
211
- updated_at INTEGER NOT NULL
212
- );
213
- `);
214
-
215
- // Create indexes separately (sql.js handles multi-statement DDL in run())
216
- db.run(`CREATE INDEX IF NOT EXISTS idx_short_term_domain ON short_term_patterns(domain)`);
217
- db.run(`CREATE INDEX IF NOT EXISTS idx_short_term_quality ON short_term_patterns(quality DESC)`);
218
- db.run(`CREATE INDEX IF NOT EXISTS idx_short_term_usage ON short_term_patterns(usage_count DESC)`);
219
- db.run(`CREATE INDEX IF NOT EXISTS idx_long_term_domain ON long_term_patterns(domain)`);
220
- db.run(`CREATE INDEX IF NOT EXISTS idx_long_term_quality ON long_term_patterns(quality DESC)`);
221
- db.run(`CREATE INDEX IF NOT EXISTS idx_trajectories_session ON trajectories(session_id)`);
222
- db.run(`CREATE INDEX IF NOT EXISTS idx_metrics_type ON learning_metrics(metric_type, timestamp)`);
223
- }
224
-
225
- // =============================================================================
226
- // HNSW Index (In-Memory with SQLite persistence)
227
- // =============================================================================
228
-
229
- class HNSWIndex {
230
- constructor(config) {
231
- this.config = config;
232
- this.vectors = new Map(); // id -> Float32Array
233
- this.idToVector = new Map(); // patternId -> vectorId
234
- this.vectorToId = new Map(); // vectorId -> patternId
235
- this.nextVectorId = 0;
236
- this.dimension = config.embedding.dimension;
237
-
238
- // Graph structure for HNSW
239
- this.layers = []; // Multi-layer graph
240
- this.entryPoint = null;
241
- this.maxLevel = 0;
242
- }
243
-
244
- // Add vector to index
245
- add(patternId, embedding) {
246
- const vectorId = this.nextVectorId++;
247
- const vector = embedding instanceof Float32Array
248
- ? embedding
249
- : new Float32Array(embedding);
250
-
251
- this.vectors.set(vectorId, vector);
252
- this.idToVector.set(patternId, vectorId);
253
- this.vectorToId.set(vectorId, patternId);
254
-
255
- // Simple HNSW insertion (simplified for performance)
256
- this._insertIntoGraph(vectorId, vector);
257
-
258
- return vectorId;
259
- }
260
-
261
- // Search for k nearest neighbors
262
- search(queryEmbedding, k = 5) {
263
- const query = queryEmbedding instanceof Float32Array
264
- ? queryEmbedding
265
- : new Float32Array(queryEmbedding);
266
-
267
- if (this.vectors.size === 0) return { results: [], searchTimeMs: 0 };
268
-
269
- const startTime = performance.now();
270
-
271
- // HNSW search with early termination
272
- const candidates = this._searchGraph(query, k * 2);
273
-
274
- // Sort by similarity and take top k
275
- const results = candidates
276
- .map(({ vectorId, distance }) => ({
277
- patternId: this.vectorToId.get(vectorId),
278
- similarity: 1 - distance,
279
- vectorId,
280
- }))
281
- .sort((a, b) => b.similarity - a.similarity)
282
- .slice(0, k);
283
-
284
- const searchTime = performance.now() - startTime;
285
-
286
- return { results, searchTimeMs: searchTime };
287
- }
288
-
289
- // Remove vector from index
290
- remove(patternId) {
291
- const vectorId = this.idToVector.get(patternId);
292
- if (vectorId === undefined) return false;
293
-
294
- this.vectors.delete(vectorId);
295
- this.idToVector.delete(patternId);
296
- this.vectorToId.delete(vectorId);
297
- this._removeFromGraph(vectorId);
298
-
299
- return true;
300
- }
301
-
302
- // Get index size
303
- size() {
304
- return this.vectors.size;
305
- }
306
-
307
- // Cosine similarity
308
- _cosineSimilarity(a, b) {
309
- let dot = 0, normA = 0, normB = 0;
310
- for (let i = 0; i < a.length; i++) {
311
- dot += a[i] * b[i];
312
- normA += a[i] * a[i];
313
- normB += b[i] * b[i];
314
- }
315
- const denom = Math.sqrt(normA) * Math.sqrt(normB);
316
- return denom > 0 ? dot / denom : 0;
317
- }
318
-
319
- // Cosine distance
320
- _cosineDistance(a, b) {
321
- return 1 - this._cosineSimilarity(a, b);
322
- }
323
-
324
- // Insert into graph (simplified HNSW)
325
- _insertIntoGraph(vectorId, vector) {
326
- if (this.entryPoint === null) {
327
- this.entryPoint = vectorId;
328
- this.layers.push(new Map([[vectorId, new Set()]]));
329
- return;
330
- }
331
-
332
- // For simplicity, use single-layer graph with neighbor limit
333
- if (this.layers.length === 0) {
334
- this.layers.push(new Map());
335
- }
336
-
337
- const layer = this.layers[0];
338
- layer.set(vectorId, new Set());
339
-
340
- // Find M nearest neighbors and connect
341
- const neighbors = this._findNearest(vector, this.config.hnsw.M);
342
- for (const { vectorId: neighborId } of neighbors) {
343
- layer.get(vectorId).add(neighborId);
344
- layer.get(neighborId)?.add(vectorId);
345
-
346
- // Prune if too many connections
347
- if (layer.get(neighborId)?.size > this.config.hnsw.M * 2) {
348
- this._pruneConnections(neighborId);
349
- }
350
- }
351
- }
352
-
353
- // Search graph for nearest neighbors
354
- _searchGraph(query, k) {
355
- if (this.vectors.size <= k) {
356
- // Brute force for small index
357
- return Array.from(this.vectors.entries())
358
- .map(([vectorId, vector]) => ({
359
- vectorId,
360
- distance: this._cosineDistance(query, vector),
361
- }))
362
- .sort((a, b) => a.distance - b.distance);
363
- }
364
-
365
- // Greedy search from entry point
366
- const visited = new Set();
367
- const candidates = new Map();
368
- const results = [];
369
-
370
- let current = this.entryPoint;
371
- let currentDist = this._cosineDistance(query, this.vectors.get(current));
372
-
373
- candidates.set(current, currentDist);
374
- results.push({ vectorId: current, distance: currentDist });
375
-
376
- const layer = this.layers[0];
377
- let improved = true;
378
- let iterations = 0;
379
- const maxIterations = this.config.hnsw.efSearch;
380
-
381
- while (improved && iterations < maxIterations) {
382
- improved = false;
383
- iterations++;
384
-
385
- // Get best unvisited candidate
386
- let bestCandidate = null;
387
- let bestDist = Infinity;
388
-
389
- for (const [id, dist] of candidates) {
390
- if (!visited.has(id) && dist < bestDist) {
391
- bestDist = dist;
392
- bestCandidate = id;
393
- }
394
- }
395
-
396
- if (bestCandidate === null) break;
397
-
398
- visited.add(bestCandidate);
399
- const neighbors = layer.get(bestCandidate) || new Set();
400
-
401
- for (const neighborId of neighbors) {
402
- if (visited.has(neighborId)) continue;
403
-
404
- const neighborVector = this.vectors.get(neighborId);
405
- if (!neighborVector) continue;
406
-
407
- const dist = this._cosineDistance(query, neighborVector);
408
-
409
- if (!candidates.has(neighborId) || candidates.get(neighborId) > dist) {
410
- candidates.set(neighborId, dist);
411
- results.push({ vectorId: neighborId, distance: dist });
412
- improved = true;
413
- }
414
- }
415
- }
416
-
417
- return results.sort((a, b) => a.distance - b.distance).slice(0, k);
418
- }
419
-
420
- // Find k nearest by brute force
421
- _findNearest(query, k) {
422
- return Array.from(this.vectors.entries())
423
- .map(([vectorId, vector]) => ({
424
- vectorId,
425
- distance: this._cosineDistance(query, vector),
426
- }))
427
- .sort((a, b) => a.distance - b.distance)
428
- .slice(0, k);
429
- }
430
-
431
- // Prune excess connections
432
- _pruneConnections(vectorId) {
433
- const layer = this.layers[0];
434
- const connections = layer.get(vectorId);
435
- if (!connections || connections.size <= this.config.hnsw.M) return;
436
-
437
- const vector = this.vectors.get(vectorId);
438
- const scored = Array.from(connections)
439
- .map(neighborId => ({
440
- neighborId,
441
- distance: this._cosineDistance(vector, this.vectors.get(neighborId)),
442
- }))
443
- .sort((a, b) => a.distance - b.distance);
444
-
445
- // Keep only M nearest
446
- const toRemove = scored.slice(this.config.hnsw.M);
447
- for (const { neighborId } of toRemove) {
448
- connections.delete(neighborId);
449
- layer.get(neighborId)?.delete(vectorId);
450
- }
451
- }
452
-
453
- // Remove from graph
454
- _removeFromGraph(vectorId) {
455
- const layer = this.layers[0];
456
- const connections = layer.get(vectorId);
457
-
458
- if (connections) {
459
- for (const neighborId of connections) {
460
- layer.get(neighborId)?.delete(vectorId);
461
- }
462
- }
463
-
464
- layer.delete(vectorId);
465
-
466
- if (this.entryPoint === vectorId) {
467
- this.entryPoint = layer.size > 0 ? layer.keys().next().value : null;
468
- }
469
- }
470
-
471
- // Serialize index for persistence
472
- serialize() {
473
- return {
474
- vectors: Array.from(this.vectors.entries()).map(([id, vec]) => [id, Array.from(vec)]),
475
- idToVector: Array.from(this.idToVector.entries()),
476
- vectorToId: Array.from(this.vectorToId.entries()),
477
- nextVectorId: this.nextVectorId,
478
- entryPoint: this.entryPoint,
479
- layers: this.layers.map(layer =>
480
- Array.from(layer.entries()).map(([k, v]) => [k, Array.from(v)])
481
- ),
482
- };
483
- }
484
-
485
- // Deserialize index
486
- static deserialize(data, config) {
487
- const index = new HNSWIndex(config);
488
-
489
- if (!data) return index;
490
-
491
- index.vectors = new Map(data.vectors?.map(([id, vec]) => [id, new Float32Array(vec)]) || []);
492
- index.idToVector = new Map(data.idToVector || []);
493
- index.vectorToId = new Map(data.vectorToId || []);
494
- index.nextVectorId = data.nextVectorId || 0;
495
- index.entryPoint = data.entryPoint;
496
- index.layers = (data.layers || []).map(layer =>
497
- new Map(layer.map(([k, v]) => [k, new Set(v)]))
498
- );
499
-
500
- return index;
501
- }
502
- }
503
-
504
- // =============================================================================
505
- // Embedding Service (ONNX via agentic-flow@alpha OptimizedEmbedder)
506
- // =============================================================================
507
-
508
- class EmbeddingService {
509
- constructor(config) {
510
- this.config = config;
511
- this.initialized = false;
512
- this.embedder = null;
513
- this.embeddingCache = new Map();
514
- this.cacheMaxSize = 1000;
515
- }
516
-
517
- async initialize() {
518
- if (this.initialized) return;
519
-
520
- try {
521
- // Dynamically import agentic-flow OptimizedEmbedder
522
- const agenticFlowPath = join(PROJECT_ROOT, 'node_modules/agentic-flow/dist/embeddings/optimized-embedder.js');
523
-
524
- if (existsSync(agenticFlowPath)) {
525
- const { getOptimizedEmbedder } = await import(agenticFlowPath);
526
- this.embedder = getOptimizedEmbedder({
527
- modelId: 'all-MiniLM-L6-v2',
528
- dimension: this.config.embedding.dimension,
529
- cacheSize: 256,
530
- autoDownload: false, // Model should already be downloaded
531
- });
532
-
533
- await this.embedder.init();
534
- this.useAgenticFlow = true;
535
- console.log('[Embedding] Initialized: agentic-flow OptimizedEmbedder (ONNX)');
536
- } else {
537
- this.useAgenticFlow = false;
538
- console.log('[Embedding] agentic-flow not found, using fallback hash embeddings');
539
- }
540
-
541
- this.initialized = true;
542
- } catch (e) {
543
- this.useAgenticFlow = false;
544
- this.initialized = true;
545
- console.log(`[Embedding] Using fallback hash-based embeddings: ${e.message}`);
546
- }
547
- }
548
-
549
- async embed(text) {
550
- if (!this.initialized) await this.initialize();
551
-
552
- // Check cache
553
- const cacheKey = text.slice(0, 200);
554
- if (this.embeddingCache.has(cacheKey)) {
555
- return this.embeddingCache.get(cacheKey);
556
- }
557
-
558
- let embedding;
559
-
560
- if (this.useAgenticFlow && this.embedder) {
561
- try {
562
- // Use agentic-flow OptimizedEmbedder
563
- embedding = await this.embedder.embed(text.slice(0, 500));
564
- } catch (e) {
565
- console.log(`[Embedding] ONNX failed, using fallback: ${e.message}`);
566
- embedding = this._fallbackEmbed(text);
567
- }
568
- } else {
569
- embedding = this._fallbackEmbed(text);
570
- }
571
-
572
- // Cache result
573
- if (this.embeddingCache.size >= this.cacheMaxSize) {
574
- const firstKey = this.embeddingCache.keys().next().value;
575
- this.embeddingCache.delete(firstKey);
576
- }
577
- this.embeddingCache.set(cacheKey, embedding);
578
-
579
- return embedding;
580
- }
581
-
582
- async embedBatch(texts) {
583
- if (this.useAgenticFlow && this.embedder) {
584
- try {
585
- return await this.embedder.embedBatch(texts.map(t => t.slice(0, 500)));
586
- } catch (e) {
587
- // Fallback to sequential
588
- return Promise.all(texts.map(t => this.embed(t)));
589
- }
590
- }
591
- return Promise.all(texts.map(t => this.embed(t)));
592
- }
593
-
594
- // Fallback: deterministic hash-based embedding
595
- _fallbackEmbed(text) {
596
- const embedding = new Float32Array(this.config.embedding.dimension);
597
- const normalized = text.toLowerCase().trim();
598
-
599
- // Create deterministic embedding from text
600
- for (let i = 0; i < embedding.length; i++) {
601
- let hash = 0;
602
- for (let j = 0; j < normalized.length; j++) {
603
- hash = ((hash << 5) - hash + normalized.charCodeAt(j) * (i + 1)) | 0;
604
- }
605
- embedding[i] = (Math.sin(hash) + 1) / 2;
606
- }
607
-
608
- // Normalize
609
- let norm = 0;
610
- for (let i = 0; i < embedding.length; i++) {
611
- norm += embedding[i] * embedding[i];
612
- }
613
- norm = Math.sqrt(norm);
614
- if (norm > 0) {
615
- for (let i = 0; i < embedding.length; i++) {
616
- embedding[i] /= norm;
617
- }
618
- }
619
-
620
- return embedding;
621
- }
622
- }
623
-
624
- // =============================================================================
625
- // Learning Service
626
- // =============================================================================
627
-
628
- class LearningService {
629
- constructor() {
630
- this.db = null;
631
- this.shortTermIndex = null;
632
- this.longTermIndex = null;
633
- this.embeddingService = null;
634
- this.sessionId = null;
635
- this.dirty = false; // Track if DB needs saving
636
- this.metrics = {
637
- patternsStored: 0,
638
- patternsRetrieved: 0,
639
- searchTimeTotal: 0,
640
- searchCount: 0,
641
- promotions: 0,
642
- consolidations: 0,
643
- };
644
- }
645
-
646
- async initialize(sessionId = null) {
647
- this.sessionId = sessionId || `session_${Date.now()}`;
648
-
649
- // Initialize database (sql.js — async)
650
- this.db = await openDb();
651
- initializeDatabase(this.db);
652
- this.dirty = true; // schema init may create tables
653
-
654
- // Initialize embedding service
655
- this.embeddingService = new EmbeddingService(CONFIG);
656
- await this.embeddingService.initialize();
657
-
658
- // Initialize HNSW indexes
659
- this.shortTermIndex = new HNSWIndex(CONFIG);
660
- this.longTermIndex = new HNSWIndex(CONFIG);
661
-
662
- // Load existing patterns into indexes
663
- await this._loadIndexes();
664
-
665
- // Record session start
666
- this._setState('current_session', this.sessionId);
667
- this._setState('session_start', Date.now().toString());
668
-
669
- console.log(`[Learning] Initialized session ${this.sessionId}`);
670
- console.log(`[Learning] Short-term patterns: ${this.shortTermIndex.size()}`);
671
- console.log(`[Learning] Long-term patterns: ${this.longTermIndex.size()}`);
672
-
673
- return {
674
- sessionId: this.sessionId,
675
- shortTermPatterns: this.shortTermIndex.size(),
676
- longTermPatterns: this.longTermIndex.size(),
677
- };
678
- }
679
-
680
- // Store a new pattern
681
- async storePattern(strategy, domain = 'general', metadata = {}) {
682
- const now = Date.now();
683
- const id = `pat_${now}_${Math.random().toString(36).slice(2, 9)}`;
684
-
685
- // Generate embedding
686
- const embedding = await this.embeddingService.embed(strategy);
687
-
688
- // Check for duplicates
689
- const { results } = this.shortTermIndex.search(embedding, 1);
690
- if (results.length > 0 && results[0].similarity > CONFIG.patterns.dedupThreshold) {
691
- // Update existing pattern instead
692
- const existingId = results[0].patternId;
693
- this._updatePatternUsage(existingId, 'short_term');
694
- return { id: existingId, action: 'updated', similarity: results[0].similarity };
695
- }
696
-
697
- // Store in database
698
- dbRun(this.db, `
699
- INSERT INTO short_term_patterns
700
- (id, strategy, domain, embedding, quality, usage_count, created_at, updated_at, session_id, metadata)
701
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
702
- `, [
703
- id, strategy, domain,
704
- new Uint8Array(embedding.buffer),
705
- metadata.quality || 0.5,
706
- 1, now, now,
707
- this.sessionId,
708
- JSON.stringify(metadata)
709
- ]);
710
- this.dirty = true;
711
-
712
- // Add to HNSW index
713
- this.shortTermIndex.add(id, embedding);
714
-
715
- this.metrics.patternsStored++;
716
-
717
- // Check if we need to prune
718
- this._pruneShortTerm();
719
-
720
- return { id, action: 'created', embedding: Array.from(embedding).slice(0, 5) };
721
- }
722
-
723
- // Search for similar patterns
724
- async searchPatterns(query, k = 5, includeShortTerm = true) {
725
- const embedding = typeof query === 'string'
726
- ? await this.embeddingService.embed(query)
727
- : query;
728
-
729
- const results = [];
730
-
731
- // Search long-term first (higher quality)
732
- const longTermResults = this.longTermIndex.search(embedding, k);
733
- results.push(...longTermResults.results.map(r => ({ ...r, type: 'long_term' })));
734
-
735
- // Search short-term if needed
736
- if (includeShortTerm) {
737
- const shortTermResults = this.shortTermIndex.search(embedding, k);
738
- results.push(...shortTermResults.results.map(r => ({ ...r, type: 'short_term' })));
739
- }
740
-
741
- // Sort by similarity and dedupe
742
- results.sort((a, b) => b.similarity - a.similarity);
743
- const seen = new Set();
744
- const deduped = results.filter(r => {
745
- if (seen.has(r.patternId)) return false;
746
- seen.add(r.patternId);
747
- return true;
748
- }).slice(0, k);
749
-
750
- // Get full pattern data
751
- const patterns = deduped.map(r => {
752
- const table = r.type === 'long_term' ? 'long_term_patterns' : 'short_term_patterns';
753
- const row = dbGet(this.db, `SELECT * FROM ${table} WHERE id = ?`, [r.patternId]);
754
- return {
755
- ...r,
756
- strategy: row?.strategy,
757
- domain: row?.domain,
758
- quality: row?.quality,
759
- usageCount: row?.usage_count,
760
- };
761
- });
762
-
763
- this.metrics.patternsRetrieved += patterns.length;
764
- this.metrics.searchCount++;
765
- this.metrics.searchTimeTotal += longTermResults.searchTimeMs;
766
-
767
- return {
768
- patterns,
769
- searchTimeMs: longTermResults.searchTimeMs,
770
- totalLongTerm: this.longTermIndex.size(),
771
- totalShortTerm: this.shortTermIndex.size(),
772
- };
773
- }
774
-
775
- // Record pattern usage (for promotion)
776
- recordPatternUsage(patternId, success = true) {
777
- // Try short-term first
778
- let updated = this._updatePatternUsage(patternId, 'short_term', success);
779
- if (!updated) {
780
- updated = this._updatePatternUsage(patternId, 'long_term', success);
781
- }
782
-
783
- // Check for promotion
784
- if (updated) {
785
- this._checkPromotion(patternId);
786
- }
787
-
788
- return updated;
789
- }
790
-
791
- // Promote patterns from short-term to long-term
792
- _checkPromotion(patternId) {
793
- const row = dbGet(this.db, `SELECT * FROM short_term_patterns WHERE id = ?`, [patternId]);
794
-
795
- if (!row) return false;
796
-
797
- // Check promotion criteria
798
- const shouldPromote =
799
- row.usage_count >= CONFIG.patterns.promotionThreshold &&
800
- row.quality >= CONFIG.patterns.qualityThreshold;
801
-
802
- if (!shouldPromote) return false;
803
-
804
- const now = Date.now();
805
-
806
- // Insert into long-term
807
- dbRun(this.db, `
808
- INSERT INTO long_term_patterns
809
- (id, strategy, domain, embedding, quality, usage_count, success_count,
810
- created_at, updated_at, promoted_at, source_pattern_id, quality_history, metadata)
811
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
812
- `, [
813
- `lt_${patternId}`,
814
- row.strategy,
815
- row.domain,
816
- row.embedding,
817
- row.quality,
818
- row.usage_count,
819
- row.success_count,
820
- row.created_at,
821
- now,
822
- now,
823
- patternId,
824
- JSON.stringify([row.quality]),
825
- row.metadata
826
- ]);
827
-
828
- // Add to long-term index
829
- this.longTermIndex.add(`lt_${patternId}`, this._bufferToFloat32Array(row.embedding));
830
-
831
- // Remove from short-term
832
- dbRun(this.db, 'DELETE FROM short_term_patterns WHERE id = ?', [patternId]);
833
- this.shortTermIndex.remove(patternId);
834
-
835
- this.dirty = true;
836
- this.metrics.promotions++;
837
- console.log(`[Learning] Promoted pattern ${patternId} to long-term`);
838
-
839
- return true;
840
- }
841
-
842
- // Update pattern usage
843
- _updatePatternUsage(patternId, table, success = true) {
844
- const tableName = table === 'long_term' ? 'long_term_patterns' : 'short_term_patterns';
845
-
846
- const result = dbRun(this.db, `
847
- UPDATE ${tableName}
848
- SET usage_count = usage_count + 1,
849
- success_count = success_count + ?,
850
- quality = (quality * usage_count + ?) / (usage_count + 1),
851
- updated_at = ?
852
- WHERE id = ?
853
- `, [success ? 1 : 0, success ? 1.0 : 0.0, Date.now(), patternId]);
854
-
855
- if (result.changes > 0) this.dirty = true;
856
- return result.changes > 0;
857
- }
858
-
859
- // Consolidate patterns (dedup, prune, merge)
860
- async consolidate() {
861
- const startTime = Date.now();
862
- const stats = {
863
- duplicatesRemoved: 0,
864
- patternsProned: 0,
865
- patternsMerged: 0,
866
- };
867
-
868
- // 1. Remove old short-term patterns
869
- const oldThreshold = Date.now() - CONFIG.patterns.shortTermMaxAge;
870
- const pruned = dbRun(this.db, `
871
- DELETE FROM short_term_patterns
872
- WHERE created_at < ? AND usage_count < ?
873
- `, [oldThreshold, CONFIG.patterns.promotionThreshold]);
874
- stats.patternsProned = pruned.changes;
875
-
876
- // 2. Rebuild indexes
877
- await this._loadIndexes();
878
-
879
- // 3. Remove duplicates in long-term
880
- const longTermPatterns = dbAll(this.db, 'SELECT * FROM long_term_patterns');
881
- for (let i = 0; i < longTermPatterns.length; i++) {
882
- for (let j = i + 1; j < longTermPatterns.length; j++) {
883
- const sim = this._cosineSimilarity(
884
- this._bufferToFloat32Array(longTermPatterns[i].embedding),
885
- this._bufferToFloat32Array(longTermPatterns[j].embedding)
886
- );
887
-
888
- if (sim > CONFIG.patterns.dedupThreshold) {
889
- // Keep the higher quality one
890
- const toRemove = longTermPatterns[i].quality >= longTermPatterns[j].quality
891
- ? longTermPatterns[j].id
892
- : longTermPatterns[i].id;
893
-
894
- dbRun(this.db, 'DELETE FROM long_term_patterns WHERE id = ?', [toRemove]);
895
- stats.duplicatesRemoved++;
896
- }
897
- }
898
- }
899
-
900
- // 4. Prune old long-term patterns
901
- const pruneAge = Date.now() - CONFIG.consolidation.pruneAge;
902
- const oldPruned = dbRun(this.db, `
903
- DELETE FROM long_term_patterns
904
- WHERE updated_at < ? AND usage_count < ?
905
- `, [pruneAge, CONFIG.consolidation.minUsageForKeep]);
906
- stats.patternsProned += oldPruned.changes;
907
-
908
- // Rebuild indexes after changes
909
- await this._loadIndexes();
910
-
911
- this.dirty = true;
912
- this.metrics.consolidations++;
913
-
914
- const duration = Date.now() - startTime;
915
- console.log(`[Learning] Consolidation complete in ${duration}ms:`, stats);
916
-
917
- return { ...stats, durationMs: duration };
918
- }
919
-
920
- // Export learning data for session end
921
- async exportSession() {
922
- const sessionPatterns = dbAll(this.db, `
923
- SELECT * FROM short_term_patterns WHERE session_id = ?
924
- `, [this.sessionId]);
925
-
926
- const trajectories = dbAll(this.db, `
927
- SELECT * FROM trajectories WHERE session_id = ?
928
- `, [this.sessionId]);
929
-
930
- return {
931
- sessionId: this.sessionId,
932
- patterns: sessionPatterns.length,
933
- trajectories: trajectories.length,
934
- metrics: this.metrics,
935
- shortTermTotal: this.shortTermIndex.size(),
936
- longTermTotal: this.longTermIndex.size(),
937
- };
938
- }
939
-
940
- // Get learning statistics
941
- getStats() {
942
- const shortTermCount = dbGet(this.db, 'SELECT COUNT(*) as count FROM short_term_patterns').count;
943
- const longTermCount = dbGet(this.db, 'SELECT COUNT(*) as count FROM long_term_patterns').count;
944
- const trajectoryCount = dbGet(this.db, 'SELECT COUNT(*) as count FROM trajectories').count;
945
-
946
- const avgRow = dbGet(this.db, `
947
- SELECT AVG(quality) as avg FROM (
948
- SELECT quality FROM short_term_patterns
949
- UNION ALL
950
- SELECT quality FROM long_term_patterns
951
- )
952
- `);
953
- const avgQuality = avgRow?.avg || 0;
954
-
955
- return {
956
- shortTermPatterns: shortTermCount,
957
- longTermPatterns: longTermCount,
958
- trajectories: trajectoryCount,
959
- avgQuality,
960
- avgSearchTimeMs: this.metrics.searchCount > 0
961
- ? this.metrics.searchTimeTotal / this.metrics.searchCount
962
- : 0,
963
- ...this.metrics,
964
- };
965
- }
966
-
967
- // Load indexes from database
968
- async _loadIndexes() {
969
- // Load short-term patterns
970
- this.shortTermIndex = new HNSWIndex(CONFIG);
971
- const shortTermPatterns = dbAll(this.db, 'SELECT id, embedding FROM short_term_patterns');
972
- for (const row of shortTermPatterns) {
973
- const embedding = this._bufferToFloat32Array(row.embedding);
974
- if (embedding) {
975
- this.shortTermIndex.add(row.id, embedding);
976
- }
977
- }
978
-
979
- // Load long-term patterns
980
- this.longTermIndex = new HNSWIndex(CONFIG);
981
- const longTermPatterns = dbAll(this.db, 'SELECT id, embedding FROM long_term_patterns');
982
- for (const row of longTermPatterns) {
983
- const embedding = this._bufferToFloat32Array(row.embedding);
984
- if (embedding) {
985
- this.longTermIndex.add(row.id, embedding);
986
- }
987
- }
988
- }
989
-
990
- // Prune short-term patterns if over limit
991
- _pruneShortTerm() {
992
- const countRow = dbGet(this.db, 'SELECT COUNT(*) as count FROM short_term_patterns');
993
- const count = countRow.count;
994
-
995
- if (count <= CONFIG.patterns.maxShortTerm) return;
996
-
997
- // Remove lowest quality patterns
998
- const toRemove = count - CONFIG.patterns.maxShortTerm;
999
- const ids = dbAll(this.db, `
1000
- SELECT id FROM short_term_patterns
1001
- ORDER BY quality ASC, usage_count ASC
1002
- LIMIT ?
1003
- `, [toRemove]).map(r => r.id);
1004
-
1005
- for (const id of ids) {
1006
- dbRun(this.db, 'DELETE FROM short_term_patterns WHERE id = ?', [id]);
1007
- this.shortTermIndex.remove(id);
1008
- }
1009
- this.dirty = true;
1010
- }
1011
-
1012
- // Get/set state
1013
- _getState(key) {
1014
- const row = dbGet(this.db, 'SELECT value FROM session_state WHERE key = ?', [key]);
1015
- return row?.value;
1016
- }
1017
-
1018
- _setState(key, value) {
1019
- dbRun(this.db, `
1020
- INSERT OR REPLACE INTO session_state (key, value, updated_at)
1021
- VALUES (?, ?, ?)
1022
- `, [key, value, Date.now()]);
1023
- this.dirty = true;
1024
- }
1025
-
1026
- // Cosine similarity helper
1027
- _cosineSimilarity(a, b) {
1028
- let dot = 0, normA = 0, normB = 0;
1029
- for (let i = 0; i < a.length; i++) {
1030
- dot += a[i] * b[i];
1031
- normA += a[i] * a[i];
1032
- normB += b[i] * b[i];
1033
- }
1034
- const denom = Math.sqrt(normA) * Math.sqrt(normB);
1035
- return denom > 0 ? dot / denom : 0;
1036
- }
1037
-
1038
- // Close database — saves to disk first if dirty
1039
- close() {
1040
- if (this.db) {
1041
- if (this.dirty) {
1042
- saveDb(this.db);
1043
- }
1044
- this.db.close();
1045
- this.db = null;
1046
- }
1047
- }
1048
-
1049
- // Helper: Safely convert sql.js BLOB to Float32Array
1050
- // sql.js returns BLOBs as Uint8Array, not Node Buffer
1051
- _bufferToFloat32Array(buffer) {
1052
- if (!buffer) return null;
1053
-
1054
- // If it's already a Float32Array, return it
1055
- if (buffer instanceof Float32Array) return buffer;
1056
-
1057
- // Get the expected number of floats based on embedding dimension
1058
- const numFloats = this.config?.embedding?.dimension || CONFIG.embedding.dimension;
1059
- const expectedBytes = numFloats * 4;
1060
-
1061
- // Create a properly aligned Uint8Array copy
1062
- const uint8 = new Uint8Array(expectedBytes);
1063
- const sourceLength = Math.min(buffer.length || buffer.byteLength || 0, expectedBytes);
1064
-
1065
- // Copy bytes — works for both Node Buffer and Uint8Array (sql.js)
1066
- for (let i = 0; i < sourceLength; i++) {
1067
- uint8[i] = buffer[i];
1068
- }
1069
-
1070
- // Create Float32Array from the aligned buffer
1071
- return new Float32Array(uint8.buffer);
1072
- }
1073
- }
1074
-
1075
- // =============================================================================
1076
- // CLI Interface
1077
- // =============================================================================
1078
-
1079
- async function main() {
1080
- const command = process.argv[2] || 'help';
1081
- const service = new LearningService();
1082
-
1083
- try {
1084
- switch (command) {
1085
- case 'init':
1086
- case 'start': {
1087
- const sessionId = process.argv[3];
1088
- const result = await service.initialize(sessionId);
1089
- console.log(JSON.stringify(result, null, 2));
1090
- break;
1091
- }
1092
-
1093
- case 'store': {
1094
- await service.initialize();
1095
- const strategy = process.argv[3];
1096
- const domain = process.argv[4] || 'general';
1097
- if (!strategy) {
1098
- console.error('Usage: learning-service.mjs store <strategy> [domain]');
1099
- process.exit(1);
1100
- }
1101
- const result = await service.storePattern(strategy, domain);
1102
- console.log(JSON.stringify(result, null, 2));
1103
- break;
1104
- }
1105
-
1106
- case 'search': {
1107
- await service.initialize();
1108
- const query = process.argv[3];
1109
- const k = parseInt(process.argv[4]) || 5;
1110
- if (!query) {
1111
- console.error('Usage: learning-service.mjs search <query> [k]');
1112
- process.exit(1);
1113
- }
1114
- const result = await service.searchPatterns(query, k);
1115
- console.log(JSON.stringify(result, null, 2));
1116
- break;
1117
- }
1118
-
1119
- case 'consolidate': {
1120
- await service.initialize();
1121
- const result = await service.consolidate();
1122
- console.log(JSON.stringify(result, null, 2));
1123
- break;
1124
- }
1125
-
1126
- case 'export': {
1127
- await service.initialize();
1128
- const result = await service.exportSession();
1129
- console.log(JSON.stringify(result, null, 2));
1130
- break;
1131
- }
1132
-
1133
- case 'stats': {
1134
- await service.initialize();
1135
- const stats = service.getStats();
1136
- console.log(JSON.stringify(stats, null, 2));
1137
- break;
1138
- }
1139
-
1140
- case 'benchmark': {
1141
- await service.initialize();
1142
-
1143
- console.log('[Benchmark] Starting HNSW performance test...');
1144
-
1145
- // Store test patterns
1146
- const testPatterns = [
1147
- 'Implement authentication with JWT tokens',
1148
- 'Fix memory leak in event handler',
1149
- 'Optimize database query performance',
1150
- 'Add unit tests for user service',
1151
- 'Refactor component to use hooks',
1152
- ];
1153
-
1154
- for (const strategy of testPatterns) {
1155
- await service.storePattern(strategy, 'code');
1156
- }
1157
-
1158
- // Benchmark search
1159
- const searchTimes = [];
1160
- for (let i = 0; i < 100; i++) {
1161
- const start = performance.now();
1162
- await service.searchPatterns('implement authentication', 3);
1163
- searchTimes.push(performance.now() - start);
1164
- }
1165
-
1166
- const avgSearch = searchTimes.reduce((a, b) => a + b) / searchTimes.length;
1167
- const p95Search = searchTimes.sort((a, b) => a - b)[Math.floor(searchTimes.length * 0.95)];
1168
-
1169
- console.log(JSON.stringify({
1170
- avgSearchMs: avgSearch.toFixed(3),
1171
- p95SearchMs: p95Search.toFixed(3),
1172
- totalPatterns: service.getStats().shortTermPatterns + service.getStats().longTermPatterns,
1173
- hnswActive: true,
1174
- searchImprovementEstimate: `${Math.round(50 / Math.max(avgSearch, 0.1))}x`,
1175
- }, null, 2));
1176
- break;
1177
- }
1178
-
1179
- case 'help':
1180
- default:
1181
- console.log(`
1182
- MoFlo V3 Learning Service
1183
-
1184
- Usage: learning-service.mjs <command> [args]
1185
-
1186
- Commands:
1187
- init [sessionId] Initialize learning service
1188
- store <strategy> [domain] Store a new pattern
1189
- search <query> [k] Search for similar patterns
1190
- consolidate Consolidate and prune patterns
1191
- export Export session learning data
1192
- stats Get learning statistics
1193
- benchmark Run HNSW performance benchmark
1194
- help Show this help message
1195
- `);
1196
- }
1197
- } finally {
1198
- service.close();
1199
- }
1200
- }
1201
-
1202
- // Export for programmatic use
1203
- export { LearningService, HNSWIndex, EmbeddingService, CONFIG };
1204
-
1205
- // Run CLI if executed directly
1206
- if (process.argv[1] === fileURLToPath(import.meta.url)) {
1207
- main().catch(e => {
1208
- console.error('Error:', e.message);
1209
- process.exit(1);
1210
- });
1211
- }