Initial commit: Claude Code Hooks with Diátaxis documentation

 Features:
- 🧠 Shadow learner that builds intelligence from command patterns
- 🛡️ Smart command validation with safety checks
- 💾 Automatic context monitoring and backup system
- 🔄 Session continuity across Claude restarts

📚 Documentation:
- Complete Diátaxis-organized documentation
- Learning-oriented tutorial for getting started
- Task-oriented how-to guides for specific problems
- Information-oriented reference for quick lookup
- Understanding-oriented explanations of architecture

🚀 Installation:
- One-command installation script
- Bootstrap prompt for installation via Claude
- Cross-platform compatibility
- Comprehensive testing suite

🎯 Ready for real-world use and community feedback!

🤖 Generated with Claude Code

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Ryan Malloy 2025-07-19 18:25:34 -06:00
commit 162ca67098
34 changed files with 5904 additions and 0 deletions

184
hooks/command_validator.py Executable file
View file

@ -0,0 +1,184 @@
#!/usr/bin/env python3
"""
Command Validator Hook - PreToolUse[Bash] hook
Validates bash commands using shadow learner insights
"""
import sys
import json
import re
import os
from pathlib import Path
# Add lib directory to path
sys.path.insert(0, str(Path(__file__).parent.parent / "lib"))
from shadow_learner import ShadowLearner
from models import ValidationResult
class CommandValidator:
"""Validates bash commands for safety and success probability"""
def __init__(self):
self.shadow_learner = ShadowLearner()
# Dangerous command patterns
self.dangerous_patterns = [
r'rm\s+-rf\s+/', # Delete root
r'mkfs\.', # Format filesystem
r'dd\s+if=.*of=/dev/', # Overwrite devices
r':(){ :|:& };:', # Fork bomb
r'curl.*\|\s*bash', # Pipe to shell
r'wget.*\|\s*sh', # Pipe to shell
r';.*rm\s+-rf', # Command chaining with rm
r'&&.*rm\s+-rf', # Command chaining with rm
]
self.suspicious_patterns = [
r'sudo\s+rm', # Sudo with rm
r'chmod\s+777', # Overly permissive
r'/etc/passwd', # System files
r'/etc/shadow', # System files
r'nc.*-l.*-p', # Netcat listener
]
def validate_command_safety(self, command: str) -> ValidationResult:
"""Comprehensive command safety validation"""
# Normalize command for analysis
normalized = command.lower().strip()
# Check for dangerous patterns
for pattern in self.dangerous_patterns:
if re.search(pattern, normalized):
return ValidationResult(
allowed=False,
reason=f"Dangerous command pattern detected",
severity="critical"
)
# Check for suspicious patterns
for pattern in self.suspicious_patterns:
if re.search(pattern, normalized):
return ValidationResult(
allowed=True, # Allow but warn
reason=f"Suspicious command pattern detected",
severity="warning"
)
return ValidationResult(allowed=True, reason="Command appears safe")
def validate_with_shadow_learner(self, command: str) -> ValidationResult:
"""Use shadow learner to predict command success"""
try:
prediction = self.shadow_learner.predict_command_outcome(command)
if not prediction["likely_success"] and prediction["confidence"] > 0.8:
suggestions = prediction.get("suggestions", [])
suggestion_text = f" Try: {suggestions[0]}" if suggestions else ""
return ValidationResult(
allowed=False,
reason=f"Command likely to fail (confidence: {prediction['confidence']:.0%}){suggestion_text}",
severity="medium",
suggestions=suggestions
)
elif prediction["warnings"]:
return ValidationResult(
allowed=True,
reason=prediction["warnings"][0],
severity="warning",
suggestions=prediction.get("suggestions", [])
)
except Exception:
# If shadow learner fails, don't block
pass
return ValidationResult(allowed=True, reason="No issues detected")
def validate_command(self, command: str) -> ValidationResult:
"""Main validation entry point"""
# Safety validation (blocking)
safety_result = self.validate_command_safety(command)
if not safety_result.allowed:
return safety_result
# Shadow learner validation (predictive)
prediction_result = self.validate_with_shadow_learner(command)
# Return most significant result
if prediction_result.severity in ["high", "critical"]:
return prediction_result
elif safety_result.severity in ["warning", "medium"]:
return safety_result
else:
return prediction_result
def main():
"""Main hook entry point"""
try:
# Read input from Claude Code
input_data = json.loads(sys.stdin.read())
# Extract command from parameters
tool = input_data.get("tool", "")
parameters = input_data.get("parameters", {})
command = parameters.get("command", "")
if tool != "Bash" or not command:
# Not a bash command, allow it
response = {"allow": True, "message": "Not a bash command"}
print(json.dumps(response))
sys.exit(0)
# Validate command
validator = CommandValidator()
result = validator.validate_command(command)
if not result.allowed:
# Block the command
response = {
"allow": False,
"message": f"⛔ Command blocked: {result.reason}"
}
print(json.dumps(response))
sys.exit(1) # Exit code 1 = block operation
elif result.severity in ["warning", "medium"]:
# Allow with warning
warning_emoji = "⚠️" if result.severity == "warning" else "🚨"
message = f"{warning_emoji} {result.reason}"
if result.suggestions:
message += f"\n💡 Suggestion: {result.suggestions[0]}"
response = {
"allow": True,
"message": message
}
print(json.dumps(response))
sys.exit(0)
else:
# Allow without warning
response = {"allow": True, "message": "Command validated"}
print(json.dumps(response))
sys.exit(0)
except Exception as e:
# Never block on validation errors - always allow operation
response = {
"allow": True,
"message": f"Validation error: {str(e)}"
}
print(json.dumps(response))
sys.exit(0)
if __name__ == "__main__":
main()

