225 lines
6.4 KiB
Python
Executable File
225 lines
6.4 KiB
Python
Executable File
#!/usr/bin/env -S uv run --script
|
|
# /// script
|
|
# requires-python = ">=3.11"
|
|
# dependencies = [
|
|
# "python-dotenv",
|
|
# ]
|
|
# ///
|
|
|
|
import argparse
|
|
import json
|
|
import subprocess
|
|
import sys
|
|
from datetime import datetime
|
|
from pathlib import Path
|
|
|
|
try:
|
|
from dotenv import load_dotenv
|
|
|
|
load_dotenv()
|
|
except ImportError:
|
|
pass # dotenv is optional
|
|
|
|
|
|
def log_session_start(input_data):
|
|
"""Log session start event to logs directory."""
|
|
# Ensure logs directory exists
|
|
log_dir = Path("logs")
|
|
log_dir.mkdir(parents=True, exist_ok=True)
|
|
log_file = log_dir / "session_start.json"
|
|
|
|
# Read existing log data or initialize empty list
|
|
if log_file.exists():
|
|
with open(log_file) as f:
|
|
try:
|
|
log_data = json.load(f)
|
|
except (json.JSONDecodeError, ValueError):
|
|
log_data = []
|
|
else:
|
|
log_data = []
|
|
|
|
# Append the entire input data
|
|
log_data.append(input_data)
|
|
|
|
# Write back to file with formatting
|
|
with open(log_file, "w") as f:
|
|
json.dump(log_data, f, indent=2)
|
|
|
|
|
|
def get_git_status():
|
|
"""Get current git status information."""
|
|
try:
|
|
# Get current branch
|
|
branch_result = subprocess.run(
|
|
["git", "rev-parse", "--abbrev-ref", "HEAD"],
|
|
capture_output=True,
|
|
text=True,
|
|
timeout=5,
|
|
)
|
|
current_branch = (
|
|
branch_result.stdout.strip() if branch_result.returncode == 0 else "unknown"
|
|
)
|
|
|
|
# Get uncommitted changes count
|
|
status_result = subprocess.run(
|
|
["git", "status", "--porcelain"], capture_output=True, text=True, timeout=5
|
|
)
|
|
if status_result.returncode == 0:
|
|
changes = (
|
|
status_result.stdout.strip().split("\n")
|
|
if status_result.stdout.strip()
|
|
else []
|
|
)
|
|
uncommitted_count = len(changes)
|
|
else:
|
|
uncommitted_count = 0
|
|
|
|
return current_branch, uncommitted_count
|
|
except Exception:
|
|
return None, None
|
|
|
|
|
|
def get_recent_issues():
|
|
"""Get recent GitHub issues if gh CLI is available."""
|
|
try:
|
|
# Check if gh is available
|
|
gh_check = subprocess.run(["which", "gh"], capture_output=True)
|
|
if gh_check.returncode != 0:
|
|
return None
|
|
|
|
# Get recent open issues
|
|
result = subprocess.run(
|
|
["gh", "issue", "list", "--limit", "5", "--state", "open"],
|
|
capture_output=True,
|
|
text=True,
|
|
timeout=10,
|
|
)
|
|
if result.returncode == 0 and result.stdout.strip():
|
|
return result.stdout.strip()
|
|
except Exception:
|
|
pass
|
|
return None
|
|
|
|
|
|
def load_development_context(source):
|
|
"""Load relevant development context based on session source."""
|
|
context_parts = []
|
|
|
|
# Add timestamp
|
|
context_parts.append(
|
|
f"Session started at: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}"
|
|
)
|
|
context_parts.append(f"Session source: {source}")
|
|
|
|
# Add git information
|
|
branch, changes = get_git_status()
|
|
if branch:
|
|
context_parts.append(f"Git branch: {branch}")
|
|
if changes > 0:
|
|
context_parts.append(f"Uncommitted changes: {changes} files")
|
|
|
|
# Load project-specific context files if they exist
|
|
context_files = [
|
|
".claude/CONTEXT.md",
|
|
".claude/TODO.md",
|
|
"TODO.md",
|
|
".github/ISSUE_TEMPLATE.md",
|
|
]
|
|
|
|
for file_path in context_files:
|
|
if Path(file_path).exists():
|
|
try:
|
|
with open(file_path) as f:
|
|
content = f.read().strip()
|
|
if content:
|
|
context_parts.append(f"\n--- Content from {file_path} ---")
|
|
context_parts.append(
|
|
content[:1000]
|
|
) # Limit to first 1000 chars
|
|
except Exception:
|
|
pass
|
|
|
|
# Add recent issues if available
|
|
issues = get_recent_issues()
|
|
if issues:
|
|
context_parts.append("\n--- Recent GitHub Issues ---")
|
|
context_parts.append(issues)
|
|
|
|
return "\n".join(context_parts)
|
|
|
|
|
|
def main():
|
|
try:
|
|
# Parse command line arguments
|
|
parser = argparse.ArgumentParser()
|
|
parser.add_argument(
|
|
"--load-context",
|
|
action="store_true",
|
|
help="Load development context at session start",
|
|
)
|
|
parser.add_argument(
|
|
"--announce", action="store_true", help="Announce session start via TTS"
|
|
)
|
|
args = parser.parse_args()
|
|
|
|
# Read JSON input from stdin
|
|
input_data = json.loads(sys.stdin.read())
|
|
|
|
# Extract fields
|
|
session_id = input_data.get("session_id", "unknown")
|
|
source = input_data.get("source", "unknown") # "startup", "resume", or "clear"
|
|
|
|
# Log the session start event
|
|
log_session_start(input_data)
|
|
|
|
# Load development context if requested
|
|
if args.load_context:
|
|
context = load_development_context(source)
|
|
if context:
|
|
# Using JSON output to add context
|
|
output = {
|
|
"hookSpecificOutput": {
|
|
"hookEventName": "SessionStart",
|
|
"additionalContext": context,
|
|
}
|
|
}
|
|
print(json.dumps(output))
|
|
sys.exit(0)
|
|
|
|
# Announce session start if requested
|
|
if args.announce:
|
|
try:
|
|
# Try to use TTS to announce session start
|
|
script_dir = Path(__file__).parent
|
|
tts_script = script_dir / "utils" / "tts" / "pyttsx3_tts.py"
|
|
|
|
if tts_script.exists():
|
|
messages = {
|
|
"startup": "Claude Code session started",
|
|
"resume": "Resuming previous session",
|
|
"clear": "Starting fresh session",
|
|
}
|
|
message = messages.get(source, "Session started")
|
|
|
|
subprocess.run(
|
|
["uv", "run", str(tts_script), message],
|
|
capture_output=True,
|
|
timeout=5,
|
|
)
|
|
except Exception:
|
|
pass
|
|
|
|
# Success
|
|
sys.exit(0)
|
|
|
|
except json.JSONDecodeError:
|
|
# Handle JSON decode errors gracefully
|
|
sys.exit(0)
|
|
except Exception:
|
|
# Handle any other errors gracefully
|
|
sys.exit(0)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|