mirror of
https://github.com/davila7/claude-code-templates.git
synced 2026-09-19 01:30:23 +08:00
043f37342e
Codebase explorer agent, deadline countdown statusline, change logger hook, deadline prep skill, commit smart skill, and test detect skill. Designed for rapid onboarding on unfamiliar codebases with time pressure. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
86 lines
2.4 KiB
Python
86 lines
2.4 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
Change Logger Hook
|
|
Logs every file mutation (Edit, Write, Bash) to a CSV file for demo prep and session review.
|
|
Output: .claude/critical_log_changes.csv
|
|
"""
|
|
|
|
import csv
|
|
import json
|
|
import os
|
|
import sys
|
|
from datetime import datetime
|
|
|
|
# Read-only commands that should not be logged
|
|
READONLY_COMMANDS = {
|
|
"cat", "head", "tail", "less", "more",
|
|
"ls", "dir", "tree", "pwd", "which", "where", "whereis",
|
|
"echo", "printf",
|
|
"grep", "rg", "find", "fd", "ag",
|
|
"git status", "git log", "git diff", "git show", "git branch",
|
|
"git remote", "git stash list", "git tag",
|
|
"node -e", "python -c", "ruby -e",
|
|
"type", "file", "wc", "du", "df",
|
|
}
|
|
|
|
CSV_PATH = ".claude/critical_log_changes.csv"
|
|
CSV_HEADERS = ["timestamp", "tool", "file_path", "action", "details"]
|
|
|
|
|
|
def is_readonly_command(command):
|
|
"""Check if a bash command is read-only and should be skipped."""
|
|
cmd_stripped = command.strip()
|
|
for ro_cmd in READONLY_COMMANDS:
|
|
if cmd_stripped.startswith(ro_cmd):
|
|
return True
|
|
return False
|
|
|
|
|
|
def log_change(tool_name, file_path, action, details=""):
|
|
"""Append a row to the CSV change log."""
|
|
os.makedirs(os.path.dirname(CSV_PATH), exist_ok=True)
|
|
|
|
write_header = not os.path.exists(CSV_PATH)
|
|
|
|
with open(CSV_PATH, "a", newline="", encoding="utf-8") as f:
|
|
writer = csv.writer(f, quoting=csv.QUOTE_MINIMAL)
|
|
if write_header:
|
|
writer.writerow(CSV_HEADERS)
|
|
writer.writerow([
|
|
datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
|
|
tool_name,
|
|
file_path,
|
|
action,
|
|
details[:200],
|
|
])
|
|
|
|
|
|
def main():
|
|
try:
|
|
data = json.load(sys.stdin)
|
|
except (json.JSONDecodeError, EOFError):
|
|
sys.exit(0)
|
|
|
|
tool_name = data.get("tool_name", "")
|
|
tool_input = data.get("tool_input", {})
|
|
|
|
if tool_name in ("Edit", "MultiEdit"):
|
|
file_path = tool_input.get("file_path", "unknown")
|
|
log_change(tool_name, file_path, "modified")
|
|
|
|
elif tool_name == "Write":
|
|
file_path = tool_input.get("file_path", "unknown")
|
|
log_change(tool_name, file_path, "created")
|
|
|
|
elif tool_name == "Bash":
|
|
command = tool_input.get("command", "")
|
|
if command and not is_readonly_command(command):
|
|
log_change(tool_name, "-", "executed", command[:200])
|
|
|
|
# Never block tool execution
|
|
sys.exit(0)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|