83
hooks/context_monitor.py Executable file
View file

@ -0,0 +1,83 @@
#!/usr/bin/env python3
"""
Context Monitor Hook - UserPromptSubmit hook
Monitors context usage and triggers backups when needed
"""
import sys
import json
import os
from pathlib import Path
# Add lib directory to path
sys.path.insert(0, str(Path(__file__).parent.parent / "lib"))
from context_monitor import ContextMonitor
from backup_manager import BackupManager
from session_state import SessionStateManager
def main():
"""Main hook entry point"""
try:
# Read input from Claude Code
input_data = json.loads(sys.stdin.read())
# Initialize components
context_monitor = ContextMonitor()
backup_manager = BackupManager()
session_manager = SessionStateManager()
# Update context estimates from prompt
context_monitor.update_from_prompt(input_data)
# Check if backup should be triggered
backup_decision = context_monitor.check_backup_triggers("UserPromptSubmit", input_data)
if backup_decision.should_backup:
# Execute backup
session_state = session_manager.get_session_summary()
backup_result = backup_manager.execute_backup(backup_decision, session_state)
# Record backup in session
session_manager.add_backup(backup_result.backup_id, {
"reason": backup_decision.reason,
"success": backup_result.success
})
# Add context snapshot
session_manager.add_context_snapshot({
"usage_ratio": context_monitor.get_context_usage_ratio(),
"prompt_count": context_monitor.prompt_count,
"tool_executions": context_monitor.tool_executions
})
# Notify about backup
if backup_result.success:
message = f"Auto-backup created: {backup_decision.reason} (usage: {context_monitor.get_context_usage_ratio():.1%})"
else:
message = f"Backup attempted but failed: {backup_result.error}"
else:
message = f"Context usage: {context_monitor.get_context_usage_ratio():.1%}"
# Always allow operation (this is a monitoring hook)
response = {
"allow": True,
"message": message
}
print(json.dumps(response))
sys.exit(0)
except Exception as e:
# Never block on errors - always allow operation
response = {
"allow": True,
"message": f"Context monitor error: {str(e)}"
}
print(json.dumps(response))
sys.exit(0)
if __name__ == "__main__":
main()

