claude-memory-layer 1.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 (127) hide show
  1. package/.claude-plugin/commands/memory-forget.md +42 -0
  2. package/.claude-plugin/commands/memory-history.md +34 -0
  3. package/.claude-plugin/commands/memory-import.md +56 -0
  4. package/.claude-plugin/commands/memory-list.md +37 -0
  5. package/.claude-plugin/commands/memory-search.md +36 -0
  6. package/.claude-plugin/commands/memory-stats.md +34 -0
  7. package/.claude-plugin/hooks.json +59 -0
  8. package/.claude-plugin/plugin.json +24 -0
  9. package/.history/package_20260201112328.json +45 -0
  10. package/.history/package_20260201113602.json +45 -0
  11. package/.history/package_20260201113713.json +45 -0
  12. package/.history/package_20260201114110.json +45 -0
  13. package/Memo.txt +558 -0
  14. package/README.md +520 -0
  15. package/context.md +636 -0
  16. package/dist/.claude-plugin/commands/memory-forget.md +42 -0
  17. package/dist/.claude-plugin/commands/memory-history.md +34 -0
  18. package/dist/.claude-plugin/commands/memory-import.md +56 -0
  19. package/dist/.claude-plugin/commands/memory-list.md +37 -0
  20. package/dist/.claude-plugin/commands/memory-search.md +36 -0
  21. package/dist/.claude-plugin/commands/memory-stats.md +34 -0
  22. package/dist/.claude-plugin/hooks.json +59 -0
  23. package/dist/.claude-plugin/plugin.json +24 -0
  24. package/dist/cli/index.js +3539 -0
  25. package/dist/cli/index.js.map +7 -0
  26. package/dist/core/index.js +4408 -0
  27. package/dist/core/index.js.map +7 -0
  28. package/dist/hooks/session-end.js +2971 -0
  29. package/dist/hooks/session-end.js.map +7 -0
  30. package/dist/hooks/session-start.js +2969 -0
  31. package/dist/hooks/session-start.js.map +7 -0
  32. package/dist/hooks/stop.js +3123 -0
  33. package/dist/hooks/stop.js.map +7 -0
  34. package/dist/hooks/user-prompt-submit.js +2960 -0
  35. package/dist/hooks/user-prompt-submit.js.map +7 -0
  36. package/dist/services/memory-service.js +2931 -0
  37. package/dist/services/memory-service.js.map +7 -0
  38. package/package.json +45 -0
  39. package/plan.md +1642 -0
  40. package/scripts/build.ts +102 -0
  41. package/spec.md +624 -0
  42. package/specs/citations-system/context.md +243 -0
  43. package/specs/citations-system/plan.md +495 -0
  44. package/specs/citations-system/spec.md +371 -0
  45. package/specs/endless-mode/context.md +305 -0
  46. package/specs/endless-mode/plan.md +620 -0
  47. package/specs/endless-mode/spec.md +455 -0
  48. package/specs/entity-edge-model/context.md +401 -0
  49. package/specs/entity-edge-model/plan.md +459 -0
  50. package/specs/entity-edge-model/spec.md +391 -0
  51. package/specs/evidence-aligner-v2/context.md +401 -0
  52. package/specs/evidence-aligner-v2/plan.md +303 -0
  53. package/specs/evidence-aligner-v2/spec.md +312 -0
  54. package/specs/mcp-desktop-integration/context.md +278 -0
  55. package/specs/mcp-desktop-integration/plan.md +550 -0
  56. package/specs/mcp-desktop-integration/spec.md +494 -0
  57. package/specs/post-tool-use-hook/context.md +319 -0
  58. package/specs/post-tool-use-hook/plan.md +469 -0
  59. package/specs/post-tool-use-hook/spec.md +364 -0
  60. package/specs/private-tags/context.md +288 -0
  61. package/specs/private-tags/plan.md +412 -0
  62. package/specs/private-tags/spec.md +345 -0
  63. package/specs/progressive-disclosure/context.md +346 -0
  64. package/specs/progressive-disclosure/plan.md +663 -0
  65. package/specs/progressive-disclosure/spec.md +415 -0
  66. package/specs/task-entity-system/context.md +297 -0
  67. package/specs/task-entity-system/plan.md +301 -0
  68. package/specs/task-entity-system/spec.md +314 -0
  69. package/specs/vector-outbox-v2/context.md +470 -0
  70. package/specs/vector-outbox-v2/plan.md +562 -0
  71. package/specs/vector-outbox-v2/spec.md +466 -0
  72. package/specs/web-viewer-ui/context.md +384 -0
  73. package/specs/web-viewer-ui/plan.md +797 -0
  74. package/specs/web-viewer-ui/spec.md +516 -0
  75. package/src/cli/index.ts +570 -0
  76. package/src/core/canonical-key.ts +186 -0
  77. package/src/core/citation-generator.ts +63 -0
  78. package/src/core/consolidated-store.ts +279 -0
  79. package/src/core/consolidation-worker.ts +384 -0
  80. package/src/core/context-formatter.ts +276 -0
  81. package/src/core/continuity-manager.ts +336 -0
  82. package/src/core/edge-repo.ts +324 -0
  83. package/src/core/embedder.ts +124 -0
  84. package/src/core/entity-repo.ts +342 -0
  85. package/src/core/event-store.ts +672 -0
  86. package/src/core/evidence-aligner.ts +635 -0
  87. package/src/core/graduation.ts +365 -0
  88. package/src/core/index.ts +32 -0
  89. package/src/core/matcher.ts +210 -0
  90. package/src/core/metadata-extractor.ts +203 -0
  91. package/src/core/privacy/filter.ts +179 -0
  92. package/src/core/privacy/index.ts +20 -0
  93. package/src/core/privacy/tag-parser.ts +145 -0
  94. package/src/core/progressive-retriever.ts +415 -0
  95. package/src/core/retriever.ts +235 -0
  96. package/src/core/task/blocker-resolver.ts +325 -0
  97. package/src/core/task/index.ts +9 -0
  98. package/src/core/task/task-matcher.ts +238 -0
  99. package/src/core/task/task-projector.ts +345 -0
  100. package/src/core/task/task-resolver.ts +414 -0
  101. package/src/core/types.ts +841 -0
  102. package/src/core/vector-outbox.ts +295 -0
  103. package/src/core/vector-store.ts +182 -0
  104. package/src/core/vector-worker.ts +488 -0
  105. package/src/core/working-set-store.ts +244 -0
  106. package/src/hooks/post-tool-use.ts +127 -0
  107. package/src/hooks/session-end.ts +78 -0
  108. package/src/hooks/session-start.ts +57 -0
  109. package/src/hooks/stop.ts +78 -0
  110. package/src/hooks/user-prompt-submit.ts +54 -0
  111. package/src/mcp/handlers.ts +212 -0
  112. package/src/mcp/index.ts +47 -0
  113. package/src/mcp/tools.ts +78 -0
  114. package/src/server/api/citations.ts +101 -0
  115. package/src/server/api/events.ts +101 -0
  116. package/src/server/api/index.ts +18 -0
  117. package/src/server/api/search.ts +98 -0
  118. package/src/server/api/sessions.ts +111 -0
  119. package/src/server/api/stats.ts +97 -0
  120. package/src/server/index.ts +91 -0
  121. package/src/services/memory-service.ts +626 -0
  122. package/src/services/session-history-importer.ts +367 -0
  123. package/tests/canonical-key.test.ts +101 -0
  124. package/tests/evidence-aligner.test.ts +152 -0
  125. package/tests/matcher.test.ts +112 -0
  126. package/tsconfig.json +24 -0
  127. package/vitest.config.ts +15 -0
