claude-memory-layer 1.0.6 → 1.0.8

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 (45) hide show
  1. package/.claude/settings.local.json +4 -1
  2. package/.claude-plugin/plugin.json +3 -3
  3. package/.history/package_20260201142928.json +46 -0
  4. package/.history/package_20260201192048.json +47 -0
  5. package/README.md +26 -26
  6. package/dist/.claude-plugin/plugin.json +3 -3
  7. package/dist/cli/index.js +1109 -25
  8. package/dist/cli/index.js.map +4 -4
  9. package/dist/core/index.js +192 -5
  10. package/dist/core/index.js.map +4 -4
  11. package/dist/hooks/session-end.js +262 -18
  12. package/dist/hooks/session-end.js.map +4 -4
  13. package/dist/hooks/session-start.js +262 -18
  14. package/dist/hooks/session-start.js.map +4 -4
  15. package/dist/hooks/stop.js +262 -18
  16. package/dist/hooks/stop.js.map +4 -4
  17. package/dist/hooks/user-prompt-submit.js +262 -18
  18. package/dist/hooks/user-prompt-submit.js.map +4 -4
  19. package/dist/server/api/index.js +4728 -0
  20. package/dist/server/api/index.js.map +7 -0
  21. package/dist/server/index.js +4790 -0
  22. package/dist/server/index.js.map +7 -0
  23. package/dist/services/memory-service.js +269 -18
  24. package/dist/services/memory-service.js.map +4 -4
  25. package/dist/ui/index.html +1225 -0
  26. package/package.json +4 -2
  27. package/scripts/build.ts +33 -3
  28. package/src/cli/index.ts +311 -6
  29. package/src/core/db-wrapper.ts +8 -1
  30. package/src/core/event-store.ts +52 -3
  31. package/src/core/graduation-worker.ts +171 -0
  32. package/src/core/graduation.ts +15 -2
  33. package/src/core/index.ts +1 -0
  34. package/src/core/retriever.ts +18 -0
  35. package/src/core/types.ts +1 -1
  36. package/src/mcp/index.ts +2 -2
  37. package/src/mcp/tools.ts +1 -1
  38. package/src/server/api/citations.ts +7 -3
  39. package/src/server/api/events.ts +7 -3
  40. package/src/server/api/search.ts +7 -3
  41. package/src/server/api/sessions.ts +7 -3
  42. package/src/server/api/stats.ts +175 -5
  43. package/src/server/index.ts +18 -9
  44. package/src/services/memory-service.ts +107 -19
  45. package/src/ui/index.html +1225 -0