180
hooks/session_finalizer.py Executable file
View file

@ -0,0 +1,180 @@
#!/usr/bin/env python3
"""
Session Finalizer Hook - Stop hook
Finalizes session, creates documentation, and saves state
"""
import sys
import json
import os
from pathlib import Path
# Add lib directory to path
sys.path.insert(0, str(Path(__file__).parent.parent / "lib"))
from session_state import SessionStateManager
from shadow_learner import ShadowLearner
from context_monitor import ContextMonitor
def main():
"""Main hook entry point"""
try:
# Read input from Claude Code (if any)
try:
input_data = json.loads(sys.stdin.read())
except:
input_data = {}
# Initialize components
session_manager = SessionStateManager()
shadow_learner = ShadowLearner()
context_monitor = ContextMonitor()
# Create session documentation
session_manager.create_continuation_docs()
# Save all learned patterns
shadow_learner.save_database()
# Get session summary for logging
session_summary = session_manager.get_session_summary()
# Create recovery guide if session was interrupted
create_recovery_info(session_summary, context_monitor)
# Clean up session
session_manager.cleanup_session()
# Log session completion
log_session_completion(session_summary)
# Always allow - this is a cleanup hook
response = {
"allow": True,
"message": f"Session finalized. Modified {len(session_summary.get('modified_files', []))} files, used {session_summary.get('session_stats', {}).get('total_tool_calls', 0)} tools."
}
print(json.dumps(response))
sys.exit(0)
except Exception as e:
# Session finalization should never block
response = {
"allow": True,
"message": f"Session finalization error: {str(e)}"
}
print(json.dumps(response))
sys.exit(0)
def create_recovery_info(session_summary: dict, context_monitor: ContextMonitor):
"""Create recovery information if needed"""
try:
context_usage = context_monitor.get_context_usage_ratio()
# If context was high when session ended, create recovery guide
if context_usage > 0.8:
recovery_content = f"""# Session Recovery Information
## Context Status
- **Context Usage**: {context_usage:.1%} when session ended
- **Reason**: Session ended with high context usage
## What This Means
Your Claude session ended while using a significant amount of context. This could mean:
1. You were working on a complex task
2. Context limits were approaching
3. Session was interrupted
## Recovery Steps
### 1. Check Your Progress
Review these recently modified files:
"""
for file_path in session_summary.get('modified_files', []):
recovery_content += f"- {file_path}\n"
recovery_content += f"""
### 2. Review Last Actions
Recent commands executed:
"""
recent_commands = session_summary.get('commands_executed', [])[-5:]
for cmd_info in recent_commands:
recovery_content += f"- `{cmd_info['command']}`\n"
recovery_content += f"""
### 3. Continue Your Work
1. Check `ACTIVE_TODOS.md` for pending tasks
2. Review `LAST_SESSION.md` for complete session history
3. Use `git status` to see current file changes
4. Consider committing your progress: `git add -A && git commit -m "Work in progress"`
### 4. Available Backups
"""
for backup in session_summary.get('backup_history', []):
status = "" if backup['success'] else ""
recovery_content += f"- {status} {backup['backup_id']} - {backup['reason']}\n"
recovery_content += f"""
## Quick Recovery Commands
```bash
# Check current status
git status
# View recent changes
git diff
# List available backups
ls .claude_hooks/backups/
# View active todos
cat ACTIVE_TODOS.md
# View last session summary
cat LAST_SESSION.md
```
*This recovery guide was created because your session ended with {context_usage:.1%} context usage.*
"""
with open("RECOVERY_GUIDE.md", 'w') as f:
f.write(recovery_content)
except Exception:
pass # Don't let recovery guide creation break session finalization
def log_session_completion(session_summary: dict):
"""Log session completion for analysis"""
try:
log_dir = Path(".claude_hooks/logs")
log_dir.mkdir(parents=True, exist_ok=True)
from datetime import datetime
completion_log = {
"timestamp": datetime.now().isoformat(),
"type": "session_completion",
"session_id": session_summary.get("session_id", "unknown"),
"duration_minutes": session_summary.get("session_stats", {}).get("duration_minutes", 0),
"total_tools": session_summary.get("session_stats", {}).get("total_tool_calls", 0),
"files_modified": len(session_summary.get("modified_files", [])),
"commands_executed": session_summary.get("session_stats", {}).get("total_commands", 0),
"backups_created": len(session_summary.get("backup_history", []))
}
log_file = log_dir / "session_completions.jsonl"
with open(log_file, 'a') as f:
f.write(json.dumps(completion_log) + "\n")
except Exception:
pass # Don't let logging errors break finalization
if __name__ == "__main__":
main()

