Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 32 additions & 0 deletions .claude/hooks/_log_common.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import json
import os
import pathlib


def get_log_path():
# Ensure log directory exists
project_dir = os.getenv('CLAUDE_PROJECT_DIR', '.')
project_dir = pathlib.Path(project_dir)
log_dir = project_dir / 'logs'
os.makedirs(log_dir, exist_ok=True)
return log_dir


def append_log_data(log_file, data):
log_file = get_log_path() / log_file

# Read existing log data if there is any
log_data = []
if os.path.exists(log_file):
with open(log_file, 'rt') as f:
try:
log_data = json.load(f)
except (json.JSONDecodeError, ValueError):
pass

# Append new data
log_data.append(data)

# Write back to file with formatting
with open(log_file, 'wt') as f:
json.dump(log_data, f, indent=2)
24 changes: 3 additions & 21 deletions .claude/hooks/notification.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@
except ImportError:
pass # dotenv is optional

from _log_common import append_log_data


def get_tts_script_path():
"""
Expand Down Expand Up @@ -92,28 +94,8 @@ def main():
# Read JSON input from stdin
input_data = json.loads(sys.stdin.read())

# Ensure log directory exists
import os
log_dir = os.path.join(os.getcwd(), 'logs')
os.makedirs(log_dir, exist_ok=True)
log_file = os.path.join(log_dir, 'notification.json')

# Read existing log data or initialize empty list
if os.path.exists(log_file):
with open(log_file, 'r') as f:
try:
log_data = json.load(f)
except (json.JSONDecodeError, ValueError):
log_data = []
else:
log_data = []

# Append new 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)
append_log_data('notification.json', input_data)

# Announce notification via TTS only if --notify flag is set
# Skip TTS for the generic "Claude is waiting for your input" message
Expand Down
24 changes: 4 additions & 20 deletions .claude/hooks/post_tool_use.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,32 +8,16 @@
import sys
from pathlib import Path

from _log_common import append_log_data


def main():
try:
# Read JSON input from stdin
input_data = json.load(sys.stdin)

# Ensure log directory exists
log_dir = Path.cwd() / 'logs'
log_dir.mkdir(parents=True, exist_ok=True)
log_path = log_dir / 'post_tool_use.json'

# Read existing log data or initialize empty list
if log_path.exists():
with open(log_path, 'r') as f:
try:
log_data = json.load(f)
except (json.JSONDecodeError, ValueError):
log_data = []
else:
log_data = []

# Append new data
log_data.append(input_data)

# Write back to file with formatting
with open(log_path, 'w') as f:
json.dump(log_data, f, indent=2)
append_log_data('post_tool_use.json', input_data)

sys.exit(0)

Expand Down
29 changes: 3 additions & 26 deletions .claude/hooks/pre_compact.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,30 +19,7 @@
except ImportError:
pass # dotenv is optional


def log_pre_compact(input_data):
"""Log pre-compact event to logs directory."""
# Ensure logs directory exists
log_dir = Path("logs")
log_dir.mkdir(parents=True, exist_ok=True)
log_file = log_dir / 'pre_compact.json'

# Read existing log data or initialize empty list
if log_file.exists():
with open(log_file, 'r') 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)
from _log_common import append_log_data, get_log_path


def backup_transcript(transcript_path, trigger):
Expand All @@ -52,7 +29,7 @@ def backup_transcript(transcript_path, trigger):
return

# Create backup directory
backup_dir = Path("logs") / "transcript_backups"
backup_dir = get_log_path() / "transcript_backups"
backup_dir.mkdir(parents=True, exist_ok=True)

# Generate backup filename with timestamp and trigger type
Expand Down Expand Up @@ -90,7 +67,7 @@ def main():
custom_instructions = input_data.get('custom_instructions', '')

# Log the pre-compact event
log_pre_compact(input_data)
append_log_data("pre_compact.json", input_data)

# Create backup if requested
backup_path = None
Expand Down
24 changes: 4 additions & 20 deletions .claude/hooks/pre_tool_use.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@
import re
from pathlib import Path

from _log_common import append_log_data


def is_dangerous_rm_command(command):
"""
Comprehensive detection of dangerous rm commands.
Expand Down Expand Up @@ -104,27 +107,8 @@ def main():
print("BLOCKED: Dangerous rm command detected and prevented", file=sys.stderr)
sys.exit(2) # Exit code 2 blocks tool call and shows error to Claude

# Ensure log directory exists
log_dir = Path.cwd() / 'logs'
log_dir.mkdir(parents=True, exist_ok=True)
log_path = log_dir / 'pre_tool_use.json'

# Read existing log data or initialize empty list
if log_path.exists():
with open(log_path, 'r') as f:
try:
log_data = json.load(f)
except (json.JSONDecodeError, ValueError):
log_data = []
else:
log_data = []

# Append new data
log_data.append(input_data)

# Write back to file with formatting
with open(log_path, 'w') as f:
json.dump(log_data, f, indent=2)
append_log_data('pre_tool_use.json', input_data)

sys.exit(0)

Expand Down
36 changes: 9 additions & 27 deletions .claude/hooks/session_start.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,30 +20,7 @@
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, 'r') 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)
from _log_common import append_log_data


