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.
- package/.claude/settings.local.json +4 -1
- package/.claude-plugin/plugin.json +3 -3
- package/.history/package_20260201142928.json +46 -0
- package/.history/package_20260201192048.json +47 -0
- package/README.md +26 -26
- package/dist/.claude-plugin/plugin.json +3 -3
- package/dist/cli/index.js +1109 -25
- package/dist/cli/index.js.map +4 -4
- package/dist/core/index.js +192 -5
- package/dist/core/index.js.map +4 -4
- package/dist/hooks/session-end.js +262 -18
- package/dist/hooks/session-end.js.map +4 -4
- package/dist/hooks/session-start.js +262 -18
- package/dist/hooks/session-start.js.map +4 -4
- package/dist/hooks/stop.js +262 -18
- package/dist/hooks/stop.js.map +4 -4
- package/dist/hooks/user-prompt-submit.js +262 -18
- package/dist/hooks/user-prompt-submit.js.map +4 -4
- package/dist/server/api/index.js +4728 -0
- package/dist/server/api/index.js.map +7 -0
- package/dist/server/index.js +4790 -0
- package/dist/server/index.js.map +7 -0
- package/dist/services/memory-service.js +269 -18
- package/dist/services/memory-service.js.map +4 -4
- package/dist/ui/index.html +1225 -0
- package/package.json +4 -2
- package/scripts/build.ts +33 -3
- package/src/cli/index.ts +311 -6
- package/src/core/db-wrapper.ts +8 -1
- package/src/core/event-store.ts +52 -3
- package/src/core/graduation-worker.ts +171 -0
- package/src/core/graduation.ts +15 -2
- package/src/core/index.ts +1 -0
- package/src/core/retriever.ts +18 -0
- package/src/core/types.ts +1 -1
- package/src/mcp/index.ts +2 -2
- package/src/mcp/tools.ts +1 -1
- 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 +175 -5
- package/src/server/index.ts +18 -9
- package/src/services/memory-service.ts +107 -19
- package/src/ui/index.html +1225 -0
package/src/server/api/stats.ts
CHANGED
|
@@ -4,16 +4,109 @@
|
|
|
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
|
+
// GET /api/stats/shared - Get shared store statistics
|
|
12
|
+
statsRouter.get('/shared', async (c) => {
|
|
13
|
+
const memoryService = getReadOnlyMemoryService();
|
|
14
|
+
try {
|
|
15
|
+
await memoryService.initialize();
|
|
16
|
+
const sharedStats = await memoryService.getSharedStoreStats();
|
|
17
|
+
return c.json({
|
|
18
|
+
troubleshooting: sharedStats?.troubleshooting || 0,
|
|
19
|
+
bestPractices: sharedStats?.bestPractices || 0,
|
|
20
|
+
commonErrors: sharedStats?.commonErrors || 0,
|
|
21
|
+
totalUsageCount: sharedStats?.totalUsageCount || 0,
|
|
22
|
+
lastUpdated: sharedStats?.lastUpdated || null
|
|
23
|
+
});
|
|
24
|
+
} catch (error) {
|
|
25
|
+
return c.json({
|
|
26
|
+
troubleshooting: 0,
|
|
27
|
+
bestPractices: 0,
|
|
28
|
+
commonErrors: 0,
|
|
29
|
+
totalUsageCount: 0,
|
|
30
|
+
lastUpdated: null
|
|
31
|
+
});
|
|
32
|
+
} finally {
|
|
33
|
+
await memoryService.shutdown();
|
|
34
|
+
}
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
// GET /api/stats/endless - Get endless mode status
|
|
38
|
+
statsRouter.get('/endless', async (c) => {
|
|
39
|
+
const projectPath = c.req.query('project') || process.cwd();
|
|
40
|
+
const memoryService = getMemoryServiceForProject(projectPath);
|
|
41
|
+
try {
|
|
42
|
+
await memoryService.initialize();
|
|
43
|
+
const status = await memoryService.getEndlessModeStatus();
|
|
44
|
+
return c.json({
|
|
45
|
+
mode: status.mode,
|
|
46
|
+
continuityScore: status.continuityScore,
|
|
47
|
+
workingSetSize: status.workingSetSize,
|
|
48
|
+
consolidatedCount: status.consolidatedCount,
|
|
49
|
+
lastConsolidation: status.lastConsolidation?.toISOString() || null
|
|
50
|
+
});
|
|
51
|
+
} catch (error) {
|
|
52
|
+
return c.json({
|
|
53
|
+
mode: 'session',
|
|
54
|
+
continuityScore: 0,
|
|
55
|
+
workingSetSize: 0,
|
|
56
|
+
consolidatedCount: 0,
|
|
57
|
+
lastConsolidation: null
|
|
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
|
+
|
|
70
|
+
// Validate level
|
|
71
|
+
const validLevels = ['L0', 'L1', 'L2', 'L3', 'L4'];
|
|
72
|
+
if (!validLevels.includes(level)) {
|
|
73
|
+
return c.json({ error: `Invalid level. Must be one of: ${validLevels.join(', ')}` }, 400);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const memoryService = getReadOnlyMemoryService();
|
|
77
|
+
try {
|
|
78
|
+
await memoryService.initialize();
|
|
79
|
+
const events = await memoryService.getEventsByLevel(level, { limit, offset });
|
|
80
|
+
const stats = await memoryService.getStats();
|
|
81
|
+
const levelStat = stats.levelStats.find(s => s.level === level);
|
|
82
|
+
|
|
83
|
+
return c.json({
|
|
84
|
+
level,
|
|
85
|
+
events: events.map(e => ({
|
|
86
|
+
id: e.id,
|
|
87
|
+
eventType: e.eventType,
|
|
88
|
+
sessionId: e.sessionId,
|
|
89
|
+
timestamp: e.timestamp.toISOString(),
|
|
90
|
+
content: e.content.slice(0, 500) + (e.content.length > 500 ? '...' : ''),
|
|
91
|
+
metadata: e.metadata
|
|
92
|
+
})),
|
|
93
|
+
total: levelStat?.count || 0,
|
|
94
|
+
limit,
|
|
95
|
+
offset,
|
|
96
|
+
hasMore: events.length === limit
|
|
97
|
+
});
|
|
98
|
+
} catch (error) {
|
|
99
|
+
return c.json({ error: (error as Error).message }, 500);
|
|
100
|
+
} finally {
|
|
101
|
+
await memoryService.shutdown();
|
|
102
|
+
}
|
|
103
|
+
});
|
|
104
|
+
|
|
11
105
|
// GET /api/stats - Get overall statistics
|
|
12
106
|
statsRouter.get('/', async (c) => {
|
|
107
|
+
const memoryService = getReadOnlyMemoryService();
|
|
13
108
|
try {
|
|
14
|
-
const memoryService = getDefaultMemoryService();
|
|
15
109
|
await memoryService.initialize();
|
|
16
|
-
|
|
17
110
|
const stats = await memoryService.getStats();
|
|
18
111
|
const recentEvents = await memoryService.getRecentEvents(10000);
|
|
19
112
|
|
|
@@ -58,17 +151,51 @@ statsRouter.get('/', async (c) => {
|
|
|
58
151
|
});
|
|
59
152
|
} catch (error) {
|
|
60
153
|
return c.json({ error: (error as Error).message }, 500);
|
|
154
|
+
} finally {
|
|
155
|
+
await memoryService.shutdown();
|
|
156
|
+
}
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
// GET /api/stats/most-accessed - Get most accessed memories
|
|
160
|
+
statsRouter.get('/most-accessed', async (c) => {
|
|
161
|
+
const limit = parseInt(c.req.query('limit') || '10', 10);
|
|
162
|
+
const projectPath = c.req.query('project') || process.cwd();
|
|
163
|
+
const memoryService = getMemoryServiceForProject(projectPath);
|
|
164
|
+
|
|
165
|
+
try {
|
|
166
|
+
await memoryService.initialize();
|
|
167
|
+
const memories = await memoryService.getMostAccessedMemories(limit);
|
|
168
|
+
|
|
169
|
+
return c.json({
|
|
170
|
+
memories: memories.map(m => ({
|
|
171
|
+
memoryId: m.memoryId,
|
|
172
|
+
summary: m.summary,
|
|
173
|
+
topics: m.topics,
|
|
174
|
+
accessCount: m.accessCount,
|
|
175
|
+
lastAccessed: m.accessedAt?.toISOString() || null,
|
|
176
|
+
confidence: m.confidence,
|
|
177
|
+
createdAt: m.createdAt.toISOString()
|
|
178
|
+
})),
|
|
179
|
+
total: memories.length
|
|
180
|
+
});
|
|
181
|
+
} catch (error) {
|
|
182
|
+
return c.json({
|
|
183
|
+
memories: [],
|
|
184
|
+
total: 0,
|
|
185
|
+
error: (error as Error).message
|
|
186
|
+
});
|
|
187
|
+
} finally {
|
|
188
|
+
await memoryService.shutdown();
|
|
61
189
|
}
|
|
62
190
|
});
|
|
63
191
|
|
|
64
192
|
// GET /api/stats/timeline - Get activity timeline
|
|
65
193
|
statsRouter.get('/timeline', async (c) => {
|
|
66
194
|
const days = parseInt(c.req.query('days') || '7', 10);
|
|
195
|
+
const memoryService = getReadOnlyMemoryService();
|
|
67
196
|
|
|
68
197
|
try {
|
|
69
|
-
const memoryService = getDefaultMemoryService();
|
|
70
198
|
await memoryService.initialize();
|
|
71
|
-
|
|
72
199
|
const recentEvents = await memoryService.getRecentEvents(10000);
|
|
73
200
|
|
|
74
201
|
const cutoff = new Date(Date.now() - days * 24 * 60 * 60 * 1000);
|
|
@@ -93,5 +220,48 @@ statsRouter.get('/timeline', async (c) => {
|
|
|
93
220
|
});
|
|
94
221
|
} catch (error) {
|
|
95
222
|
return c.json({ error: (error as Error).message }, 500);
|
|
223
|
+
} finally {
|
|
224
|
+
await memoryService.shutdown();
|
|
96
225
|
}
|
|
97
226
|
});
|
|
227
|
+
|
|
228
|
+
// POST /api/stats/graduation/run - Force graduation evaluation
|
|
229
|
+
statsRouter.post('/graduation/run', async (c) => {
|
|
230
|
+
const memoryService = getReadOnlyMemoryService();
|
|
231
|
+
try {
|
|
232
|
+
await memoryService.initialize();
|
|
233
|
+
const result = await memoryService.forceGraduation();
|
|
234
|
+
|
|
235
|
+
return c.json({
|
|
236
|
+
success: true,
|
|
237
|
+
evaluated: result.evaluated,
|
|
238
|
+
graduated: result.graduated,
|
|
239
|
+
byLevel: result.byLevel
|
|
240
|
+
});
|
|
241
|
+
} catch (error) {
|
|
242
|
+
return c.json({
|
|
243
|
+
success: false,
|
|
244
|
+
error: (error as Error).message
|
|
245
|
+
}, 500);
|
|
246
|
+
} finally {
|
|
247
|
+
await memoryService.shutdown();
|
|
248
|
+
}
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
// GET /api/stats/graduation - Get graduation criteria info
|
|
252
|
+
statsRouter.get('/graduation', async (c) => {
|
|
253
|
+
return c.json({
|
|
254
|
+
criteria: {
|
|
255
|
+
L0toL1: { minAccessCount: 1, minConfidence: 0.5, minCrossSessionRefs: 0, maxAgeDays: 30 },
|
|
256
|
+
L1toL2: { minAccessCount: 3, minConfidence: 0.7, minCrossSessionRefs: 1, maxAgeDays: 60 },
|
|
257
|
+
L2toL3: { minAccessCount: 5, minConfidence: 0.85, minCrossSessionRefs: 2, maxAgeDays: 90 },
|
|
258
|
+
L3toL4: { minAccessCount: 10, minConfidence: 0.92, minCrossSessionRefs: 3, maxAgeDays: 180 }
|
|
259
|
+
},
|
|
260
|
+
description: {
|
|
261
|
+
accessCount: 'Number of times the memory was retrieved/referenced',
|
|
262
|
+
confidence: 'Match confidence score when retrieved (0.0-1.0)',
|
|
263
|
+
crossSessionRefs: 'Number of different sessions that referenced this memory',
|
|
264
|
+
maxAgeDays: 'Maximum days since last access (prevents stale promotion)'
|
|
265
|
+
}
|
|
266
|
+
});
|
|
267
|
+
});
|
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
|
}
|
|
@@ -42,10 +42,12 @@ import { WorkingSetStore, createWorkingSetStore } from '../core/working-set-stor
|
|
|
42
42
|
import { ConsolidatedStore, createConsolidatedStore } from '../core/consolidated-store.js';
|
|
43
43
|
import { ConsolidationWorker, createConsolidationWorker } from '../core/consolidation-worker.js';
|
|
44
44
|
import { ContinuityManager, createContinuityManager } from '../core/continuity-manager.js';
|
|
45
|
+
import { GraduationWorker, createGraduationWorker, GraduationRunResult } from '../core/graduation-worker.js';
|
|
45
46
|
|
|
46
47
|
export interface MemoryServiceConfig {
|
|
47
48
|
storagePath: string;
|
|
48
49
|
embeddingModel?: string;
|
|
50
|
+
readOnly?: boolean;
|
|
49
51
|
}
|
|
50
52
|
|
|
51
53
|
// ============================================================
|
|
@@ -170,6 +172,7 @@ export class MemoryService {
|
|
|
170
172
|
private readonly retriever: Retriever;
|
|
171
173
|
private readonly graduation: GraduationPipeline;
|
|
172
174
|
private vectorWorker: VectorWorker | null = null;
|
|
175
|
+
private graduationWorker: GraduationWorker | null = null;
|
|
173
176
|
private initialized = false;
|
|
174
177
|
|
|
175
178
|
// Endless Mode components
|
|
@@ -187,11 +190,14 @@ export class MemoryService {
|
|
|
187
190
|
private sharedStoreConfig: SharedStoreConfig | null = null;
|
|
188
191
|
private projectHash: string | null = null;
|
|
189
192
|
|
|
193
|
+
private readonly readOnly: boolean;
|
|
194
|
+
|
|
190
195
|
constructor(config: MemoryServiceConfig & { projectHash?: string; sharedStoreConfig?: SharedStoreConfig }) {
|
|
191
196
|
const storagePath = this.expandPath(config.storagePath);
|
|
197
|
+
this.readOnly = config.readOnly ?? false;
|
|
192
198
|
|
|
193
|
-
// Ensure storage directory exists
|
|
194
|
-
if (!fs.existsSync(storagePath)) {
|
|
199
|
+
// Ensure storage directory exists (only if not read-only)
|
|
200
|
+
if (!this.readOnly && !fs.existsSync(storagePath)) {
|
|
195
201
|
fs.mkdirSync(storagePath, { recursive: true });
|
|
196
202
|
}
|
|
197
203
|
|
|
@@ -201,7 +207,7 @@ export class MemoryService {
|
|
|
201
207
|
this.sharedStoreConfig = config.sharedStoreConfig ?? { enabled: true };
|
|
202
208
|
|
|
203
209
|
// Initialize components
|
|
204
|
-
this.eventStore = new EventStore(path.join(storagePath, 'events.duckdb'));
|
|
210
|
+
this.eventStore = new EventStore(path.join(storagePath, 'events.duckdb'), { readOnly: this.readOnly });
|
|
205
211
|
this.vectorStore = new VectorStore(path.join(storagePath, 'vectors'));
|
|
206
212
|
this.embedder = config.embeddingModel
|
|
207
213
|
? new Embedder(config.embeddingModel)
|
|
@@ -226,24 +232,37 @@ export class MemoryService {
|
|
|
226
232
|
await this.vectorStore.initialize();
|
|
227
233
|
await this.embedder.initialize();
|
|
228
234
|
|
|
229
|
-
//
|
|
230
|
-
this.
|
|
231
|
-
|
|
232
|
-
this.
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
235
|
+
// Skip write-related workers in read-only mode
|
|
236
|
+
if (!this.readOnly) {
|
|
237
|
+
// Start vector worker
|
|
238
|
+
this.vectorWorker = createVectorWorker(
|
|
239
|
+
this.eventStore,
|
|
240
|
+
this.vectorStore,
|
|
241
|
+
this.embedder
|
|
242
|
+
);
|
|
243
|
+
this.vectorWorker.start();
|
|
236
244
|
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
245
|
+
// Connect graduation pipeline to retriever for access tracking
|
|
246
|
+
this.retriever.setGraduationPipeline(this.graduation);
|
|
247
|
+
|
|
248
|
+
// Start graduation worker for automatic level promotion
|
|
249
|
+
this.graduationWorker = createGraduationWorker(
|
|
250
|
+
this.eventStore,
|
|
251
|
+
this.graduation
|
|
252
|
+
);
|
|
253
|
+
this.graduationWorker.start();
|
|
243
254
|
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
255
|
+
// Load endless mode setting
|
|
256
|
+
const savedMode = await this.eventStore.getEndlessConfig('mode') as MemoryMode | null;
|
|
257
|
+
if (savedMode === 'endless') {
|
|
258
|
+
this.endlessMode = 'endless';
|
|
259
|
+
await this.initializeEndlessMode();
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
// Initialize shared store (enabled by default)
|
|
263
|
+
if (this.sharedStoreConfig?.enabled !== false) {
|
|
264
|
+
await this.initializeSharedStore();
|
|
265
|
+
}
|
|
247
266
|
}
|
|
248
267
|
|
|
249
268
|
this.initialized = true;
|
|
@@ -499,6 +518,22 @@ export class MemoryService {
|
|
|
499
518
|
return 0;
|
|
500
519
|
}
|
|
501
520
|
|
|
521
|
+
/**
|
|
522
|
+
* Get events by memory level
|
|
523
|
+
*/
|
|
524
|
+
async getEventsByLevel(level: string, options?: { limit?: number; offset?: number }): Promise<MemoryEvent[]> {
|
|
525
|
+
await this.initialize();
|
|
526
|
+
return this.eventStore.getEventsByLevel(level, options);
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
/**
|
|
530
|
+
* Get memory level for a specific event
|
|
531
|
+
*/
|
|
532
|
+
async getEventLevel(eventId: string): Promise<string | null> {
|
|
533
|
+
await this.initialize();
|
|
534
|
+
return this.eventStore.getEventLevel(eventId);
|
|
535
|
+
}
|
|
536
|
+
|
|
502
537
|
/**
|
|
503
538
|
* Format retrieval results as context for Claude
|
|
504
539
|
*/
|
|
@@ -713,6 +748,22 @@ export class MemoryService {
|
|
|
713
748
|
return this.consolidatedStore.getAll({ limit });
|
|
714
749
|
}
|
|
715
750
|
|
|
751
|
+
/**
|
|
752
|
+
* Get most accessed consolidated memories
|
|
753
|
+
*/
|
|
754
|
+
async getMostAccessedMemories(limit: number = 10): Promise<ConsolidatedMemory[]> {
|
|
755
|
+
if (!this.consolidatedStore) return [];
|
|
756
|
+
return this.consolidatedStore.getMostAccessed(limit);
|
|
757
|
+
}
|
|
758
|
+
|
|
759
|
+
/**
|
|
760
|
+
* Mark a consolidated memory as accessed
|
|
761
|
+
*/
|
|
762
|
+
async markMemoryAccessed(memoryId: string): Promise<void> {
|
|
763
|
+
if (!this.consolidatedStore) return;
|
|
764
|
+
await this.consolidatedStore.markAccessed(memoryId);
|
|
765
|
+
}
|
|
766
|
+
|
|
716
767
|
/**
|
|
717
768
|
* Calculate continuity score for current context
|
|
718
769
|
*/
|
|
@@ -822,10 +873,32 @@ export class MemoryService {
|
|
|
822
873
|
return parts.join('\n');
|
|
823
874
|
}
|
|
824
875
|
|
|
876
|
+
/**
|
|
877
|
+
* Force a graduation evaluation run
|
|
878
|
+
*/
|
|
879
|
+
async forceGraduation(): Promise<GraduationRunResult> {
|
|
880
|
+
if (!this.graduationWorker) {
|
|
881
|
+
return { evaluated: 0, graduated: 0, byLevel: {} };
|
|
882
|
+
}
|
|
883
|
+
return this.graduationWorker.forceRun();
|
|
884
|
+
}
|
|
885
|
+
|
|
886
|
+
/**
|
|
887
|
+
* Record access to a memory event (for graduation scoring)
|
|
888
|
+
*/
|
|
889
|
+
recordMemoryAccess(eventId: string, sessionId: string, confidence: number = 1.0): void {
|
|
890
|
+
this.graduation.recordAccess(eventId, sessionId, confidence);
|
|
891
|
+
}
|
|
892
|
+
|
|
825
893
|
/**
|
|
826
894
|
* Shutdown service
|
|
827
895
|
*/
|
|
828
896
|
async shutdown(): Promise<void> {
|
|
897
|
+
// Stop graduation worker
|
|
898
|
+
if (this.graduationWorker) {
|
|
899
|
+
this.graduationWorker.stop();
|
|
900
|
+
}
|
|
901
|
+
|
|
829
902
|
// Stop endless mode components
|
|
830
903
|
if (this.consolidationWorker) {
|
|
831
904
|
this.consolidationWorker.stop();
|
|
@@ -861,6 +934,7 @@ export class MemoryService {
|
|
|
861
934
|
// Instance cache: Map from project hash (or '__global__') to MemoryService
|
|
862
935
|
const serviceCache = new Map<string, MemoryService>();
|
|
863
936
|
const GLOBAL_KEY = '__global__';
|
|
937
|
+
const GLOBAL_READONLY_KEY = '__global_readonly__';
|
|
864
938
|
|
|
865
939
|
/**
|
|
866
940
|
* Get the global memory service (backward compatibility)
|
|
@@ -875,6 +949,20 @@ export function getDefaultMemoryService(): MemoryService {
|
|
|
875
949
|
return serviceCache.get(GLOBAL_KEY)!;
|
|
876
950
|
}
|
|
877
951
|
|
|
952
|
+
/**
|
|
953
|
+
* Get a read-only global memory service
|
|
954
|
+
* Use this for web server/dashboard that only needs to read data
|
|
955
|
+
* Creates a fresh connection each time to avoid blocking the main writer process
|
|
956
|
+
*/
|
|
957
|
+
export function getReadOnlyMemoryService(): MemoryService {
|
|
958
|
+
// Don't cache - create fresh instance each time to avoid holding locks
|
|
959
|
+
// The connection will be closed when the request completes
|
|
960
|
+
return new MemoryService({
|
|
961
|
+
storagePath: '~/.claude-code/memory',
|
|
962
|
+
readOnly: true
|
|
963
|
+
});
|
|
964
|
+
}
|
|
965
|
+
|
|
878
966
|
/**
|
|
879
967
|
* Get memory service for a specific project path
|
|
880
968
|
* Creates isolated storage at ~/.claude-code/memory/projects/{hash}/
|