124
hooks/session_logger.py Executable file
View file

@ -0,0 +1,124 @@
#!/usr/bin/env python3
"""
Session Logger Hook - PostToolUse[*] hook
Logs all tool usage and feeds data to shadow learner
"""
import sys
import json
import os
from datetime import datetime
from pathlib import Path
# Add lib directory to path
sys.path.insert(0, str(Path(__file__).parent.parent / "lib"))
from shadow_learner import ShadowLearner
from session_state import SessionStateManager
from context_monitor import ContextMonitor
from models import ToolExecution
def main():
"""Main hook entry point"""
try:
# Read input from Claude Code
input_data = json.loads(sys.stdin.read())
# Extract tool execution data
tool = input_data.get("tool", "")
parameters = input_data.get("parameters", {})
success = input_data.get("success", True)
error = input_data.get("error", "")
execution_time = input_data.get("execution_time", 0.0)
# Create tool execution record
execution = ToolExecution(
timestamp=datetime.now(),
tool=tool,
parameters=parameters,
success=success,
error_message=error if error else None,
execution_time=execution_time,
context={}
)
# Initialize components
shadow_learner = ShadowLearner()
session_manager = SessionStateManager()
context_monitor = ContextMonitor()
# Feed execution to shadow learner
shadow_learner.learn_from_execution(execution)
# Update session state
session_manager.update_from_tool_use(input_data)
# Update context monitor
context_monitor.update_from_tool_use(input_data)
# Save learned patterns periodically
# (Only save every 10 executions to avoid too much disk I/O)
if context_monitor.tool_executions % 10 == 0:
shadow_learner.save_database()
# Log execution to file for debugging (optional)
log_execution(execution)
# Always allow - this is a post-execution hook
response = {
"allow": True,
"message": f"Logged {tool} execution"
}
print(json.dumps(response))
sys.exit(0)
except Exception as e:
# Post-execution hooks should never block
response = {
"allow": True,
"message": f"Logging error: {str(e)}"
}
print(json.dumps(response))
sys.exit(0)
def log_execution(execution: ToolExecution):
"""Log execution to file for debugging and analysis"""
try:
log_dir = Path(".claude_hooks/logs")
log_dir.mkdir(parents=True, exist_ok=True)
# Create daily log file
log_file = log_dir / f"executions_{datetime.now().strftime('%Y%m%d')}.jsonl"
# Append execution record
with open(log_file, 'a') as f:
f.write(json.dumps(execution.to_dict()) + "\n")
# Clean up old log files (keep last 7 days)
cleanup_old_logs(log_dir)
except Exception:
# Don't let logging errors break the hook
pass
def cleanup_old_logs(log_dir: Path):
"""Clean up log files older than 7 days"""
try:
import time
cutoff_time = time.time() - (7 * 24 * 3600) # 7 days ago
for log_file in log_dir.glob("executions_*.jsonl"):
if log_file.stat().st_mtime < cutoff_time:
log_file.unlink()
except Exception:
pass
if __name__ == "__main__":
main()