@@ -0,0 +1,171 @@
1
+ /**
2
+ * Graduation Worker
3
+ * Periodically evaluates memory events for promotion to higher levels
4
+ * L0 → L1 → L2 → L3 → L4 based on access patterns and confidence
5
+ */
6
+
7
+ import type { MemoryLevel } from './types.js';
8
+ import { EventStore } from './event-store.js';
9
+ import { GraduationPipeline } from './graduation.js';
10
+
11
+ export interface GraduationWorkerConfig {
12
+ /** How often to run graduation evaluation (ms) */
13
+ evaluationIntervalMs: number;
14
+ /** Batch size for graduation evaluation */
15
+ batchSize: number;
16
+ /** Minimum time between evaluations of the same event (ms) */
17
+ cooldownMs: number;
18
+ }
19
+
20
+ const DEFAULT_CONFIG: GraduationWorkerConfig = {
21
+ evaluationIntervalMs: 300000, // 5 minutes
22
+ batchSize: 50,
23
+ cooldownMs: 3600000 // 1 hour cooldown between evaluations
24
+ };
25
+
26
+ export class GraduationWorker {
27
+ private running = false;
28
+ private timeout: NodeJS.Timeout | null = null;
29
+ private lastEvaluated: Map<string, number> = new Map();
30
+
31
+ constructor(
32
+ private eventStore: EventStore,
33
+ private graduation: GraduationPipeline,
34
+ private config: GraduationWorkerConfig = DEFAULT_CONFIG
35
+ ) {}
36
+
37
+ /**
38
+ * Start the graduation worker
39
+ */
40
+ start(): void {
41
+ if (this.running) return;
42
+ this.running = true;
43
+ this.scheduleNext();
44
+ }
45
+
46
+ /**
47
+ * Stop the graduation worker
48
+ */
49
+ stop(): void {
50
+ this.running = false;
51
+ if (this.timeout) {
52
+ clearTimeout(this.timeout);
53
+ this.timeout = null;
54
+ }
55
+ }
56
+
57
+ /**
58
+ * Check if currently running
59
+ */
60
+ isRunning(): boolean {
61
+ return this.running;
62
+ }
63
+
64
+ /**
65
+ * Force a graduation evaluation run
66
+ */
67
+ async forceRun(): Promise<GraduationRunResult> {
68
+ return await this.runGraduation();
69
+ }
70
+
71
+ /**
72
+ * Schedule the next graduation check
73
+ */
74
+ private scheduleNext(): void {
75
+ if (!this.running) return;
76
+
77
+ this.timeout = setTimeout(
78
+ () => this.run(),
79
+ this.config.evaluationIntervalMs
80
+ );
81
+ }
82
+
83
+ /**
84
+ * Run graduation evaluation
85
+ */
86
+ private async run(): Promise<void> {
87
+ if (!this.running) return;
88
+
89
+ try {
90
+ await this.runGraduation();
91
+ } catch (error) {
92
+ console.error('Graduation error:', error);
93
+ }
94
+
95
+ this.scheduleNext();
96
+ }
97
+
98
+ /**
99
+ * Perform graduation evaluation across all levels
100
+ */
101
+ private async runGraduation(): Promise<GraduationRunResult> {
102
+ const result: GraduationRunResult = {
103
+ evaluated: 0,
104
+ graduated: 0,
105
+ byLevel: {}
106
+ };
107
+
108
+ const levels: MemoryLevel[] = ['L0', 'L1', 'L2', 'L3'];
109
+ const now = Date.now();
110
+
111
+ for (const level of levels) {
112
+ const events = await this.eventStore.getEventsByLevel(level, {
113
+ limit: this.config.batchSize
114
+ });
115
+
116
+ let levelGraduated = 0;
117
+
118
+ for (const event of events) {
119
+ // Check cooldown
120
+ const lastEval = this.lastEvaluated.get(event.id);
121
+ if (lastEval && (now - lastEval) < this.config.cooldownMs) {
122
+ continue;
123
+ }
124
+
125
+ result.evaluated++;
126
+ this.lastEvaluated.set(event.id, now);
127
+
128
+ const gradResult = await this.graduation.evaluateGraduation(event.id, level);
129
+
130
+ if (gradResult.success) {
131
+ result.graduated++;
132
+ levelGraduated++;
133
+ }
134
+ }
135
+
136
+ if (levelGraduated > 0) {
137
+ result.byLevel[level] = levelGraduated;
138
+ }
139
+ }
140
+
141
+ // Clean up old cooldown entries (keep last 1000)
142
+ if (this.lastEvaluated.size > 1000) {
143
+ const entries = Array.from(this.lastEvaluated.entries());
144
+ entries.sort((a, b) => b[1] - a[1]);
145
+ this.lastEvaluated = new Map(entries.slice(0, 1000));
146
+ }
147
+
148
+ return result;
149
+ }
150
+ }
151
+
152
+ export interface GraduationRunResult {
153
+ evaluated: number;
154
+ graduated: number;
155
+ byLevel: Record<string, number>;
156
+ }
157
+
158
+ /**
159
+ * Create a Graduation Worker instance
160
+ */
161
+ export function createGraduationWorker(
162
+ eventStore: EventStore,
163
+ graduation: GraduationPipeline,
164
+ config?: Partial<GraduationWorkerConfig>
165
+ ): GraduationWorker {
166
+ return new GraduationWorker(
167
+ eventStore,
168
+ graduation,
169
+ { ...DEFAULT_CONFIG, ...config }
170
+ );
171
+ }
@@ -84,18 +84,31 @@ export class GraduationPipeline {
84
84
  };
85
85
  }
86
86
 
87
+ // Track which sessions have accessed each event
88
+ private readonly sessionAccesses: Map<string, Set<string>> = new Map();
89
+
87
90
  /**
88
91
  * Record an access to an event (used for graduation scoring)
89
92
  */
