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>
227 lines
6.2 KiB
Bash
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
|