Files
gh-shakes-tzd-contextune/hooks/session_end_recorder.py
2025-11-30 08:56:10 +08:00

165 lines
4.7 KiB
Python
Executable File

#!/usr/bin/env -S uv run --script
# /// script
# requires-python = ">=3.10"
# dependencies = ["pyyaml>=6.0"]
# ///
"""
SessionEnd Recorder - Capture Session Metadata for Next Session
Records lightweight session metadata at session end:
- Session ID and timestamp
- Last commit hash (for git diff baseline)
- Current branch
- Files worked on
- Brief summary
**Token Overhead:** ~100 tokens (write only, no injection)
**Blocking:** No
Hook Protocol:
- Input: JSON via stdin with session data
- Output: JSON via stdout
- IMPORTANT: Never blocks (always {"continue": true})
"""
import json
import subprocess
import sys
from pathlib import Path
from datetime import datetime
import yaml
def get_current_commit_hash() -> str:
"""Get current git commit hash."""
try:
result = subprocess.run(
["git", "rev-parse", "HEAD"],
capture_output=True,
text=True,
timeout=1,
)
return result.stdout.strip()
except Exception as e:
print(f"DEBUG: Failed to get commit hash: {e}", file=sys.stderr)
return "unknown"
def get_current_branch() -> str:
"""Get current git branch."""
try:
result = subprocess.run(
["git", "branch", "--show-current"],
capture_output=True,
text=True,
timeout=1,
)
return result.stdout.strip()
except Exception as e:
print(f"DEBUG: Failed to get branch: {e}", file=sys.stderr)
return "unknown"
def get_files_changed_in_session(start_hash: str) -> list[str]:
"""Get files changed between start_hash and HEAD."""
try:
# Get files changed since session start
result = subprocess.run(
["git", "diff", "--name-only", f"{start_hash}..HEAD"],
capture_output=True,
text=True,
timeout=2,
)
files = [f.strip() for f in result.stdout.split("\n") if f.strip()]
# Also include uncommitted changes
result2 = subprocess.run(
["git", "status", "--short"],
capture_output=True,
text=True,
timeout=1,
)
for line in result2.stdout.split("\n"):
if line.strip():
# Extract filename (format: "M file.py")
parts = line.split(maxsplit=1)
if len(parts) == 2:
files.append(parts[1])
# Deduplicate
return list(set(files))
except Exception as e:
print(f"DEBUG: Failed to get changed files: {e}", file=sys.stderr)
return []
def main():
"""SessionEnd recorder entry point."""
try:
# Read hook data
hook_data = json.loads(sys.stdin.read())
session_id = hook_data.get("session_id", "unknown")
print(f"DEBUG: SessionEnd recorder triggered", file=sys.stderr)
print(f"DEBUG: Session: {session_id}", file=sys.stderr)
# Get git state
commit_hash = get_current_commit_hash()
branch = get_current_branch()
# Load previous session data to calculate files worked on
cache_dir = Path.home() / ".claude" / "plugins" / "contextune" / ".cache"
cache_dir.mkdir(parents=True, exist_ok=True)
last_session_file = cache_dir / "last_session.yaml"
# Get session start commit hash (if tracked)
session_start_hash = commit_hash
if last_session_file.exists():
try:
with open(last_session_file) as f:
prev_session = yaml.safe_load(f) or {}
session_start_hash = prev_session.get("last_commit", commit_hash)
except:
pass
# Get files changed during this session
files_worked_on = get_files_changed_in_session(session_start_hash)
# Create session record
session_record = {
"session_id": session_id,
"ended_at": datetime.now().isoformat() + "Z",
"last_commit": commit_hash,
"branch": branch,
"files_worked_on": files_worked_on[:20], # Limit to 20 files
"file_count": len(files_worked_on),
}
# Write to cache
with open(last_session_file, "w") as f:
yaml.dump(session_record, f, default_flow_style=False)
print(
f"DEBUG: ✅ Recorded session metadata ({len(files_worked_on)} files)",
file=sys.stderr,
)
except Exception as e:
print(f"DEBUG: SessionEnd recorder error: {e}", file=sys.stderr)
import traceback
traceback.print_exc(file=sys.stderr)
# Always continue (don't block session end)
response = {"continue": True}
print(json.dumps(response))
if __name__ == "__main__":
main()