@@ -0,0 +1,626 @@
1
+ /**
2
+ * Memory Service - Main entry point for memory operations
3
+ * Coordinates EventStore, VectorStore, Retriever, and Graduation
4
+ */
5
+
6
+ import * as path from 'path';
7
+ import * as os from 'os';
8
+ import * as fs from 'fs';
9
+ import * as crypto from 'crypto';
10
+
11
+ import { EventStore } from '../core/event-store.js';
12
+ import { VectorStore } from '../core/vector-store.js';
13
+ import { Embedder, getDefaultEmbedder } from '../core/embedder.js';
14
+ import { VectorWorker, createVectorWorker } from '../core/vector-worker.js';
15
+ import { Matcher, getDefaultMatcher } from '../core/matcher.js';
16
+ import { Retriever, createRetriever, RetrievalResult } from '../core/retriever.js';
17
+ import { GraduationPipeline, createGraduationPipeline } from '../core/graduation.js';
18
+ import type {
19
+ MemoryEventInput,
20
+ AppendResult,
21
+ MemoryEvent,
22
+ Config,
23
+ ConfigSchema,
24
+ ToolObservationPayload,
25
+ MemoryMode,
26
+ EndlessModeConfig,
27
+ EndlessModeConfigSchema,
28
+ WorkingSet,
29
+ ConsolidatedMemory,
30
+ EndlessModeStatus,
31
+ ContextSnapshot,
32
+ ContinuityScore
33
+ } from '../core/types.js';
34
+ import { createToolObservationEmbedding } from '../core/metadata-extractor.js';
35
+ import { WorkingSetStore, createWorkingSetStore } from '../core/working-set-store.js';
36
+ import { ConsolidatedStore, createConsolidatedStore } from '../core/consolidated-store.js';
37
+ import { ConsolidationWorker, createConsolidationWorker } from '../core/consolidation-worker.js';
38
+ import { ContinuityManager, createContinuityManager } from '../core/continuity-manager.js';
39
+
40
+ export interface MemoryServiceConfig {
41
+ storagePath: string;
42
+ embeddingModel?: string;
43
+ }
44
+
45
+ export class MemoryService {
46
+ private readonly eventStore: EventStore;
47
+ private readonly vectorStore: VectorStore;
48
+ private readonly embedder: Embedder;
49
+ private readonly matcher: Matcher;
50
+ private readonly retriever: Retriever;
51
+ private readonly graduation: GraduationPipeline;
52
+ private vectorWorker: VectorWorker | null = null;
53
+ private initialized = false;
54
+
55
+ // Endless Mode components
56
+ private workingSetStore: WorkingSetStore | null = null;
57
+ private consolidatedStore: ConsolidatedStore | null = null;
58
+ private consolidationWorker: ConsolidationWorker | null = null;
59
+ private continuityManager: ContinuityManager | null = null;
60
+ private endlessMode: MemoryMode = 'session';
61
+
62
+ constructor(config: MemoryServiceConfig) {
63
+ const storagePath = this.expandPath(config.storagePath);
64
+
65
+ // Ensure storage directory exists
66
+ if (!fs.existsSync(storagePath)) {
67
+ fs.mkdirSync(storagePath, { recursive: true });
68
+ }
69
+
70
+ // Initialize components
71
+ this.eventStore = new EventStore(path.join(storagePath, 'events.duckdb'));
72
+ this.vectorStore = new VectorStore(path.join(storagePath, 'vectors'));
73
+ this.embedder = config.embeddingModel
74
+ ? new Embedder(config.embeddingModel)
75
+ : getDefaultEmbedder();
76
+ this.matcher = getDefaultMatcher();
77
+ this.retriever = createRetriever(
78
+ this.eventStore,
79
+ this.vectorStore,
80
+ this.embedder,
81
+ this.matcher
82
+ );
83
+ this.graduation = createGraduationPipeline(this.eventStore);
84
+ }
85
+
86
+ /**
87
+ * Initialize all components
88
+ */
89
+ async initialize(): Promise<void> {
90
+ if (this.initialized) return;
91
+
92
+ await this.eventStore.initialize();
93
+ await this.vectorStore.initialize();
94
+ await this.embedder.initialize();
95
+
96
+ // Start vector worker
97
+ this.vectorWorker = createVectorWorker(
98
+ this.eventStore,
99
+ this.vectorStore,
100
+ this.embedder
101
+ );
102
+ this.vectorWorker.start();
103
+
104
+ // Load endless mode setting
105
+ const savedMode = await this.eventStore.getEndlessConfig('mode') as MemoryMode | null;
106
+ if (savedMode === 'endless') {
107
+ this.endlessMode = 'endless';
108
+ await this.initializeEndlessMode();
109
+ }
110
+
111
+ this.initialized = true;
112
+ }
113
+
114
+ /**
115
+ * Start a new session
116
+ */
117
+ async startSession(sessionId: string, projectPath?: string): Promise<void> {
118
+ await this.initialize();
119
+
120
+ await this.eventStore.upsertSession({
121
+ id: sessionId,
122
+ startedAt: new Date(),
123
+ projectPath
124
+ });
125
+ }
126
+
127
+ /**
128
+ * End a session
129
+ */
130
+ async endSession(sessionId: string, summary?: string): Promise<void> {
131
+ await this.initialize();
132
+
133
+ await this.eventStore.upsertSession({
134
+ id: sessionId,
135
+ endedAt: new Date(),
136
+ summary
137
+ });
138
+ }
139
+
140
+ /**
141
+ * Store a user prompt
142
+ */
143
+ async storeUserPrompt(
144
+ sessionId: string,
145
+ content: string,
146
+ metadata?: Record<string, unknown>
147
+ ): Promise<AppendResult> {
148
+ await this.initialize();
149
+
150
+ const result = await this.eventStore.append({
151
+ eventType: 'user_prompt',
152
+ sessionId,
153
+ timestamp: new Date(),
154
+ content,
155
+ metadata
156
+ });
157
+
158
+ // Enqueue for embedding if new
159
+ if (result.success && !result.isDuplicate) {
160
+ await this.eventStore.enqueueForEmbedding(result.eventId, content);
161
+ }
162
+
163
+ return result;
164
+ }
165
+
166
+ /**
167
+ * Store an agent response
168
+ */
169
+ async storeAgentResponse(
170
+ sessionId: string,
171
+ content: string,
172
+ metadata?: Record<string, unknown>
173
+ ): Promise<AppendResult> {
174
+ await this.initialize();
175
+
176
+ const result = await this.eventStore.append({
177
+ eventType: 'agent_response',
178
+ sessionId,
179
+ timestamp: new Date(),
180
+ content,
181
+ metadata
182
+ });
183
+
184
+ // Enqueue for embedding if new
185
+ if (result.success && !result.isDuplicate) {
186
+ await this.eventStore.enqueueForEmbedding(result.eventId, content);
187
+ }
188
+
189
+ return result;
190
+ }
191
+
192
+ /**
193
+ * Store a session summary
194
+ */
195
+ async storeSessionSummary(
196
+ sessionId: string,
197
+ summary: string
198
+ ): Promise<AppendResult> {
199
+ await this.initialize();
200
+
201
+ const result = await this.eventStore.append({
202
+ eventType: 'session_summary',
203
+ sessionId,
204
+ timestamp: new Date(),
205
+ content: summary
206
+ });
207
+
208
+ if (result.success && !result.isDuplicate) {
209
+ await this.eventStore.enqueueForEmbedding(result.eventId, summary);
210
+ }
211
+
212
+ return result;
213
+ }
214
+
215
+ /**
216
+ * Store a tool observation
217
+ */
218
+ async storeToolObservation(
219
+ sessionId: string,
220
+ payload: ToolObservationPayload
221
+ ): Promise<AppendResult> {
222
+ await this.initialize();
223
+
224
+ // Create content for storage (JSON stringified payload)
225
+ const content = JSON.stringify(payload);
226
+
227
+ const result = await this.eventStore.append({
228
+ eventType: 'tool_observation',
229
+ sessionId,
230
+ timestamp: new Date(),
231
+ content,
232
+ metadata: {
233
+ toolName: payload.toolName,
234
+ success: payload.success
235
+ }
236
+ });
237
+
238
+ // Create embedding content (optimized for search)
239
+ if (result.success && !result.isDuplicate) {
240
+ const embeddingContent = createToolObservationEmbedding(
241
+ payload.toolName,
242
+ payload.metadata || {},
243
+ payload.success
244
+ );
245
+ await this.eventStore.enqueueForEmbedding(result.eventId, embeddingContent);
246
+ }
247
+
248
+ return result;
249
+ }
250
+
251
+ /**
252
+ * Retrieve relevant memories for a query
253
+ */
254
+ async retrieveMemories(
255
+ query: string,
256
+ options?: {
257
+ topK?: number;
258
+ minScore?: number;
259
+ sessionId?: string;
260
+ }
261
+ ): Promise<RetrievalResult> {
262
+ await this.initialize();
263
+
264
+ // Process any pending embeddings first
265
+ if (this.vectorWorker) {
266
+ await this.vectorWorker.processAll();
267
+ }
268
+
269
+ return this.retriever.retrieve(query, options);
270
+ }
271
+
272
+ /**
273
+ * Get session history
274
+ */
275
+ async getSessionHistory(sessionId: string): Promise<MemoryEvent[]> {
276
+ await this.initialize();
277
+ return this.eventStore.getSessionEvents(sessionId);
278
+ }
279
+
280
+ /**
281
+ * Get recent events
282
+ */
283
+ async getRecentEvents(limit: number = 100): Promise<MemoryEvent[]> {
284
+ await this.initialize();
285
+ return this.eventStore.getRecentEvents(limit);
286
+ }
287
+
288
+ /**
289
+ * Get memory statistics
290
+ */
291
+ async getStats(): Promise<{
292
+ totalEvents: number;
293
+ vectorCount: number;
294
+ levelStats: Array<{ level: string; count: number }>;
295
+ }> {
296
+ await this.initialize();
297
+
298
+ const recentEvents = await this.eventStore.getRecentEvents(10000);
299
+ const vectorCount = await this.vectorStore.count();
300
+ const levelStats = await this.graduation.getStats();
301
+
302
+ return {
303
+ totalEvents: recentEvents.length,
304
+ vectorCount,
305
+ levelStats
306
+ };
307
+ }
308
+
309
+ /**
310
+ * Process pending embeddings
311
+ */
312
+ async processPendingEmbeddings(): Promise<number> {
313
+ if (this.vectorWorker) {
314
+ return this.vectorWorker.processAll();
315
+ }
316
+ return 0;
317
+ }
318
+
319
+ /**
320
+ * Format retrieval results as context for Claude
321
+ */
322
+ formatAsContext(result: RetrievalResult): string {
323
+ if (!result.context) {
324
+ return '';
325
+ }
326
+
327
+ const confidence = result.matchResult.confidence;
328
+ let header = '';
329
+
330
+ if (confidence === 'high') {
331
+ header = '🎯 **High-confidence memory match found:**\n\n';
332
+ } else if (confidence === 'suggested') {
333
+ header = '💡 **Suggested memories (may be relevant):**\n\n';
334
+ }
335
+
336
+ return header + result.context;
337
+ }
338
+
339
+ // ============================================================
340
+ // Endless Mode Methods
341
+ // ============================================================
342
+
343
+ /**
344
+ * Get the default endless mode config
345
+ */
346
+ private getDefaultEndlessConfig(): EndlessModeConfig {
347
+ return {
348
+ enabled: true,
349
+ workingSet: {
350
+ maxEvents: 100,
351
+ timeWindowHours: 24,
352
+ minRelevanceScore: 0.5
353
+ },
354
+ consolidation: {
355
+ triggerIntervalMs: 3600000, // 1 hour
356
+ triggerEventCount: 100,
357
+ triggerIdleMs: 1800000, // 30 minutes
358
+ useLLMSummarization: false
359
+ },
360
+ continuity: {
361
+ minScoreForSeamless: 0.7,
362
+ topicDecayHours: 48
363
+ }
364
+ };
365
+ }
366
+
367
+ /**
368
+ * Initialize Endless Mode components
369
+ */
370
+ async initializeEndlessMode(): Promise<void> {
371
+ const config = await this.getEndlessConfig();
372
+
373
+ this.workingSetStore = createWorkingSetStore(this.eventStore, config);
374
+ this.consolidatedStore = createConsolidatedStore(this.eventStore);
375
+ this.consolidationWorker = createConsolidationWorker(
376
+ this.workingSetStore,
377
+ this.consolidatedStore,
378
+ config
379
+ );
380
+ this.continuityManager = createContinuityManager(this.eventStore, config);
381
+
382
+ // Start consolidation worker
383
+ this.consolidationWorker.start();
384
+ }
385
+
386
+ /**
387
+ * Get Endless Mode configuration
388
+ */
389
+ async getEndlessConfig(): Promise<EndlessModeConfig> {
390
+ const savedConfig = await this.eventStore.getEndlessConfig('config') as EndlessModeConfig | null;
391
+ return savedConfig || this.getDefaultEndlessConfig();
392
+ }
393
+
394
+ /**
395
+ * Set Endless Mode configuration
396
+ */
397
+ async setEndlessConfig(config: Partial<EndlessModeConfig>): Promise<void> {
398
+ const current = await this.getEndlessConfig();
399
+ const merged = { ...current, ...config };
400
+ await this.eventStore.setEndlessConfig('config', merged);
401
+ }
402
+
403
+ /**
404
+ * Set memory mode (session or endless)
405
+ */
406
+ async setMode(mode: MemoryMode): Promise<void> {
407
+ await this.initialize();
408
+
409
+ if (mode === this.endlessMode) return;
410
+
411
+ this.endlessMode = mode;
412
+ await this.eventStore.setEndlessConfig('mode', mode);
413
+
414
+ if (mode === 'endless') {
415
+ await this.initializeEndlessMode();
416
+ } else {
417
+ // Stop endless mode components
418
+ if (this.consolidationWorker) {
419
+ this.consolidationWorker.stop();
420
+ this.consolidationWorker = null;
421
+ }
422
+ this.workingSetStore = null;
423
+ this.consolidatedStore = null;
424
+ this.continuityManager = null;
425
+ }
426
+ }
427
+
428
+ /**
429
+ * Get current memory mode
430
+ */
431
+ getMode(): MemoryMode {
432
+ return this.endlessMode;
433
+ }
434
+
435
+ /**
436
+ * Check if endless mode is active
437
+ */
438
+ isEndlessModeActive(): boolean {
439
+ return this.endlessMode === 'endless';
440
+ }
441
+
442
+ /**
443
+ * Add event to Working Set (Endless Mode)
444
+ */
445
+ async addToWorkingSet(eventId: string, relevanceScore?: number): Promise<void> {
446
+ if (!this.workingSetStore) return;
447
+ await this.workingSetStore.add(eventId, relevanceScore);
448
+ }
449
+
450
+ /**
451
+ * Get the current Working Set
452
+ */
453
+ async getWorkingSet(): Promise<WorkingSet | null> {
454
+ if (!this.workingSetStore) return null;
455
+ return this.workingSetStore.get();
456
+ }
457
+
458
+ /**
459
+ * Search consolidated memories
460
+ */
461
+ async searchConsolidated(
462
+ query: string,
463
+ options?: { topK?: number }
464
+ ): Promise<ConsolidatedMemory[]> {
465
+ if (!this.consolidatedStore) return [];
466
+ return this.consolidatedStore.search(query, options);
467
+ }
468
+
469
+ /**
470
+ * Get all consolidated memories
471
+ */
472
+ async getConsolidatedMemories(limit?: number): Promise<ConsolidatedMemory[]> {
473
+ if (!this.consolidatedStore) return [];
474
+ return this.consolidatedStore.getAll({ limit });
475
+ }
476
+
477
+ /**
478
+ * Calculate continuity score for current context
479
+ */
480
+ async calculateContinuity(
481
+ content: string,
482
+ metadata?: { files?: string[]; entities?: string[] }
483
+ ): Promise<ContinuityScore | null> {
484
+ if (!this.continuityManager) return null;
485
+
486
+ const snapshot = this.continuityManager.createSnapshot(
487
+ crypto.randomUUID(),
488
+ content,
489
+ metadata
490
+ );
491
+
492
+ return this.continuityManager.calculateScore(snapshot);
493
+ }
494
+
495
+ /**
496
+ * Record activity (for consolidation idle trigger)
497
+ */
498
+ recordActivity(): void {
499
+ if (this.consolidationWorker) {
500
+ this.consolidationWorker.recordActivity();
501
+ }
502
+ }
503
+
504
+ /**
505
+ * Force a consolidation run
506
+ */
507
+ async forceConsolidation(): Promise<number> {
508
+ if (!this.consolidationWorker) return 0;
509
+ return this.consolidationWorker.forceRun();
510
+ }
511
+
512
+ /**
513
+ * Get Endless Mode status
514
+ */
515
+ async getEndlessModeStatus(): Promise<EndlessModeStatus> {
516
+ await this.initialize();
517
+
518
+ let workingSetSize = 0;
519
+ let continuityScore = 0.5;
520
+ let consolidatedCount = 0;
521
+ let lastConsolidation: Date | null = null;
522
+
523
+ if (this.workingSetStore) {
524
+ workingSetSize = await this.workingSetStore.count();
525
+ const workingSet = await this.workingSetStore.get();
526
+ continuityScore = workingSet.continuityScore;
527
+ }
528
+
529
+ if (this.consolidatedStore) {
530
+ consolidatedCount = await this.consolidatedStore.count();
531
+ lastConsolidation = await this.consolidatedStore.getLastConsolidationTime();
532
+ }
533
+
534
+ return {
535
+ mode: this.endlessMode,
536
+ workingSetSize,
537
+ continuityScore,
538
+ consolidatedCount,
539
+ lastConsolidation
540
+ };
541
+ }
542
+
543
+ /**
544
+ * Format Endless Mode context for Claude
545
+ */
546
+ async formatEndlessContext(query: string): Promise<string> {
547
+ if (!this.isEndlessModeActive()) {
548
+ return '';
549
+ }
550
+
551
+ const workingSet = await this.getWorkingSet();
552
+ const consolidated = await this.searchConsolidated(query, { topK: 3 });
553
+ const continuity = await this.calculateContinuity(query);
554
+
555
+ const parts: string[] = [];
556
+
557
+ // Continuity status
558
+ if (continuity) {
559
+ const statusEmoji = continuity.transitionType === 'seamless' ? '🔗' :
560
+ continuity.transitionType === 'topic_shift' ? '↪️' : '🆕';
561
+ parts.push(`${statusEmoji} Context: ${continuity.transitionType} (score: ${continuity.score.toFixed(2)})`);
562
+ }
563
+
564
+ // Working set summary
565
+ if (workingSet && workingSet.recentEvents.length > 0) {
566
+ parts.push('\n## Recent Context (Working Set)');
567
+ const recent = workingSet.recentEvents.slice(0, 5);
568
+ for (const event of recent) {
569
+ const preview = event.content.slice(0, 80) + (event.content.length > 80 ? '...' : '');
570
+ const time = event.timestamp.toLocaleTimeString();
571
+ parts.push(`- ${time} [${event.eventType}] ${preview}`);
572
+ }
573
+ }
574
+
575
+ // Consolidated memories
576
+ if (consolidated.length > 0) {
577
+ parts.push('\n## Related Knowledge (Consolidated)');
578
+ for (const memory of consolidated) {
579
+ parts.push(`- ${memory.topics.slice(0, 3).join(', ')}: ${memory.summary.slice(0, 100)}...`);
580
+ }
581
+ }
582
+
583
+ return parts.join('\n');
584
+ }
585
+
586
+ /**
587
+ * Shutdown service
588
+ */
589
+ async shutdown(): Promise<void> {
590
+ // Stop endless mode components
591
+ if (this.consolidationWorker) {
592
+ this.consolidationWorker.stop();
593
+ }
594
+
595
+ if (this.vectorWorker) {
596
+ this.vectorWorker.stop();
597
+ }
598
+ await this.eventStore.close();
599
+ }
600
+
601
+ /**
602
+ * Expand ~ to home directory
603
+ */
604
+ private expandPath(p: string): string {
605
+ if (p.startsWith('~')) {
606
+ return path.join(os.homedir(), p.slice(1));
607
+ }
608
+ return p;
609
+ }
610
+ }
611
+
612
+ // Default instance
613
+ let defaultService: MemoryService | null = null;
614
+
615
+ export function getDefaultMemoryService(): MemoryService {
616
+ if (!defaultService) {
617
+ defaultService = new MemoryService({
618
+ storagePath: '~/.claude-code/memory'
619
+ });
620
+ }
621
+ return defaultService;
622
+ }
623
+
624
+ export function createMemoryService(config: MemoryServiceConfig): MemoryService {
625
+ return new MemoryService(config);
626
+ }