refactor(mem0-plugin): improve hook scripts, config parsing, and skill definitions
Update git commit capture with better error handling, enhance user prompt hook, extend config parser capabilities, and refine skill docs for dream, health, mcp, pin, and stats skills.
This commit is contained in:
@@ -1,13 +1,14 @@
|
||||
#!/usr/bin/env bash
|
||||
# Hook: PreToolUse (matcher: Bash)
|
||||
#
|
||||
# Detects `git commit` commands and fires on_pre_commit.py in the background
|
||||
# to capture staged changes as a mem0 memory. Never blocks the commit.
|
||||
# Detects `git commit` commands and:
|
||||
# 1. Fires on_pre_commit.py in the background to capture staged changes as memory
|
||||
# 2. Searches for relevant memories about the changed files and surfaces them
|
||||
#
|
||||
# Input: JSON on stdin with tool_name, tool_input
|
||||
# Output: none (always exit 0)
|
||||
# Output: JSON with additionalContext (relevant memories for the commit)
|
||||
|
||||
set -euo pipefail
|
||||
set -uo pipefail
|
||||
|
||||
INPUT=$(cat)
|
||||
|
||||
@@ -27,7 +28,7 @@ esac
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
|
||||
if [ ! -x "$SCRIPT_DIR/on_pre_commit.py" ] && [ ! -f "$SCRIPT_DIR/on_pre_commit.py" ]; then
|
||||
if [ ! -f "$SCRIPT_DIR/on_pre_commit.py" ]; then
|
||||
exit 0
|
||||
fi
|
||||
|
||||
@@ -36,6 +37,56 @@ if [ -z "$API_KEY" ]; then
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Background: capture staged changes as memory
|
||||
git diff --cached --stat 2>/dev/null | python3 "$SCRIPT_DIR/on_pre_commit.py" &
|
||||
|
||||
# Foreground: search for relevant memories about changed files
|
||||
CHANGED_FILES=$(git diff --cached --name-only 2>/dev/null | head -10 | tr '\n' ', ' | sed 's/,$//')
|
||||
if [ -z "$CHANGED_FILES" ]; then
|
||||
exit 0
|
||||
fi
|
||||
|
||||
USER_ID="${MEM0_RESOLVED_USER_ID:-$USER}"
|
||||
PROJECT_ID="${MEM0_PROJECT_ID:-unknown}"
|
||||
|
||||
CONTEXT=$(python3 -c "
|
||||
import json, urllib.request, os
|
||||
api_key = os.environ.get('MEM0_API_KEY', os.environ.get('CLAUDE_PLUGIN_OPTION_MEM0_API_KEY', ''))
|
||||
user_id = '$USER_ID'
|
||||
app_id = '$PROJECT_ID'
|
||||
files = '$CHANGED_FILES'
|
||||
first_file = files.split(',')[0].strip()
|
||||
body = json.dumps({
|
||||
'query': f'changes to {files}',
|
||||
'filters': {'AND': [{'user_id': user_id}, {'app_id': app_id}]},
|
||||
'top_k': 3,
|
||||
}).encode()
|
||||
req = urllib.request.Request(
|
||||
'https://api.mem0.ai/v2/memories/search/',
|
||||
data=body,
|
||||
headers={'Authorization': f'Token {api_key}', 'Content-Type': 'application/json'},
|
||||
method='POST',
|
||||
)
|
||||
try:
|
||||
with urllib.request.urlopen(req, timeout=5) as r:
|
||||
results = json.loads(r.read())
|
||||
memories = results if isinstance(results, list) else results.get('results', [])
|
||||
if memories:
|
||||
lines = ['## Pre-Commit Memory Check', '', 'Relevant memories for files being committed (' + files + '):', '']
|
||||
for m in memories[:3]:
|
||||
mid = m.get('id', '?')[:8]
|
||||
text = m.get('memory', '')[:200]
|
||||
cat = (m.get('metadata') or {}).get('type', 'unknown')
|
||||
lines.append(f'- [{cat}] {text} [mem0:{mid}]')
|
||||
lines.append('')
|
||||
lines.append('Consider: does this commit introduce a learning worth saving? If so, suggest storing it after the commit completes.')
|
||||
print('\\n'.join(lines))
|
||||
except Exception:
|
||||
pass
|
||||
" 2>/dev/null || true)
|
||||
|
||||
if [ -n "$CONTEXT" ]; then
|
||||
jq -nc --arg ctx "$CONTEXT" '{hookSpecificOutput:{hookEventName:"PreToolUse",additionalContext:$ctx}}'
|
||||
fi
|
||||
|
||||
exit 0
|
||||
|
||||
@@ -90,9 +90,10 @@ if [ -n "$FILE_PATHS" ]; then
|
||||
cat <<EOF
|
||||
|
||||
**FILE PATHS detected:** \`$FILE_PATHS\`
|
||||
Search mem0 for context about these files:
|
||||
Search mem0 for context about these files using the \`contains\` operator on \`metadata.files\`:
|
||||
- \`search_memories(query="<filename>", filters={"AND": [{"user_id": "$USER_ID"}, {"app_id": "$MEM0_PROJECT_ID"}, {"metadata.files": {"contains": "<filename>"}}]})\`
|
||||
- Also run a broader text search without the files filter as fallback:
|
||||
- \`search_memories(query="<filename without extension>", filters={"AND": [{"user_id": "$USER_ID"}, {"app_id": "$MEM0_PROJECT_ID"}]})\`
|
||||
Memories tagged with \`metadata.files\` containing these paths will surface via text match.
|
||||
EOF
|
||||
fi
|
||||
|
||||
|
||||
@@ -132,10 +132,33 @@ def parse_section_list(content: str, heading: str) -> list[str]:
|
||||
return items
|
||||
|
||||
|
||||
def parse_ignore_patterns(content: str) -> list[str]:
|
||||
"""Parse the ``## Ignore`` section of *content*.
|
||||
|
||||
Each non-blank line is a glob pattern (e.g., ``node_modules``, ``*.lock``).
|
||||
Lines starting with ``#`` are comments and skipped.
|
||||
"""
|
||||
pattern = r"^##\s+Ignore[^\n]*\n(.*?)(?=^##\s|\Z)"
|
||||
match = re.search(pattern, content, flags=re.MULTILINE | re.DOTALL | re.IGNORECASE)
|
||||
if not match:
|
||||
return []
|
||||
|
||||
patterns: list[str] = []
|
||||
for line in match.group(1).splitlines():
|
||||
line = line.strip()
|
||||
if not line or line.startswith("#"):
|
||||
continue
|
||||
line = re.sub(r"^[-*]\s+", "", line).strip()
|
||||
if line:
|
||||
patterns.append(line)
|
||||
return patterns
|
||||
|
||||
|
||||
def load_full_config(cwd: str | None = None) -> dict:
|
||||
"""Load all config sections from mem0.md.
|
||||
|
||||
Returns a dict with keys: retention, search, categories, identity.
|
||||
Returns a dict with keys: retention, search, categories, identity,
|
||||
ignore, project_id.
|
||||
Each is populated only if the corresponding ``##`` section exists.
|
||||
"""
|
||||
if cwd is None:
|
||||
@@ -164,10 +187,17 @@ def load_full_config(cwd: str | None = None) -> dict:
|
||||
categories = parse_section_list(content, "Categories")
|
||||
if categories:
|
||||
config["categories"] = categories
|
||||
config["default_categories"] = categories
|
||||
|
||||
identity = parse_section_kv(content, "Identity")
|
||||
if identity:
|
||||
config["identity"] = identity
|
||||
if "project_id" in identity:
|
||||
config["project_id"] = identity["project_id"]
|
||||
|
||||
ignore = parse_ignore_patterns(content)
|
||||
if ignore:
|
||||
config["ignore"] = ignore
|
||||
|
||||
return config
|
||||
|
||||
|
||||
Reference in New Issue
Block a user