Files
claudetools/.claude/hooks/periodic-context-save
Mike Swanson 25f3759ecc [Config] Add coding guidelines and code-fixer agent
Major additions:
- Add CODING_GUIDELINES.md with "NO EMOJIS" rule
- Create code-fixer agent for automated violation fixes
- Add offline mode v2 hooks with local caching/queue
- Add periodic context save with invisible Task Scheduler setup
- Add agent coordination rules and database connection docs

Infrastructure:
- Update hooks: task-complete-v2, user-prompt-submit-v2
- Add periodic_save_check.py for auto-save every 5min
- Add PowerShell scripts: setup_periodic_save.ps1, update_to_invisible.ps1
- Add sync-contexts script for queue synchronization

Documentation:
- OFFLINE_MODE.md, PERIODIC_SAVE_INVISIBLE_SETUP.md
- Migration procedures and verification docs
- Fix flashing window guide

Updates:
- Update agent configs (backup, code-review, coding, database, gitea, testing)
- Update claude.md with coding guidelines reference
- Update .gitignore for new cache/queue directories

Status: Pre-automated-fixer baseline commit

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-01-17 12:51:43 -07:00

227 lines
6.2 KiB
Bash

#!/bin/bash
#
# Periodic Context Save Hook
# Runs as a background daemon to save context every 5 minutes of active time
#
# Usage: bash .claude/hooks/periodic-context-save start
# bash .claude/hooks/periodic-context-save stop
# bash .claude/hooks/periodic-context-save status
#
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
CLAUDE_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
PID_FILE="$CLAUDE_DIR/.periodic-save.pid"
STATE_FILE="$CLAUDE_DIR/.periodic-save-state"
CONFIG_FILE="$CLAUDE_DIR/context-recall-config.env"
# Load configuration
if [ -f "$CONFIG_FILE" ]; then
source "$CONFIG_FILE"
fi
# Configuration
SAVE_INTERVAL_SECONDS=300 # 5 minutes
CHECK_INTERVAL_SECONDS=60 # Check every minute
API_URL="${CLAUDE_API_URL:-http://172.16.3.30:8001}"
# Detect project ID
detect_project_id() {
# Try git config first
PROJECT_ID=$(git config --local claude.projectid 2>/dev/null)
if [ -z "$PROJECT_ID" ]; then
# Try to derive from git remote URL
GIT_REMOTE=$(git config --get remote.origin.url 2>/dev/null)
if [ -n "$GIT_REMOTE" ]; then
PROJECT_ID=$(echo -n "$GIT_REMOTE" | md5sum | cut -d' ' -f1)
fi
fi
echo "$PROJECT_ID"
}
# Check if Claude Code is active (not idle)
is_claude_active() {
# Check if there are recent Claude Code processes or activity
# This is a simple heuristic - can be improved
# On Windows with Git Bash, check for claude process
if command -v tasklist.exe >/dev/null 2>&1; then
tasklist.exe 2>/dev/null | grep -i claude >/dev/null 2>&1
return $?
fi
# Assume active if we can't detect
return 0
}
# Get active time from state file
get_active_time() {
if [ -f "$STATE_FILE" ]; then
cat "$STATE_FILE" | grep "^active_seconds=" | cut -d'=' -f2
else
echo "0"
fi
}
# Update active time in state file
update_active_time() {
local active_seconds=$1
echo "active_seconds=$active_seconds" > "$STATE_FILE"
echo "last_update=$(date -u +"%Y-%m-%dT%H:%M:%SZ")" >> "$STATE_FILE"
}
# Save context to database
save_periodic_context() {
local project_id=$(detect_project_id)
# Generate context summary
local title="Periodic Save - $(date +"%Y-%m-%d %H:%M")"
local summary="Auto-saved context after 5 minutes of active work. Session in progress on project: ${project_id:-unknown}"
# Create JSON payload
local payload=$(cat <<EOF
{
"context_type": "session_summary",
"title": "$title",
"dense_summary": "$summary",
"relevance_score": 5.0,
"tags": "[\"auto-save\", \"periodic\", \"active-session\"]"
}
EOF
)
# POST to API
if [ -n "$JWT_TOKEN" ]; then
curl -s -X POST "${API_URL}/api/conversation-contexts" \
-H "Authorization: Bearer ${JWT_TOKEN}" \
-H "Content-Type: application/json" \
-d "$payload" >/dev/null 2>&1
if [ $? -eq 0 ]; then
echo "[$(date)] Context saved successfully" >&2
else
echo "[$(date)] Failed to save context" >&2
fi
else
echo "[$(date)] No JWT token - cannot save context" >&2
fi
}
# Main monitoring loop
monitor_loop() {
local active_seconds=0
echo "[$(date)] Periodic context save daemon started (PID: $$)" >&2
echo "[$(date)] Will save context every ${SAVE_INTERVAL_SECONDS}s of active time" >&2
while true; do
# Check if Claude is active
if is_claude_active; then
# Increment active time
active_seconds=$((active_seconds + CHECK_INTERVAL_SECONDS))
update_active_time $active_seconds
# Check if we've reached the save interval
if [ $active_seconds -ge $SAVE_INTERVAL_SECONDS ]; then
echo "[$(date)] ${SAVE_INTERVAL_SECONDS}s of active time reached - saving context" >&2
save_periodic_context
# Reset timer
active_seconds=0
update_active_time 0
fi
else
echo "[$(date)] Claude Code inactive - not counting time" >&2
fi
# Wait before next check
sleep $CHECK_INTERVAL_SECONDS
done
}
# Start daemon
start_daemon() {
if [ -f "$PID_FILE" ]; then
local pid=$(cat "$PID_FILE")
if kill -0 $pid 2>/dev/null; then
echo "Periodic context save daemon already running (PID: $pid)"
return 1
fi
fi
# Start in background
nohup bash "$0" _monitor >> "$CLAUDE_DIR/periodic-save.log" 2>&1 &
local pid=$!
echo $pid > "$PID_FILE"
echo "Started periodic context save daemon (PID: $pid)"
echo "Logs: $CLAUDE_DIR/periodic-save.log"
}
# Stop daemon
stop_daemon() {
if [ ! -f "$PID_FILE" ]; then
echo "Periodic context save daemon not running"
return 1
fi
local pid=$(cat "$PID_FILE")
if kill $pid 2>/dev/null; then
echo "Stopped periodic context save daemon (PID: $pid)"
rm -f "$PID_FILE"
rm -f "$STATE_FILE"
else
echo "Failed to stop daemon (PID: $pid) - may not be running"
rm -f "$PID_FILE"
fi
}
# Check status
check_status() {
if [ -f "$PID_FILE" ]; then
local pid=$(cat "$PID_FILE")
if kill -0 $pid 2>/dev/null; then
local active_seconds=$(get_active_time)
echo "Periodic context save daemon is running (PID: $pid)"
echo "Active time: ${active_seconds}s / ${SAVE_INTERVAL_SECONDS}s"
return 0
else
echo "Daemon PID file exists but process not running"
rm -f "$PID_FILE"
return 1
fi
else
echo "Periodic context save daemon not running"
return 1
fi
}
# Command dispatcher
case "$1" in
start)
start_daemon
;;
stop)
stop_daemon
;;
status)
check_status
;;
_monitor)
# Internal command - run monitor loop
monitor_loop
;;
*)
echo "Usage: $0 {start|stop|status}"
echo ""
echo "Periodic context save daemon - saves context every 5 minutes of active time"
echo ""
echo "Commands:"
echo " start - Start the background daemon"
echo " stop - Stop the daemon"
echo " status - Check daemon status"
exit 1
;;
esac