claude-memory-layer 1.0.7 → 1.0.9

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 (53) hide show
  1. package/.claude/settings.local.json +10 -1
  2. package/.claude-memory/test.sqlite +0 -0
  3. package/.history/package_20260201192048.json +47 -0
  4. package/.history/package_20260202114053.json +49 -0
  5. package/HANDOFF.md +92 -0
  6. package/dist/cli/index.js +1711 -102
  7. package/dist/cli/index.js.map +4 -4
  8. package/dist/core/index.js +1257 -84
  9. package/dist/core/index.js.map +4 -4
  10. package/dist/hooks/post-tool-use.js +5589 -0
  11. package/dist/hooks/post-tool-use.js.map +7 -0
  12. package/dist/hooks/session-end.js +1382 -85
  13. package/dist/hooks/session-end.js.map +4 -4
  14. package/dist/hooks/session-start.js +1377 -84
  15. package/dist/hooks/session-start.js.map +4 -4
  16. package/dist/hooks/stop.js +1383 -86
  17. package/dist/hooks/stop.js.map +4 -4
  18. package/dist/hooks/user-prompt-submit.js +1412 -84
  19. package/dist/hooks/user-prompt-submit.js.map +4 -4
  20. package/dist/server/api/index.js +1576 -136
  21. package/dist/server/api/index.js.map +4 -4
  22. package/dist/server/index.js +1585 -143
  23. package/dist/server/index.js.map +4 -4
  24. package/dist/services/memory-service.js +1392 -84
  25. package/dist/services/memory-service.js.map +4 -4
  26. package/dist/ui/app.js +304 -0
  27. package/dist/ui/index.html +202 -715
  28. package/dist/ui/style.css +595 -0
  29. package/package.json +4 -1
  30. package/scripts/build.ts +5 -2
  31. package/src/cli/index.ts +226 -0
  32. package/src/core/db-wrapper.ts +8 -1
  33. package/src/core/event-store.ts +70 -3
  34. package/src/core/graduation-worker.ts +171 -0
  35. package/src/core/graduation.ts +15 -2
  36. package/src/core/index.ts +4 -0
  37. package/src/core/retriever.ts +21 -0
  38. package/src/core/sqlite-event-store.ts +849 -0
  39. package/src/core/sqlite-wrapper.ts +108 -0
  40. package/src/core/sync-worker.ts +228 -0
  41. package/src/core/vector-worker.ts +44 -14
  42. package/src/hooks/user-prompt-submit.ts +53 -4
  43. package/src/server/api/citations.ts +7 -3
  44. package/src/server/api/events.ts +7 -3
  45. package/src/server/api/search.ts +7 -3
  46. package/src/server/api/sessions.ts +7 -3
  47. package/src/server/api/stats.ts +159 -12
  48. package/src/server/index.ts +18 -9
  49. package/src/services/memory-service.ts +263 -46
  50. package/src/ui/app.js +304 -0
  51. package/src/ui/index.html +202 -715
  52. package/src/ui/style.css +595 -0
  53. package/test_access.js +49 -0
@@ -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
  });
@@ -4,18 +4,16 @@
4
4
  */
5
5
 
6
6
  import { Hono } from 'hono';
7
- import { getDefaultMemoryService, getMemoryServiceForProject } from '../../services/memory-service.js';
7
+ import { getReadOnlyMemoryService, getMemoryServiceForProject } from '../../services/memory-service.js';
8
8
 
9
9
  export const statsRouter = new Hono();
10
10
 
11
11
  // GET /api/stats/shared - Get shared store statistics
