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.
@@ -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()
@@ -2,7 +2,11 @@
2
2
  "permissions": {
3
3
  "allow": [
4
4
  "Bash(npm view:*)",
5
- "mcp__perplexity-mcp__search"
5
+ "mcp__perplexity-mcp__search",
6
+ "Bash(npm run build:*)",
7
+ "Bash(git add:*)",
8
+ "Bash(git commit:*)",
9
+ "Bash(claude-recall --version)"
6
10
  ],
7
11
  "deny": [],
8
12
  "ask": []
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 (v0.3.2+)
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 (v0.7.0+)
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 0.3.3 or later
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 Claude Code runs in WSL):
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**: Install globally in WSL only:
103
+ **Solution**: Use WSL-specific local installation:
104
104
  ```bash
105
- # From WSL:
106
- npm install -g claude-recall
105
+ # From WSL, in your project directory:
106
+ cd /path/to/your-project
107
+ npm install claude-recall
107
108
 
108
- # Verify:
109
- claude-recall --version
109
+ # This installs Linux binaries AND hook scripts
110
+ # MCP server configured in ~/.claude.json works for all projects
110
111
  ```
111
112
 
112
- **Important**: Global installation does NOT affect project-specific memory scoping! See [How Project Scoping Works](#how-project-scoping-works-installation-location) below.
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
- **Everyone else:** Both local and global work, but local is still recommended for the benefits above.
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
- ### Schema Migration (v0.7.6+)
155
+ ### Database Migration
140
156
 
141
- **Automatic Migration:**
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
- Starting with v0.7.6, Claude Recall automatically migrates your database schema when needed. The first time you run any command after upgrading, missing columns will be added automatically.
159
+ ### Automatic Memory Search Enforcement
144
160
 
145
- **What Gets Migrated:**
146
- - `sophistication_level` column (added in v0.7.0)
147
- - `scope` column (added in v0.7.2)
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
- You'll see console messages like:
165
+ **The Learning Loop:**
150
166
  ```
151
- 📋 Migrating database schema: Adding scope column...
152
- Added scope column
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
- **Manual Migration (Optional):**
172
+ **How It Works:**
156
173
 
157
- If you prefer to run the migration explicitly:
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
- ```bash
160
- # Check current schema and migrate if needed
161
- npx claude-recall migrate schema
181
+ **Hook Behavior:**
162
182
 
163
- # Create backup before migration
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
- **Troubleshooting Schema Errors:**
189
+ Before executing Write, please search memories first:
168
190
 
169
- If you see errors like:
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
- Run the manual migration:
174
- ```bash
175
- npx claude-recall migrate schema --backup
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
- This will:
179
- 1. Create a backup of your database (if `--backup` flag used)
180
- 2. Add any missing columns
181
- 3. Create necessary indexes
182
- 4. Verify the migration succeeded
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 (v0.5.0+)
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 (v0.7.0+)
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 (v0.7.4+):**
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 (v0.7.6+)
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 (v0.7.5+)
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 (v0.7.2+)
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
- // Also run legacy pattern detection as fallback
395
- const { PatternService } = await Promise.resolve().then(() => __importStar(require('./pattern-service')));
396
- const patternService = PatternService.getInstance();
397
- const patterns = patternService.analyzePrompt(promptContent);
398
- if (patterns) {
399
- memoryService.store({
400
- key: `pattern-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`,
401
- value: patterns,
402
- type: 'detected-pattern',
403
- context: payload.context || {}
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
- // Store the analyzed patterns if they exist
414
- if (patterns) {
415
- const { MemoryService } = await Promise.resolve().then(() => __importStar(require('./memory')));
416
- const memoryService = MemoryService.getInstance();
417
- memoryService.store({
418
- key: `response-pattern-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`,
419
- value: patterns,
420
- type: 'response-pattern',
421
- context: payload.context || {}
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
- // Store the analyzed patterns if they exist
543
- if (patterns) {
544
- const { MemoryService } = await Promise.resolve().then(() => __importStar(require('./memory')));
545
- const memoryService = MemoryService.getInstance();
546
- memoryService.store({
547
- key: `pattern-detection-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`,
548
- value: patterns,
549
- type: 'pattern-analysis',
550
- context: message.payload.context || {}
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.6",
4
- "description": "Persistent memory for Claude Code with automatic capture, failure learning, sophistication tracking, project scoping, project registry, and automatic schema migration via MCP server",
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"
@@ -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.');