def get_git_status():
Expand All @@ -52,6 +29,7 @@ def get_git_status():
# Get current branch
branch_result = subprocess.run(
['git', 'rev-parse', '--abbrev-ref', 'HEAD'],
cwd=os.getenv('CLAUDE_PROJECT_DIR'),
capture_output=True,
text=True,
timeout=5
Expand All @@ -61,6 +39,7 @@ def get_git_status():
# Get uncommitted changes count
status_result = subprocess.run(
['git', 'status', '--porcelain'],
cwd=os.getenv('CLAUDE_PROJECT_DIR'),
capture_output=True,
text=True,
timeout=5
Expand All @@ -87,6 +66,7 @@ def get_recent_issues():
# Get recent open issues
result = subprocess.run(
['gh', 'issue', 'list', '--limit', '5', '--state', 'open'],
cwd=os.getenv('CLAUDE_PROJECT_DIR'),
capture_output=True,
text=True,
timeout=10
Expand Down Expand Up @@ -120,11 +100,13 @@ def load_development_context(source):
"TODO.md",
".github/ISSUE_TEMPLATE.md"
]
project_dir = Path(os.getenv("CLAUDE_PROJECT_DIR", ""))

for file_path in context_files:
if Path(file_path).exists():
full_path = project_dir / file_path
if full_path.exists():
try:
with open(file_path, 'r') as f:
with open(full_path, 'rt') as f:
content = f.read().strip()
if content:
context_parts.append(f"\n--- Content from {file_path} ---")
Expand Down Expand Up @@ -159,7 +141,7 @@ def main():
source = input_data.get('source', 'unknown') # "startup", "resume", or "clear"

# Log the session start event
log_session_start(input_data)
append_log_data('session_start.json', input_data)

# Load development context if requested
if args.load_context:
Expand Down
28 changes: 5 additions & 23 deletions .claude/hooks/stop.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
except ImportError:
pass # dotenv is optional

from _log_common import append_log_data, get_log_path

def get_completion_messages():
"""Return list of friendly completion messages."""
Expand Down Expand Up @@ -152,27 +153,8 @@ def main():
session_id = input_data.get("session_id", "")
stop_hook_active = input_data.get("stop_hook_active", False)

# Ensure log directory exists
log_dir = os.path.join(os.getcwd(), "logs")
os.makedirs(log_dir, exist_ok=True)
log_path = os.path.join(log_dir, "stop.json")

# Read existing log data or initialize empty list
if os.path.exists(log_path):
with open(log_path, 'r') as f:
try:
log_data = json.load(f)
except (json.JSONDecodeError, ValueError):
log_data = []
else:
log_data = []

# Append new data
log_data.append(input_data)

# Write back to file with formatting
with open(log_path, 'w') as f:
json.dump(log_data, f, indent=2)
append_log_data("stop.json", input_data)

# Handle --chat switch
if args.chat and 'transcript_path' in input_data:
Expand All @@ -181,7 +163,7 @@ def main():
# Read .jsonl file and convert to JSON array
chat_data = []
try:
with open(transcript_path, 'r') as f:
with open(transcript_path, 'rt') as f:
for line in f:
line = line.strip()
if line:
Expand All @@ -191,8 +173,8 @@ def main():
pass # Skip invalid lines

# Write to logs/chat.json
chat_file = os.path.join(log_dir, 'chat.json')
with open(chat_file, 'w') as f:
chat_file = get_log_path() / 'chat.json'
with open(chat_file, 'wt') as f:
json.dump(chat_data, f, indent=2)
except Exception:
pass # Fail silently
Expand Down
29 changes: 6 additions & 23 deletions .claude/hooks/subagent_stop.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@
except ImportError:
pass # dotenv is optional

from _log_common import append_log_data, get_log_path


def get_tts_script_path():
"""
Expand Down Expand Up @@ -90,27 +92,8 @@ def main():
session_id = input_data.get("session_id", "")
stop_hook_active = input_data.get("stop_hook_active", False)

# Ensure log directory exists
log_dir = os.path.join(os.getcwd(), "logs")
os.makedirs(log_dir, exist_ok=True)
log_path = os.path.join(log_dir, "subagent_stop.json")

# Read existing log data or initialize empty list
if os.path.exists(log_path):
with open(log_path, 'r') as f:
try:
log_data = json.load(f)
except (json.JSONDecodeError, ValueError):
log_data = []
else:
log_data = []

# Append new data
log_data.append(input_data)

# Write back to file with formatting
with open(log_path, 'w') as f:
json.dump(log_data, f, indent=2)
append_log_data("subagent_stop.json", input_data)

# Handle --chat switch (same as stop.py)
if args.chat and 'transcript_path' in input_data:
Expand All @@ -119,7 +102,7 @@ def main():
# Read .jsonl file and convert to JSON array
chat_data = []
try:
with open(transcript_path, 'r') as f:
with open(transcript_path, 'rt') as f:
for line in f:
line = line.strip()
if line:
Expand All @@ -129,8 +112,8 @@ def main():
pass # Skip invalid lines

# Write to logs/chat.json
chat_file = os.path.join(log_dir, 'chat.json')
with open(chat_file, 'w') as f:
chat_file = get_log_path() / 'chat.json'
with open(chat_file, 'wt') as f:
json.dump(chat_data, f, indent=2)
except Exception:
pass # Fail silently
Expand Down
Loading