12
12
  statsRouter.get('/shared', async (c) => {
13
+ const memoryService = getReadOnlyMemoryService();
13
14
  try {
14
- const memoryService = getDefaultMemoryService();
15
15
  await memoryService.initialize();
16
-
17
16
  const sharedStats = await memoryService.getSharedStoreStats();
18
-
19
17
  return c.json({
20
18
  troubleshooting: sharedStats?.troubleshooting || 0,
21
19
  bestPractices: sharedStats?.bestPractices || 0,
@@ -31,18 +29,18 @@ statsRouter.get('/shared', async (c) => {
31
29
  totalUsageCount: 0,
32
30
  lastUpdated: null
33
31
  });
32
+ } finally {
33
+ await memoryService.shutdown();
34
34
  }
35
35
  });
36
36
 
37
37
  // GET /api/stats/endless - Get endless mode status
38
38
  statsRouter.get('/endless', async (c) => {
39
+ const projectPath = c.req.query('project') || process.cwd();
40
+ const memoryService = getMemoryServiceForProject(projectPath);
39
41
  try {
40
- const projectPath = c.req.query('project') || process.cwd();
41
- const memoryService = getMemoryServiceForProject(projectPath);
42
42
  await memoryService.initialize();
43
-
44
43
  const status = await memoryService.getEndlessModeStatus();
45
-
46
44
  return c.json({
47
45
  mode: status.mode,
48
46
  continuityScore: status.continuityScore,
@@ -58,15 +56,84 @@ statsRouter.get('/endless', async (c) => {
58
56
  consolidatedCount: 0,
59
57
  lastConsolidation: null
60
58
  });
59
+ } finally {
60
+ await memoryService.shutdown();
61
+ }
62
+ });
63
+
64
+ // GET /api/stats/levels/:level - Get events by memory level
65
+ statsRouter.get('/levels/:level', async (c) => {
66
+ const { level } = c.req.param();
67
+ const limit = parseInt(c.req.query('limit') || '20', 10);
68
+ const offset = parseInt(c.req.query('offset') || '0', 10);
69
+ const sort = c.req.query('sort') || 'recent';
70
+
71
+ // Validate level
72
+ const validLevels = ['L0', 'L1', 'L2', 'L3', 'L4'];
73
+ if (!validLevels.includes(level)) {
74
+ return c.json({ error: `Invalid level. Must be one of: ${validLevels.join(', ')}` }, 400);
75
+ }
76
+
77
+ const memoryService = getReadOnlyMemoryService();
78
+ try {
79
+ await memoryService.initialize();
80
+ let events = await memoryService.getEventsByLevel(level, { limit: limit * 2, offset });
81
+ const stats = await memoryService.getStats();
82
+ const levelStat = stats.levelStats.find(s => s.level === level);
83
+
84
+ // Apply sorting
85
+ if (sort === 'accessed') {
86
+ // Sort by access count (will need to get from SQLite)
87
+ // For now, add access count from SQLite if available
88
+ const sqliteStore = (memoryService as any).sqliteEventStore;
89
+ if (sqliteStore) {
90
+ const eventIds = events.map(e => e.id);
91
+ const accessedEvents = await sqliteStore.getMostAccessed(1000);
92
+ const accessMap = new Map(accessedEvents.map((e: any) => [e.id, e.access_count || 0]));
93
+ events = events.map((e: any) => ({
94
+ ...e,
95
+ accessCount: accessMap.get(e.id) || 0
96
+ }));
97
+ events.sort((a: any, b: any) => b.accessCount - a.accessCount);
98
+ }
99
+ } else if (sort === 'oldest') {
100
+ events.sort((a, b) => a.timestamp.getTime() - b.timestamp.getTime());
101
+ } else {
102
+ // 'recent' - default sorting (newest first)
103
+ events.sort((a, b) => b.timestamp.getTime() - a.timestamp.getTime());
104
+ }
105
+
106
+ // Apply limit after sorting
107
+ events = events.slice(0, limit);
108
+
109
+ return c.json({
110
+ level,
111
+ events: events.map((e: any) => ({
112
+ id: e.id,
113
+ eventType: e.eventType,
114
+ sessionId: e.sessionId,
115
+ timestamp: e.timestamp.toISOString(),
116
+ content: e.content.slice(0, 500) + (e.content.length > 500 ? '...' : ''),
117
+ metadata: e.metadata,
118
+ accessCount: e.accessCount || 0
119
+ })),
120
+ total: levelStat?.count || 0,
121
+ limit,
122
+ offset,
123
+ hasMore: events.length === limit
124
+ });
125
+ } catch (error) {
126
+ return c.json({ error: (error as Error).message }, 500);
127
+ } finally {
128
+ await memoryService.shutdown();
61
129
  }
62
130
  });
63
131
 
64
132
  // GET /api/stats - Get overall statistics
65
133
  statsRouter.get('/', async (c) => {
134
+ const memoryService = getReadOnlyMemoryService();
66
135
  try {
67
- const memoryService = getDefaultMemoryService();
68
136
  await memoryService.initialize();
69
-
70
137
  const stats = await memoryService.getStats();
71
138
  const recentEvents = await memoryService.getRecentEvents(10000);
72
139
 
@@ -111,17 +178,54 @@ statsRouter.get('/', async (c) => {
111
178
  });
112
179
  } catch (error) {
113
180
  return c.json({ error: (error as Error).message }, 500);
181
+ } finally {
182
+ await memoryService.shutdown();
183
+ }
184
+ });
185
+
186
+ // GET /api/stats/most-accessed - Get most accessed memories
187
+ statsRouter.get('/most-accessed', async (c) => {
188
+ const limit = parseInt(c.req.query('limit') || '10', 10);
189
+ // Use the same read-only service that other stats endpoints use
190
+ const memoryService = getReadOnlyMemoryService();
191
+
192
+ try {
193
+ await memoryService.initialize();
194
+ console.log('[most-accessed] Fetching most accessed memories, limit:', limit);
195
+ const memories = await memoryService.getMostAccessedMemories(limit);
196
+ console.log('[most-accessed] Got memories:', memories.length);
197
+
198
+ return c.json({
199
+ memories: memories.map(m => ({
200
+ memoryId: m.memoryId,
201
+ summary: m.summary,
202
+ topics: m.topics,
203
+ accessCount: m.accessCount,
204
+ lastAccessed: m.lastAccessed || null,
205
+ confidence: m.confidence,
206
+ createdAt: m.createdAt instanceof Date ? m.createdAt.toISOString() : m.createdAt
207
+ })),
208
+ total: memories.length
209
+ });
210
+ } catch (error) {
211
+ console.error('[most-accessed] Error:', error);
212
+ return c.json({
213
+ memories: [],
214
+ total: 0,
215
+ error: (error as Error).message
216
+ });
217
+ } finally {
218
+ await memoryService.shutdown();
114
219
  }
115
220
  });
116
221
 
117
222
  // GET /api/stats/timeline - Get activity timeline
118
223
  statsRouter.get('/timeline', async (c) => {
119
224
  const days = parseInt(c.req.query('days') || '7', 10);
225
+ const memoryService = getReadOnlyMemoryService();
120
226
 
121
227
  try {
122
- const memoryService = getDefaultMemoryService();
123
228
  await memoryService.initialize();
124
-
125
229
  const recentEvents = await memoryService.getRecentEvents(10000);
126
230
 
127
231
  const cutoff = new Date(Date.now() - days * 24 * 60 * 60 * 1000);
@@ -146,5 +250,48 @@ statsRouter.get('/timeline', async (c) => {
146
250
  });
147
251
  } catch (error) {
148
252
  return c.json({ error: (error as Error).message }, 500);
253
+ } finally {
254
+ await memoryService.shutdown();
149
255
  }
150
256
  });
257
+
258
+ // POST /api/stats/graduation/run - Force graduation evaluation
259
+ statsRouter.post('/graduation/run', async (c) => {
260
+ const memoryService = getReadOnlyMemoryService();
261
+ try {
262
+ await memoryService.initialize();
263
+ const result = await memoryService.forceGraduation();
264
+
265
+ return c.json({
266
+ success: true,
267
+ evaluated: result.evaluated,
268
+ graduated: result.graduated,
269
+ byLevel: result.byLevel
270
+ });
271
+ } catch (error) {
272
+ return c.json({
273
+ success: false,
274
+ error: (error as Error).message
275
+ }, 500);
276
+ } finally {
277
+ await memoryService.shutdown();
278
+ }
279
+ });
280
+
281
+ // GET /api/stats/graduation - Get graduation criteria info
282
+ statsRouter.get('/graduation', async (c) => {
283
+ return c.json({
284
+ criteria: {
285
+ L0toL1: { minAccessCount: 1, minConfidence: 0.5, minCrossSessionRefs: 0, maxAgeDays: 30 },
286
+ L1toL2: { minAccessCount: 3, minConfidence: 0.7, minCrossSessionRefs: 1, maxAgeDays: 60 },
287
+ L2toL3: { minAccessCount: 5, minConfidence: 0.85, minCrossSessionRefs: 2, maxAgeDays: 90 },
288
+ L3toL4: { minAccessCount: 10, minConfidence: 0.92, minCrossSessionRefs: 3, maxAgeDays: 180 }
289
+ },
290
+ description: {
291
+ accessCount: 'Number of times the memory was retrieved/referenced',
292
+ confidence: 'Match confidence score when retrieved (0.0-1.0)',
293
+ crossSessionRefs: 'Number of different sessions that referenced this memory',
294
+ maxAgeDays: 'Maximum days since last access (prevents stale promotion)'
295
+ }
296
+ });
297
+ });
@@ -3,12 +3,18 @@
3
3
  * Provides REST API and serves static UI files
4
4
  */
5
5
 
6
+ // These are injected by the esbuild banner
7
+ declare const __dirname: string;
8
+ declare const __filename: string;
9
+
6
10
  import { Hono } from 'hono';
7
11
  import { cors } from 'hono/cors';
8
12
  import { logger } from 'hono/logger';
9
- import { serveStatic } from 'hono/bun';
13
+ import { serve } from '@hono/node-server';
14
+ import { serveStatic } from '@hono/node-server/serve-static';
10
15
  import * as path from 'path';
11
16
  import * as fs from 'fs';
17
+ import type { Server } from 'http';
12
18
 
13
19
  import { apiRouter } from './api/index.js';
14
20
 
@@ -25,7 +31,7 @@ app.route('/api', apiRouter);
25
31
  app.get('/health', (c) => c.json({ status: 'ok', timestamp: new Date().toISOString() }));
26
32
 
27
33
  // Static files (UI)
28
- const uiPath = path.join(import.meta.dir, '../../dist/ui');
34
+ const uiPath = path.join(__dirname, '../../dist/ui');
29
35
  if (fs.existsSync(uiPath)) {
30
36
  app.use('/*', serveStatic({ root: uiPath }));
31
37
  }
@@ -41,20 +47,20 @@ app.get('*', (c) => {
41
47
 
42
48
  export { app };
43
49
 
44
- let serverInstance: ReturnType<typeof Bun.serve> | null = null;
50
+ let serverInstance: Server | null = null;
45
51
 
46
52
  /**
47
53
  * Start the HTTP server
48
54
  */
49
- export function startServer(port: number = 37777): ReturnType<typeof Bun.serve> {
55
+ export function startServer(port: number = 37777): Server {
50
56
  if (serverInstance) {
51
57
  return serverInstance;
52
58
  }
53
59
 
54
- serverInstance = Bun.serve({
55
- hostname: '127.0.0.1',
60
+ serverInstance = serve({
61
+ fetch: app.fetch,
56
62
  port,
57
- fetch: app.fetch
63
+ hostname: '127.0.0.1'
58
64
  });
59
65
 
60
66
  console.log(`🧠 Code Memory viewer started at http://localhost:${port}`);
@@ -67,7 +73,7 @@ export function startServer(port: number = 37777): ReturnType<typeof Bun.serve>
67
73
  */
68
74
  export function stopServer(): void {
69
75
  if (serverInstance) {
70
- serverInstance.stop();
76
+ serverInstance.close();
71
77
  serverInstance = null;
72
78
  }
73
79
  }
@@ -85,7 +91,10 @@ export async function isServerRunning(port: number = 37777): Promise<boolean> {
85
91
  }
86
92
 
87
93
  // Start server if run directly
88
- if (import.meta.main) {
94
+ // Check if this file is being run directly (not imported)
95
+ const isMainModule = process.argv[1]?.includes('server/index') ||
96
+ process.argv[1]?.endsWith('server.js');
97
+ if (isMainModule) {
89
98
  const port = parseInt(process.env.PORT || '37777', 10);
90
99
  startServer(port);
91
100
  }