feat(mem0-plugin): smart retrieval, citations, pre-commit capture (Tier 7)
Smart retrieval tier: - on_user_prompt.sh: stack-trace detection (auto-searches anti_patterns on errors) - on_user_prompt.sh: file-aware retrieval (extracts file paths, suggests scoped search) - on_user_prompt.sh: recency boost guidance for state-related queries - mem0-mcp/SKILL.md: inline citation format [mem0:<8char_id>] - on_pre_commit.py: captures staged diff as commit_context memory via REST API
This commit is contained in:
@@ -0,0 +1,116 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Pre-commit memory capture.
|
||||
|
||||
Captures a summary of staged changes as a mem0 memory before each commit.
|
||||
Runs as a background fire-and-forget call — never blocks the commit.
|
||||
|
||||
Usage:
|
||||
git diff --cached --stat | python3 on_pre_commit.py
|
||||
# or with full diff:
|
||||
git diff --cached | python3 on_pre_commit.py --full
|
||||
|
||||
Env vars required: MEM0_API_KEY (or CLAUDE_PLUGIN_OPTION_MEM0_API_KEY)
|
||||
Env vars optional: MEM0_RESOLVED_USER_ID, MEM0_PROJECT_ID, MEM0_BRANCH
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
import urllib.error
|
||||
import urllib.request
|
||||
|
||||
sys.path.insert(0, os.path.dirname(__file__))
|
||||
from _identity import resolve_api_key, resolve_user_id
|
||||
|
||||
try:
|
||||
from _project import resolve_branch, resolve_project_id
|
||||
except ImportError:
|
||||
def resolve_project_id() -> str:
|
||||
return os.path.basename(os.getcwd())
|
||||
|
||||
def resolve_branch() -> str:
|
||||
return "unknown"
|
||||
|
||||
|
||||
def get_commit_message() -> str:
|
||||
try:
|
||||
result = subprocess.run(
|
||||
["git", "log", "-1", "--format=%s", "HEAD"],
|
||||
capture_output=True, text=True, timeout=5,
|
||||
)
|
||||
return result.stdout.strip() if result.returncode == 0 else ""
|
||||
except Exception:
|
||||
return ""
|
||||
|
||||
|
||||
def get_staged_summary() -> str:
|
||||
try:
|
||||
result = subprocess.run(
|
||||
["git", "diff", "--cached", "--stat"],
|
||||
capture_output=True, text=True, timeout=10,
|
||||
)
|
||||
return result.stdout.strip() if result.returncode == 0 else ""
|
||||
except Exception:
|
||||
return ""
|
||||
|
||||
|
||||
def main() -> int:
|
||||
api_key = resolve_api_key()
|
||||
if not api_key:
|
||||
return 0
|
||||
|
||||
diff_input = sys.stdin.read().strip() if not sys.stdin.isatty() else ""
|
||||
staged = diff_input or get_staged_summary()
|
||||
if not staged or len(staged) < 10:
|
||||
return 0
|
||||
|
||||
user_id = os.environ.get("MEM0_RESOLVED_USER_ID") or resolve_user_id()
|
||||
project_id = os.environ.get("MEM0_PROJECT_ID") or resolve_project_id()
|
||||
branch = os.environ.get("MEM0_BRANCH") or resolve_branch()
|
||||
commit_msg = get_commit_message()
|
||||
|
||||
lines = staged.splitlines()
|
||||
if len(lines) > 30:
|
||||
staged = "\n".join(lines[:30]) + f"\n... ({len(lines) - 30} more lines)"
|
||||
|
||||
content = f"## Commit Context\n\nBranch: {branch}\n"
|
||||
if commit_msg:
|
||||
content += f"Message: {commit_msg}\n"
|
||||
content += f"\n### Staged Changes\n```\n{staged}\n```"
|
||||
|
||||
body = json.dumps({
|
||||
"messages": [{"role": "user", "content": content}],
|
||||
"user_id": user_id,
|
||||
"app_id": project_id,
|
||||
"metadata": {
|
||||
"type": "commit_context",
|
||||
"branch": branch,
|
||||
"source": "pre-commit",
|
||||
},
|
||||
"infer": False,
|
||||
}).encode()
|
||||
|
||||
req = urllib.request.Request(
|
||||
"https://api.mem0.ai/v3/memories/add/",
|
||||
data=body,
|
||||
headers={
|
||||
"Authorization": f"Token {api_key}",
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
method="POST",
|
||||
)
|
||||
|
||||
try:
|
||||
with urllib.request.urlopen(req, timeout=10):
|
||||
pass
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
Reference in New Issue
Block a user