claude-recall 0.7.6 → 0.7.9
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/pre_tool_search_enforcer.py +177 -0
- package/.claude/hooks/user_prompt_capture.py +71 -0
- package/.claude/settings.local.json +5 -1
- package/README.md +130 -45
- package/dist/cli/claude-recall-cli.js +3 -0
- package/dist/cli/commands/hook-commands.js +99 -0
- package/dist/services/queue-api.js +40 -34
- package/package.json +2 -2
- package/scripts/postinstall.js +81 -0
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Pre-tool hook to enforce memory search before file operations.
|
|
4
|
+
Ensures Phase 1 (Pre-Action) of the learning loop always happens.
|
|
5
|
+
|
|
6
|
+
This hook intercepts tool calls and blocks execution if memory search
|
|
7
|
+
wasn't performed first. It's part of Claude Recall's learning loop:
|
|
8
|
+
|
|
9
|
+
Phase 1: Search memories BEFORE task (enforced by this hook)
|
|
10
|
+
Phase 2: Execute with found context
|
|
11
|
+
Phase 3: Capture outcome after task
|
|
12
|
+
|
|
13
|
+
Exit codes:
|
|
14
|
+
- 0: Allow execution (search was performed)
|
|
15
|
+
- 2: Block execution (search required)
|
|
16
|
+
"""
|
|
17
|
+
import json
|
|
18
|
+
import sys
|
|
19
|
+
import subprocess
|
|
20
|
+
from typing import Dict, Any, List
|
|
21
|
+
|
|
22
|
+
# Tools that require memory search before execution
|
|
23
|
+
SEARCH_REQUIRED_TOOLS = ['Write', 'Edit']
|
|
24
|
+
|
|
25
|
+
# Tools that benefit from search but aren't strictly required
|
|
26
|
+
SEARCH_RECOMMENDED_TOOLS = ['Read', 'Bash']
|
|
27
|
+
|
|
28
|
+
# Keywords that indicate dangerous/important operations
|
|
29
|
+
CRITICAL_BASH_KEYWORDS = ['rm', 'git', 'npm', 'build', 'test', 'deploy', 'publish']
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def should_require_search(tool_name: str, tool_input: Dict[str, Any]) -> bool:
|
|
33
|
+
"""Determine if this tool call requires a memory search first."""
|
|
34
|
+
# Always require search for file creation/modification
|
|
35
|
+
if tool_name in SEARCH_REQUIRED_TOOLS:
|
|
36
|
+
return True
|
|
37
|
+
|
|
38
|
+
# Require search for critical bash commands
|
|
39
|
+
if tool_name == 'Bash':
|
|
40
|
+
command = tool_input.get('command', '')
|
|
41
|
+
if any(keyword in command.lower() for keyword in CRITICAL_BASH_KEYWORDS):
|
|
42
|
+
return True
|
|
43
|
+
|
|
44
|
+
return False
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def check_recent_search(session_id: str) -> bool:
|
|
48
|
+
"""Check if memory search was performed recently in this session."""
|
|
49
|
+
if not session_id:
|
|
50
|
+
# No session ID, be permissive
|
|
51
|
+
return True
|
|
52
|
+
|
|
53
|
+
try:
|
|
54
|
+
# Call claude-recall CLI to check recent tool usage
|
|
55
|
+
result = subprocess.run(
|
|
56
|
+
['claude-recall', 'recent-tools', '--session', session_id, '--limit', '5'],
|
|
57
|
+
capture_output=True,
|
|
58
|
+
text=True,
|
|
59
|
+
timeout=2
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
if result.returncode != 0:
|
|
63
|
+
# Command failed, be permissive (don't block)
|
|
64
|
+
return True
|
|
65
|
+
|
|
66
|
+
# Check if mcp__claude-recall__search was called recently
|
|
67
|
+
return 'mcp__claude-recall__search' in result.stdout
|
|
68
|
+
|
|
69
|
+
except (subprocess.TimeoutExpired, FileNotFoundError, Exception):
|
|
70
|
+
# If check fails, be permissive (don't block)
|
|
71
|
+
return True
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def generate_search_query(tool_name: str, tool_input: Dict[str, Any]) -> str:
|
|
75
|
+
"""Generate suggested search query based on tool and input."""
|
|
76
|
+
queries: List[str] = []
|
|
77
|
+
|
|
78
|
+
# Extract file path if present
|
|
79
|
+
file_path = tool_input.get('file_path', '')
|
|
80
|
+
if file_path:
|
|
81
|
+
# Extract relevant keywords from path
|
|
82
|
+
parts = file_path.split('/')
|
|
83
|
+
filename = parts[-1].split('.')[0] if parts else ''
|
|
84
|
+
if filename and filename not in ['.', '..']:
|
|
85
|
+
queries.append(filename)
|
|
86
|
+
|
|
87
|
+
# Add file extension for language-specific preferences
|
|
88
|
+
if '.' in file_path:
|
|
89
|
+
ext = file_path.split('.')[-1]
|
|
90
|
+
queries.append(ext)
|
|
91
|
+
|
|
92
|
+
# Add tool-specific keywords
|
|
93
|
+
if tool_name == 'Write':
|
|
94
|
+
queries.extend(['create', 'new file'])
|
|
95
|
+
elif tool_name == 'Edit':
|
|
96
|
+
queries.extend(['update', 'modify'])
|
|
97
|
+
elif tool_name == 'Bash':
|
|
98
|
+
command = tool_input.get('command', '')
|
|
99
|
+
if 'git' in command:
|
|
100
|
+
queries.append('git')
|
|
101
|
+
if 'npm' in command or 'yarn' in command:
|
|
102
|
+
queries.append('build')
|
|
103
|
+
if 'test' in command:
|
|
104
|
+
queries.append('testing')
|
|
105
|
+
|
|
106
|
+
# Always include learning loop keywords
|
|
107
|
+
queries.extend(['preferences', 'success', 'failure', 'correction'])
|
|
108
|
+
|
|
109
|
+
# Deduplicate and limit to 8 keywords
|
|
110
|
+
seen = set()
|
|
111
|
+
unique_queries = []
|
|
112
|
+
for q in queries:
|
|
113
|
+
if q.lower() not in seen:
|
|
114
|
+
seen.add(q.lower())
|
|
115
|
+
unique_queries.append(q)
|
|
116
|
+
|
|
117
|
+
return ' '.join(unique_queries[:8])
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
def main():
|
|
121
|
+
# Read JSON from stdin
|
|
122
|
+
try:
|
|
123
|
+
hook_data = json.load(sys.stdin)
|
|
124
|
+
except json.JSONDecodeError as e:
|
|
125
|
+
# If we can't parse input, be permissive
|
|
126
|
+
print(f"Warning: Failed to parse hook input: {e}", file=sys.stderr)
|
|
127
|
+
sys.exit(0)
|
|
128
|
+
|
|
129
|
+
tool_name = hook_data.get('tool_name', '')
|
|
130
|
+
tool_input = hook_data.get('tool_input', {})
|
|
131
|
+
session_id = hook_data.get('session_id', '')
|
|
132
|
+
|
|
133
|
+
# Check if this tool requires a memory search
|
|
134
|
+
if not should_require_search(tool_name, tool_input):
|
|
135
|
+
sys.exit(0) # Allow execution
|
|
136
|
+
|
|
137
|
+
# Check if search was performed recently
|
|
138
|
+
if check_recent_search(session_id):
|
|
139
|
+
sys.exit(0) # Search was done, allow execution
|
|
140
|
+
|
|
141
|
+
# BLOCK: Search not performed
|
|
142
|
+
search_query = generate_search_query(tool_name, tool_input)
|
|
143
|
+
|
|
144
|
+
error_msg = f"""
|
|
145
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
146
|
+
🔍 MEMORY SEARCH REQUIRED (Phase 1 - Pre-Action)
|
|
147
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
148
|
+
|
|
149
|
+
Before executing {tool_name}, please search memories first:
|
|
150
|
+
|
|
151
|
+
mcp__claude-recall__search("{search_query}")
|
|
152
|
+
|
|
153
|
+
WHY THIS MATTERS:
|
|
154
|
+
✓ User preferences (coding style, tools, conventions)
|
|
155
|
+
✓ Past successes (what worked before)
|
|
156
|
+
✓ Past failures (what to avoid)
|
|
157
|
+
✓ Recent corrections (highest priority!)
|
|
158
|
+
|
|
159
|
+
THE LEARNING LOOP:
|
|
160
|
+
1. Search memories BEFORE task ← YOU ARE HERE (Phase 1)
|
|
161
|
+
2. Execute with found context (Phase 2)
|
|
162
|
+
3. Capture outcome after task (Phase 3)
|
|
163
|
+
|
|
164
|
+
This ensures you never repeat yourself and apply learned patterns automatically.
|
|
165
|
+
|
|
166
|
+
Suggested search query: "{search_query}"
|
|
167
|
+
|
|
168
|
+
To disable this check, remove the PreToolUse hook from .claude/settings.json
|
|
169
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
170
|
+
"""
|
|
171
|
+
|
|
172
|
+
print(error_msg.strip(), file=sys.stderr)
|
|
173
|
+
sys.exit(2) # Block tool execution
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
if __name__ == '__main__':
|
|
177
|
+
main()
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
User prompt capture hook for Claude Recall.
|
|
4
|
+
Captures user prompts and passes them to the MCP server for processing.
|
|
5
|
+
|
|
6
|
+
This hook is part of Phase 3 (Post-Action) of the learning loop:
|
|
7
|
+
- Captures user prompts for preference extraction
|
|
8
|
+
- Logs prompt submission events
|
|
9
|
+
- Enables pattern detection and learning
|
|
10
|
+
|
|
11
|
+
Exit codes:
|
|
12
|
+
- 0: Always allow (this hook is non-blocking)
|
|
13
|
+
"""
|
|
14
|
+
import json
|
|
15
|
+
import sys
|
|
16
|
+
import subprocess
|
|
17
|
+
from typing import Dict, Any
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def capture_prompt(hook_data: Dict[str, Any]) -> None:
|
|
21
|
+
"""Send prompt data to Claude Recall for processing."""
|
|
22
|
+
try:
|
|
23
|
+
# Extract prompt content
|
|
24
|
+
# The structure varies based on Claude Code version
|
|
25
|
+
prompt_content = (
|
|
26
|
+
hook_data.get('prompt', '') or
|
|
27
|
+
hook_data.get('content', '') or
|
|
28
|
+
hook_data.get('message', '')
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
if not prompt_content:
|
|
32
|
+
return
|
|
33
|
+
|
|
34
|
+
session_id = hook_data.get('session_id', '')
|
|
35
|
+
|
|
36
|
+
# Store prompt metadata via CLI
|
|
37
|
+
# This will be picked up by the MCP server's hook event processor
|
|
38
|
+
subprocess.run(
|
|
39
|
+
[
|
|
40
|
+
'claude-recall',
|
|
41
|
+
'capture-prompt',
|
|
42
|
+
'--session', session_id,
|
|
43
|
+
'--content', prompt_content
|
|
44
|
+
],
|
|
45
|
+
capture_output=True,
|
|
46
|
+
text=True,
|
|
47
|
+
timeout=2
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
except Exception as e:
|
|
51
|
+
# Don't block on errors - this is best-effort capture
|
|
52
|
+
print(f"Warning: Prompt capture failed: {e}", file=sys.stderr)
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def main():
|
|
56
|
+
# Read JSON from stdin
|
|
57
|
+
try:
|
|
58
|
+
hook_data = json.load(sys.stdin)
|
|
59
|
+
except json.JSONDecodeError as e:
|
|
60
|
+
print(f"Warning: Failed to parse hook input: {e}", file=sys.stderr)
|
|
61
|
+
sys.exit(0) # Always allow
|
|
62
|
+
|
|
63
|
+
# Capture the prompt (non-blocking)
|
|
64
|
+
capture_prompt(hook_data)
|
|
65
|
+
|
|
66
|
+
# Always allow prompt to proceed
|
|
67
|
+
sys.exit(0)
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
if __name__ == '__main__':
|
|
71
|
+
main()
|
package/README.md
CHANGED
|
@@ -20,7 +20,7 @@ Every time you start a new conversation with Claude, you're starting from scratc
|
|
|
20
20
|
- **Categorized storage** - Organizes memories by type (preferences, project knowledge, corrections)
|
|
21
21
|
- **Priority-based** - More important or frequently used memories are prioritized
|
|
22
22
|
|
|
23
|
-
### ⚡ Advanced Features
|
|
23
|
+
### ⚡ Advanced Features
|
|
24
24
|
- **Direct MCP calls** - Fast memory search without agent overhead (milliseconds)
|
|
25
25
|
- **Comprehensive search** - Single search finds preferences, successes, failures, and corrections
|
|
26
26
|
- **Outcome capture** - Stores what worked and what didn't for future learning
|
|
@@ -28,7 +28,7 @@ Every time you start a new conversation with Claude, you're starting from scratc
|
|
|
28
28
|
- **Optional agent** - Advanced context-manager agent available for complex workflows
|
|
29
29
|
- **Correction priority** - User corrections given highest priority (never repeat mistakes)
|
|
30
30
|
|
|
31
|
-
### 🧠 Intelligence & Evolution
|
|
31
|
+
### 🧠 Intelligence & Evolution
|
|
32
32
|
- **Sophistication tracking** - Measures agent progression from basic tool use to compositional reasoning
|
|
33
33
|
- **Failure learning** - Captures what failed, why it failed, and what should be done instead (counterfactual reasoning)
|
|
34
34
|
- **Evolution metrics** - View progression score, confidence trends, and failure rates over time
|
|
@@ -61,7 +61,7 @@ npm install claude-recall
|
|
|
61
61
|
|
|
62
62
|
**After installation, verify:**
|
|
63
63
|
```bash
|
|
64
|
-
npx claude-recall --version # Should show
|
|
64
|
+
npx claude-recall --version # Should show installed version
|
|
65
65
|
```
|
|
66
66
|
|
|
67
67
|
### Why Local Over Global?
|
|
@@ -96,22 +96,38 @@ Claude Recall works on **Windows, Linux, and macOS**. Native binaries (SQLite) c
|
|
|
96
96
|
|
|
97
97
|
**WSL Users - Special Case:**
|
|
98
98
|
|
|
99
|
-
If you use **both Windows and WSL** for the same project (e.g., Electron app runs on Windows, but
|
|
99
|
+
If you use **both Windows and WSL** for the same project (e.g., Electron app runs on Windows, but you use WSL):
|
|
100
100
|
|
|
101
101
|
**Problem**: Installing locally creates Windows binaries, but WSL needs Linux binaries → "invalid ELF header" errors.
|
|
102
102
|
|
|
103
|
-
**Solution**:
|
|
103
|
+
**Solution**: Use WSL-specific local installation:
|
|
104
104
|
```bash
|
|
105
|
-
# From WSL:
|
|
106
|
-
|
|
105
|
+
# From WSL, in your project directory:
|
|
106
|
+
cd /path/to/your-project
|
|
107
|
+
npm install claude-recall
|
|
107
108
|
|
|
108
|
-
#
|
|
109
|
-
claude
|
|
109
|
+
# This installs Linux binaries AND hook scripts
|
|
110
|
+
# MCP server configured in ~/.claude.json works for all projects
|
|
110
111
|
```
|
|
111
112
|
|
|
112
|
-
**
|
|
113
|
+
**Why this works**:
|
|
114
|
+
- WSL gets Linux binaries (no ELF errors)
|
|
115
|
+
- Hooks installed to project's `.claude/hooks/` (automatic memory search enforcement works)
|
|
116
|
+
- MCP server in `~/.claude.json` is global (works everywhere)
|
|
117
|
+
- Memory database `~/.claude-recall/` is global (shared across projects)
|
|
118
|
+
|
|
119
|
+
**Alternative (NOT recommended)**: If you absolutely need global installation:
|
|
120
|
+
```bash
|
|
121
|
+
npm install -g claude-recall
|
|
122
|
+
|
|
123
|
+
# ⚠️ WARNING: You MUST manually install hooks to each project:
|
|
124
|
+
cd your-project
|
|
125
|
+
mkdir -p .claude/hooks
|
|
126
|
+
cp $(npm root -g)/claude-recall/.claude/hooks/* .claude/hooks/
|
|
127
|
+
cp $(npm root -g)/claude-recall/.claude/settings.json .claude/settings.json
|
|
128
|
+
```
|
|
113
129
|
|
|
114
|
-
|
|
130
|
+
This manual approach loses automatic setup and team sharing via `package.json`.
|
|
115
131
|
|
|
116
132
|
## Updating Claude Recall
|
|
117
133
|
|
|
@@ -136,50 +152,119 @@ npm install -g claude-recall@latest
|
|
|
136
152
|
|
|
137
153
|
**Note:** MCP server configuration in `~/.claude.json` persists across updates. You only need to update the package.
|
|
138
154
|
|
|
139
|
-
###
|
|
155
|
+
### Database Migration
|
|
140
156
|
|
|
141
|
-
|
|
157
|
+
Claude Recall automatically migrates your database schema when updating. See [CHANGELOG.md](CHANGELOG.md) for version-specific migration notes if upgrading from older versions.
|
|
142
158
|
|
|
143
|
-
|
|
159
|
+
### Automatic Memory Search Enforcement
|
|
144
160
|
|
|
145
|
-
**
|
|
146
|
-
|
|
147
|
-
|
|
161
|
+
**How It Works:**
|
|
162
|
+
|
|
163
|
+
Claude Recall uses **Claude Code hooks** to automatically enforce the learning loop workflow. This ensures memories are searched BEFORE executing file operations (Phase 1 - Pre-Action).
|
|
148
164
|
|
|
149
|
-
|
|
165
|
+
**The Learning Loop:**
|
|
150
166
|
```
|
|
151
|
-
|
|
152
|
-
|
|
167
|
+
Phase 1: Search memories BEFORE task ← Enforced by PreToolUse hook
|
|
168
|
+
Phase 2: Execute with found context
|
|
169
|
+
Phase 3: Capture outcome after task ← Handled by UserPromptSubmit hook
|
|
153
170
|
```
|
|
154
171
|
|
|
155
|
-
**
|
|
172
|
+
**How It Works:**
|
|
156
173
|
|
|
157
|
-
|
|
174
|
+
When you `npm install claude-recall`, the installer automatically:
|
|
175
|
+
1. Creates `.claude/hooks/` directory in your project
|
|
176
|
+
2. Installs two Python hook scripts:
|
|
177
|
+
- `pre_tool_search_enforcer.py` - Blocks Write/Edit until memory search is performed
|
|
178
|
+
- `user_prompt_capture.py` - Captures prompts for preference extraction
|
|
179
|
+
3. Configures `.claude/settings.json` with hook bindings
|
|
158
180
|
|
|
159
|
-
|
|
160
|
-
# Check current schema and migrate if needed
|
|
161
|
-
npx claude-recall migrate schema
|
|
181
|
+
**Hook Behavior:**
|
|
162
182
|
|
|
163
|
-
|
|
164
|
-
npx claude-recall migrate schema --backup
|
|
183
|
+
When Claude Code tries to create or edit files without searching memories first:
|
|
165
184
|
```
|
|
185
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
186
|
+
🔍 MEMORY SEARCH REQUIRED (Phase 1 - Pre-Action)
|
|
187
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
166
188
|
|
|
167
|
-
|
|
189
|
+
Before executing Write, please search memories first:
|
|
168
190
|
|
|
169
|
-
|
|
170
|
-
- `"no such column: scope"`
|
|
171
|
-
- `"no such column: sophistication_level"`
|
|
191
|
+
mcp__claude-recall__search("filename create preferences success failure correction")
|
|
172
192
|
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
193
|
+
WHY THIS MATTERS:
|
|
194
|
+
✓ User preferences (coding style, tools, conventions)
|
|
195
|
+
✓ Past successes (what worked before)
|
|
196
|
+
✓ Past failures (what to avoid)
|
|
197
|
+
✓ Recent corrections (highest priority!)
|
|
198
|
+
|
|
199
|
+
Suggested search query: "filename create preferences success failure correction"
|
|
200
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
The tool execution is **blocked** until Claude Code performs the search.
|
|
204
|
+
|
|
205
|
+
**Benefits:**
|
|
206
|
+
- ✅ **Enforces Phase 1** - Ensures memories are searched before actions
|
|
207
|
+
- ✅ **Prevents repetition** - User never has to repeat preferences
|
|
208
|
+
- ✅ **Educational** - Teaches Claude Code the learning loop
|
|
209
|
+
- ✅ **Smart blocking** - Only blocks file creation/modification, not reads
|
|
210
|
+
- ✅ **Session-aware** - One search covers multiple subsequent operations
|
|
211
|
+
|
|
212
|
+
**Configuration:**
|
|
213
|
+
|
|
214
|
+
Hooks are configured in `.claude/settings.json`:
|
|
215
|
+
|
|
216
|
+
```json
|
|
217
|
+
{
|
|
218
|
+
"hooks": {
|
|
219
|
+
"PreToolUse": [
|
|
220
|
+
{
|
|
221
|
+
"matcher": "Write|Edit",
|
|
222
|
+
"hooks": [
|
|
223
|
+
{
|
|
224
|
+
"type": "command",
|
|
225
|
+
"command": "python3 .claude/hooks/pre_tool_search_enforcer.py"
|
|
226
|
+
}
|
|
227
|
+
]
|
|
228
|
+
}
|
|
229
|
+
],
|
|
230
|
+
"UserPromptSubmit": [
|
|
231
|
+
{
|
|
232
|
+
"hooks": [
|
|
233
|
+
{
|
|
234
|
+
"type": "command",
|
|
235
|
+
"command": "python3 .claude/hooks/user_prompt_capture.py"
|
|
236
|
+
}
|
|
237
|
+
]
|
|
238
|
+
}
|
|
239
|
+
]
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
**Disabling Hooks:**
|
|
245
|
+
|
|
246
|
+
If you want to disable enforcement (not recommended):
|
|
247
|
+
|
|
248
|
+
```json
|
|
249
|
+
{
|
|
250
|
+
"hooks": {}
|
|
251
|
+
}
|
|
176
252
|
```
|
|
177
253
|
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
3
|
|
182
|
-
|
|
254
|
+
Or remove specific hooks from the `hooks` object.
|
|
255
|
+
|
|
256
|
+
**Requirements:**
|
|
257
|
+
- Python 3 must be available (pre-installed on most systems)
|
|
258
|
+
- Hook scripts are installed automatically during `npm install`
|
|
259
|
+
- Works with Claude Code CLI hooks system
|
|
260
|
+
|
|
261
|
+
**Troubleshooting:**
|
|
262
|
+
|
|
263
|
+
If hooks aren't working:
|
|
264
|
+
1. Check Python is available: `python3 --version`
|
|
265
|
+
2. Verify hooks are installed: `ls .claude/hooks/`
|
|
266
|
+
3. Check configuration: `cat .claude/settings.json`
|
|
267
|
+
4. Reinstall: `npm install claude-recall --force`
|
|
183
268
|
|
|
184
269
|
## Verifying Claude Recall is Working
|
|
185
270
|
|
|
@@ -263,7 +348,7 @@ You: "No, put scripts in scripts/ directory not root"
|
|
|
263
348
|
Next time: Search finds correction and applies automatically
|
|
264
349
|
```
|
|
265
350
|
|
|
266
|
-
## Claude Code Skills Integration
|
|
351
|
+
## Claude Code Skills Integration
|
|
267
352
|
|
|
268
353
|
Claude Recall now integrates with **Claude Code Skills** for better LLM compliance and automatic memory management.
|
|
269
354
|
|
|
@@ -457,7 +542,7 @@ npx claude-recall store "I prefer TypeScript with strict mode"
|
|
|
457
542
|
npx claude-recall store "Use PostgreSQL" --type project-knowledge
|
|
458
543
|
```
|
|
459
544
|
|
|
460
|
-
### Intelligence & Evolution
|
|
545
|
+
### Intelligence & Evolution
|
|
461
546
|
|
|
462
547
|
**View memory evolution and sophistication metrics:**
|
|
463
548
|
```bash
|
|
@@ -536,7 +621,7 @@ npx claude-recall clear --force # Clear everything (requires --f
|
|
|
536
621
|
**What are MCP commands?**
|
|
537
622
|
The MCP (Model Context Protocol) server is how Claude Code communicates with Claude Recall. When you install claude-recall, it automatically configures `~/.claude.json` to start the MCP server.
|
|
538
623
|
|
|
539
|
-
**Process Management
|
|
624
|
+
**Process Management:**
|
|
540
625
|
```bash
|
|
541
626
|
# Start/stop
|
|
542
627
|
npx claude-recall mcp start # Start MCP server (auto-started by Claude Code)
|
|
@@ -576,7 +661,7 @@ npx claude-recall test-memory-search # Test if Claude searches before
|
|
|
576
661
|
|
|
577
662
|
**Migration:**
|
|
578
663
|
```bash
|
|
579
|
-
# Database schema migration
|
|
664
|
+
# Database schema migration
|
|
580
665
|
npx claude-recall migrate schema # Migrate database schema (automatic)
|
|
581
666
|
npx claude-recall migrate schema --backup # Create backup before migration
|
|
582
667
|
|
|
@@ -591,7 +676,7 @@ All commands support:
|
|
|
591
676
|
- `--config <path>` - Use custom config file
|
|
592
677
|
- `-h, --help` - Show help for any command
|
|
593
678
|
|
|
594
|
-
## Project Management
|
|
679
|
+
## Project Management
|
|
595
680
|
|
|
596
681
|
Claude Recall maintains a **project registry** to track all projects using it, enabling better organization and visibility across multiple projects.
|
|
597
682
|
|
|
@@ -683,7 +768,7 @@ Format:
|
|
|
683
768
|
|
|
684
769
|
**Note**: The registry is separate from your memory database. Cleaning the registry does NOT affect your stored memories.
|
|
685
770
|
|
|
686
|
-
## Project Scoping
|
|
771
|
+
## Project Scoping
|
|
687
772
|
|
|
688
773
|
Claude Recall now supports **project-specific memory isolation** while keeping universal preferences available everywhere.
|
|
689
774
|
|
|
@@ -49,6 +49,7 @@ const queue_integration_1 = require("../services/queue-integration");
|
|
|
49
49
|
const memory_evolution_1 = require("../services/memory-evolution");
|
|
50
50
|
const mcp_commands_1 = require("./commands/mcp-commands");
|
|
51
51
|
const project_commands_1 = require("./commands/project-commands");
|
|
52
|
+
const hook_commands_1 = require("./commands/hook-commands");
|
|
52
53
|
const program = new commander_1.Command();
|
|
53
54
|
class ClaudeRecallCLI {
|
|
54
55
|
constructor(options) {
|
|
@@ -617,6 +618,8 @@ async function main() {
|
|
|
617
618
|
project_commands_1.ProjectCommands.register(program);
|
|
618
619
|
// Migration commands
|
|
619
620
|
migrate_1.MigrateCommand.register(program);
|
|
621
|
+
// Hook commands (used by Claude Code hooks)
|
|
622
|
+
hook_commands_1.HookCommands.register(program);
|
|
620
623
|
// Register live test command
|
|
621
624
|
new live_test_1.LiveTestCommand().register(program);
|
|
622
625
|
// Search command
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.HookCommands = void 0;
|
|
4
|
+
const memory_1 = require("../../services/memory");
|
|
5
|
+
/**
|
|
6
|
+
* Hook-related CLI commands for Claude Code integration
|
|
7
|
+
* These commands are called by hook scripts to interact with Claude Recall
|
|
8
|
+
*/
|
|
9
|
+
class HookCommands {
|
|
10
|
+
static register(program) {
|
|
11
|
+
const hookCmd = program
|
|
12
|
+
.command('recent-tools')
|
|
13
|
+
.description('Get recent tool usage for session (used by PreToolUse hook)')
|
|
14
|
+
.option('-s, --session <sessionId>', 'Session ID to check')
|
|
15
|
+
.option('-l, --limit <number>', 'Maximum number of recent tools to return', '10')
|
|
16
|
+
.action(async (options) => {
|
|
17
|
+
await HookCommands.recentTools(options.session, parseInt(options.limit));
|
|
18
|
+
});
|
|
19
|
+
program
|
|
20
|
+
.command('capture-prompt')
|
|
21
|
+
.description('Capture user prompt for processing (used by UserPromptSubmit hook)')
|
|
22
|
+
.option('-s, --session <sessionId>', 'Session ID')
|
|
23
|
+
.option('-c, --content <content>', 'Prompt content')
|
|
24
|
+
.action(async (options) => {
|
|
25
|
+
await HookCommands.capturePrompt(options.session, options.content);
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Get recent tool usage for a session
|
|
30
|
+
* Used by PreToolUse hook to check if memory search was performed
|
|
31
|
+
*/
|
|
32
|
+
static async recentTools(sessionId, limit) {
|
|
33
|
+
try {
|
|
34
|
+
if (!sessionId) {
|
|
35
|
+
console.log(JSON.stringify({ error: 'Session ID required' }));
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
const memoryService = memory_1.MemoryService.getInstance();
|
|
39
|
+
// Search for recent tool-use memories in this session
|
|
40
|
+
const memories = memoryService.search(`session:${sessionId} tool-use`);
|
|
41
|
+
// Get the most recent entries
|
|
42
|
+
const recentTools = memories
|
|
43
|
+
.filter(m => m.type === 'tool-use')
|
|
44
|
+
.sort((a, b) => (b.timestamp || 0) - (a.timestamp || 0))
|
|
45
|
+
.slice(0, limit)
|
|
46
|
+
.map(m => ({
|
|
47
|
+
tool: m.key,
|
|
48
|
+
timestamp: m.timestamp,
|
|
49
|
+
// Include mcp__claude-recall__search in the key for detection
|
|
50
|
+
toolName: m.key.split(':')[0] || m.key
|
|
51
|
+
}));
|
|
52
|
+
// Output as newline-separated tool names (easier for shell scripts to parse)
|
|
53
|
+
if (recentTools.length > 0) {
|
|
54
|
+
console.log(recentTools.map(t => t.toolName).join('\n'));
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
catch (error) {
|
|
58
|
+
console.error(`Error retrieving recent tools: ${error}`);
|
|
59
|
+
process.exit(1);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Capture user prompt for processing
|
|
64
|
+
* Used by UserPromptSubmit hook to enable preference extraction
|
|
65
|
+
*/
|
|
66
|
+
static async capturePrompt(sessionId, content) {
|
|
67
|
+
try {
|
|
68
|
+
if (!content) {
|
|
69
|
+
// No content to capture
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
const memoryService = memory_1.MemoryService.getInstance();
|
|
73
|
+
// Store prompt as a special memory type for later processing
|
|
74
|
+
// This will be picked up by the queue system and processed asynchronously
|
|
75
|
+
memoryService.store({
|
|
76
|
+
key: `prompt:${sessionId}:${Date.now()}`,
|
|
77
|
+
value: {
|
|
78
|
+
content,
|
|
79
|
+
sessionId,
|
|
80
|
+
capturedAt: Date.now(),
|
|
81
|
+
source: 'hook:UserPromptSubmit'
|
|
82
|
+
},
|
|
83
|
+
type: 'prompt-capture',
|
|
84
|
+
context: {
|
|
85
|
+
sessionId,
|
|
86
|
+
timestamp: Date.now(),
|
|
87
|
+
tool: 'UserPromptSubmit'
|
|
88
|
+
}
|
|
89
|
+
});
|
|
90
|
+
// Note: Preference extraction happens asynchronously via the queue system
|
|
91
|
+
// The HookEventProcessor will process this prompt
|
|
92
|
+
}
|
|
93
|
+
catch (error) {
|
|
94
|
+
// Don't fail the hook - this is best-effort
|
|
95
|
+
console.error(`Warning: Prompt capture failed: ${error}`);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
exports.HookCommands = HookCommands;
|
|
@@ -391,18 +391,20 @@ class HookEventProcessor extends queue_system_1.QueueProcessor {
|
|
|
391
391
|
});
|
|
392
392
|
}
|
|
393
393
|
}
|
|
394
|
-
//
|
|
395
|
-
|
|
396
|
-
const
|
|
397
|
-
const
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
}
|
|
394
|
+
// Pattern detection kept for PreferenceExtractor, but storage disabled (v0.7.7)
|
|
395
|
+
// These detected-pattern memories were system-generated noise with no retrieval/utility
|
|
396
|
+
// const { PatternService } = await import('./pattern-service');
|
|
397
|
+
// const patternService = PatternService.getInstance();
|
|
398
|
+
// const patterns = patternService.analyzePrompt(promptContent);
|
|
399
|
+
//
|
|
400
|
+
// if (patterns) {
|
|
401
|
+
// memoryService.store({
|
|
402
|
+
// key: `pattern-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`,
|
|
403
|
+
// value: patterns,
|
|
404
|
+
// type: 'detected-pattern',
|
|
405
|
+
// context: payload.context || {}
|
|
406
|
+
// });
|
|
407
|
+
// }
|
|
406
408
|
}
|
|
407
409
|
async processClaudeResponseEvent(payload) {
|
|
408
410
|
// Process Claude's response for pattern detection and learning
|
|
@@ -410,17 +412,19 @@ class HookEventProcessor extends queue_system_1.QueueProcessor {
|
|
|
410
412
|
const patternService = PatternService.getInstance();
|
|
411
413
|
// Analyze the response for patterns
|
|
412
414
|
const patterns = patternService.analyzePrompt(payload.content);
|
|
413
|
-
//
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
}
|
|
415
|
+
// Pattern detection kept for PreferenceExtractor, but storage disabled (v0.7.7)
|
|
416
|
+
// These response-pattern memories were system-generated noise with no retrieval/utility
|
|
417
|
+
// if (patterns) {
|
|
418
|
+
// const { MemoryService } = await import('./memory');
|
|
419
|
+
// const memoryService = MemoryService.getInstance();
|
|
420
|
+
//
|
|
421
|
+
// memoryService.store({
|
|
422
|
+
// key: `response-pattern-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`,
|
|
423
|
+
// value: patterns,
|
|
424
|
+
// type: 'response-pattern',
|
|
425
|
+
// context: payload.context || {}
|
|
426
|
+
// });
|
|
427
|
+
// }
|
|
424
428
|
}
|
|
425
429
|
}
|
|
426
430
|
/**
|
|
@@ -539,16 +543,18 @@ class PatternDetectionProcessor extends queue_system_1.QueueProcessor {
|
|
|
539
543
|
const patternService = PatternService.getInstance();
|
|
540
544
|
// Analyze the content for patterns
|
|
541
545
|
const patterns = patternService.analyzePrompt(message.payload.content);
|
|
542
|
-
//
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
}
|
|
546
|
+
// Pattern detection is kept for PreferenceExtractor, but storage disabled (v0.7.7)
|
|
547
|
+
// These pattern-analysis memories were system-generated noise with no retrieval/utility
|
|
548
|
+
// if (patterns) {
|
|
549
|
+
// const { MemoryService } = await import('./memory');
|
|
550
|
+
// const memoryService = MemoryService.getInstance();
|
|
551
|
+
//
|
|
552
|
+
// memoryService.store({
|
|
553
|
+
// key: `pattern-detection-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`,
|
|
554
|
+
// value: patterns,
|
|
555
|
+
// type: 'pattern-analysis',
|
|
556
|
+
// context: message.payload.context || {}
|
|
557
|
+
// });
|
|
558
|
+
// }
|
|
553
559
|
}
|
|
554
560
|
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "claude-recall",
|
|
3
|
-
"version": "0.7.
|
|
4
|
-
"description": "Persistent memory for Claude Code with automatic capture, failure learning, sophistication tracking, project scoping, project registry,
|
|
3
|
+
"version": "0.7.9",
|
|
4
|
+
"description": "Persistent memory for Claude Code with automatic capture, failure learning, sophistication tracking, project scoping, project registry, automatic schema migration, noise reduction, and enforced pre-action memory search via MCP server and hooks",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"bin": {
|
|
7
7
|
"claude-recall": "dist/cli/claude-recall-cli.js"
|
package/scripts/postinstall.js
CHANGED
|
@@ -103,6 +103,87 @@ try {
|
|
|
103
103
|
console.log('⚠️ Failed to register project (non-fatal):', error.message);
|
|
104
104
|
}
|
|
105
105
|
|
|
106
|
+
// Install hook scripts to .claude/hooks/ directory
|
|
107
|
+
try {
|
|
108
|
+
const cwd = process.cwd();
|
|
109
|
+
const projectName = path.basename(cwd);
|
|
110
|
+
|
|
111
|
+
// Only install hooks for actual projects (not in claude-recall itself or node_modules)
|
|
112
|
+
if (projectName !== 'claude-recall' && !cwd.includes('node_modules/.pnpm') && !cwd.includes('node_modules/claude-recall')) {
|
|
113
|
+
const claudeDir = path.join(cwd, '.claude');
|
|
114
|
+
const hooksDir = path.join(claudeDir, 'hooks');
|
|
115
|
+
|
|
116
|
+
// Create .claude/hooks directory
|
|
117
|
+
if (!fs.existsSync(hooksDir)) {
|
|
118
|
+
fs.mkdirSync(hooksDir, { recursive: true });
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// Copy hook scripts from package
|
|
122
|
+
const packageHooksDir = path.join(__dirname, '../.claude/hooks');
|
|
123
|
+
const hookScripts = [
|
|
124
|
+
'pre_tool_search_enforcer.py',
|
|
125
|
+
'user_prompt_capture.py'
|
|
126
|
+
];
|
|
127
|
+
|
|
128
|
+
for (const script of hookScripts) {
|
|
129
|
+
const source = path.join(packageHooksDir, script);
|
|
130
|
+
const dest = path.join(hooksDir, script);
|
|
131
|
+
|
|
132
|
+
if (fs.existsSync(source)) {
|
|
133
|
+
fs.copyFileSync(source, dest);
|
|
134
|
+
// Make executable
|
|
135
|
+
fs.chmodSync(dest, 0o755);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
console.log('✅ Installed hook scripts to .claude/hooks/');
|
|
140
|
+
|
|
141
|
+
// Create or update .claude/settings.json with hook configuration
|
|
142
|
+
const settingsPath = path.join(claudeDir, 'settings.json');
|
|
143
|
+
let settings = {};
|
|
144
|
+
|
|
145
|
+
if (fs.existsSync(settingsPath)) {
|
|
146
|
+
const settingsContent = fs.readFileSync(settingsPath, 'utf8');
|
|
147
|
+
settings = JSON.parse(settingsContent);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// Add hook configuration if not already present
|
|
151
|
+
if (!settings.hooks) {
|
|
152
|
+
settings.hooks = {
|
|
153
|
+
PreToolUse: [
|
|
154
|
+
{
|
|
155
|
+
matcher: "Write|Edit",
|
|
156
|
+
hooks: [
|
|
157
|
+
{
|
|
158
|
+
type: "command",
|
|
159
|
+
command: "python3 .claude/hooks/pre_tool_search_enforcer.py"
|
|
160
|
+
}
|
|
161
|
+
]
|
|
162
|
+
}
|
|
163
|
+
],
|
|
164
|
+
UserPromptSubmit: [
|
|
165
|
+
{
|
|
166
|
+
hooks: [
|
|
167
|
+
{
|
|
168
|
+
type: "command",
|
|
169
|
+
command: "python3 .claude/hooks/user_prompt_capture.py"
|
|
170
|
+
}
|
|
171
|
+
]
|
|
172
|
+
}
|
|
173
|
+
]
|
|
174
|
+
};
|
|
175
|
+
|
|
176
|
+
fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2));
|
|
177
|
+
console.log('✅ Configured hooks in .claude/settings.json');
|
|
178
|
+
console.log(' → PreToolUse: Enforces memory search before file operations');
|
|
179
|
+
console.log(' → UserPromptSubmit: Captures prompts for preference extraction');
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
} catch (error) {
|
|
183
|
+
// Don't fail installation if hook setup fails
|
|
184
|
+
console.log('⚠️ Failed to install hooks (non-fatal):', error.message);
|
|
185
|
+
}
|
|
186
|
+
|
|
106
187
|
console.log('\n📝 Installation complete!');
|
|
107
188
|
console.log(' Claude Recall MCP server is now configured.');
|
|
108
189
|
console.log(' Restart your terminal to activate the memory system.');
|