90
93
  recordAccess(eventId: string, fromSessionId: string, confidence: number = 1.0): void {
91
94
  const existing = this.metrics.get(eventId);
92
95
 
96
+ // Track sessions that have accessed this event
97
+ if (!this.sessionAccesses.has(eventId)) {
98
+ this.sessionAccesses.set(eventId, new Set());
99
+ }
100
+ const sessions = this.sessionAccesses.get(eventId)!;
101
+ const isNewSession = !sessions.has(fromSessionId);
102
+ sessions.add(fromSessionId);
103
+
93
104
  if (existing) {
94
105
  existing.accessCount++;
95
106
  existing.lastAccessed = new Date();
96
107
  existing.confidence = Math.max(existing.confidence, confidence);
97
- // Track cross-session references
98
- // This would need more sophisticated tracking in production
108
+ // Update cross-session references count
109
+ if (isNewSession && sessions.size > 1) {
110
+ existing.crossSessionRefs = sessions.size - 1;
111
+ }
99
112
  } else {
100
113
  this.metrics.set(eventId, {
101
114
  eventId,
package/src/core/index.ts CHANGED
@@ -27,6 +27,7 @@ export * from './evidence-aligner.js';
27
27
  // Retrieval & Graduation
28
28
  export * from './retriever.js';
29
29
  export * from './graduation.js';
30
+ export * from './graduation-worker.js';
30
31
 
31
32
  // Task Entity System
32
33
  export * from './task/index.js';
@@ -9,6 +9,7 @@ import { Embedder } from './embedder.js';
9
9
  import { Matcher } from './matcher.js';
10
10
  import { SharedStore } from './shared-store.js';
11
11
  import { SharedVectorStore } from './shared-vector-store.js';
12
+ import { GraduationPipeline } from './graduation.js';
12
13
  import type { MemoryEvent, MatchResult, Config, SharedTroubleshootingEntry } from './types.js';
13
14
 
14
15
  export interface RetrievalOptions {
@@ -60,6 +61,7 @@ export class Retriever {
60
61
  private readonly matcher: Matcher;
61
62
  private sharedStore?: SharedStore;
62
63
  private sharedVectorStore?: SharedVectorStore;
64
+ private graduation?: GraduationPipeline;
63
65
 
64
66
  constructor(
65
67
  eventStore: EventStore,
@@ -76,6 +78,13 @@ export class Retriever {
76
78
  this.sharedVectorStore = sharedOptions?.sharedVectorStore;
77
79
  }
78
80
 
81
+ /**
82
+ * Set graduation pipeline for access tracking
83
+ */
84
+ setGraduationPipeline(graduation: GraduationPipeline): void {
85
+ this.graduation = graduation;
86
+ }
87
+
79
88
  /**
80
89
  * Set shared stores after construction
81
90
  */
@@ -237,6 +246,15 @@ export class Retriever {
237
246
  const event = await this.eventStore.getEvent(result.eventId);
238
247
  if (!event) continue;
239
248
 
249
+ // Record access for graduation scoring
250
+ if (this.graduation) {
251
+ this.graduation.recordAccess(
252
+ event.id,
253
+ options.sessionId || 'unknown',
254
+ result.score
255
+ );
256
+ }
257
+
240
258
  let sessionContext: string | undefined;
241
259
  if (options.includeSessionContext) {
242
260
  sessionContext = await this.getSessionContext(event.sessionId, event.id);
package/src/core/types.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Core types for code-memory plugin
2
+ * Core types for claude-memory-layer plugin
3
3
  * Idris2 inspired: Complete, immutable type definitions with Zod validation
4
4
  */
5
5
 
package/src/mcp/index.ts CHANGED
@@ -16,7 +16,7 @@ import { handleToolCall } from './handlers.js';
16
16
 
17
17
  const server = new Server(
18
18
  {
19
- name: 'code-memory-mcp',
19
+ name: 'claude-memory-layer-mcp',
20
20
  version: '1.0.0'
21
21
  },
22
22
  {
@@ -41,7 +41,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
41
41
  async function main() {
42
42
  const transport = new StdioServerTransport();
43
43
  await server.connect(transport);
44
- console.error('code-memory MCP server started');
44
+ console.error('claude-memory-layer MCP server started');
45
45
  }
46
46
 
47
47
  main().catch(console.error);
package/src/mcp/tools.ts CHANGED
@@ -8,7 +8,7 @@ import type { Tool } from '@modelcontextprotocol/sdk/types.js';
8
8
  export const tools: Tool[] = [
9
9
  {
10
10
  name: 'mem-search',
11
- description: 'Search code-memory for relevant past conversations and insights. Returns a compact index of results - use mem-details to get full content.',
11
+ description: 'Search claude-memory-layer for relevant past conversations and insights. Returns a compact index of results - use mem-details to get full content.',
12
12
  inputSchema: {
13
13
  type: 'object',
14
14
  properties: {
@@ -4,7 +4,7 @@
4
4
  */
5
5
 
6
6
  import { Hono } from 'hono';
7
- import { getDefaultMemoryService } from '../../services/memory-service.js';
7
+ import { getReadOnlyMemoryService } from '../../services/memory-service.js';
8
8
  import { generateCitationId, parseCitationId } from '../../core/citation-generator.js';
9
9
 
10
10
  export const citationsRouter = new Hono();
@@ -15,9 +15,9 @@ citationsRouter.get('/:id', async (c) => {
15
15
 
16
16
  // Support both formats: "a7Bc3x" or "mem:a7Bc3x"
17
17
  const citationId = parseCitationId(id) || id;
18
+ const memoryService = getReadOnlyMemoryService();
18
19
 
19
20
  try {
20
- const memoryService = getDefaultMemoryService();
21
21
  await memoryService.initialize();
22
22
 
23
23
  // Search through recent events to find the one matching this citation ID
@@ -48,6 +48,8 @@ citationsRouter.get('/:id', async (c) => {
48
48
  });
49
49
  } catch (error) {
50
50
  return c.json({ error: (error as Error).message }, 500);
51
+ } finally {
52
+ await memoryService.shutdown();
51
53
  }
52
54
  });
53
55
 
@@ -55,9 +57,9 @@ citationsRouter.get('/:id', async (c) => {
55
57
  citationsRouter.get('/:id/related', async (c) => {
56
58
  const { id } = c.req.param();
57
59
  const citationId = parseCitationId(id) || id;
60
+ const memoryService = getReadOnlyMemoryService();
58
61
 
59
62
  try {
60
- const memoryService = getDefaultMemoryService();
61
63
  await memoryService.initialize();
62
64
 
63
65
  const recentEvents = await memoryService.getRecentEvents(10000);
@@ -97,5 +99,7 @@ citationsRouter.get('/:id/related', async (c) => {
97
99
  });
98
100
  } catch (error) {
99
101
  return c.json({ error: (error as Error).message }, 500);
102
+ } finally {
103
+ await memoryService.shutdown();
100
104
  }
101
105
  });
@@ -4,7 +4,7 @@
4
4
  */
5
5
 
6
6
  import { Hono } from 'hono';
7
- import { getDefaultMemoryService } from '../../services/memory-service.js';
7
+ import { getReadOnlyMemoryService } from '../../services/memory-service.js';
8
8
 
9
9
  export const eventsRouter = new Hono();
10
10
 
@@ -14,9 +14,9 @@ eventsRouter.get('/', async (c) => {
14
14
  const eventType = c.req.query('type');
15
15
  const limit = parseInt(c.req.query('limit') || '100', 10);
16
16
  const offset = parseInt(c.req.query('offset') || '0', 10);
17
+ const memoryService = getReadOnlyMemoryService();
17
18
 
18
19
  try {
19
- const memoryService = getDefaultMemoryService();
20
20
  await memoryService.initialize();
21
21
 
22
22
  let events = await memoryService.getRecentEvents(limit + offset + 1000);
@@ -51,15 +51,17 @@ eventsRouter.get('/', async (c) => {
51
51
  });
52
52
  } catch (error) {
53
53
  return c.json({ error: (error as Error).message }, 500);
54
+ } finally {
55
+ await memoryService.shutdown();
54
56
  }
55
57
  });
56
58
 
57
59
  // GET /api/events/:id - Get event details
58
60
  eventsRouter.get('/:id', async (c) => {
59
61
  const { id } = c.req.param();
62
+ const memoryService = getReadOnlyMemoryService();
60
63
 
61
64
  try {
62
- const memoryService = getDefaultMemoryService();
63
65
  await memoryService.initialize();
64
66
 
65
67
  const recentEvents = await memoryService.getRecentEvents(10000);
@@ -97,5 +99,7 @@ eventsRouter.get('/:id', async (c) => {
97
99
  });
98
100
  } catch (error) {
99
101
  return c.json({ error: (error as Error).message }, 500);
102
+ } finally {
103
+ await memoryService.shutdown();
100
104
  }
101
105
  });
@@ -4,7 +4,7 @@
4
4
  */
5
5
 
6
6
  import { Hono } from 'hono';
7
- import { getDefaultMemoryService } from '../../services/memory-service.js';
7
+ import { getReadOnlyMemoryService } from '../../services/memory-service.js';
8
8
 
9
9
  export const searchRouter = new Hono();
10
10
 
@@ -20,6 +20,7 @@ interface SearchRequest {
20
20
 
21
21
  // POST /api/search - Search memories
22
22
  searchRouter.post('/', async (c) => {
23
+ const memoryService = getReadOnlyMemoryService();
23
24
  try {
24
25
  const body = await c.req.json<SearchRequest>();
25
26
 
@@ -27,7 +28,6 @@ searchRouter.post('/', async (c) => {
27
28
  return c.json({ error: 'Query is required' }, 400);
28
29
  }
29
30
 
30
- const memoryService = getDefaultMemoryService();
31
31
  await memoryService.initialize();
32
32
 
33
33
  const startTime = Date.now();
@@ -60,6 +60,8 @@ searchRouter.post('/', async (c) => {
60
60
  });
61
61
  } catch (error) {
62
62
  return c.json({ error: (error as Error).message }, 500);
63
+ } finally {
64
+ await memoryService.shutdown();
63
65
  }
64
66
  });
65
67
 
@@ -72,9 +74,9 @@ searchRouter.get('/', async (c) => {
72
74
  }
73
75
 
74
76
  const topK = parseInt(c.req.query('topK') || '5', 10);
77
+ const memoryService = getReadOnlyMemoryService();
75
78
 
76
79
  try {
77
- const memoryService = getDefaultMemoryService();
78
80
  await memoryService.initialize();
79
81
 
80
82
  const result = await memoryService.retrieveMemories(query, { topK });
@@ -94,5 +96,7 @@ searchRouter.get('/', async (c) => {
94
96
  });
95
97
  } catch (error) {
96
98
  return c.json({ error: (error as Error).message }, 500);
99
+ } finally {
100
+ await memoryService.shutdown();
97
101
  }
98
102
  });
@@ -4,7 +4,7 @@
4
4
  */
5
5
 
6
6
  import { Hono } from 'hono';
7
- import { getDefaultMemoryService } from '../../services/memory-service.js';
7
+ import { getReadOnlyMemoryService } from '../../services/memory-service.js';
8
8
 
9
9
  export const sessionsRouter = new Hono();
10
10
 
@@ -12,9 +12,9 @@ export const sessionsRouter = new Hono();
12
12
  sessionsRouter.get('/', async (c) => {
13
13
  const page = parseInt(c.req.query('page') || '1', 10);
14
14
  const pageSize = parseInt(c.req.query('pageSize') || '20', 10);
15
+ const memoryService = getReadOnlyMemoryService();
15
16
 
16
17
  try {
17
- const memoryService = getDefaultMemoryService();
18
18
  await memoryService.initialize();
19
19
 
20
20
  // Get recent events and extract sessions
@@ -65,15 +65,17 @@ sessionsRouter.get('/', async (c) => {
65
65
  });
66
66
  } catch (error) {
67
67
  return c.json({ error: (error as Error).message }, 500);
68
+ } finally {
69
+ await memoryService.shutdown();
68
70
  }
69
71
  });
70
72
 
71
73
  // GET /api/sessions/:id - Get session details
72
74
  sessionsRouter.get('/:id', async (c) => {
73
75
  const { id } = c.req.param();
76
+ const memoryService = getReadOnlyMemoryService();
74
77
 
75
78
  try {
76
- const memoryService = getDefaultMemoryService();
77
79
  await memoryService.initialize();
78
80
 
79
81
  const events = await memoryService.getSessionHistory(id);
@@ -107,5 +109,7 @@ sessionsRouter.get('/:id', async (c) => {
107
109
  });
108
110
  } catch (error) {
109
111
  return c.json({ error: (error as Error).message }, 500);
112
+ } finally {
113
+ await memoryService.shutdown();
110
114
  }
111
115
  });