@aitytech/agentkits-memory 1.0.1 → 2.0.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 (105) hide show
  1. package/README.md +54 -5
  2. package/dist/better-sqlite3-backend.d.ts +192 -0
  3. package/dist/better-sqlite3-backend.d.ts.map +1 -0
  4. package/dist/better-sqlite3-backend.js +801 -0
  5. package/dist/better-sqlite3-backend.js.map +1 -0
  6. package/dist/cli/save.js +0 -0
  7. package/dist/cli/setup.d.ts +6 -2
  8. package/dist/cli/setup.d.ts.map +1 -1
  9. package/dist/cli/setup.js +289 -42
  10. package/dist/cli/setup.js.map +1 -1
  11. package/dist/cli/viewer.js +25 -56
  12. package/dist/cli/viewer.js.map +1 -1
  13. package/dist/cli/web-viewer.d.ts +2 -1
  14. package/dist/cli/web-viewer.d.ts.map +1 -1
  15. package/dist/cli/web-viewer.js +791 -141
  16. package/dist/cli/web-viewer.js.map +1 -1
  17. package/dist/embeddings/embedding-cache.d.ts +131 -0
  18. package/dist/embeddings/embedding-cache.d.ts.map +1 -0
  19. package/dist/embeddings/embedding-cache.js +217 -0
  20. package/dist/embeddings/embedding-cache.js.map +1 -0
  21. package/dist/embeddings/index.d.ts +11 -0
  22. package/dist/embeddings/index.d.ts.map +1 -0
  23. package/dist/embeddings/index.js +11 -0
  24. package/dist/embeddings/index.js.map +1 -0
  25. package/dist/embeddings/local-embeddings.d.ts +140 -0
  26. package/dist/embeddings/local-embeddings.d.ts.map +1 -0
  27. package/dist/embeddings/local-embeddings.js +293 -0
  28. package/dist/embeddings/local-embeddings.js.map +1 -0
  29. package/dist/hooks/context.d.ts +6 -1
  30. package/dist/hooks/context.d.ts.map +1 -1
  31. package/dist/hooks/context.js +12 -2
  32. package/dist/hooks/context.js.map +1 -1
  33. package/dist/hooks/observation.d.ts +6 -1
  34. package/dist/hooks/observation.d.ts.map +1 -1
  35. package/dist/hooks/observation.js +12 -2
  36. package/dist/hooks/observation.js.map +1 -1
  37. package/dist/hooks/service.d.ts +1 -6
  38. package/dist/hooks/service.d.ts.map +1 -1
  39. package/dist/hooks/service.js +33 -85
  40. package/dist/hooks/service.js.map +1 -1
  41. package/dist/hooks/session-init.d.ts +6 -1
  42. package/dist/hooks/session-init.d.ts.map +1 -1
  43. package/dist/hooks/session-init.js +12 -2
  44. package/dist/hooks/session-init.js.map +1 -1
  45. package/dist/hooks/summarize.d.ts +6 -1
  46. package/dist/hooks/summarize.d.ts.map +1 -1
  47. package/dist/hooks/summarize.js +12 -2
  48. package/dist/hooks/summarize.js.map +1 -1
  49. package/dist/index.d.ts +10 -17
  50. package/dist/index.d.ts.map +1 -1
  51. package/dist/index.js +172 -94
  52. package/dist/index.js.map +1 -1
  53. package/dist/mcp/server.js +17 -3
  54. package/dist/mcp/server.js.map +1 -1
  55. package/dist/migration.js +3 -3
  56. package/dist/migration.js.map +1 -1
  57. package/dist/search/hybrid-search.d.ts +262 -0
  58. package/dist/search/hybrid-search.d.ts.map +1 -0
  59. package/dist/search/hybrid-search.js +688 -0
  60. package/dist/search/hybrid-search.js.map +1 -0
  61. package/dist/search/index.d.ts +13 -0
  62. package/dist/search/index.d.ts.map +1 -0
  63. package/dist/search/index.js +13 -0
  64. package/dist/search/index.js.map +1 -0
  65. package/dist/search/token-economics.d.ts +161 -0
  66. package/dist/search/token-economics.d.ts.map +1 -0
  67. package/dist/search/token-economics.js +239 -0
  68. package/dist/search/token-economics.js.map +1 -0
  69. package/dist/types.d.ts +0 -68
  70. package/dist/types.d.ts.map +1 -1
  71. package/dist/types.js.map +1 -1
  72. package/package.json +5 -3
  73. package/src/__tests__/better-sqlite3-backend.test.ts +1466 -0
  74. package/src/__tests__/cache-manager.test.ts +499 -0
  75. package/src/__tests__/embedding-integration.test.ts +481 -0
  76. package/src/__tests__/hnsw-index.test.ts +727 -0
  77. package/src/__tests__/index.test.ts +432 -0
  78. package/src/better-sqlite3-backend.ts +1000 -0
  79. package/src/cli/setup.ts +358 -47
  80. package/src/cli/viewer.ts +28 -63
  81. package/src/cli/web-viewer.ts +936 -182
  82. package/src/embeddings/__tests__/embedding-cache.test.ts +269 -0
  83. package/src/embeddings/__tests__/local-embeddings.test.ts +495 -0
  84. package/src/embeddings/embedding-cache.ts +318 -0
  85. package/src/embeddings/index.ts +20 -0
  86. package/src/embeddings/local-embeddings.ts +419 -0
  87. package/src/hooks/__tests__/handlers.test.ts +58 -17
  88. package/src/hooks/__tests__/integration.test.ts +77 -26
  89. package/src/hooks/context.ts +13 -2
  90. package/src/hooks/observation.ts +13 -2
  91. package/src/hooks/service.ts +39 -100
  92. package/src/hooks/session-init.ts +13 -2
  93. package/src/hooks/summarize.ts +13 -2
  94. package/src/index.ts +210 -116
  95. package/src/mcp/server.ts +20 -3
  96. package/src/search/__tests__/hybrid-search.test.ts +669 -0
  97. package/src/search/__tests__/token-economics.test.ts +276 -0
  98. package/src/search/hybrid-search.ts +968 -0
  99. package/src/search/index.ts +29 -0
  100. package/src/search/token-economics.ts +367 -0
  101. package/src/types.ts +0 -96
  102. package/src/__tests__/sqljs-backend.test.ts +0 -410
  103. package/src/migration.ts +0 -574
  104. package/src/sql.js.d.ts +0 -70
  105. package/src/sqljs-backend.ts +0 -789
