Files
gh-kylesnowschwartz-simplec…/hooks/handlers/stop_handler.rb
2025-11-30 08:36:27 +08:00

235 lines
6.1 KiB
Ruby
Executable File

#!/usr/bin/env ruby
# frozen_string_literal: true
require 'claude_hooks'
# Stop Handler
#
# PURPOSE: Control and customize Claude Code's stopping behavior
# TRIGGERS: When Claude Code is about to stop execution or end a session
#
# COMMON USE CASES:
# - Save session state and cleanup resources
# - Generate session summaries or reports
# - Backup important work or temporary files
# - Log session metrics and analytics
# - Send completion notifications
# - Trigger follow-up actions or workflows
#
# SETTINGS.JSON CONFIGURATION:
# {
# "hooks": {
# "Stop": [{
# "matcher": "",
# "hooks": [{
# "type": "command",
# "command": "$CLAUDE_PROJECT_DIR/.claude/hooks/entrypoints/stop.rb"
# }]
# }]
# }
# }
class StopHandler < ClaudeHooks::Stop
def call
log 'Processing session stop request'
# Example: Save session state
# save_session_state
# Example: Generate session summary
# generate_session_summary
# Example: Cleanup temporary resources
# cleanup_resources
# Example: Send completion notifications
# send_completion_notifications
# Allow stopping to proceed
allow_continue!
output_data
end
private
def save_session_state
log 'Saving session state'
# Example: Save important session data
{
session_id: session_id,
end_time: Time.now,
working_directory: cwd || Dir.pwd,
transcript_path: transcript_path
}
# Example: Write to session cache
# write_session_cache(session_data)
# Example: Update project metadata
# update_project_metadata(session_data)
end
def generate_session_summary
log 'Generating session summary'
# Example: Read transcript and generate summary
return unless transcript_path && File.exist?(transcript_path)
transcript = read_transcript
summary = analyze_transcript(transcript)
log "Session summary: #{summary[:total_interactions]} interactions, #{summary[:tools_used].length} tools used"
# Example: Save summary to file
# save_session_summary(summary)
end
def cleanup_resources
log 'Cleaning up session resources'
# Example: Remove temporary files
# cleanup_temp_files
# Example: Close database connections
# close_database_connections
# Example: Clear caches
# clear_session_caches
# Example: Stop background processes
# stop_background_processes
end
def send_completion_notifications
log 'Sending session completion notifications'
# Example: Notify team about session completion
# notify_team_completion
# Example: Send analytics data
# send_analytics_data
# Example: Update project dashboard
# update_project_dashboard
end
def analyze_transcript(transcript)
# Simple transcript analysis
lines = transcript.split("\n")
{
total_lines: lines.length,
total_interactions: count_user_interactions(lines),
tools_used: extract_tools_used(lines),
errors_encountered: count_errors(lines),
session_duration: calculate_session_duration(lines)
}
end
def count_user_interactions(lines)
# Count lines that look like user messages
lines.count { |line| line.match?(/^(user:|User:)/i) }
end
def extract_tools_used(lines)
# Extract tool names from transcript
tools = []
lines.each do |line|
tools << ::Regexp.last_match(1) if line.match?(/Using tool: (\w+)/) || line.match?(/Tool call: (\w+)/)
end
tools.uniq
end
def count_errors(lines)
# Count error messages in transcript
error_patterns = [
/error:/i,
/failed:/i,
/exception:/i,
/cannot/i
]
lines.count do |line|
error_patterns.any? { |pattern| line.match?(pattern) }
end
end
def calculate_session_duration(lines)
# Simple duration calculation based on first and last timestamps
first_timestamp = extract_first_timestamp(lines)
last_timestamp = extract_last_timestamp(lines)
if first_timestamp && last_timestamp
(last_timestamp - first_timestamp).to_i
else
0
end
end
def extract_first_timestamp(_lines)
# Extract timestamp from first line (implementation depends on transcript format)
# This is a placeholder - actual implementation would depend on transcript format
nil
end
def extract_last_timestamp(_lines)
# Extract timestamp from last line (implementation depends on transcript format)
# This is a placeholder - actual implementation would depend on transcript format
nil
end
def write_session_cache(data)
# Example: Write session data to cache file
project_path_for('cache/sessions')
# FileUtils.mkdir_p(cache_dir)
# File.write("#{cache_dir}/#{session_id}.json", JSON.pretty_generate(data))
log "Would write session cache: #{data.keys.join(', ')}"
end
def update_project_metadata(_data)
# Example: Update project-level metadata
log 'Would update project metadata with session data'
end
def save_session_summary(summary)
# Example: Save session summary to reports directory
project_path_for('reports/sessions')
# FileUtils.mkdir_p(reports_dir)
# File.write("#{reports_dir}/#{session_id}_summary.json", JSON.pretty_generate(summary))
log "Would save session summary: #{summary.keys.join(', ')}"
end
def cleanup_temp_files
# Example: Remove temporary files created during session
temp_patterns = [
project_path_for('tmp/**/*'),
project_path_for('.temp/**/*'),
project_path_for('*.tmp')
]
log "Would cleanup temporary files matching patterns: #{temp_patterns.join(', ')}"
end
def notify_team_completion
# Example: Send Slack/Teams notification about session completion
log 'Would notify team about session completion'
end
def send_analytics_data
# Example: Send session metrics to analytics service
log 'Would send analytics data for session'
end
end
# Testing support - run this file directly to test with sample data
if __FILE__ == $PROGRAM_NAME
ClaudeHooks::CLI.test_runner(StopHandler) do |input_data|
input_data['session_id'] = 'test-session-01'
input_data['reason'] = 'user_requested'
end
end