claude-recall 0.15.13 → 0.15.16
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/hooks/search_enforcer.py +20 -2
- package/.claude/settings.json +48 -1
- package/.claude/skills/auto-corrections/SKILL.md +4 -1
- package/.claude/skills/auto-corrections/manifest.json +6 -3
- package/.claude/skills/auto-preferences/SKILL.md +18 -1
- package/.claude/skills/auto-preferences/manifest.json +20 -3
- package/dist/cli/claude-recall-cli.js +33 -1
- package/dist/hooks/memory-stop-hook.js +52 -0
- package/dist/mcp/tools/memory-tools.js +10 -0
- package/dist/memory/storage.js +51 -0
- package/dist/services/memory.js +41 -0
- package/docs/agentic-reasoning-opportunities.md +68 -0
- package/docs/expected-behavior.md +30 -0
- package/docs/installation.md +1 -4
- package/docs/learning-loop.md +3 -4
- package/docs/project-scoping.md +0 -17
- package/docs/quickstart.md +0 -2
- package/docs/security.md +4 -34
- package/docs/troubleshooting.md +7 -27
- package/package.json +2 -1
|
@@ -32,7 +32,11 @@ READ_ONLY_BASH = [
|
|
|
32
32
|
'ls', 'cat', 'head', 'tail', 'less', 'more', 'file', 'stat', 'wc',
|
|
33
33
|
'find', 'locate', 'which', 'whereis', 'type', 'pwd', 'whoami',
|
|
34
34
|
'git status', 'git log', 'git diff', 'git show', 'git branch',
|
|
35
|
-
'git remote', 'git fetch', 'git stash
|
|
35
|
+
'git remote', 'git fetch', 'git stash', 'git tag',
|
|
36
|
+
'git add', 'git commit', 'git push', 'git pull', 'git merge',
|
|
37
|
+
'git rebase', 'git checkout', 'git switch', 'git cherry-pick',
|
|
38
|
+
'npm install', 'npm version', 'npm publish', 'npm pack',
|
|
39
|
+
'npx',
|
|
36
40
|
'npm list', 'npm ls', 'npm view', 'npm outdated', 'npm audit',
|
|
37
41
|
'npm test', 'npm run test', 'npm run build', 'npm run lint',
|
|
38
42
|
'pip list', 'pip show', 'pip freeze',
|
|
@@ -125,7 +129,21 @@ def main():
|
|
|
125
129
|
if (now - last_search) <= SEARCH_TTL_MS:
|
|
126
130
|
sys.exit(0)
|
|
127
131
|
|
|
128
|
-
|
|
132
|
+
# TTL expired but rules were loaded earlier — degrade to warn, not block.
|
|
133
|
+
# This prevents deadlock when MCP server disconnects mid-session.
|
|
134
|
+
msg = f"""
|
|
135
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
136
|
+
STALE RULES — consider reloading before {tool_name}
|
|
137
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
138
|
+
|
|
139
|
+
Rules were loaded earlier but TTL expired.
|
|
140
|
+
Run: mcp__claude-recall__load_rules({{}})
|
|
141
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
142
|
+
"""
|
|
143
|
+
print(msg.strip(), file=sys.stderr)
|
|
144
|
+
sys.exit(0) # Warn only — allow the action
|
|
145
|
+
|
|
146
|
+
# Never loaded in this session — block
|
|
129
147
|
msg = f"""
|
|
130
148
|
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
131
149
|
LOAD RULES REQUIRED before {tool_name}
|
package/.claude/settings.json
CHANGED
|
@@ -1 +1,48 @@
|
|
|
1
|
-
{
|
|
1
|
+
{
|
|
2
|
+
"hooks": {
|
|
3
|
+
"PreToolUse": [
|
|
4
|
+
{
|
|
5
|
+
"matcher": "mcp__claude-recall__.*|Write|Edit|Bash|Task",
|
|
6
|
+
"hooks": [
|
|
7
|
+
{
|
|
8
|
+
"type": "command",
|
|
9
|
+
"command": "python3 /home/ebiarao/repos-wsl/personal-projects/claude-recall/.claude/hooks/search_enforcer.py"
|
|
10
|
+
}
|
|
11
|
+
]
|
|
12
|
+
}
|
|
13
|
+
],
|
|
14
|
+
"UserPromptSubmit": [
|
|
15
|
+
{
|
|
16
|
+
"hooks": [
|
|
17
|
+
{
|
|
18
|
+
"type": "command",
|
|
19
|
+
"command": "npx claude-recall@latest hook run correction-detector"
|
|
20
|
+
}
|
|
21
|
+
]
|
|
22
|
+
}
|
|
23
|
+
],
|
|
24
|
+
"Stop": [
|
|
25
|
+
{
|
|
26
|
+
"hooks": [
|
|
27
|
+
{
|
|
28
|
+
"type": "command",
|
|
29
|
+
"command": "npx claude-recall@latest hook run memory-stop",
|
|
30
|
+
"timeout": 30
|
|
31
|
+
}
|
|
32
|
+
]
|
|
33
|
+
}
|
|
34
|
+
],
|
|
35
|
+
"PreCompact": [
|
|
36
|
+
{
|
|
37
|
+
"hooks": [
|
|
38
|
+
{
|
|
39
|
+
"type": "command",
|
|
40
|
+
"command": "npx claude-recall@latest hook run precompact-preserve",
|
|
41
|
+
"timeout": 60
|
|
42
|
+
}
|
|
43
|
+
]
|
|
44
|
+
}
|
|
45
|
+
]
|
|
46
|
+
},
|
|
47
|
+
"hooksVersion": "4.0.0"
|
|
48
|
+
}
|
|
@@ -8,7 +8,7 @@ source: claude-recall
|
|
|
8
8
|
|
|
9
9
|
# Corrections
|
|
10
10
|
|
|
11
|
-
Auto-generated from
|
|
11
|
+
Auto-generated from 25 memories. Last updated: 2026-02-26.
|
|
12
12
|
|
|
13
13
|
## Rules
|
|
14
14
|
|
|
@@ -33,6 +33,9 @@ Auto-generated from 22 memories. Last updated: 2026-02-18.
|
|
|
33
33
|
- CORRECTION: Memory with complex metadata
|
|
34
34
|
- CORRECTION: Memory with complex metadata
|
|
35
35
|
- CORRECTION: Memory with complex metadata
|
|
36
|
+
- CORRECTION: Memory with complex metadata
|
|
37
|
+
- CORRECTION: Memory with complex metadata
|
|
38
|
+
- CORRECTION: Memory with complex metadata
|
|
36
39
|
- CORRECTION: use spaces not tabs for indentation
|
|
37
40
|
|
|
38
41
|
---
|
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"topicId": "corrections",
|
|
3
|
-
"sourceHash": "
|
|
4
|
-
"memoryCount":
|
|
5
|
-
"generatedAt": "2026-02-
|
|
3
|
+
"sourceHash": "9db7378bea9322948eab03d138983fc4e4173aa593ea16e1487f0551b91cecc3",
|
|
4
|
+
"memoryCount": 25,
|
|
5
|
+
"generatedAt": "2026-02-26T10:01:12.122Z",
|
|
6
6
|
"memoryKeys": [
|
|
7
|
+
"memory_1772100072113_a6za43yqy",
|
|
8
|
+
"memory_1771937053521_wayeeo1gr",
|
|
9
|
+
"memory_1771936515420_zm9348oh9",
|
|
7
10
|
"memory_1771441991246_26wk2e6q3",
|
|
8
11
|
"memory_1771432628423_6l1fpuu77",
|
|
9
12
|
"memory_1771432266123_aevced1ym",
|
|
@@ -8,10 +8,25 @@ source: claude-recall
|
|
|
8
8
|
|
|
9
9
|
# Preferences
|
|
10
10
|
|
|
11
|
-
Auto-generated from
|
|
11
|
+
Auto-generated from 140 memories. Last updated: 2026-02-26.
|
|
12
12
|
|
|
13
13
|
## Rules
|
|
14
14
|
|
|
15
|
+
- Session test preference 1772100072188
|
|
16
|
+
- Test preference 1772100072127-2
|
|
17
|
+
- Test preference 1772100072127-1
|
|
18
|
+
- Test preference 1772100072127-0
|
|
19
|
+
- Test memory content
|
|
20
|
+
- Session test preference 1771937053612
|
|
21
|
+
- Test preference 1771937053539-2
|
|
22
|
+
- Test preference 1771937053539-1
|
|
23
|
+
- Test preference 1771937053539-0
|
|
24
|
+
- Test memory content
|
|
25
|
+
- Session test preference 1771936515500
|
|
26
|
+
- Test preference 1771936515435-2
|
|
27
|
+
- Test preference 1771936515435-1
|
|
28
|
+
- Test preference 1771936515435-0
|
|
29
|
+
- Test memory content
|
|
15
30
|
- Session test preference 1771441991453
|
|
16
31
|
- Test preference 1771441991283-2
|
|
17
32
|
- Test preference 1771441991283-1
|
|
@@ -132,6 +147,8 @@ Auto-generated from 123 memories. Last updated: 2026-02-18.
|
|
|
132
147
|
- Test memory 1770503266492-0
|
|
133
148
|
- Memory with complex metadata
|
|
134
149
|
- Test memory content
|
|
150
|
+
- When user says 'jam', it means 'just answer me' — a shorthand directive to provide direct answers without elaboration
|
|
151
|
+
- When user says 'jam', they mean 'just answer me' — respond directly without explanation
|
|
135
152
|
- always use tabs for indentation
|
|
136
153
|
- always use tabs
|
|
137
154
|
- always use tabs
|
|
@@ -1,9 +1,24 @@
|
|
|
1
1
|
{
|
|
2
2
|
"topicId": "preferences",
|
|
3
|
-
"sourceHash": "
|
|
4
|
-
"memoryCount":
|
|
5
|
-
"generatedAt": "2026-02-
|
|
3
|
+
"sourceHash": "6abccdcd61e745a5953d71937d9d76bc0e63e4fdea57efd12e82ee5364c5cb7e",
|
|
4
|
+
"memoryCount": 140,
|
|
5
|
+
"generatedAt": "2026-02-26T10:01:12.200Z",
|
|
6
6
|
"memoryKeys": [
|
|
7
|
+
"memory_1772100072189_1z5ky8vqz",
|
|
8
|
+
"memory_1772100072157_1dhc3az8z",
|
|
9
|
+
"memory_1772100072143_9wwr89ivr",
|
|
10
|
+
"memory_1772100072128_v5em20s0r",
|
|
11
|
+
"memory_1772100072057_gpuspbnd5",
|
|
12
|
+
"memory_1771937053614_p4252awle",
|
|
13
|
+
"memory_1771937053577_7hkwaug2z",
|
|
14
|
+
"memory_1771937053557_2tv5gy1kn",
|
|
15
|
+
"memory_1771937053540_byc4aoei6",
|
|
16
|
+
"memory_1771937053479_5b1aq5bo4",
|
|
17
|
+
"memory_1771936515501_7rxf04yr7",
|
|
18
|
+
"memory_1771936515470_5amfyz2c2",
|
|
19
|
+
"memory_1771936515454_yuee3hady",
|
|
20
|
+
"memory_1771936515436_2zas4g86g",
|
|
21
|
+
"memory_1771936515373_x886pahdp",
|
|
7
22
|
"memory_1771441991455_q068p8n23",
|
|
8
23
|
"memory_1771441991411_dv9e76ojs",
|
|
9
24
|
"memory_1771441991351_8esutde44",
|
|
@@ -124,6 +139,8 @@
|
|
|
124
139
|
"memory_1770503266494_f3p8e15xv",
|
|
125
140
|
"memory_1770503266473_23fy8pzky",
|
|
126
141
|
"memory_1770503266442_289cuenj7",
|
|
142
|
+
"hook_preference_1771934420257_l4pawjgn5",
|
|
143
|
+
"hook_preference_1771934405021_mnhek0x7b",
|
|
127
144
|
"hook_preference_1771112119815_z02zal3z6",
|
|
128
145
|
"hook_preference_1771112104493_d3nz1o2ye",
|
|
129
146
|
"hook_preference_1771112075356_3gh724ucm"
|
|
@@ -108,6 +108,8 @@ class ClaudeRecallCLI {
|
|
|
108
108
|
this.showSkillsEvolution();
|
|
109
109
|
// Token Savings Estimate
|
|
110
110
|
this.showTokenSavings(stats);
|
|
111
|
+
// Rule Compliance
|
|
112
|
+
this.showComplianceStats();
|
|
111
113
|
console.log('\n');
|
|
112
114
|
this.logger.info('CLI', 'Stats displayed', stats);
|
|
113
115
|
}
|
|
@@ -190,6 +192,34 @@ class ClaudeRecallCLI {
|
|
|
190
192
|
console.log(` Total saved: ~${totalSavings.toLocaleString()} tokens`);
|
|
191
193
|
console.log(` (vs repeating preferences or loading all reference files)`);
|
|
192
194
|
}
|
|
195
|
+
/**
|
|
196
|
+
* Show rule compliance statistics
|
|
197
|
+
*/
|
|
198
|
+
showComplianceStats() {
|
|
199
|
+
const report = this.memoryService.getComplianceReport();
|
|
200
|
+
if (report.rules.length === 0)
|
|
201
|
+
return;
|
|
202
|
+
console.log('\n📋 Rule Compliance:');
|
|
203
|
+
const loaded = report.rules.filter(r => r.load_count > 0);
|
|
204
|
+
const cited = loaded.filter(r => r.cite_count > 0);
|
|
205
|
+
console.log(` Rules loaded at least once: ${loaded.length}`);
|
|
206
|
+
console.log(` Rules cited at least once: ${cited.length}`);
|
|
207
|
+
const neverCited = loaded.filter(r => r.load_count >= 5 && r.cite_count === 0);
|
|
208
|
+
if (neverCited.length > 0) {
|
|
209
|
+
console.log(` ⚠️ Never cited (loaded 5+ times): ${neverCited.length}`);
|
|
210
|
+
neverCited.slice(0, 3).forEach(r => {
|
|
211
|
+
let val;
|
|
212
|
+
try {
|
|
213
|
+
const parsed = JSON.parse(r.value);
|
|
214
|
+
val = parsed?.content || parsed?.value || r.value;
|
|
215
|
+
}
|
|
216
|
+
catch {
|
|
217
|
+
val = r.value;
|
|
218
|
+
}
|
|
219
|
+
console.log(` - "${String(val).substring(0, 60)}..." (loaded ${r.load_count}x)`);
|
|
220
|
+
});
|
|
221
|
+
}
|
|
222
|
+
}
|
|
193
223
|
/**
|
|
194
224
|
* Show failure memories (v0.7.0)
|
|
195
225
|
*/
|
|
@@ -529,7 +559,9 @@ async function main() {
|
|
|
529
559
|
if (fs.existsSync(hooksDir)) {
|
|
530
560
|
const oldHooks = [
|
|
531
561
|
'memory_enforcer.py', // Old v0.8.x hook
|
|
532
|
-
|
|
562
|
+
// search_enforcer.py intentionally NOT listed — it's the current hook.
|
|
563
|
+
// copyFileSync overwrites it during install; deleting first risks leaving
|
|
564
|
+
// it missing if the copy source doesn't resolve (e.g. in the source project).
|
|
533
565
|
'mcp_tool_tracker.py',
|
|
534
566
|
'pubnub_pre_tool_hook.py',
|
|
535
567
|
'pubnub_prompt_hook.py',
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
10
10
|
exports.handleMemoryStop = handleMemoryStop;
|
|
11
11
|
const shared_1 = require("./shared");
|
|
12
|
+
const memory_1 = require("../services/memory");
|
|
12
13
|
const MAX_STORE = 3;
|
|
13
14
|
async function handleMemoryStop(input) {
|
|
14
15
|
const transcriptPath = input?.transcript_path ?? '';
|
|
@@ -60,4 +61,55 @@ async function handleMemoryStop(input) {
|
|
|
60
61
|
(0, shared_1.hookLog)('memory-stop', `Captured ${result.type}: ${result.extract.substring(0, 80)}`);
|
|
61
62
|
}
|
|
62
63
|
(0, shared_1.hookLog)('memory-stop', `Session end: stored ${stored} memories from ${entries.length} entries`);
|
|
64
|
+
// Scan for citations in assistant messages to track compliance
|
|
65
|
+
scanForCitations(transcriptPath);
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Scan transcript for (applied from memory: ...) citations and increment cite_count.
|
|
69
|
+
* Uses a wider window (last 50 entries) since citations appear throughout assistant responses.
|
|
70
|
+
*/
|
|
71
|
+
function scanForCitations(transcriptPath) {
|
|
72
|
+
try {
|
|
73
|
+
const entries = (0, shared_1.readTranscriptTail)(transcriptPath, 50);
|
|
74
|
+
if (entries.length === 0)
|
|
75
|
+
return;
|
|
76
|
+
const citationRegex = /\(applied from memory:\s*(.+?)\)/g;
|
|
77
|
+
const citations = [];
|
|
78
|
+
for (const entry of entries) {
|
|
79
|
+
// Only scan assistant entries (opposite of isUserEntry)
|
|
80
|
+
if ((0, shared_1.isUserEntry)(entry))
|
|
81
|
+
continue;
|
|
82
|
+
const text = (0, shared_1.extractTextFromEntry)(entry);
|
|
83
|
+
if (!text)
|
|
84
|
+
continue;
|
|
85
|
+
let match;
|
|
86
|
+
while ((match = citationRegex.exec(text)) !== null) {
|
|
87
|
+
citations.push(match[1].trim());
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
if (citations.length === 0)
|
|
91
|
+
return;
|
|
92
|
+
(0, shared_1.hookLog)('memory-stop', `Found ${citations.length} citation(s) in transcript`);
|
|
93
|
+
const memoryService = memory_1.MemoryService.getInstance();
|
|
94
|
+
for (const cite of citations) {
|
|
95
|
+
// Search for rules that match this citation text
|
|
96
|
+
const existing = (0, shared_1.searchExisting)(cite.substring(0, 100));
|
|
97
|
+
if (existing.length === 0)
|
|
98
|
+
continue;
|
|
99
|
+
// Fuzzy-match: citations are often paraphrased, so use a looser threshold
|
|
100
|
+
for (const mem of existing) {
|
|
101
|
+
const memContent = typeof mem.value === 'string'
|
|
102
|
+
? mem.value
|
|
103
|
+
: (mem.value?.content || JSON.stringify(mem.value));
|
|
104
|
+
if ((0, shared_1.jaccardSimilarity)(cite, memContent) >= 0.5) {
|
|
105
|
+
memoryService.incrementCiteCount(mem.key);
|
|
106
|
+
(0, shared_1.hookLog)('memory-stop', `Citation matched: "${cite.substring(0, 50)}" → rule ${mem.key}`);
|
|
107
|
+
break; // One match per citation
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
catch (error) {
|
|
113
|
+
(0, shared_1.hookLog)('memory-stop', `Citation scan error: ${error}`);
|
|
114
|
+
}
|
|
63
115
|
}
|
|
@@ -276,6 +276,16 @@ class MemoryTools {
|
|
|
276
276
|
return `- ${val}`;
|
|
277
277
|
}).join('\n'));
|
|
278
278
|
}
|
|
279
|
+
// Add compliance section for rules loaded frequently but never cited
|
|
280
|
+
const compliance = this.memoryService.getComplianceReport(projectId || context.projectId);
|
|
281
|
+
const lowCompliance = compliance.rules.filter(r => r.load_count >= 5 && r.cite_count === 0);
|
|
282
|
+
if (lowCompliance.length > 0) {
|
|
283
|
+
sections.push('## Rule Health\nThese rules are loaded frequently but never cited — consider rewording or removing:\n' +
|
|
284
|
+
lowCompliance.map(r => {
|
|
285
|
+
const val = typeof r.value === 'string' ? (JSON.parse(r.value)?.content || r.value) : r.value;
|
|
286
|
+
return `- "${String(val).substring(0, 80)}" (loaded ${r.load_count}x, cited 0x)`;
|
|
287
|
+
}).join('\n'));
|
|
288
|
+
}
|
|
279
289
|
const totalRules = rules.preferences.length + rules.corrections.length +
|
|
280
290
|
rules.failures.length + rules.devops.length;
|
|
281
291
|
const resultTokens = this.estimateTokens([
|
package/dist/memory/storage.js
CHANGED
|
@@ -103,6 +103,18 @@ class MemoryStorage {
|
|
|
103
103
|
this.db.exec('CREATE INDEX IF NOT EXISTS idx_memories_scope_project ON memories(scope, project_id)');
|
|
104
104
|
console.log('✅ Added scope column');
|
|
105
105
|
}
|
|
106
|
+
// Add load_count if missing (compliance tracking v0.15.14)
|
|
107
|
+
if (!columnNames.includes('load_count')) {
|
|
108
|
+
console.log('📋 Migrating database schema: Adding load_count column...');
|
|
109
|
+
this.db.exec('ALTER TABLE memories ADD COLUMN load_count INTEGER DEFAULT 0');
|
|
110
|
+
console.log('✅ Added load_count column');
|
|
111
|
+
}
|
|
112
|
+
// Add cite_count if missing (compliance tracking v0.15.14)
|
|
113
|
+
if (!columnNames.includes('cite_count')) {
|
|
114
|
+
console.log('📋 Migrating database schema: Adding cite_count column...');
|
|
115
|
+
this.db.exec('ALTER TABLE memories ADD COLUMN cite_count INTEGER DEFAULT 0');
|
|
116
|
+
console.log('✅ Added cite_count column');
|
|
117
|
+
}
|
|
106
118
|
// Add content_hash if missing (content dedup)
|
|
107
119
|
if (!columnNames.includes('content_hash')) {
|
|
108
120
|
console.log('📋 Migrating database schema: Adding content_hash column...');
|
|
@@ -386,6 +398,45 @@ class MemoryStorage {
|
|
|
386
398
|
const rows = stmt.all(...params);
|
|
387
399
|
return rows.map(row => this.rowToMemory(row));
|
|
388
400
|
}
|
|
401
|
+
/**
|
|
402
|
+
* Bulk-increment load_count for a set of memory IDs.
|
|
403
|
+
* Called when rules are loaded via load_rules.
|
|
404
|
+
*/
|
|
405
|
+
incrementLoadCounts(ids) {
|
|
406
|
+
if (ids.length === 0)
|
|
407
|
+
return;
|
|
408
|
+
const placeholders = ids.map(() => '?').join(',');
|
|
409
|
+
this.db.prepare(`UPDATE memories SET load_count = load_count + 1 WHERE id IN (${placeholders})`).run(...ids);
|
|
410
|
+
}
|
|
411
|
+
/**
|
|
412
|
+
* Increment cite_count for a single memory by ID.
|
|
413
|
+
* Called when a citation is detected in the transcript.
|
|
414
|
+
*/
|
|
415
|
+
incrementCiteCount(id) {
|
|
416
|
+
this.db.prepare('UPDATE memories SET cite_count = cite_count + 1 WHERE id = ?').run(id);
|
|
417
|
+
}
|
|
418
|
+
/**
|
|
419
|
+
* Get rules with their compliance metrics (load_count, cite_count).
|
|
420
|
+
* Returns rules that have been loaded at least once.
|
|
421
|
+
*/
|
|
422
|
+
getRulesWithCompliance(projectId) {
|
|
423
|
+
let query = 'SELECT id, key, type, value, load_count, cite_count FROM memories WHERE load_count > 0';
|
|
424
|
+
const params = [];
|
|
425
|
+
if (projectId) {
|
|
426
|
+
query += ' AND (project_id = ? OR scope = ? OR project_id IS NULL)';
|
|
427
|
+
params.push(projectId, 'universal');
|
|
428
|
+
}
|
|
429
|
+
query += ' ORDER BY load_count DESC';
|
|
430
|
+
const rows = this.db.prepare(query).all(...params);
|
|
431
|
+
return rows.map(row => ({
|
|
432
|
+
id: row.id,
|
|
433
|
+
key: row.key,
|
|
434
|
+
type: row.type,
|
|
435
|
+
value: row.value,
|
|
436
|
+
load_count: row.load_count,
|
|
437
|
+
cite_count: row.cite_count
|
|
438
|
+
}));
|
|
439
|
+
}
|
|
389
440
|
getDatabase() {
|
|
390
441
|
return this.db;
|
|
391
442
|
}
|
package/dist/services/memory.js
CHANGED
|
@@ -369,6 +369,13 @@ class MemoryService {
|
|
|
369
369
|
? `Loaded ${counts.join(', ')}`
|
|
370
370
|
: 'No active rules found';
|
|
371
371
|
this.logger.info('MemoryService', summary, { projectId: pid });
|
|
372
|
+
// Increment load_count for all returned rules
|
|
373
|
+
const allIds = [
|
|
374
|
+
...preferences, ...corrections, ...failures, ...devops
|
|
375
|
+
].map(m => m.id).filter((id) => id !== undefined);
|
|
376
|
+
if (allIds.length > 0) {
|
|
377
|
+
this.storage.incrementLoadCounts(allIds);
|
|
378
|
+
}
|
|
372
379
|
return { preferences, corrections, failures, devops, summary };
|
|
373
380
|
}
|
|
374
381
|
catch (error) {
|
|
@@ -376,6 +383,40 @@ class MemoryService {
|
|
|
376
383
|
return { preferences: [], corrections: [], failures: [], devops: [], summary: 'Error loading rules' };
|
|
377
384
|
}
|
|
378
385
|
}
|
|
386
|
+
/**
|
|
387
|
+
* Increment cite_count for a rule matched by key.
|
|
388
|
+
* Called by the citation scanner in the stop hook.
|
|
389
|
+
*/
|
|
390
|
+
incrementCiteCount(key) {
|
|
391
|
+
const memory = this.storage.retrieve(key);
|
|
392
|
+
if (memory?.id) {
|
|
393
|
+
this.storage.incrementCiteCount(memory.id);
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
/**
|
|
397
|
+
* Get compliance report showing load vs cite rates for rules.
|
|
398
|
+
*/
|
|
399
|
+
getComplianceReport(projectId) {
|
|
400
|
+
const pid = projectId || this.config.getProjectId();
|
|
401
|
+
const rules = this.storage.getRulesWithCompliance(pid);
|
|
402
|
+
const loaded = rules.filter(r => r.load_count > 0);
|
|
403
|
+
const cited = loaded.filter(r => r.cite_count > 0);
|
|
404
|
+
const neverCited = loaded.filter(r => r.load_count >= 5 && r.cite_count === 0);
|
|
405
|
+
return {
|
|
406
|
+
rules: rules.map(r => ({
|
|
407
|
+
key: r.key,
|
|
408
|
+
type: r.type,
|
|
409
|
+
value: r.value,
|
|
410
|
+
load_count: r.load_count,
|
|
411
|
+
cite_count: r.cite_count
|
|
412
|
+
})),
|
|
413
|
+
summary: {
|
|
414
|
+
totalLoaded: loaded.length,
|
|
415
|
+
totalCited: cited.length,
|
|
416
|
+
neverCited: neverCited.length
|
|
417
|
+
}
|
|
418
|
+
};
|
|
419
|
+
}
|
|
379
420
|
/**
|
|
380
421
|
* Mark a preference as superseded
|
|
381
422
|
*/
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
# Agentic Reasoning Opportunities for Claude Recall
|
|
2
|
+
|
|
3
|
+
Based on "Agentic Reasoning for Large Language Models" (arXiv:2601.12538) and real-world feedback from consumer projects.
|
|
4
|
+
|
|
5
|
+
## Core Problem
|
|
6
|
+
|
|
7
|
+
Claude loads rules, cites them, and then doesn't follow through. The directive says "MUST output Applying memories:" — Claude sometimes does, sometimes doesn't. Even when it does, it may cite a rule and still skip the action the rule requires. This is the gap between passive memory retrieval and active reasoning with memory.
|
|
8
|
+
|
|
9
|
+
## Key Insight
|
|
10
|
+
|
|
11
|
+
We're treating memory as **passive context injection** when it should be an **active constraint in the execution loop**. The paper's "self-evolving agentic reasoning" dimension — feedback, memory across interactions, adaptive strategies — points to closing the loop mechanically rather than relying on model reasoning.
|
|
12
|
+
|
|
13
|
+
## Opportunities
|
|
14
|
+
|
|
15
|
+
### 1. Executable Pre-Checks on Rules (Highest leverage)
|
|
16
|
+
|
|
17
|
+
Rules currently: prose text Claude reads and may ignore.
|
|
18
|
+
Rules could be: executable commands the system runs automatically before allowing actions.
|
|
19
|
+
|
|
20
|
+
Example — current rule:
|
|
21
|
+
```
|
|
22
|
+
BEFORE making any code changes: always check (1) what branch am I on? (2) is everything committed?
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
Proposed rule with executable pre-check:
|
|
26
|
+
```json
|
|
27
|
+
{
|
|
28
|
+
"content": "Check git state before code changes",
|
|
29
|
+
"type": "devops",
|
|
30
|
+
"pre_check": "git branch --show-current && git status --short",
|
|
31
|
+
"applies_to": ["Write", "Edit"]
|
|
32
|
+
}
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
The hook runs `pre_check` before allowing Write/Edit and injects the output into context. Claude doesn't need to "decide" to follow through — it happens mechanically. Removes the model from the compliance path entirely.
|
|
36
|
+
|
|
37
|
+
### 2. Post-Action Validation Hook
|
|
38
|
+
|
|
39
|
+
Pre-action enforcement exists (load rules before Write/Edit). Post-action checking does not.
|
|
40
|
+
|
|
41
|
+
A PostToolUse hook could compare what Claude did against loaded rules and flag violations: "You wrote a file without running `git branch` first — rule #4 says to check." Claude can't retroactively ignore a visible violation notice.
|
|
42
|
+
|
|
43
|
+
### 3. Compliance Tracking
|
|
44
|
+
|
|
45
|
+
Track which rules get loaded vs. cited vs. actually followed across sessions. Surface patterns:
|
|
46
|
+
- "This rule was loaded 12 times, followed 3 times"
|
|
47
|
+
- "This rule has low compliance. Rewrite it as a concrete command?"
|
|
48
|
+
|
|
49
|
+
Persistent non-compliance signal means either the rule is too vague or needs reformulation as an executable check.
|
|
50
|
+
|
|
51
|
+
### 4. Rule Reformulation Feedback
|
|
52
|
+
|
|
53
|
+
Rules that are consistently not followed might be too abstract. The system could suggest:
|
|
54
|
+
- "This rule has low compliance. Rewrite it as a concrete command?"
|
|
55
|
+
- Turning prose rules into executable pre-checks closes the reasoning gap
|
|
56
|
+
|
|
57
|
+
## Priority Order
|
|
58
|
+
|
|
59
|
+
1. **Executable pre-checks** — Removes model from compliance path. Highest impact, moderate effort.
|
|
60
|
+
2. **Post-action validation** — Catches failures after the fact. Medium impact, medium effort.
|
|
61
|
+
3. **Compliance tracking** — Informs which rules need reformulation. Medium impact, low effort.
|
|
62
|
+
4. **Rule reformulation** — Improves rule quality over time. Lower impact, depends on #3.
|
|
63
|
+
|
|
64
|
+
## Reference
|
|
65
|
+
|
|
66
|
+
- Paper: https://arxiv.org/pdf/2601.12538
|
|
67
|
+
- Feedback source: Consumer project testing (v0.15.9–v0.15.13)
|
|
68
|
+
- Core failure mode: "I understood the rule, acknowledged it, and still took the shortcut" — Claude's own assessment
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# Claude Recall — Expected Behavior (v0.15.13)
|
|
2
|
+
|
|
3
|
+
Use this document to evaluate how well Claude Recall is performing in consumer projects. Paste the assessment prompt below into a conversation and ask Claude to self-evaluate.
|
|
4
|
+
|
|
5
|
+
## Assessment Prompt
|
|
6
|
+
|
|
7
|
+
Evaluate how well Claude Recall is performing against these expectations:
|
|
8
|
+
|
|
9
|
+
**1. Load rules before acting**
|
|
10
|
+
- The `search_enforcer` hook blocks Write/Edit/Bash until `load_rules` is called (1-minute TTL, so it re-fires per task)
|
|
11
|
+
- Expected: Claude calls `load_rules` before its first code action in each task. This should be automatic due to the hook — not something Claude decides to skip.
|
|
12
|
+
|
|
13
|
+
**2. Visible rule application**
|
|
14
|
+
- The `load_rules` response includes a directive requiring Claude to output an "Applying memories:" section before its first action
|
|
15
|
+
- Expected: Before every Write/Edit/Bash, Claude states which loaded rules apply to the current task. If none apply, it says so. This is visible to the user, not internal reasoning.
|
|
16
|
+
|
|
17
|
+
**3. Actual compliance with loaded rules**
|
|
18
|
+
- Expected: If a rule says "check git branch before code changes," Claude actually runs `git branch`. Not just citing the rule and skipping the action. Loading + citing without following through is compliance theater.
|
|
19
|
+
|
|
20
|
+
**4. Inline citations**
|
|
21
|
+
- Expected: When a rule influences an action, Claude adds `(applied from memory: <rule summary>)` inline so the user sees which memories are driving decisions.
|
|
22
|
+
|
|
23
|
+
**5. Auto-capture quality**
|
|
24
|
+
- Hooks auto-capture corrections, preferences from user messages
|
|
25
|
+
- Expected: Only actionable, reusable rules are stored. Conversational fragments, questions, typo-filled casual messages, and Claude's own responses should NOT be stored. If junk gets through, note the content.
|
|
26
|
+
|
|
27
|
+
**6. Store with permission**
|
|
28
|
+
- Expected: Before calling `store_memory`, Claude tells the user what it plans to store and asks for confirmation. No silent storing.
|
|
29
|
+
|
|
30
|
+
**Please assess**: For each of the 6 points above, is it working, partially working, or not working? Provide specific examples from this session.
|
package/docs/installation.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# Installation
|
|
2
2
|
|
|
3
3
|
Claude Recall supports macOS, Linux, Windows, and WSL.
|
|
4
|
-
Memory remains fully local (SQLite)
|
|
4
|
+
Memory remains fully local (SQLite).
|
|
5
5
|
|
|
6
6
|
---
|
|
7
7
|
|
|
@@ -11,7 +11,6 @@ Memory remains fully local (SQLite), while PubNub is used for lightweight realti
|
|
|
11
11
|
|----------|---------|-------|
|
|
12
12
|
| Node.js | **20+** | Required (`better-sqlite3`) |
|
|
13
13
|
| Python | **3.x** | Required for Claude Code hook scripts |
|
|
14
|
-
| PubNub | Included via npm | Used only for metadata events |
|
|
15
14
|
| Claude Code | Latest | Required for MCP integration |
|
|
16
15
|
| OS | macOS / Linux / Windows | WSL supported |
|
|
17
16
|
|
|
@@ -101,8 +100,6 @@ If using Ubuntu under WSL2:
|
|
|
101
100
|
```
|
|
102
101
|
3. Ensure VSCode's terminal is *also* WSL.
|
|
103
102
|
|
|
104
|
-
PubNub works normally under WSL since it uses outbound-only secure connections.
|
|
105
|
-
|
|
106
103
|
---
|
|
107
104
|
|
|
108
105
|
## Node 20 Requirement
|
package/docs/learning-loop.md
CHANGED
|
@@ -7,10 +7,9 @@ Claude Recall uses a 5-phase learning process that improves over time.
|
|
|
7
7
|
## 1. Pre-Action Search
|
|
8
8
|
Before Claude writes or edits a file:
|
|
9
9
|
|
|
10
|
-
- Hook
|
|
11
|
-
-
|
|
12
|
-
-
|
|
13
|
-
- Sends relevant memories back
|
|
10
|
+
- Hook enforces `load_rules` call
|
|
11
|
+
- MCP server searches SQLite memory
|
|
12
|
+
- Returns relevant rules to Claude
|
|
14
13
|
|
|
15
14
|
Claude now knows:
|
|
16
15
|
- your preferences
|
package/docs/project-scoping.md
CHANGED
|
@@ -8,7 +8,6 @@ Claude Recall maintains a registry of all projects using it.
|
|
|
8
8
|
|
|
9
9
|
Each project gets:
|
|
10
10
|
- a unique ID
|
|
11
|
-
- a PubNub presence channel
|
|
12
11
|
- its own memory namespace
|
|
13
12
|
- its own context directory inside `~/.claude-recall/projects`
|
|
14
13
|
|
|
@@ -30,21 +29,6 @@ No user configuration needed.
|
|
|
30
29
|
|
|
31
30
|
---
|
|
32
31
|
|
|
33
|
-
## Presence Channel
|
|
34
|
-
|
|
35
|
-
Memory Agent publishes heartbeats:
|
|
36
|
-
|
|
37
|
-
```
|
|
38
|
-
claude-presence:<projectId>
|
|
39
|
-
```
|
|
40
|
-
|
|
41
|
-
Used to ensure:
|
|
42
|
-
- at most one Agent per project
|
|
43
|
-
- consistent event processing
|
|
44
|
-
- clean agent shutdown
|
|
45
|
-
|
|
46
|
-
---
|
|
47
|
-
|
|
48
32
|
## Monorepos
|
|
49
33
|
|
|
50
34
|
Claude Recall:
|
|
@@ -57,7 +41,6 @@ Claude Recall:
|
|
|
57
41
|
## Multi-Project Workflows
|
|
58
42
|
|
|
59
43
|
Switching projects inside VSCode triggers:
|
|
60
|
-
- PubNub presence update
|
|
61
44
|
- registry notification
|
|
62
45
|
- memory namespace change
|
|
63
46
|
|
package/docs/quickstart.md
CHANGED
package/docs/security.md
CHANGED
|
@@ -9,7 +9,7 @@ Claude Recall is designed to be **local-first** and **privacy-safe**.
|
|
|
9
9
|
All memory is stored in:
|
|
10
10
|
|
|
11
11
|
```
|
|
12
|
-
~/.claude-recall/
|
|
12
|
+
~/.claude-recall/claude-recall.db
|
|
13
13
|
```
|
|
14
14
|
|
|
15
15
|
Properties:
|
|
@@ -21,42 +21,12 @@ Properties:
|
|
|
21
21
|
|
|
22
22
|
---
|
|
23
23
|
|
|
24
|
-
# PubNub Security Model
|
|
25
|
-
|
|
26
|
-
PubNub is used only as a **realtime metadata bus**.
|
|
27
|
-
|
|
28
|
-
### What PubNub does NOT transmit:
|
|
29
|
-
- code
|
|
30
|
-
- text
|
|
31
|
-
- file contents
|
|
32
|
-
- prompts
|
|
33
|
-
- memory content
|
|
34
|
-
- embeddings
|
|
35
|
-
- project data
|
|
36
|
-
|
|
37
|
-
### What PubNub DOES transmit:
|
|
38
|
-
- tool names
|
|
39
|
-
- file paths
|
|
40
|
-
- event types
|
|
41
|
-
- token counts
|
|
42
|
-
- memory suggestion IDs
|
|
43
|
-
- agent heartbeat metadata
|
|
44
|
-
|
|
45
|
-
### Why this is safe:
|
|
46
|
-
- payloads are small
|
|
47
|
-
- no sensitive content ever leaves machine
|
|
48
|
-
- events are ephemeral
|
|
49
|
-
- keys stored locally only
|
|
50
|
-
- no cloud persistence
|
|
51
|
-
|
|
52
|
-
---
|
|
53
|
-
|
|
54
24
|
# SQLite Security
|
|
55
25
|
|
|
56
26
|
- file-based permissions
|
|
57
27
|
- no remote access
|
|
58
28
|
- ACID-compliant
|
|
59
|
-
-
|
|
29
|
+
- WAL mode for concurrency
|
|
60
30
|
|
|
61
31
|
---
|
|
62
32
|
|
|
@@ -67,8 +37,8 @@ You can:
|
|
|
67
37
|
- inspect memory
|
|
68
38
|
- delete memory
|
|
69
39
|
- purge memory
|
|
70
|
-
- view
|
|
71
|
-
- stop the
|
|
40
|
+
- view compliance stats
|
|
41
|
+
- stop the MCP server
|
|
72
42
|
|
|
73
43
|
Claude Recall provides full visibility.
|
|
74
44
|
|
package/docs/troubleshooting.md
CHANGED
|
@@ -26,43 +26,23 @@ nvm install 20
|
|
|
26
26
|
|
|
27
27
|
---
|
|
28
28
|
|
|
29
|
-
## PubNub connection errors
|
|
30
|
-
|
|
31
|
-
Symptoms:
|
|
32
|
-
|
|
33
|
-
* Agent not receiving events
|
|
34
|
-
* No memory suggestions
|
|
35
|
-
* watch mode shows nothing
|
|
36
|
-
|
|
37
|
-
Solution:
|
|
38
|
-
|
|
39
|
-
* restart clamps
|
|
40
|
-
* check network firewall
|
|
41
|
-
* ensure project-specific keys exist in `~/.claude-recall/keys.json`
|
|
42
|
-
|
|
43
|
-
PubNub uses outbound-only connections.
|
|
44
|
-
|
|
45
|
-
---
|
|
46
|
-
|
|
47
29
|
## Memory not updating
|
|
48
30
|
|
|
49
|
-
|
|
31
|
+
Check MCP server status:
|
|
50
32
|
|
|
51
33
|
```bash
|
|
52
|
-
npx claude-recall
|
|
53
|
-
```
|
|
54
|
-
|
|
55
|
-
Look for:
|
|
56
|
-
|
|
57
|
-
```
|
|
58
|
-
[agent] heartbeat ok
|
|
34
|
+
npx claude-recall mcp status
|
|
59
35
|
```
|
|
60
36
|
|
|
61
37
|
---
|
|
62
38
|
|
|
63
39
|
## Hooks not firing
|
|
64
40
|
|
|
65
|
-
Check `.claude/hooks/` directory exists.
|
|
41
|
+
Check `.claude/hooks/` directory exists and `search_enforcer.py` is present:
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
npx claude-recall hooks check
|
|
45
|
+
```
|
|
66
46
|
|
|
67
47
|
---
|
|
68
48
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "claude-recall",
|
|
3
|
-
"version": "0.15.
|
|
3
|
+
"version": "0.15.16",
|
|
4
4
|
"description": "Persistent memory for Claude Code with native Skills integration, automatic capture, failure learning, and project scoping via MCP server",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"bin": {
|
|
@@ -88,6 +88,7 @@
|
|
|
88
88
|
"@anthropic-ai/sdk": "^0.39.0",
|
|
89
89
|
"better-sqlite3": "^12.2.0",
|
|
90
90
|
"chalk": "^5.5.0",
|
|
91
|
+
"claude-recall": "^0.15.15",
|
|
91
92
|
"commander": "^12.1.0"
|
|
92
93
|
}
|
|
93
94
|
}
|