claude-memory-layer 1.0.10 → 1.0.12
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/AGENTS.md +60 -0
- package/README.md +166 -2
- package/bootstrap-kb/decisions/decisions.md +244 -0
- package/bootstrap-kb/glossary/glossary.md +46 -0
- package/bootstrap-kb/modules/.claude-plugin.md +22 -0
- package/bootstrap-kb/modules/agents.md.md +15 -0
- package/bootstrap-kb/modules/claude.md.md +15 -0
- package/bootstrap-kb/modules/context.md.md +15 -0
- package/bootstrap-kb/modules/docs.md +18 -0
- package/bootstrap-kb/modules/handoff.md.md +15 -0
- package/bootstrap-kb/modules/package-lock.json.md +15 -0
- package/bootstrap-kb/modules/package.json.md +15 -0
- package/bootstrap-kb/modules/plan.md.md +15 -0
- package/bootstrap-kb/modules/readme.md.md +15 -0
- package/bootstrap-kb/modules/scripts.md +26 -0
- package/bootstrap-kb/modules/spec.md.md +15 -0
- package/bootstrap-kb/modules/specs.md +20 -0
- package/bootstrap-kb/modules/src.md +51 -0
- package/bootstrap-kb/modules/tests.md +42 -0
- package/bootstrap-kb/modules/tsconfig.json.md +15 -0
- package/bootstrap-kb/modules/vitest.config.ts.md +15 -0
- package/bootstrap-kb/overview/overview.md +40 -0
- package/bootstrap-kb/sources/manifest.json +950 -0
- package/bootstrap-kb/sources/manifest.md +227 -0
- package/bootstrap-kb/timeline/timeline.md +57 -0
- package/d.sh +3 -0
- package/deploy.sh +3 -0
- package/dist/cli/index.js +3577 -389
- package/dist/cli/index.js.map +4 -4
- package/dist/core/index.js +1383 -138
- package/dist/core/index.js.map +4 -4
- package/dist/hooks/post-tool-use.js +1917 -214
- package/dist/hooks/post-tool-use.js.map +4 -4
- package/dist/hooks/session-end.js +1813 -231
- package/dist/hooks/session-end.js.map +4 -4
- package/dist/hooks/session-start.js +1802 -205
- package/dist/hooks/session-start.js.map +4 -4
- package/dist/hooks/stop.js +1909 -248
- package/dist/hooks/stop.js.map +4 -4
- package/dist/hooks/user-prompt-submit.js +1861 -206
- package/dist/hooks/user-prompt-submit.js.map +4 -4
- package/dist/server/api/index.js +2341 -217
- package/dist/server/api/index.js.map +4 -4
- package/dist/server/index.js +2350 -226
- package/dist/server/index.js.map +4 -4
- package/dist/services/memory-service.js +1805 -206
- package/dist/services/memory-service.js.map +4 -4
- package/dist/ui/app.js +1447 -55
- package/dist/ui/index.html +318 -147
- package/dist/ui/style.css +892 -0
- package/docs/MCP_MEMORY_SERVICE_COMPARATIVE_REVIEW.md +271 -0
- package/docs/MEMU_ADOPTION.md +40 -0
- package/docs/OPERATIONS.md +18 -0
- package/memory/.claude-plugin/commands/2026-02-25.md +263 -0
- package/memory/_index.md +405 -0
- package/memory/default/uncategorized/2026-02-25.md +4839 -0
- package/memory/specs/20260207-dashboard-upgrade/2026-02-25.md +142 -0
- package/memory/specs/citations-system/2026-02-25.md +1121 -0
- package/memory/specs/endless-mode/2026-02-25.md +1392 -0
- package/memory/specs/entity-edge-model/2026-02-25.md +1263 -0
- package/memory/specs/evidence-aligner-v2/2026-02-25.md +1028 -0
- package/memory/specs/mcp-desktop-integration/2026-02-25.md +1334 -0
- package/memory/specs/post-tool-use-hook/2026-02-25.md +1164 -0
- package/memory/specs/private-tags/2026-02-25.md +1057 -0
- package/memory/specs/progressive-disclosure/2026-02-25.md +1436 -0
- package/memory/specs/task-entity-system/2026-02-25.md +924 -0
- package/memory/specs/vector-outbox-v2/2026-02-25.md +1510 -0
- package/memory/specs/web-viewer-ui/2026-02-25.md +1709 -0
- package/package.json +9 -2
- package/scripts/build.ts +6 -0
- package/scripts/fix-sync-gap.js +32 -0
- package/scripts/heartbeat-memory-orchestrator.sh +28 -0
- package/scripts/report-sync-gap.js +26 -0
- package/scripts/review-queue-auto-resolve.js +21 -0
- package/scripts/sync-gap-auto-heal.sh +17 -0
- package/specs/20260207-dashboard-upgrade/context.md +38 -0
- package/specs/20260207-dashboard-upgrade/spec.md +96 -0
- package/src/cli/index.ts +391 -60
- package/src/core/consolidated-store.ts +63 -1
- package/src/core/consolidation-worker.ts +115 -6
- package/src/core/event-store.ts +14 -0
- package/src/core/index.ts +1 -0
- package/src/core/ingest-interceptor.ts +80 -0
- package/src/core/markdown-mirror.ts +70 -0
- package/src/core/md-mirror.ts +92 -0
- package/src/core/mongo-sync-config.ts +165 -0
- package/src/core/mongo-sync-worker.ts +381 -0
- package/src/core/retriever.ts +540 -150
- package/src/core/sqlite-event-store.ts +794 -7
- package/src/core/sqlite-wrapper.ts +8 -0
- package/src/core/tag-taxonomy.ts +51 -0
- package/src/core/turn-state.ts +159 -0
- package/src/core/types.ts +51 -8
- package/src/core/vector-store.ts +21 -3
- package/src/hooks/post-tool-use.ts +68 -23
- package/src/hooks/session-end.ts +8 -3
- package/src/hooks/stop.ts +96 -25
- package/src/hooks/user-prompt-submit.ts +44 -5
- package/src/server/api/chat.ts +244 -0
- package/src/server/api/citations.ts +3 -3
- package/src/server/api/events.ts +30 -5
- package/src/server/api/health.ts +53 -0
- package/src/server/api/index.ts +9 -1
- package/src/server/api/projects.ts +74 -0
- package/src/server/api/search.ts +3 -3
- package/src/server/api/sessions.ts +3 -3
- package/src/server/api/stats.ts +89 -8
- package/src/server/api/turns.ts +143 -0
- package/src/server/api/utils.ts +46 -0
- package/src/services/bootstrap-organizer.ts +443 -0
- package/src/services/codex-session-history-importer.ts +474 -0
- package/src/services/memory-service.ts +508 -71
- package/src/services/session-history-importer.ts +215 -51
- package/src/ui/app.js +1447 -55
- package/src/ui/index.html +318 -147
- package/src/ui/style.css +892 -0
- package/tests/bootstrap-organizer.test.ts +111 -0
- package/tests/consolidation-worker.test.ts +75 -0
- package/tests/ingest-interceptor.test.ts +38 -0
- package/tests/markdown-mirror.test.ts +85 -0
- package/tests/md-mirror.test.ts +50 -0
- package/tests/retriever-fallback-chain.test.ts +223 -0
- package/tests/retriever-strategy-scope.test.ts +97 -0
- package/tests/retriever.memu-adoption.test.ts +122 -0
- package/tests/sqlite-event-store-replication.test.ts +92 -0
- package/.claude/settings.local.json +0 -27
- package/.claude-memory/test.sqlite +0 -0
- package/.history/package_20260201112328.json +0 -45
- package/.history/package_20260201113602.json +0 -45
- package/.history/package_20260201113713.json +0 -45
- package/.history/package_20260201114110.json +0 -45
- package/.history/package_20260201114632.json +0 -46
- package/.history/package_20260201133143.json +0 -45
- package/.history/package_20260201134319.json +0 -45
- package/.history/package_20260201134326.json +0 -45
- package/.history/package_20260201134334.json +0 -45
- package/.history/package_20260201134912.json +0 -45
- package/.history/package_20260201142928.json +0 -46
- package/.history/package_20260201192048.json +0 -47
- package/.history/package_20260202114053.json +0 -49
- package/.history/package_20260202121115.json +0 -49
- package/test_access.js +0 -49
package/src/server/api/stats.ts
CHANGED
|
@@ -4,13 +4,14 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import { Hono } from 'hono';
|
|
7
|
-
import {
|
|
7
|
+
import { getMemoryServiceForProject } from '../../services/memory-service.js';
|
|
8
|
+
import { getServiceFromQuery } from './utils.js';
|
|
8
9
|
|
|
9
10
|
export const statsRouter = new Hono();
|
|
10
11
|
|
|
11
12
|
// GET /api/stats/shared - Get shared store statistics
|
|
12
13
|
statsRouter.get('/shared', async (c) => {
|
|
13
|
-
const memoryService =
|
|
14
|
+
const memoryService = getServiceFromQuery(c);
|
|
14
15
|
try {
|
|
15
16
|
await memoryService.initialize();
|
|
16
17
|
const sharedStats = await memoryService.getSharedStoreStats();
|
|
@@ -74,7 +75,7 @@ statsRouter.get('/levels/:level', async (c) => {
|
|
|
74
75
|
return c.json({ error: `Invalid level. Must be one of: ${validLevels.join(', ')}` }, 400);
|
|
75
76
|
}
|
|
76
77
|
|
|
77
|
-
const memoryService =
|
|
78
|
+
const memoryService = getServiceFromQuery(c);
|
|
78
79
|
try {
|
|
79
80
|
await memoryService.initialize();
|
|
80
81
|
let events = await memoryService.getEventsByLevel(level, { limit: limit * 2, offset });
|
|
@@ -131,7 +132,7 @@ statsRouter.get('/levels/:level', async (c) => {
|
|
|
131
132
|
|
|
132
133
|
// GET /api/stats - Get overall statistics
|
|
133
134
|
statsRouter.get('/', async (c) => {
|
|
134
|
-
const memoryService =
|
|
135
|
+
const memoryService = getServiceFromQuery(c);
|
|
135
136
|
try {
|
|
136
137
|
await memoryService.initialize();
|
|
137
138
|
const stats = await memoryService.getStats();
|
|
@@ -157,6 +158,8 @@ statsRouter.get('/', async (c) => {
|
|
|
157
158
|
return acc;
|
|
158
159
|
}, {} as Record<string, number>);
|
|
159
160
|
|
|
161
|
+
const retrievalTrace = await memoryService.getRetrievalTraceStats();
|
|
162
|
+
|
|
160
163
|
return c.json({
|
|
161
164
|
storage: {
|
|
162
165
|
eventCount: stats.totalEvents,
|
|
@@ -174,7 +177,8 @@ statsRouter.get('/', async (c) => {
|
|
|
174
177
|
heapUsed: Math.round(process.memoryUsage().heapUsed / 1024 / 1024),
|
|
175
178
|
heapTotal: Math.round(process.memoryUsage().heapTotal / 1024 / 1024)
|
|
176
179
|
},
|
|
177
|
-
levelStats: stats.levelStats
|
|
180
|
+
levelStats: stats.levelStats,
|
|
181
|
+
retrievalTrace
|
|
178
182
|
});
|
|
179
183
|
} catch (error) {
|
|
180
184
|
return c.json({ error: (error as Error).message }, 500);
|
|
@@ -187,7 +191,7 @@ statsRouter.get('/', async (c) => {
|
|
|
187
191
|
statsRouter.get('/most-accessed', async (c) => {
|
|
188
192
|
const limit = parseInt(c.req.query('limit') || '10', 10);
|
|
189
193
|
// Use the same read-only service that other stats endpoints use
|
|
190
|
-
const memoryService =
|
|
194
|
+
const memoryService = getServiceFromQuery(c);
|
|
191
195
|
|
|
192
196
|
try {
|
|
193
197
|
await memoryService.initialize();
|
|
@@ -222,7 +226,7 @@ statsRouter.get('/most-accessed', async (c) => {
|
|
|
222
226
|
// GET /api/stats/timeline - Get activity timeline
|
|
223
227
|
statsRouter.get('/timeline', async (c) => {
|
|
224
228
|
const days = parseInt(c.req.query('days') || '7', 10);
|
|
225
|
-
const memoryService =
|
|
229
|
+
const memoryService = getServiceFromQuery(c);
|
|
226
230
|
|
|
227
231
|
try {
|
|
228
232
|
await memoryService.initialize();
|
|
@@ -255,9 +259,86 @@ statsRouter.get('/timeline', async (c) => {
|
|
|
255
259
|
}
|
|
256
260
|
});
|
|
257
261
|
|
|
262
|
+
// GET /api/stats/helpfulness - Get helpfulness statistics and top helpful memories
|
|
263
|
+
statsRouter.get('/helpfulness', async (c) => {
|
|
264
|
+
const limit = parseInt(c.req.query('limit') || '10', 10);
|
|
265
|
+
const memoryService = getServiceFromQuery(c);
|
|
266
|
+
|
|
267
|
+
try {
|
|
268
|
+
await memoryService.initialize();
|
|
269
|
+
const stats = await memoryService.getHelpfulnessStats();
|
|
270
|
+
const topMemories = await memoryService.getHelpfulMemories(limit);
|
|
271
|
+
|
|
272
|
+
return c.json({
|
|
273
|
+
...stats,
|
|
274
|
+
topMemories: topMemories.map(m => ({
|
|
275
|
+
eventId: m.eventId,
|
|
276
|
+
summary: m.summary,
|
|
277
|
+
helpfulnessScore: m.helpfulnessScore,
|
|
278
|
+
accessCount: m.accessCount,
|
|
279
|
+
evaluationCount: m.evaluationCount
|
|
280
|
+
}))
|
|
281
|
+
});
|
|
282
|
+
} catch (error) {
|
|
283
|
+
return c.json({
|
|
284
|
+
avgScore: 0,
|
|
285
|
+
totalEvaluated: 0,
|
|
286
|
+
totalRetrievals: 0,
|
|
287
|
+
helpful: 0,
|
|
288
|
+
neutral: 0,
|
|
289
|
+
unhelpful: 0,
|
|
290
|
+
topMemories: []
|
|
291
|
+
});
|
|
292
|
+
} finally {
|
|
293
|
+
await memoryService.shutdown();
|
|
294
|
+
}
|
|
295
|
+
});
|
|
296
|
+
|
|
297
|
+
|
|
298
|
+
|
|
299
|
+
// GET /api/stats/retrieval-traces - Get recent retrieval traces (query -> selected context)
|
|
300
|
+
statsRouter.get('/retrieval-traces', async (c) => {
|
|
301
|
+
const limit = parseInt(c.req.query('limit') || '50', 10);
|
|
302
|
+
const memoryService = getServiceFromQuery(c);
|
|
303
|
+
|
|
304
|
+
try {
|
|
305
|
+
await memoryService.initialize();
|
|
306
|
+
const traces = await memoryService.getRecentRetrievalTraces(limit);
|
|
307
|
+
const traceStats = await memoryService.getRetrievalTraceStats();
|
|
308
|
+
|
|
309
|
+
return c.json({
|
|
310
|
+
stats: traceStats,
|
|
311
|
+
traces: traces.map((t) => ({
|
|
312
|
+
traceId: t.traceId,
|
|
313
|
+
sessionId: t.sessionId || null,
|
|
314
|
+
projectHash: t.projectHash || null,
|
|
315
|
+
queryText: t.queryText,
|
|
316
|
+
strategy: t.strategy || null,
|
|
317
|
+
candidateEventIds: t.candidateEventIds,
|
|
318
|
+
selectedEventIds: t.selectedEventIds,
|
|
319
|
+
candidateDetails: t.candidateDetails || [],
|
|
320
|
+
selectedDetails: t.selectedDetails || [],
|
|
321
|
+
candidateCount: t.candidateCount,
|
|
322
|
+
selectedCount: t.selectedCount,
|
|
323
|
+
confidence: t.confidence || null,
|
|
324
|
+
fallbackTrace: t.fallbackTrace,
|
|
325
|
+
createdAt: t.createdAt.toISOString()
|
|
326
|
+
}))
|
|
327
|
+
});
|
|
328
|
+
} catch (error) {
|
|
329
|
+
return c.json({
|
|
330
|
+
stats: { totalQueries: 0, avgCandidateCount: 0, avgSelectedCount: 0, selectionRate: 0 },
|
|
331
|
+
traces: [],
|
|
332
|
+
error: (error as Error).message
|
|
333
|
+
}, 500);
|
|
334
|
+
} finally {
|
|
335
|
+
await memoryService.shutdown();
|
|
336
|
+
}
|
|
337
|
+
});
|
|
338
|
+
|
|
258
339
|
// POST /api/stats/graduation/run - Force graduation evaluation
|
|
259
340
|
statsRouter.post('/graduation/run', async (c) => {
|
|
260
|
-
const memoryService =
|
|
341
|
+
const memoryService = getServiceFromQuery(c);
|
|
261
342
|
try {
|
|
262
343
|
await memoryService.initialize();
|
|
263
344
|
const result = await memoryService.forceGraduation();
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Turns API
|
|
3
|
+
* Endpoints for viewing events grouped by conversation turn
|
|
4
|
+
*
|
|
5
|
+
* A "turn" groups a user_prompt with its associated tool_observations
|
|
6
|
+
* and the final agent_response into a single logical unit.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { Hono } from 'hono';
|
|
10
|
+
import { getServiceFromQuery } from './utils.js';
|
|
11
|
+
|
|
12
|
+
export const turnsRouter = new Hono();
|
|
13
|
+
|
|
14
|
+
// GET /api/turns?sessionId=xxx - List turns for a session
|
|
15
|
+
turnsRouter.get('/', async (c) => {
|
|
16
|
+
const sessionId = c.req.query('sessionId');
|
|
17
|
+
const limit = parseInt(c.req.query('limit') || '20', 10);
|
|
18
|
+
const offset = parseInt(c.req.query('offset') || '0', 10);
|
|
19
|
+
|
|
20
|
+
if (!sessionId) {
|
|
21
|
+
return c.json({ error: 'sessionId is required' }, 400);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const memoryService = getServiceFromQuery(c);
|
|
25
|
+
|
|
26
|
+
try {
|
|
27
|
+
await memoryService.initialize();
|
|
28
|
+
|
|
29
|
+
const turns = await memoryService.getSessionTurns(sessionId, { limit, offset });
|
|
30
|
+
const totalTurns = await memoryService.countSessionTurns(sessionId);
|
|
31
|
+
|
|
32
|
+
return c.json({
|
|
33
|
+
turns: turns.map(t => ({
|
|
34
|
+
turnId: t.turnId,
|
|
35
|
+
startedAt: t.startedAt.toISOString(),
|
|
36
|
+
promptPreview: t.promptPreview,
|
|
37
|
+
eventCount: t.eventCount,
|
|
38
|
+
toolCount: t.toolCount,
|
|
39
|
+
hasResponse: t.hasResponse,
|
|
40
|
+
events: t.events.map(e => ({
|
|
41
|
+
id: e.id,
|
|
42
|
+
eventType: e.eventType,
|
|
43
|
+
timestamp: e.timestamp instanceof Date ? e.timestamp.toISOString() : e.timestamp,
|
|
44
|
+
preview: e.content.slice(0, 300) + (e.content.length > 300 ? '...' : ''),
|
|
45
|
+
contentLength: e.content.length
|
|
46
|
+
}))
|
|
47
|
+
})),
|
|
48
|
+
total: totalTurns,
|
|
49
|
+
limit,
|
|
50
|
+
offset,
|
|
51
|
+
hasMore: offset + limit < totalTurns
|
|
52
|
+
});
|
|
53
|
+
} catch (error) {
|
|
54
|
+
return c.json({ error: (error as Error).message }, 500);
|
|
55
|
+
} finally {
|
|
56
|
+
await memoryService.shutdown();
|
|
57
|
+
}
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
// GET /api/turns/:turnId - Get full turn details
|
|
61
|
+
turnsRouter.get('/:turnId', async (c) => {
|
|
62
|
+
const { turnId } = c.req.param();
|
|
63
|
+
const memoryService = getServiceFromQuery(c);
|
|
64
|
+
|
|
65
|
+
try {
|
|
66
|
+
await memoryService.initialize();
|
|
67
|
+
|
|
68
|
+
const events = await memoryService.getEventsByTurn(turnId);
|
|
69
|
+
|
|
70
|
+
if (events.length === 0) {
|
|
71
|
+
return c.json({ error: 'Turn not found' }, 404);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const promptEvent = events.find(e => e.eventType === 'user_prompt');
|
|
75
|
+
const toolEvents = events.filter(e => e.eventType === 'tool_observation');
|
|
76
|
+
const responseEvents = events.filter(e => e.eventType === 'agent_response');
|
|
77
|
+
|
|
78
|
+
return c.json({
|
|
79
|
+
turnId,
|
|
80
|
+
sessionId: events[0].sessionId,
|
|
81
|
+
startedAt: events[0].timestamp instanceof Date
|
|
82
|
+
? events[0].timestamp.toISOString()
|
|
83
|
+
: events[0].timestamp,
|
|
84
|
+
prompt: promptEvent ? {
|
|
85
|
+
id: promptEvent.id,
|
|
86
|
+
content: promptEvent.content,
|
|
87
|
+
timestamp: promptEvent.timestamp instanceof Date
|
|
88
|
+
? promptEvent.timestamp.toISOString()
|
|
89
|
+
: promptEvent.timestamp
|
|
90
|
+
} : null,
|
|
91
|
+
tools: toolEvents.map(e => {
|
|
92
|
+
let toolName = '';
|
|
93
|
+
let success = true;
|
|
94
|
+
try {
|
|
95
|
+
const parsed = JSON.parse(e.content);
|
|
96
|
+
toolName = parsed.toolName || '';
|
|
97
|
+
success = parsed.success !== false;
|
|
98
|
+
} catch { /* ignore */ }
|
|
99
|
+
|
|
100
|
+
return {
|
|
101
|
+
id: e.id,
|
|
102
|
+
toolName,
|
|
103
|
+
success,
|
|
104
|
+
timestamp: e.timestamp instanceof Date ? e.timestamp.toISOString() : e.timestamp,
|
|
105
|
+
preview: e.content.slice(0, 500) + (e.content.length > 500 ? '...' : '')
|
|
106
|
+
};
|
|
107
|
+
}),
|
|
108
|
+
responses: responseEvents.map(e => ({
|
|
109
|
+
id: e.id,
|
|
110
|
+
content: e.content,
|
|
111
|
+
timestamp: e.timestamp instanceof Date ? e.timestamp.toISOString() : e.timestamp
|
|
112
|
+
})),
|
|
113
|
+
totalEvents: events.length
|
|
114
|
+
});
|
|
115
|
+
} catch (error) {
|
|
116
|
+
return c.json({ error: (error as Error).message }, 500);
|
|
117
|
+
} finally {
|
|
118
|
+
await memoryService.shutdown();
|
|
119
|
+
}
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
// POST /api/turns/backfill - Backfill turn_ids from metadata
|
|
123
|
+
turnsRouter.post('/backfill', async (c) => {
|
|
124
|
+
const memoryService = getServiceFromQuery(c);
|
|
125
|
+
|
|
126
|
+
try {
|
|
127
|
+
await memoryService.initialize();
|
|
128
|
+
const updated = await memoryService.backfillTurnIds();
|
|
129
|
+
|
|
130
|
+
return c.json({
|
|
131
|
+
success: true,
|
|
132
|
+
updated,
|
|
133
|
+
message: `Backfilled turn_id for ${updated} events`
|
|
134
|
+
});
|
|
135
|
+
} catch (error) {
|
|
136
|
+
return c.json({
|
|
137
|
+
success: false,
|
|
138
|
+
error: (error as Error).message
|
|
139
|
+
}, 500);
|
|
140
|
+
} finally {
|
|
141
|
+
await memoryService.shutdown();
|
|
142
|
+
}
|
|
143
|
+
});
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* API Utilities
|
|
3
|
+
* Shared helpers for API endpoints
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type { Context } from 'hono';
|
|
7
|
+
import * as path from 'path';
|
|
8
|
+
import * as os from 'os';
|
|
9
|
+
import { getReadOnlyMemoryService } from '../../services/memory-service.js';
|
|
10
|
+
import { MemoryService } from '../../services/memory-service.js';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Get the appropriate MemoryService based on the ?project= query parameter.
|
|
14
|
+
* - If ?project=<hash> is set (8 hex chars), resolves directly to project storage
|
|
15
|
+
* - If ?project=<path> is set, computes hash from path
|
|
16
|
+
* - Otherwise, returns the global read-only service
|
|
17
|
+
*
|
|
18
|
+
* Always creates read-only services for the dashboard API to avoid
|
|
19
|
+
* VectorWorker lifecycle issues with per-request services.
|
|
20
|
+
*/
|
|
21
|
+
export function getServiceFromQuery(c: Context): MemoryService {
|
|
22
|
+
const project = c.req.query('project');
|
|
23
|
+
if (project) {
|
|
24
|
+
// Check if it's a hash (8 hex chars) or a path
|
|
25
|
+
const isHash = /^[a-f0-9]{8}$/.test(project);
|
|
26
|
+
let storagePath: string;
|
|
27
|
+
|
|
28
|
+
if (isHash) {
|
|
29
|
+
storagePath = path.join(os.homedir(), '.claude-code', 'memory', 'projects', project);
|
|
30
|
+
} else {
|
|
31
|
+
// Import hashProjectPath dynamically to compute the hash from path
|
|
32
|
+
const crypto = require('crypto');
|
|
33
|
+
const normalized = project.replace(/\/+$/, '') || '/';
|
|
34
|
+
const hash = crypto.createHash('sha256').update(normalized).digest('hex').slice(0, 8);
|
|
35
|
+
storagePath = path.join(os.homedir(), '.claude-code', 'memory', 'projects', hash);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
return new MemoryService({
|
|
39
|
+
storagePath,
|
|
40
|
+
readOnly: true,
|
|
41
|
+
analyticsEnabled: false,
|
|
42
|
+
sharedStoreConfig: { enabled: false }
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
return getReadOnlyMemoryService();
|
|
46
|
+
}
|