claude-memory-layer 1.0.15 → 1.0.17
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/README.md +3 -0
- package/dist/cli/index.js +105 -74
- package/dist/cli/index.js.map +2 -2
- package/dist/core/index.js +53 -40
- package/dist/core/index.js.map +2 -2
- package/dist/hooks/post-tool-use.js +53 -40
- package/dist/hooks/post-tool-use.js.map +2 -2
- package/dist/hooks/session-end.js +53 -40
- package/dist/hooks/session-end.js.map +2 -2
- package/dist/hooks/session-start.js +53 -40
- package/dist/hooks/session-start.js.map +2 -2
- package/dist/hooks/stop.js +53 -40
- package/dist/hooks/stop.js.map +2 -2
- package/dist/hooks/user-prompt-submit.js +72 -43
- package/dist/hooks/user-prompt-submit.js.map +2 -2
- package/dist/server/api/index.js +53 -40
- package/dist/server/api/index.js.map +2 -2
- package/dist/server/index.js +53 -40
- package/dist/server/index.js.map +2 -2
- package/dist/services/memory-service.js +53 -40
- package/dist/services/memory-service.js.map +2 -2
- package/memory/_index.md +3 -0
- package/memory/agent_response/uncategorized/2026-02-26.md +26 -0
- package/memory/tool_observation/uncategorized/2026-02-26.md +201 -0
- package/memory/user_prompt/uncategorized/2026-02-26.md +16 -0
- package/package.json +1 -1
- package/src/cli/index.ts +55 -33
- package/src/core/sqlite-event-store.ts +52 -40
- package/src/hooks/user-prompt-submit.ts +22 -3
package/src/cli/index.ts
CHANGED
|
@@ -32,6 +32,7 @@ interface ClaudeSettings {
|
|
|
32
32
|
PostToolUse?: Array<{ matcher: string; hooks: Array<{ type: string; command: string }> }>;
|
|
33
33
|
SessionStart?: Array<{ matcher: string; hooks: Array<{ type: string; command: string }> }>;
|
|
34
34
|
Stop?: Array<{ matcher: string; hooks: Array<{ type: string; command: string }> }>;
|
|
35
|
+
SessionEnd?: Array<{ matcher: string; hooks: Array<{ type: string; command: string }> }>;
|
|
35
36
|
};
|
|
36
37
|
[key: string]: unknown;
|
|
37
38
|
}
|
|
@@ -79,30 +80,39 @@ function saveClaudeSettings(settings: ClaudeSettings): void {
|
|
|
79
80
|
fs.renameSync(tempPath, CLAUDE_SETTINGS_PATH);
|
|
80
81
|
}
|
|
81
82
|
|
|
83
|
+
const REQUIRED_HOOK_FILES = [
|
|
84
|
+
'user-prompt-submit.js',
|
|
85
|
+
'post-tool-use.js',
|
|
86
|
+
'session-start.js',
|
|
87
|
+
'stop.js',
|
|
88
|
+
'session-end.js'
|
|
89
|
+
] as const;
|
|
90
|
+
|
|
91
|
+
function hasHook(settings: ClaudeSettings, hookName: keyof NonNullable<ClaudeSettings['hooks']>, commandFragment: string): boolean {
|
|
92
|
+
const hookEntries = settings.hooks?.[hookName];
|
|
93
|
+
if (!hookEntries) return false;
|
|
94
|
+
return hookEntries.some((entry) => entry.hooks?.some((hook) => hook.command?.includes(commandFragment)));
|
|
95
|
+
}
|
|
96
|
+
|
|
82
97
|
function getHooksConfig(pluginPath: string): ClaudeSettings['hooks'] {
|
|
98
|
+
const makeHook = (fileName: string) => [
|
|
99
|
+
{
|
|
100
|
+
matcher: '',
|
|
101
|
+
hooks: [
|
|
102
|
+
{
|
|
103
|
+
type: 'command',
|
|
104
|
+
command: `node ${path.join(pluginPath, 'hooks', fileName)}`
|
|
105
|
+
}
|
|
106
|
+
]
|
|
107
|
+
}
|
|
108
|
+
];
|
|
109
|
+
|
|
83
110
|
return {
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
type: 'command',
|
|
90
|
-
command: `node ${path.join(pluginPath, 'hooks', 'user-prompt-submit.js')}`
|
|
91
|
-
}
|
|
92
|
-
]
|
|
93
|
-
}
|
|
94
|
-
],
|
|
95
|
-
PostToolUse: [
|
|
96
|
-
{
|
|
97
|
-
matcher: '',
|
|
98
|
-
hooks: [
|
|
99
|
-
{
|
|
100
|
-
type: 'command',
|
|
101
|
-
command: `node ${path.join(pluginPath, 'hooks', 'post-tool-use.js')}`
|
|
102
|
-
}
|
|
103
|
-
]
|
|
104
|
-
}
|
|
105
|
-
]
|
|
111
|
+
SessionStart: makeHook('session-start.js'),
|
|
112
|
+
UserPromptSubmit: makeHook('user-prompt-submit.js'),
|
|
113
|
+
PostToolUse: makeHook('post-tool-use.js'),
|
|
114
|
+
Stop: makeHook('stop.js'),
|
|
115
|
+
SessionEnd: makeHook('session-end.js')
|
|
106
116
|
};
|
|
107
117
|
}
|
|
108
118
|
|
|
@@ -129,9 +139,12 @@ program
|
|
|
129
139
|
const pluginPath = options.path || getPluginPath();
|
|
130
140
|
|
|
131
141
|
// Verify hooks exist
|
|
132
|
-
const
|
|
133
|
-
|
|
142
|
+
const missingHooks = REQUIRED_HOOK_FILES.filter((file) =>
|
|
143
|
+
!fs.existsSync(path.join(pluginPath, 'hooks', file))
|
|
144
|
+
);
|
|
145
|
+
if (missingHooks.length > 0) {
|
|
134
146
|
console.error(`\n❌ Hook files not found at: ${pluginPath}`);
|
|
147
|
+
console.error(` Missing: ${missingHooks.join(', ')}`);
|
|
135
148
|
console.error(' Make sure you have built the plugin with "npm run build"');
|
|
136
149
|
process.exit(1);
|
|
137
150
|
}
|
|
@@ -151,8 +164,11 @@ program
|
|
|
151
164
|
|
|
152
165
|
console.log('\n✅ Claude Memory Layer installed!\n');
|
|
153
166
|
console.log('Hooks registered:');
|
|
167
|
+
console.log(' - SessionStart: Register session -> project mapping');
|
|
154
168
|
console.log(' - UserPromptSubmit: Memory retrieval on user input');
|
|
155
|
-
console.log(' - PostToolUse: Store tool observations
|
|
169
|
+
console.log(' - PostToolUse: Store tool observations');
|
|
170
|
+
console.log(' - Stop: Store assistant responses');
|
|
171
|
+
console.log(' - SessionEnd: Persist session summary\n');
|
|
156
172
|
console.log('Plugin path:', pluginPath);
|
|
157
173
|
console.log('\n⚠️ Restart Claude Code for changes to take effect.\n');
|
|
158
174
|
console.log('Commands:');
|
|
@@ -183,8 +199,11 @@ program
|
|
|
183
199
|
}
|
|
184
200
|
|
|
185
201
|
// Remove our hooks
|
|
202
|
+
delete settings.hooks.SessionStart;
|
|
186
203
|
delete settings.hooks.UserPromptSubmit;
|
|
187
204
|
delete settings.hooks.PostToolUse;
|
|
205
|
+
delete settings.hooks.Stop;
|
|
206
|
+
delete settings.hooks.SessionEnd;
|
|
188
207
|
|
|
189
208
|
// Clean up empty hooks object
|
|
190
209
|
if (Object.keys(settings.hooks).length === 0) {
|
|
@@ -219,19 +238,22 @@ program
|
|
|
219
238
|
console.log('\n🧠 Claude Memory Layer Status\n');
|
|
220
239
|
|
|
221
240
|
// Check hooks
|
|
222
|
-
const
|
|
223
|
-
|
|
224
|
-
);
|
|
225
|
-
const
|
|
226
|
-
|
|
227
|
-
);
|
|
241
|
+
const hasSessionStartHook = hasHook(settings, 'SessionStart', 'session-start');
|
|
242
|
+
const hasUserPromptHook = hasHook(settings, 'UserPromptSubmit', 'user-prompt-submit');
|
|
243
|
+
const hasPostToolHook = hasHook(settings, 'PostToolUse', 'post-tool-use');
|
|
244
|
+
const hasStopHook = hasHook(settings, 'Stop', 'stop');
|
|
245
|
+
const hasSessionEndHook = hasHook(settings, 'SessionEnd', 'session-end');
|
|
228
246
|
|
|
229
247
|
console.log('Hooks:');
|
|
248
|
+
console.log(` SessionStart: ${hasSessionStartHook ? '✅ Installed' : '❌ Not installed'}`);
|
|
230
249
|
console.log(` UserPromptSubmit: ${hasUserPromptHook ? '✅ Installed' : '❌ Not installed'}`);
|
|
231
250
|
console.log(` PostToolUse: ${hasPostToolHook ? '✅ Installed' : '❌ Not installed'}`);
|
|
251
|
+
console.log(` Stop: ${hasStopHook ? '✅ Installed' : '❌ Not installed'}`);
|
|
252
|
+
console.log(` SessionEnd: ${hasSessionEndHook ? '✅ Installed' : '❌ Not installed'}`);
|
|
232
253
|
|
|
233
254
|
// Check plugin files
|
|
234
|
-
const hooksExist =
|
|
255
|
+
const hooksExist = REQUIRED_HOOK_FILES
|
|
256
|
+
.every((file) => fs.existsSync(path.join(pluginPath, 'hooks', file)));
|
|
235
257
|
console.log(`\nPlugin files: ${hooksExist ? '✅ Found' : '❌ Not found'}`);
|
|
236
258
|
console.log(` Path: ${pluginPath}`);
|
|
237
259
|
|
|
@@ -239,7 +261,7 @@ program
|
|
|
239
261
|
const dashboardRunning = await isServerRunning(37777);
|
|
240
262
|
console.log(`\nDashboard: ${dashboardRunning ? '✅ Running at http://localhost:37777' : '⏹️ Not running'}`);
|
|
241
263
|
|
|
242
|
-
if (!hasUserPromptHook || !hasPostToolHook) {
|
|
264
|
+
if (!hasSessionStartHook || !hasUserPromptHook || !hasPostToolHook || !hasStopHook || !hasSessionEndHook) {
|
|
243
265
|
console.log('\n💡 Run "claude-memory-layer install" to set up hooks.\n');
|
|
244
266
|
} else {
|
|
245
267
|
console.log('\n✅ Plugin is fully installed and configured.\n');
|
|
@@ -1446,28 +1446,33 @@ export class SQLiteEventStore {
|
|
|
1446
1446
|
}>> {
|
|
1447
1447
|
await this.initialize();
|
|
1448
1448
|
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1449
|
+
try {
|
|
1450
|
+
const rows = sqliteAll<Record<string, unknown>>(
|
|
1451
|
+
this.db,
|
|
1452
|
+
`SELECT * FROM retrieval_traces ORDER BY created_at DESC LIMIT ?`,
|
|
1453
|
+
[limit]
|
|
1454
|
+
);
|
|
1454
1455
|
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1456
|
+
return rows.map((row) => ({
|
|
1457
|
+
traceId: row.trace_id as string,
|
|
1458
|
+
sessionId: (row.session_id as string) || undefined,
|
|
1459
|
+
projectHash: (row.project_hash as string) || undefined,
|
|
1460
|
+
queryText: row.query_text as string,
|
|
1461
|
+
strategy: (row.strategy as string) || undefined,
|
|
1462
|
+
candidateEventIds: row.candidate_event_ids ? JSON.parse(row.candidate_event_ids as string) : [],
|
|
1463
|
+
selectedEventIds: row.selected_event_ids ? JSON.parse(row.selected_event_ids as string) : [],
|
|
1464
|
+
candidateDetails: row.candidate_details_json ? JSON.parse(row.candidate_details_json as string) : [],
|
|
1465
|
+
selectedDetails: row.selected_details_json ? JSON.parse(row.selected_details_json as string) : [],
|
|
1466
|
+
candidateCount: Number(row.candidate_count || 0),
|
|
1467
|
+
selectedCount: Number(row.selected_count || 0),
|
|
1468
|
+
confidence: (row.confidence as string) || undefined,
|
|
1469
|
+
fallbackTrace: row.fallback_trace ? JSON.parse(row.fallback_trace as string) : [],
|
|
1470
|
+
createdAt: toDateFromSQLite(row.created_at),
|
|
1471
|
+
}));
|
|
1472
|
+
} catch (err: any) {
|
|
1473
|
+
if (err?.message?.includes('no such table')) return [];
|
|
1474
|
+
throw err;
|
|
1475
|
+
}
|
|
1471
1476
|
}
|
|
1472
1477
|
|
|
1473
1478
|
async getRetrievalTraceStats(): Promise<{
|
|
@@ -1478,26 +1483,33 @@ export class SQLiteEventStore {
|
|
|
1478
1483
|
}> {
|
|
1479
1484
|
await this.initialize();
|
|
1480
1485
|
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1486
|
+
try {
|
|
1487
|
+
const row = sqliteGet<Record<string, unknown>>(
|
|
1488
|
+
this.db,
|
|
1489
|
+
`SELECT
|
|
1490
|
+
COUNT(*) as total_queries,
|
|
1491
|
+
AVG(candidate_count) as avg_candidate_count,
|
|
1492
|
+
AVG(selected_count) as avg_selected_count,
|
|
1493
|
+
CASE
|
|
1494
|
+
WHEN SUM(candidate_count) > 0 THEN (SUM(selected_count) * 1.0 / SUM(candidate_count))
|
|
1495
|
+
ELSE 0
|
|
1496
|
+
END as selection_rate
|
|
1497
|
+
FROM retrieval_traces`,
|
|
1498
|
+
[]
|
|
1499
|
+
);
|
|
1494
1500
|
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
+
return {
|
|
1502
|
+
totalQueries: Number(row?.total_queries || 0),
|
|
1503
|
+
avgCandidateCount: Number(row?.avg_candidate_count || 0),
|
|
1504
|
+
avgSelectedCount: Number(row?.avg_selected_count || 0),
|
|
1505
|
+
selectionRate: Number(row?.selection_rate || 0),
|
|
1506
|
+
};
|
|
1507
|
+
} catch (err: any) {
|
|
1508
|
+
if (err?.message?.includes('no such table')) {
|
|
1509
|
+
return { totalQueries: 0, avgCandidateCount: 0, avgSelectedCount: 0, selectionRate: 0 };
|
|
1510
|
+
}
|
|
1511
|
+
throw err;
|
|
1512
|
+
}
|
|
1501
1513
|
}
|
|
1502
1514
|
|
|
1503
1515
|
/**
|
|
@@ -17,7 +17,9 @@ import type { UserPromptSubmitInput, UserPromptSubmitOutput } from '../core/type
|
|
|
17
17
|
|
|
18
18
|
// Configuration
|
|
19
19
|
const MAX_MEMORIES = parseInt(process.env.CLAUDE_MEMORY_MAX_COUNT || '5');
|
|
20
|
-
|
|
20
|
+
// Tuned default for noise/recall balance on shopping_assistant-like corpus
|
|
21
|
+
const BASE_MIN_SCORE = parseFloat(process.env.CLAUDE_MEMORY_MIN_SCORE || '0.4');
|
|
22
|
+
const FALLBACK_MIN_SCORE = parseFloat(process.env.CLAUDE_MEMORY_FALLBACK_MIN_SCORE || '0.3');
|
|
21
23
|
const ENABLE_SEARCH = process.env.CLAUDE_MEMORY_SEARCH !== 'false';
|
|
22
24
|
|
|
23
25
|
/**
|
|
@@ -32,6 +34,14 @@ function shouldStorePrompt(prompt: string): boolean {
|
|
|
32
34
|
return true;
|
|
33
35
|
}
|
|
34
36
|
|
|
37
|
+
|
|
38
|
+
function getDynamicMinScore(prompt: string): number {
|
|
39
|
+
const len = prompt.trim().length;
|
|
40
|
+
if (len <= 20) return Math.min(0.55, BASE_MIN_SCORE + 0.1); // short query → stricter
|
|
41
|
+
if (len >= 80) return Math.max(0.3, BASE_MIN_SCORE - 0.05); // long query → slightly looser
|
|
42
|
+
return BASE_MIN_SCORE;
|
|
43
|
+
}
|
|
44
|
+
|
|
35
45
|
async function main(): Promise<void> {
|
|
36
46
|
// Read input from stdin
|
|
37
47
|
const inputData = await readStdin();
|
|
@@ -61,11 +71,20 @@ async function main(): Promise<void> {
|
|
|
61
71
|
|
|
62
72
|
// Fast keyword search if enabled
|
|
63
73
|
if (ENABLE_SEARCH && input.prompt.length > 10) {
|
|
64
|
-
const
|
|
74
|
+
const minScore = getDynamicMinScore(input.prompt);
|
|
75
|
+
let results = await memoryService.keywordSearch(input.prompt, {
|
|
65
76
|
topK: MAX_MEMORIES,
|
|
66
|
-
minScore
|
|
77
|
+
minScore
|
|
67
78
|
});
|
|
68
79
|
|
|
80
|
+
// recall rescue: if nothing found at tuned threshold, retry with fallback floor
|
|
81
|
+
if (results.length === 0 && FALLBACK_MIN_SCORE < minScore) {
|
|
82
|
+
results = await memoryService.keywordSearch(input.prompt, {
|
|
83
|
+
topK: MAX_MEMORIES,
|
|
84
|
+
minScore: FALLBACK_MIN_SCORE
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
|
|
69
88
|
if (results.length > 0) {
|
|
70
89
|
// Increment access count for found memories
|
|
71
90
|
const eventIds = results.map(r => r.event.id);
|