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.
Files changed (142) hide show
  1. package/AGENTS.md +60 -0
  2. package/README.md +166 -2
  3. package/bootstrap-kb/decisions/decisions.md +244 -0
  4. package/bootstrap-kb/glossary/glossary.md +46 -0
  5. package/bootstrap-kb/modules/.claude-plugin.md +22 -0
  6. package/bootstrap-kb/modules/agents.md.md +15 -0
  7. package/bootstrap-kb/modules/claude.md.md +15 -0
  8. package/bootstrap-kb/modules/context.md.md +15 -0
  9. package/bootstrap-kb/modules/docs.md +18 -0
  10. package/bootstrap-kb/modules/handoff.md.md +15 -0
  11. package/bootstrap-kb/modules/package-lock.json.md +15 -0
  12. package/bootstrap-kb/modules/package.json.md +15 -0
  13. package/bootstrap-kb/modules/plan.md.md +15 -0
  14. package/bootstrap-kb/modules/readme.md.md +15 -0
  15. package/bootstrap-kb/modules/scripts.md +26 -0
  16. package/bootstrap-kb/modules/spec.md.md +15 -0
  17. package/bootstrap-kb/modules/specs.md +20 -0
  18. package/bootstrap-kb/modules/src.md +51 -0
  19. package/bootstrap-kb/modules/tests.md +42 -0
  20. package/bootstrap-kb/modules/tsconfig.json.md +15 -0
  21. package/bootstrap-kb/modules/vitest.config.ts.md +15 -0
  22. package/bootstrap-kb/overview/overview.md +40 -0
  23. package/bootstrap-kb/sources/manifest.json +950 -0
  24. package/bootstrap-kb/sources/manifest.md +227 -0
  25. package/bootstrap-kb/timeline/timeline.md +57 -0
  26. package/d.sh +3 -0
  27. package/deploy.sh +3 -0
  28. package/dist/cli/index.js +3577 -389
  29. package/dist/cli/index.js.map +4 -4
  30. package/dist/core/index.js +1383 -138
  31. package/dist/core/index.js.map +4 -4
  32. package/dist/hooks/post-tool-use.js +1917 -214
  33. package/dist/hooks/post-tool-use.js.map +4 -4
  34. package/dist/hooks/session-end.js +1813 -231
  35. package/dist/hooks/session-end.js.map +4 -4
  36. package/dist/hooks/session-start.js +1802 -205
  37. package/dist/hooks/session-start.js.map +4 -4
  38. package/dist/hooks/stop.js +1909 -248
  39. package/dist/hooks/stop.js.map +4 -4
  40. package/dist/hooks/user-prompt-submit.js +1861 -206
  41. package/dist/hooks/user-prompt-submit.js.map +4 -4
  42. package/dist/server/api/index.js +2341 -217
  43. package/dist/server/api/index.js.map +4 -4
  44. package/dist/server/index.js +2350 -226
  45. package/dist/server/index.js.map +4 -4
  46. package/dist/services/memory-service.js +1805 -206
  47. package/dist/services/memory-service.js.map +4 -4
  48. package/dist/ui/app.js +1447 -55
  49. package/dist/ui/index.html +318 -147
  50. package/dist/ui/style.css +892 -0
  51. package/docs/MCP_MEMORY_SERVICE_COMPARATIVE_REVIEW.md +271 -0
  52. package/docs/MEMU_ADOPTION.md +40 -0
  53. package/docs/OPERATIONS.md +18 -0
  54. package/memory/.claude-plugin/commands/2026-02-25.md +263 -0
  55. package/memory/_index.md +405 -0
  56. package/memory/default/uncategorized/2026-02-25.md +4839 -0
  57. package/memory/specs/20260207-dashboard-upgrade/2026-02-25.md +142 -0
  58. package/memory/specs/citations-system/2026-02-25.md +1121 -0
  59. package/memory/specs/endless-mode/2026-02-25.md +1392 -0
  60. package/memory/specs/entity-edge-model/2026-02-25.md +1263 -0
  61. package/memory/specs/evidence-aligner-v2/2026-02-25.md +1028 -0
  62. package/memory/specs/mcp-desktop-integration/2026-02-25.md +1334 -0
  63. package/memory/specs/post-tool-use-hook/2026-02-25.md +1164 -0
  64. package/memory/specs/private-tags/2026-02-25.md +1057 -0
  65. package/memory/specs/progressive-disclosure/2026-02-25.md +1436 -0
  66. package/memory/specs/task-entity-system/2026-02-25.md +924 -0
  67. package/memory/specs/vector-outbox-v2/2026-02-25.md +1510 -0
  68. package/memory/specs/web-viewer-ui/2026-02-25.md +1709 -0
  69. package/package.json +9 -2
  70. package/scripts/build.ts +6 -0
  71. package/scripts/fix-sync-gap.js +32 -0
  72. package/scripts/heartbeat-memory-orchestrator.sh +28 -0
  73. package/scripts/report-sync-gap.js +26 -0
  74. package/scripts/review-queue-auto-resolve.js +21 -0
  75. package/scripts/sync-gap-auto-heal.sh +17 -0
  76. package/specs/20260207-dashboard-upgrade/context.md +38 -0
  77. package/specs/20260207-dashboard-upgrade/spec.md +96 -0
  78. package/src/cli/index.ts +391 -60
  79. package/src/core/consolidated-store.ts +63 -1
  80. package/src/core/consolidation-worker.ts +115 -6
  81. package/src/core/event-store.ts +14 -0
  82. package/src/core/index.ts +1 -0
  83. package/src/core/ingest-interceptor.ts +80 -0
  84. package/src/core/markdown-mirror.ts +70 -0
  85. package/src/core/md-mirror.ts +92 -0
  86. package/src/core/mongo-sync-config.ts +165 -0
  87. package/src/core/mongo-sync-worker.ts +381 -0
  88. package/src/core/retriever.ts +540 -150
  89. package/src/core/sqlite-event-store.ts +794 -7
  90. package/src/core/sqlite-wrapper.ts +8 -0
  91. package/src/core/tag-taxonomy.ts +51 -0
  92. package/src/core/turn-state.ts +159 -0
  93. package/src/core/types.ts +51 -8
  94. package/src/core/vector-store.ts +21 -3
  95. package/src/hooks/post-tool-use.ts +68 -23
  96. package/src/hooks/session-end.ts +8 -3
  97. package/src/hooks/stop.ts +96 -25
  98. package/src/hooks/user-prompt-submit.ts +44 -5
  99. package/src/server/api/chat.ts +244 -0
  100. package/src/server/api/citations.ts +3 -3
  101. package/src/server/api/events.ts +30 -5
  102. package/src/server/api/health.ts +53 -0
  103. package/src/server/api/index.ts +9 -1
  104. package/src/server/api/projects.ts +74 -0
  105. package/src/server/api/search.ts +3 -3
  106. package/src/server/api/sessions.ts +3 -3
  107. package/src/server/api/stats.ts +89 -8
  108. package/src/server/api/turns.ts +143 -0
  109. package/src/server/api/utils.ts +46 -0
  110. package/src/services/bootstrap-organizer.ts +443 -0
  111. package/src/services/codex-session-history-importer.ts +474 -0
  112. package/src/services/memory-service.ts +508 -71
  113. package/src/services/session-history-importer.ts +215 -51
  114. package/src/ui/app.js +1447 -55
  115. package/src/ui/index.html +318 -147
  116. package/src/ui/style.css +892 -0
  117. package/tests/bootstrap-organizer.test.ts +111 -0
  118. package/tests/consolidation-worker.test.ts +75 -0
  119. package/tests/ingest-interceptor.test.ts +38 -0
  120. package/tests/markdown-mirror.test.ts +85 -0
  121. package/tests/md-mirror.test.ts +50 -0
  122. package/tests/retriever-fallback-chain.test.ts +223 -0
  123. package/tests/retriever-strategy-scope.test.ts +97 -0
  124. package/tests/retriever.memu-adoption.test.ts +122 -0
  125. package/tests/sqlite-event-store-replication.test.ts +92 -0
  126. package/.claude/settings.local.json +0 -27
  127. package/.claude-memory/test.sqlite +0 -0
  128. package/.history/package_20260201112328.json +0 -45
  129. package/.history/package_20260201113602.json +0 -45
  130. package/.history/package_20260201113713.json +0 -45
  131. package/.history/package_20260201114110.json +0 -45
  132. package/.history/package_20260201114632.json +0 -46
  133. package/.history/package_20260201133143.json +0 -45
  134. package/.history/package_20260201134319.json +0 -45
  135. package/.history/package_20260201134326.json +0 -45
  136. package/.history/package_20260201134334.json +0 -45
  137. package/.history/package_20260201134912.json +0 -45
  138. package/.history/package_20260201142928.json +0 -46
  139. package/.history/package_20260201192048.json +0 -47
  140. package/.history/package_20260202114053.json +0 -49
  141. package/.history/package_20260202121115.json +0 -49
  142. package/test_access.js +0 -49
@@ -4,13 +4,14 @@
4
4
  */
5
5
 
6
6
  import { Hono } from 'hono';
7
- import { getReadOnlyMemoryService, getMemoryServiceForProject } from '../../services/memory-service.js';
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 = getReadOnlyMemoryService();
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 = getReadOnlyMemoryService();
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 = getReadOnlyMemoryService();
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 = getReadOnlyMemoryService();
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 = getReadOnlyMemoryService();
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 = getReadOnlyMemoryService();
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
+ }