claude-memory-mcp/hooks/user-prompt-recall.py
Viktor Barzin 0d1cff3038
Add Claude Code plugin scaffold for single-repo install
Consolidates plugin components (hooks, commands, skills, MCP config)
into this repo so it can be installed with:
  claude plugins install github:ViktorBarzin/claude-memory-mcp

- .claude-plugin/plugin.json: manifest with all hook events
- mcp/memory-mcp.json: MCP server config using existing src/
- hooks/: compaction survival, auto-recall, auto-learn, auto-approve
- commands/: /remember and /recall slash commands
- skills/: memory-management skill
- Bump MCP server to v2.0.0 with metaclaw migration fallback
- Update README with quick install and plugin hooks docs
2026-03-14 14:49:18 +00:00

88 lines
2.9 KiB
Python

#!/usr/bin/env python3
"""
UserPromptSubmit hook: nudge Claude to check memories before responding.
Instead of searching SQLite directly (which can't generate expanded_query),
this hook injects a prompt instruction telling Claude to call memory_recall
with proper expanded_query. The model handles the semantic expansion.
Skips injection for short prompts, code blocks, and tool outputs.
"""
import json
import os
import sys
RECALL_INSTRUCTION = (
"MANDATORY: Call memory_recall with context and expanded_query BEFORE responding "
"to this message. This is a BLOCKING REQUIREMENT — do NOT answer, summarize, or "
"take action until you have checked stored memories. The user's preferences, past "
"corrections, and decisions are stored there and MUST inform your response.\n\n"
"ONLY EXCEPTION: Skip memory_recall if the message is a direct technical command "
"with zero personal context (e.g., 'fix this lint error', 'run tests', 'read file X'). "
"Questions, discussions, opinions, design tasks, and anything involving preferences "
"are NOT exceptions — check memory for those.\n\n"
"NEVER mention the memory check to the user — not when you call it, not when you skip it, "
"not when it returns nothing. It should be completely invisible. "
"If it returns results, incorporate them naturally into your response."
)
def main() -> None:
# Read hook input from stdin
try:
hook_input = json.load(sys.stdin)
except (json.JSONDecodeError, EOFError):
return
# Extract user's prompt
prompt = ""
if isinstance(hook_input, dict):
prompt = hook_input.get("prompt", "")
if not prompt:
prompt = hook_input.get("user_prompt", "")
if not prompt:
content = hook_input.get("content", "")
if isinstance(content, str):
prompt = content
if not prompt or len(prompt.strip()) < 10:
return # Too short to warrant memory check
# Skip obviously irrelevant prompts
stripped = prompt.strip()
if (
stripped.startswith("```")
or stripped.startswith("{")
or stripped.startswith("<")
):
return
# Skip if memory DB doesn't exist (no memories to recall)
memory_home = os.environ.get(
"MEMORY_HOME", os.path.expanduser("~/.claude/claude-memory")
)
db_path = os.path.join(memory_home, "memory", "memory.db")
# Also check legacy path for migration
legacy_home = os.path.expanduser("~/.claude/metaclaw")
legacy_db = os.path.join(legacy_home, "memory", "memory.db")
if not os.path.exists(db_path) and not os.path.exists(legacy_db):
return
# Inject the recall instruction
output = json.dumps(
{
"hookSpecificOutput": {
"hookEventName": "UserPromptSubmit",
"additionalContext": RECALL_INSTRUCTION,
}
}
)
print(output)
if __name__ == "__main__":
main()