@@ -22,9 +22,20 @@ import { MemoryHookService } from './service.js';
22
22
  */
23
23
  export class SummarizeHook implements EventHandler {
24
24
  private service: MemoryHookService;
25
+ private ownsService: boolean;
25
26
 
26
- constructor(service: MemoryHookService) {
27
+ constructor(service: MemoryHookService, ownsService = false) {
27
28
  this.service = service;
29
+ this.ownsService = ownsService;
30
+ }
31
+
32
+ /**
33
+ * Shutdown the hook (closes database if owned)
34
+ */
35
+ async shutdown(): Promise<void> {
36
+ if (this.ownsService) {
37
+ await this.service.shutdown();
38
+ }
28
39
  }
29
40
 
30
41
  /**
@@ -83,7 +94,7 @@ export class SummarizeHook implements EventHandler {
83
94
  */
84
95
  export function createSummarizeHook(cwd: string): SummarizeHook {
85
96
  const service = new MemoryHookService(cwd);
86
- return new SummarizeHook(service);
97
+ return new SummarizeHook(service, true);
87
98
  }
88
99
 
89
100
  export default SummarizeHook;
package/src/index.ts CHANGED
@@ -42,7 +42,7 @@
42
42
  */
43
43
 
44
44
  import { EventEmitter } from 'node:events';
45
- import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
45
+ import { existsSync, mkdirSync } from 'node:fs';
46
46
  import * as path from 'node:path';
47
47
  import {
48
48
  IMemoryBackend,
@@ -56,23 +56,53 @@ import {
56
56
  HealthCheckResult,
57
57
  EmbeddingGenerator,
58
58
  SessionInfo,
59
- MigrationResult,
60
59
  createDefaultEntry,
61
60
  generateSessionId,
62
61
  DEFAULT_NAMESPACES,
63
62
  NAMESPACE_TYPE_MAP,
64
63
  } from './types.js';
65
- import { SqlJsBackend, SqlJsBackendConfig } from './sqljs-backend.js';
64
+ import { BetterSqlite3Backend } from './better-sqlite3-backend.js';
66
65
  import { CacheManager } from './cache-manager.js';
67
66
  import { HNSWIndex } from './hnsw-index.js';
68
- import { MemoryMigrator, migrateMarkdownMemory } from './migration.js';
69
67
 
70
68
  // Re-export types
71
69
  export * from './types.js';
72
- export { SqlJsBackend } from './sqljs-backend.js';
73
70
  export { CacheManager, TieredCacheManager } from './cache-manager.js';
74
71
  export { HNSWIndex } from './hnsw-index.js';
75
- export { MemoryMigrator, migrateMarkdownMemory } from './migration.js';
72
+ export {
73
+ LocalEmbeddingsService,
74
+ createLocalEmbeddings,
75
+ createEmbeddingGenerator,
76
+ PersistentEmbeddingCache,
77
+ createPersistentEmbeddingCache,
78
+ } from './embeddings/index.js';
79
+
80
+ export {
81
+ HybridSearchEngine,
82
+ createHybridSearchEngine,
83
+ TokenEconomicsTracker,
84
+ createTokenEconomicsTracker,
85
+ } from './search/index.js';
86
+
87
+ export {
88
+ BetterSqlite3Backend,
89
+ createBetterSqlite3Backend,
90
+ createJapaneseOptimizedBackend,
91
+ } from './better-sqlite3-backend.js';
92
+
93
+ /**
94
+ * Create a better-sqlite3 backend with FTS5 trigram tokenizer for CJK support
95
+ */
96
+ export function createAutoBackend(
97
+ databasePath: string,
98
+ options: { verbose?: boolean } = {}
99
+ ): IMemoryBackend {
100
+ return new BetterSqlite3Backend({
101
+ databasePath,
102
+ ftsTokenizer: 'trigram', // Full CJK support
103
+ verbose: options.verbose,
104
+ });
105
+ }
76
106
 
77
107
  /**
78
108
  * Configuration for ProjectMemoryService
@@ -140,7 +170,7 @@ const DEFAULT_CONFIG: ProjectMemoryConfig = {
140
170
  */
141
171
  export class ProjectMemoryService extends EventEmitter implements IMemoryBackend {
142
172
  private config: ProjectMemoryConfig;
143
- private backend: SqlJsBackend;
173
+ private backend: IMemoryBackend | null = null;
144
174
  private cache: CacheManager<MemoryEntry> | null = null;
145
175
  private vectorIndex: HNSWIndex | null = null;
146
176
  private initialized: boolean = false;
@@ -161,14 +191,7 @@ export class ProjectMemoryService extends EventEmitter implements IMemoryBackend
161
191
  mkdirSync(this.config.baseDir, { recursive: true });
162
192
  }
163
193
 
164
- // Initialize backend
165
- const dbPath = path.join(this.config.baseDir, this.config.dbFilename);
166
- this.backend = new SqlJsBackend({
167
- databasePath: dbPath,
168
- autoPersistInterval: this.config.autoPersistInterval,
169
- maxEntries: this.config.maxEntries,
170
- verbose: this.config.verbose,
171
- });
194
+ // Backend is created lazily in initialize() for auto-detection
172
195
 
173
196
  // Initialize cache if enabled
174
197
  if (this.config.cacheEnabled) {
@@ -189,12 +212,6 @@ export class ProjectMemoryService extends EventEmitter implements IMemoryBackend
189
212
  metric: 'cosine',
190
213
  });
191
214
  }
192
-
193
- // Forward backend events
194
- this.backend.on('entry:stored', (data) => this.emit('entry:stored', data));
195
- this.backend.on('entry:updated', (data) => this.emit('entry:updated', data));
196
- this.backend.on('entry:deleted', (data) => this.emit('entry:deleted', data));
197
- this.backend.on('persisted', (data) => this.emit('persisted', data));
198
215
  }
