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.
- package/.claude/settings.local.json +10 -1
- package/.claude-memory/test.sqlite +0 -0
- package/.history/package_20260201192048.json +47 -0
- package/.history/package_20260202114053.json +49 -0
- package/HANDOFF.md +92 -0
- package/dist/cli/index.js +1711 -102
- package/dist/cli/index.js.map +4 -4
- package/dist/core/index.js +1257 -84
- package/dist/core/index.js.map +4 -4
- package/dist/hooks/post-tool-use.js +5589 -0
- package/dist/hooks/post-tool-use.js.map +7 -0
- package/dist/hooks/session-end.js +1382 -85
- package/dist/hooks/session-end.js.map +4 -4
- package/dist/hooks/session-start.js +1377 -84
- package/dist/hooks/session-start.js.map +4 -4
- package/dist/hooks/stop.js +1383 -86
- package/dist/hooks/stop.js.map +4 -4
- package/dist/hooks/user-prompt-submit.js +1412 -84
- package/dist/hooks/user-prompt-submit.js.map +4 -4
- package/dist/server/api/index.js +1576 -136
- package/dist/server/api/index.js.map +4 -4
- package/dist/server/index.js +1585 -143
- package/dist/server/index.js.map +4 -4
- package/dist/services/memory-service.js +1392 -84
- package/dist/services/memory-service.js.map +4 -4
- package/dist/ui/app.js +304 -0
- package/dist/ui/index.html +202 -715
- package/dist/ui/style.css +595 -0
- package/package.json +4 -1
- package/scripts/build.ts +5 -2
- package/src/cli/index.ts +226 -0
- package/src/core/db-wrapper.ts +8 -1
- package/src/core/event-store.ts +70 -3
- package/src/core/graduation-worker.ts +171 -0
- package/src/core/graduation.ts +15 -2
- package/src/core/index.ts +4 -0
- package/src/core/retriever.ts +21 -0
- package/src/core/sqlite-event-store.ts +849 -0
- package/src/core/sqlite-wrapper.ts +108 -0
- package/src/core/sync-worker.ts +228 -0
- package/src/core/vector-worker.ts +44 -14
- package/src/hooks/user-prompt-submit.ts +53 -4
- package/src/server/api/citations.ts +7 -3
- package/src/server/api/events.ts +7 -3
- package/src/server/api/search.ts +7 -3
- package/src/server/api/sessions.ts +7 -3
- package/src/server/api/stats.ts +159 -12
- package/src/server/index.ts +18 -9
- package/src/services/memory-service.ts +263 -46
- package/src/ui/app.js +304 -0
- package/src/ui/index.html +202 -715
- package/src/ui/style.css +595 -0
- package/test_access.js +49 -0
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import { Hono } from 'hono';
|
|
7
|
-
import {
|
|
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
|
});
|
package/src/server/api/stats.ts
CHANGED
|
@@ -4,18 +4,16 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import { Hono } from 'hono';
|
|
7
|
-
import {
|
|
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
|
+
});
|
package/src/server/index.ts
CHANGED
|
@@ -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 {
|
|
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(
|
|
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:
|
|
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):
|
|
55
|
+
export function startServer(port: number = 37777): Server {
|
|
50
56
|
if (serverInstance) {
|
|
51
57
|
return serverInstance;
|
|
52
58
|
}
|
|
53
59
|
|
|
54
|
-
serverInstance =
|
|
55
|
-
|
|
60
|
+
serverInstance = serve({
|
|
61
|
+
fetch: app.fetch,
|
|
56
62
|
port,
|
|
57
|
-
|
|
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.
|
|
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 (
|
|
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
|
}
|