feat(mem0-plugin): v0.2.5 — fix identity scoping, skill param bugs, add checklists (#5247)
This commit is contained in:
Executable
+158
@@ -0,0 +1,158 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Decide whether the Stop hook should block, and build context for Claude.
|
||||
|
||||
Reads the transcript to determine if meaningful work happened.
|
||||
Outputs JSON: {"should_block": bool, "context": "..."}.
|
||||
|
||||
Called by on_stop.sh with the hook input JSON on stdin.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import sys
|
||||
|
||||
MAX_TAIL_LINES = 500
|
||||
MAX_USER_MESSAGES = 30
|
||||
MAX_BASH_COMMANDS = 20
|
||||
|
||||
|
||||
def tail_lines(filepath: str, n: int) -> list[str]:
|
||||
try:
|
||||
with open(filepath, "rb") as f:
|
||||
f.seek(0, 2)
|
||||
file_size = f.tell()
|
||||
if file_size == 0:
|
||||
return []
|
||||
chunk_size = min(file_size, n * 4096)
|
||||
f.seek(max(0, file_size - chunk_size))
|
||||
data = f.read().decode("utf-8", errors="replace")
|
||||
return data.splitlines()[-n:]
|
||||
except OSError:
|
||||
return []
|
||||
|
||||
|
||||
def parse_transcript(lines: list[str]) -> dict:
|
||||
user_messages: list[str] = []
|
||||
files_modified: set[str] = set()
|
||||
bash_commands: list[str] = []
|
||||
tool_calls: int = 0
|
||||
|
||||
for line in lines:
|
||||
line = line.strip()
|
||||
if not line:
|
||||
continue
|
||||
try:
|
||||
entry = json.loads(line)
|
||||
except json.JSONDecodeError:
|
||||
continue
|
||||
|
||||
entry_type = entry.get("type")
|
||||
if entry_type not in ("user", "assistant"):
|
||||
continue
|
||||
if entry.get("isSidechain"):
|
||||
continue
|
||||
|
||||
message = entry.get("message", {})
|
||||
content_blocks = message.get("content", [])
|
||||
|
||||
if entry_type == "user":
|
||||
parts = []
|
||||
if isinstance(content_blocks, str):
|
||||
parts.append(content_blocks)
|
||||
elif isinstance(content_blocks, list):
|
||||
for block in content_blocks:
|
||||
if isinstance(block, str):
|
||||
parts.append(block)
|
||||
elif isinstance(block, dict) and block.get("type") == "text":
|
||||
parts.append(block.get("text", ""))
|
||||
text = "\n".join(parts).strip()
|
||||
if text and len(text) > 10 and not text.startswith("<"):
|
||||
user_messages.append(text[:300])
|
||||
|
||||
elif entry_type == "assistant":
|
||||
for block in content_blocks:
|
||||
if not isinstance(block, dict):
|
||||
continue
|
||||
if block.get("type") == "tool_use":
|
||||
tool_calls += 1
|
||||
tool_name = block.get("name", "")
|
||||
tool_input = block.get("input", {})
|
||||
if tool_name in ("Write", "Edit"):
|
||||
fp = tool_input.get("file_path", "")
|
||||
if fp:
|
||||
files_modified.add(fp)
|
||||
elif tool_name == "Bash":
|
||||
cmd = tool_input.get("command", "")
|
||||
if cmd:
|
||||
bash_commands.append(cmd[:200])
|
||||
|
||||
return {
|
||||
"user_messages": user_messages[-MAX_USER_MESSAGES:],
|
||||
"files_modified": sorted(files_modified),
|
||||
"bash_commands": bash_commands[-MAX_BASH_COMMANDS:],
|
||||
"tool_calls": tool_calls,
|
||||
}
|
||||
|
||||
|
||||
def should_block(state: dict) -> bool:
|
||||
if state["files_modified"]:
|
||||
return True
|
||||
if state["tool_calls"] >= 3:
|
||||
return True
|
||||
git_commands = [c for c in state["bash_commands"] if "git " in c]
|
||||
if git_commands:
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def build_context(state: dict) -> str:
|
||||
parts = []
|
||||
|
||||
if state["files_modified"]:
|
||||
files = state["files_modified"][:10]
|
||||
parts.append(f"Files modified this session: {', '.join(files)}")
|
||||
|
||||
git_commits = [c for c in state["bash_commands"] if "git commit" in c]
|
||||
if git_commits:
|
||||
parts.append(f"Git commits made: {len(git_commits)}")
|
||||
|
||||
if state["user_messages"]:
|
||||
tasks = []
|
||||
for msg in state["user_messages"][-5:]:
|
||||
first_line = msg.split("\n")[0][:150]
|
||||
tasks.append(f" - {first_line}")
|
||||
parts.append("User requests:\n" + "\n".join(tasks))
|
||||
|
||||
return "\n".join(parts)
|
||||
|
||||
|
||||
def main():
|
||||
try:
|
||||
hook_input = json.loads(sys.stdin.read())
|
||||
except (json.JSONDecodeError, OSError):
|
||||
json.dump({"should_block": False, "context": ""}, sys.stdout)
|
||||
return
|
||||
|
||||
transcript_path = hook_input.get("transcript_path", "")
|
||||
if not transcript_path:
|
||||
json.dump({"should_block": False, "context": ""}, sys.stdout)
|
||||
return
|
||||
|
||||
lines = tail_lines(transcript_path, MAX_TAIL_LINES)
|
||||
if not lines:
|
||||
json.dump({"should_block": False, "context": ""}, sys.stdout)
|
||||
return
|
||||
|
||||
state = parse_transcript(lines)
|
||||
block = should_block(state)
|
||||
context = build_context(state) if block else ""
|
||||
|
||||
json.dump({"should_block": block, "context": context}, sys.stdout)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
try:
|
||||
main()
|
||||
except Exception:
|
||||
json.dump({"should_block": False, "context": ""}, sys.stdout)
|
||||
Reference in New Issue
Block a user