199
216
 
200
217
  // ===== Lifecycle =====
@@ -202,6 +219,19 @@ export class ProjectMemoryService extends EventEmitter implements IMemoryBackend
202
219
  async initialize(): Promise<void> {
203
220
  if (this.initialized) return;
204
221
 
222
+ // Create backend with better-sqlite3 (FTS5 trigram tokenizer for CJK support)
223
+ const dbPath = path.join(this.config.baseDir, this.config.dbFilename);
224
+ this.backend = createAutoBackend(dbPath, { verbose: this.config.verbose });
225
+
226
+ // Forward backend events (if backend is an EventEmitter)
227
+ const backendAsEmitter = this.backend as unknown as EventEmitter | undefined;
228
+ if (backendAsEmitter && typeof backendAsEmitter.on === 'function') {
229
+ backendAsEmitter.on('entry:stored', (data) => this.emit('entry:stored', data));
230
+ backendAsEmitter.on('entry:updated', (data) => this.emit('entry:updated', data));
231
+ backendAsEmitter.on('entry:deleted', (data) => this.emit('entry:deleted', data));
232
+ backendAsEmitter.on('persisted', (data) => this.emit('persisted', data));
233
+ }
234
+
205
235
  await this.backend.initialize();
206
236
 
207
237
  // Rebuild vector index from existing embeddings
@@ -226,7 +256,9 @@ export class ProjectMemoryService extends EventEmitter implements IMemoryBackend
226
256
  this.cache.shutdown();
227
257
  }
228
258
 
229
- await this.backend.shutdown();
259
+ if (this.backend) {
260
+ await this.backend.shutdown();
261
+ }
230
262
  this.initialized = false;
231
263
  this.emit('shutdown');
232
264
  }
@@ -234,7 +266,7 @@ export class ProjectMemoryService extends EventEmitter implements IMemoryBackend
234
266
  // ===== IMemoryBackend Implementation =====
235
267
 
236
268
  async store(entry: MemoryEntry): Promise<void> {
237
- this.ensureInitialized();
269
+ const backend = this.ensureInitialized();
238
270
 
239
271
  // Generate embedding if enabled and not present
240
272
  if (this.config.embeddingGenerator && !entry.embedding) {
@@ -253,7 +285,7 @@ export class ProjectMemoryService extends EventEmitter implements IMemoryBackend
253
285
  }
254
286
 
255
287
  // Store in backend
256
- await this.backend.store(entry);
288
+ await backend.store(entry);
257
289
 
258
290
  // Update cache
259
291
  if (this.cache) {
@@ -268,7 +300,7 @@ export class ProjectMemoryService extends EventEmitter implements IMemoryBackend
268
300
  }
269
301
 
270
302
  async get(id: string): Promise<MemoryEntry | null> {
271
- this.ensureInitialized();
303
+ const backend = this.ensureInitialized();
272
304
 
273
305
  // Check cache first
274
306
  if (this.cache) {
@@ -276,7 +308,7 @@ export class ProjectMemoryService extends EventEmitter implements IMemoryBackend
276
308
  if (cached) return cached;
277
309
  }
278
310
 
279
- const entry = await this.backend.get(id);
311
+ const entry = await backend.get(id);
280
312
 
281
313
  // Update cache
282
314
  if (entry && this.cache) {
@@ -287,7 +319,7 @@ export class ProjectMemoryService extends EventEmitter implements IMemoryBackend
287
319
  }
288
320
 
289
321
  async getByKey(namespace: string, key: string): Promise<MemoryEntry | null> {
290
- this.ensureInitialized();
322
+ const backend = this.ensureInitialized();
291
323
 
292
324
  const cacheKey = `${namespace}:${key}`;
293
325
 
@@ -297,7 +329,7 @@ export class ProjectMemoryService extends EventEmitter implements IMemoryBackend
297
329
  if (cached) return cached;
298
330
  }
299
331
 
300
- const entry = await this.backend.getByKey(namespace, key);
332
+ const entry = await backend.getByKey(namespace, key);
301
333
 
302
334
  // Update cache
303
335
  if (entry && this.cache) {
@@ -309,16 +341,16 @@ export class ProjectMemoryService extends EventEmitter implements IMemoryBackend
309
341
  }
310
342
 
311
343
  async update(id: string, update: MemoryEntryUpdate): Promise<MemoryEntry | null> {
312
- this.ensureInitialized();
344
+ const backend = this.ensureInitialized();
313
345
 
314
- const updated = await this.backend.update(id, update);
346
+ const updated = await backend.update(id, update);
315
347
 
316
348
  if (updated) {
317
349
  // Regenerate embedding if content changed
318
350
  if (update.content && this.config.embeddingGenerator) {
319
351
  try {
320
352
  updated.embedding = await this.config.embeddingGenerator(updated.content);
321
- await this.backend.store(updated);
353
+ await backend.store(updated);
322
354
 
323
355
  // Update vector index
324
356
  if (this.vectorIndex && updated.embedding) {
@@ -341,12 +373,12 @@ export class ProjectMemoryService extends EventEmitter implements IMemoryBackend
341
373
  }
342
374
 
343
375
  async delete(id: string): Promise<boolean> {
344
- this.ensureInitialized();
376
+ const backend = this.ensureInitialized();
345
377
 
346
378
  const entry = await this.get(id);
347
379
  if (!entry) return false;
348
380
 
349
- const result = await this.backend.delete(id);
381
+ const result = await backend.delete(id);
350
382
 
351
383
  if (result) {
352
384
  // Remove from cache
@@ -365,12 +397,136 @@ export class ProjectMemoryService extends EventEmitter implements IMemoryBackend
365
397
  }
366
398
 
367
399
  async query(query: MemoryQuery): Promise<MemoryEntry[]> {
368
- this.ensureInitialized();
369
- return this.backend.query(query);
400
+ const backend = this.ensureInitialized();
401
+
402
+ // If no content search term, just use basic query (ordered by date)
403
+ if (!query.content && !query.embedding) {
404
+ return backend.query(query);
405
+ }
406
+
407
+ // Handle different search types
408
+ // 'semantic' = vector only, 'hybrid' = combined, others fall back to FTS
409
+ const searchType = query.type || 'hybrid';
410
+ const limit = query.limit || 100;
411
+
412
+ // For exact, prefix, tag search - use backend directly
413
+ if (searchType === 'exact' || searchType === 'prefix' || searchType === 'tag') {
414
+ return backend.query(query);
415
+ }
416
+
417
+ // For semantic or hybrid search, need embedding
418
+ let embedding = query.embedding;
419
+ if (!embedding && query.content && this.config.embeddingGenerator) {
420
+ try {
421
+ embedding = await this.config.embeddingGenerator(query.content);
422
+ } catch (error) {
423
+ console.warn('[ProjectMemoryService] Failed to generate embedding, falling back to text search');
424
+ const betterBackend = backend as BetterSqlite3Backend;
425
+ if (typeof betterBackend.searchFts === 'function') {
426
+ return betterBackend.searchFts(query.content, {
427
+ namespace: query.namespace,
428
+ limit,
429
+ });
430
+ }
431
+ return backend.query(query);
432
+ }
433
+ }
434
+
435
+ if (!embedding) {
436
+ // No embedding available, fall back to FTS
437
+ const betterBackend = backend as BetterSqlite3Backend;
438
+ if (query.content && typeof betterBackend.searchFts === 'function') {
439
+ return betterBackend.searchFts(query.content, {
440
+ namespace: query.namespace,
441
+ limit,
442
+ });
443
+ }
444
+ return backend.query(query);
445
+ }
446
+
447
+ // For semantic-only (vector) search
448
+ if (searchType === 'semantic') {
449
+ const results = await this.search(embedding, {
450
+ k: limit,
451
+ threshold: query.threshold,
452
+ });
453
+
454
+ // Filter by namespace if specified
455
+ const filtered = query.namespace
456
+ ? results.filter((r) => r.entry.namespace === query.namespace)
457
+ : results;
458
+
459
+ return filtered.map((r) => r.entry);
460
+ }
461
+
462
+ // For hybrid search: combine FTS and vector results
463
+ const vectorResults = await this.search(embedding, {
464
+ k: limit * 2, // Get more candidates for fusion
465
+ threshold: 0.1, // Low threshold to get more candidates
466
+ });
467
+
468
+ // Filter vector results by namespace if specified
469
+ const filteredVectorResults = query.namespace
470
+ ? vectorResults.filter((r) => r.entry.namespace === query.namespace)
471
+ : vectorResults;
472
+
473
+ // Get FTS results
474
+ const betterBackend = backend as BetterSqlite3Backend;
475
+ let ftsResults: MemoryEntry[] = [];
476
+ if (query.content && typeof betterBackend.searchFts === 'function') {
477
+ ftsResults = await betterBackend.searchFts(query.content, {
478
+ namespace: query.namespace,
479
+ limit: limit * 2,
480
+ });
481
+ }
482
+
483
+ // Combine and rank results using score fusion
484
+ const scoreMap = new Map<string, { entry: MemoryEntry; score: number; vectorScore: number; ftsScore: number }>();
485
+
486
+ // Add vector results with scores (semantic weight: 0.7)
487
+ filteredVectorResults.forEach((r) => {
488
+ const vectorScore = r.score; // Already 0-1 similarity
489
+ scoreMap.set(r.entry.id, {
490
+ entry: r.entry,
491
+ score: vectorScore * 0.7,
492
+ vectorScore,
493
+ ftsScore: 0,
494
+ });
495
+ });
496
+
497
+ // Add FTS results with scores (keyword weight: 0.3)
498
+ ftsResults.forEach((entry, index) => {
499
+ const ftsScore = 1 - (index / (ftsResults.length || 1)); // Rank-based score
500
+ const existing = scoreMap.get(entry.id);
501
+ if (existing) {
502
+ existing.ftsScore = ftsScore;
503
+ existing.score += ftsScore * 0.3;
504
+ } else {
505
+ scoreMap.set(entry.id, {
506
+ entry,
507
+ score: ftsScore * 0.3,
508
+ vectorScore: 0,
509
+ ftsScore,
510
+ });
511
+ }
512
+ });
513
+
514
+ // Sort by combined score and return top results
515
+ const sorted = Array.from(scoreMap.values())
516
+ .sort((a, b) => b.score - a.score)
517
+ .slice(0, limit);
518
+
519
+ // Attach scores to entries for debugging/display
520
+ return sorted.map((r) => ({
521
+ ...r.entry,
522
+ _score: r.score,
523
+ _vectorScore: r.vectorScore,
524
+ _ftsScore: r.ftsScore,
525
+ }));
370
526
  }
371
527
 
372
528
  async search(embedding: Float32Array, options: SearchOptions): Promise<SearchResult[]> {
373
- this.ensureInitialized();
529
+ const backend = this.ensureInitialized();
374
530
 
375
531
  if (this.vectorIndex) {
376
532
  // Use HNSW index for fast search
@@ -392,11 +548,11 @@ export class ProjectMemoryService extends EventEmitter implements IMemoryBackend
392
548
  }
393
549
 
394
550
  // Fallback to brute-force search in backend
395
- return this.backend.search(embedding, options);
551
+ return backend.search(embedding, options);
396
552
  }
397
553
 
398
554
  async bulkInsert(entries: MemoryEntry[]): Promise<void> {
399
- this.ensureInitialized();
555
+ this.ensureInitialized(); // store() already gets backend
400
556
 
401
557
  for (const entry of entries) {
402
558
  await this.store(entry);
@@ -418,30 +574,30 @@ export class ProjectMemoryService extends EventEmitter implements IMemoryBackend
418
574
  }
419
575
 
420
576
  async count(namespace?: string): Promise<number> {
421
- this.ensureInitialized();
422
- return this.backend.count(namespace);
577
+ const backend = this.ensureInitialized();
578
+ return backend.count(namespace);
423
579
  }
424
580
 
425
581
  async listNamespaces(): Promise<string[]> {
426
- this.ensureInitialized();
427
- return this.backend.listNamespaces();
582
+ const backend = this.ensureInitialized();
583
+ return backend.listNamespaces();
428
584
  }
429
585
 
430
586
  async clearNamespace(namespace: string): Promise<number> {
431
- this.ensureInitialized();
587
+ const backend = this.ensureInitialized();
432
588
 
433
589
  // Clear from cache
434
590
  if (this.cache) {
435
591
  this.cache.invalidatePattern(new RegExp(`^${namespace}:`));
436
592
  }
437
593
 
438
- return this.backend.clearNamespace(namespace);
594
+ return backend.clearNamespace(namespace);
439
595
  }
440
596
 
441
597
  async getStats(): Promise<BackendStats> {
442
- this.ensureInitialized();
598
+ const backend = this.ensureInitialized();
443
599
 
444
- const stats = await this.backend.getStats();
600
+ const stats = await backend.getStats();
445
601
 
446
602
  // Add HNSW stats if available
447
603
  if (this.vectorIndex) {
@@ -457,8 +613,8 @@ export class ProjectMemoryService extends EventEmitter implements IMemoryBackend
457
613
  }
458
614
 
459
615
  async healthCheck(): Promise<HealthCheckResult> {
460
- this.ensureInitialized();
461
- return this.backend.healthCheck();
616
+ const backend = this.ensureInitialized();
617
+ return backend.healthCheck();
462
618
  }
463
619
 
464
620
  // ===== Convenience Methods =====
@@ -626,82 +782,20 @@ export class ProjectMemoryService extends EventEmitter implements IMemoryBackend
626
782
  .filter((s): s is SessionInfo => s !== null);
627
783
  }
628
784
 
629
- // ===== Migration =====
630
-
631
- /**
632
- * Migrate from existing markdown memory files
633
- */
634
- async migrateFromMarkdown(options: { generateEmbeddings?: boolean } = {}): Promise<MigrationResult> {
635
- this.ensureInitialized();
636
-
637
- const result = await migrateMarkdownMemory(
638
- this.config.baseDir,
639
- async (entry) => this.store(entry),
640
- {
641
- generateEmbeddings: options.generateEmbeddings ?? false,
642
- }
643
- );
644
-
645
- this.emit('migration:completed', result);
646
- return result;
647
- }
648
-
649
- // ===== Export =====
650
-
651
- /**
652
- * Export namespace to markdown (for git-friendly backup)
653
- */
654
- async exportToMarkdown(namespace: string, outputPath?: string): Promise<string> {
655
- const entries = await this.getByNamespace(namespace);
656
- const filePath = outputPath || path.join(this.config.baseDir, `${namespace}.md`);
657
-
658
- let markdown = `---\nnamespace: ${namespace}\nexported: ${new Date().toISOString()}\nentries: ${entries.length}\n---\n\n`;
659
-
660
- for (const entry of entries) {
661
- markdown += `## ${entry.key}\n\n`;
662
- markdown += entry.content;
663
- markdown += '\n\n';
664
-
665
- if (entry.tags.length > 0) {
666
- markdown += `*Tags: ${entry.tags.join(', ')}*\n\n`;
667
- }
668
-
669
- markdown += '---\n\n';
670
- }
671
-
672
- writeFileSync(filePath, markdown, 'utf-8');
673
-
674
- return filePath;
675
- }
676
-
677
- /**
678
- * Export all namespaces to markdown
679
- */
680
- async exportAllToMarkdown(): Promise<string[]> {
681
- const namespaces = await this.listNamespaces();
682
- const files: string[] = [];
683
-
684
- for (const namespace of namespaces) {
685
- const file = await this.exportToMarkdown(namespace);
686
- files.push(file);
687
- }
688
-
689
- return files;
690
- }
691
-
692
785
  // ===== Private Methods =====
693
786
 
694
- private ensureInitialized(): void {
695
- if (!this.initialized) {
787
+ private ensureInitialized(): IMemoryBackend {
788
+ if (!this.initialized || !this.backend) {
696
789
  throw new Error('ProjectMemoryService not initialized. Call initialize() first.');
697
790
  }
791
+ return this.backend;
698
792
  }
699
793
 
700
794
  private async rebuildVectorIndex(): Promise<void> {
701
- if (!this.vectorIndex) return;
795
+ if (!this.vectorIndex || !this.backend) return;
702
796
 
703
- // Get all entries with embeddings
704
- const entries = await this.query({
797
+ // Get all entries with embeddings (use backend directly to avoid ensureInitialized check)
798
+ const entries = await this.backend.query({
705
799
  type: 'hybrid',
706
800
  limit: this.config.maxEntries,
707
801
  });
package/src/mcp/server.ts CHANGED
@@ -20,7 +20,8 @@
20
20
  */
21
21
 
22
22
  import * as readline from 'node:readline';
23
- import { ProjectMemoryService, MemoryEntry, MemoryQuery, DEFAULT_NAMESPACES } from '../index.js';
23
+ import * as path from 'node:path';
24
+ import { ProjectMemoryService, MemoryEntry, MemoryQuery, DEFAULT_NAMESPACES, LocalEmbeddingsService } from '../index.js';
24
25
  import { MEMORY_TOOLS } from './tools.js';
25
26
  import type {
26
27
  JSONRPCRequest,
@@ -47,6 +48,7 @@ const CATEGORY_TO_NAMESPACE: Record<string, string> = {
47
48
  */
48
49
  class MemoryMCPServer {
49
50
  private service: ProjectMemoryService | null = null;
51
+ private embeddingsService: LocalEmbeddingsService | null = null;
50
52
  private projectDir: string;
51
53
  private initialized = false;
52
54
 
@@ -55,13 +57,28 @@ class MemoryMCPServer {
55
57
  }
56
58
 
57
59
  /**
58
- * Initialize the memory service
60
+ * Initialize the memory service with embeddings support
59
61
  */
60
62
  private async ensureInitialized(): Promise<ProjectMemoryService> {
61
63
  if (!this.service || !this.initialized) {
64
+ const baseDir = path.join(this.projectDir, '.claude/memory');
65
+
66
+ // Initialize embeddings service
67
+ this.embeddingsService = new LocalEmbeddingsService({
68
+ cacheDir: path.join(baseDir, 'embeddings-cache'),
69
+ });
70
+ await this.embeddingsService.initialize();
71
+
72
+ // Create embedding generator function
73
+ const embeddingGenerator = async (text: string): Promise<Float32Array> => {
74
+ const result = await this.embeddingsService!.embed(text);
75
+ return result.embedding;
76
+ };
77
+
62
78
  this.service = new ProjectMemoryService({
63
- baseDir: `${this.projectDir}/.claude/memory`,
79
+ baseDir,
64
80
  dbFilename: 'memory.db',
81
+ embeddingGenerator,
65
82
  });
66
83
  await this.service.initialize();
67
84
  this.initialized = true;