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>
183 lines
5.4 KiB
Bash
183 lines
5.4 KiB
Bash
#!/bin/bash
|
|
#
|
|
# Claude Code Hook: task-complete (v2 - with offline support)
|
|
# Runs AFTER a task is completed
|
|
# Saves conversation context to the database for future recall
|
|
# FALLBACK: Queues locally when API is unavailable, syncs later
|
|
#
|
|
# Expected environment variables:
|
|
# CLAUDE_PROJECT_ID - UUID of the current project
|
|
# JWT_TOKEN - Authentication token for API
|
|
# CLAUDE_API_URL - API base URL (default: http://172.16.3.30:8001)
|
|
# CONTEXT_RECALL_ENABLED - Set to "false" to disable (default: true)
|
|
# TASK_SUMMARY - Summary of completed task (auto-generated by Claude)
|
|
# TASK_FILES - Files modified during task (comma-separated)
|
|
#
|
|
|
|
# Load configuration if exists
|
|
CONFIG_FILE="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)/context-recall-config.env"
|
|
if [ -f "$CONFIG_FILE" ]; then
|
|
source "$CONFIG_FILE"
|
|
fi
|
|
|
|
# Default values
|
|
API_URL="${CLAUDE_API_URL:-http://172.16.3.30:8001}"
|
|
ENABLED="${CONTEXT_RECALL_ENABLED:-true}"
|
|
|
|
# Local storage paths
|
|
CLAUDE_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
|
QUEUE_DIR="$CLAUDE_DIR/context-queue"
|
|
PENDING_DIR="$QUEUE_DIR/pending"
|
|
UPLOADED_DIR="$QUEUE_DIR/uploaded"
|
|
|
|
# Exit early if disabled
|
|
if [ "$ENABLED" != "true" ]; then
|
|
exit 0
|
|
fi
|
|
|
|
# Detect project ID (same logic as user-prompt-submit)
|
|
if [ -z "$CLAUDE_PROJECT_ID" ]; then
|
|
PROJECT_ID=$(git config --local claude.projectid 2>/dev/null)
|
|
|
|
if [ -z "$PROJECT_ID" ]; then
|
|
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
|
|
else
|
|
PROJECT_ID="$CLAUDE_PROJECT_ID"
|
|
fi
|
|
|
|
# Exit if no project ID
|
|
if [ -z "$PROJECT_ID" ]; then
|
|
exit 0
|
|
fi
|
|
|
|
# Create queue directories if they don't exist
|
|
mkdir -p "$PENDING_DIR" "$UPLOADED_DIR" 2>/dev/null
|
|
|
|
# Gather task information
|
|
TIMESTAMP=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
|
|
TIMESTAMP_FILENAME=$(date -u +"%Y%m%d_%H%M%S")
|
|
GIT_BRANCH=$(git rev-parse --abbrev-ref HEAD 2>/dev/null || echo "unknown")
|
|
GIT_COMMIT=$(git rev-parse --short HEAD 2>/dev/null || echo "none")
|
|
|
|
# Get recent git changes
|
|
CHANGED_FILES=$(git diff --name-only HEAD~1 2>/dev/null | head -10 | tr '\n' ',' | sed 's/,$//')
|
|
if [ -z "$CHANGED_FILES" ]; then
|
|
CHANGED_FILES="${TASK_FILES:-}"
|
|
fi
|
|
|
|
# Create task summary
|
|
if [ -z "$TASK_SUMMARY" ]; then
|
|
# Generate basic summary from git log if no summary provided
|
|
TASK_SUMMARY=$(git log -1 --pretty=format:"%s" 2>/dev/null || echo "Task completed")
|
|
fi
|
|
|
|
# Build context payload
|
|
CONTEXT_TITLE="Session: ${TIMESTAMP}"
|
|
CONTEXT_TYPE="session_summary"
|
|
RELEVANCE_SCORE=7.0
|
|
|
|
# Create dense summary
|
|
DENSE_SUMMARY="Task completed on branch '${GIT_BRANCH}' (commit: ${GIT_COMMIT}).
|
|
|
|
Summary: ${TASK_SUMMARY}
|
|
|
|
Modified files: ${CHANGED_FILES:-none}
|
|
|
|
Timestamp: ${TIMESTAMP}"
|
|
|
|
# Escape JSON strings
|
|
escape_json() {
|
|
echo "$1" | python3 -c "import sys, json; print(json.dumps(sys.stdin.read())[1:-1])"
|
|
}
|
|
|
|
ESCAPED_TITLE=$(escape_json "$CONTEXT_TITLE")
|
|
ESCAPED_SUMMARY=$(escape_json "$DENSE_SUMMARY")
|
|
|
|
# Save context to database
|
|
CONTEXT_PAYLOAD=$(cat <<EOF
|
|
{
|
|
"project_id": "${PROJECT_ID}",
|
|
"context_type": "${CONTEXT_TYPE}",
|
|
"title": ${ESCAPED_TITLE},
|
|
"dense_summary": ${ESCAPED_SUMMARY},
|
|
"relevance_score": ${RELEVANCE_SCORE},
|
|
"metadata": {
|
|
"git_branch": "${GIT_BRANCH}",
|
|
"git_commit": "${GIT_COMMIT}",
|
|
"files_modified": "${CHANGED_FILES}",
|
|
"timestamp": "${TIMESTAMP}"
|
|
}
|
|
}
|
|
EOF
|
|
)
|
|
|
|
# Update project state
|
|
PROJECT_STATE_PAYLOAD=$(cat <<EOF
|
|
{
|
|
"project_id": "${PROJECT_ID}",
|
|
"state_data": {
|
|
"last_task_completion": "${TIMESTAMP}",
|
|
"last_git_commit": "${GIT_COMMIT}",
|
|
"last_git_branch": "${GIT_BRANCH}",
|
|
"recent_files": "${CHANGED_FILES}"
|
|
},
|
|
"state_type": "task_completion"
|
|
}
|
|
EOF
|
|
)
|
|
|
|
# Try to POST to API if we have a JWT token
|
|
API_SUCCESS=false
|
|
if [ -n "$JWT_TOKEN" ]; then
|
|
RESPONSE=$(curl -s --max-time 5 -w "\n%{http_code}" \
|
|
-X POST "${API_URL}/api/conversation-contexts" \
|
|
-H "Authorization: Bearer ${JWT_TOKEN}" \
|
|
-H "Content-Type: application/json" \
|
|
-d "$CONTEXT_PAYLOAD" 2>/dev/null)
|
|
|
|
HTTP_CODE=$(echo "$RESPONSE" | tail -n1)
|
|
RESPONSE_BODY=$(echo "$RESPONSE" | sed '$d')
|
|
|
|
if [ "$HTTP_CODE" = "200" ] || [ "$HTTP_CODE" = "201" ]; then
|
|
API_SUCCESS=true
|
|
|
|
# Also update project state
|
|
curl -s --max-time 5 \
|
|
-X POST "${API_URL}/api/project-states" \
|
|
-H "Authorization: Bearer ${JWT_TOKEN}" \
|
|
-H "Content-Type: application/json" \
|
|
-d "$PROJECT_STATE_PAYLOAD" 2>/dev/null >/dev/null
|
|
fi
|
|
fi
|
|
|
|
# If API call failed, queue locally
|
|
if [ "$API_SUCCESS" = "false" ]; then
|
|
# Save context to pending queue
|
|
QUEUE_FILE="$PENDING_DIR/${PROJECT_ID}_${TIMESTAMP_FILENAME}_context.json"
|
|
echo "$CONTEXT_PAYLOAD" > "$QUEUE_FILE"
|
|
|
|
# Save project state to pending queue
|
|
STATE_QUEUE_FILE="$PENDING_DIR/${PROJECT_ID}_${TIMESTAMP_FILENAME}_state.json"
|
|
echo "$PROJECT_STATE_PAYLOAD" > "$STATE_QUEUE_FILE"
|
|
|
|
echo "⚠ Context queued locally (API unavailable) - will sync when online" >&2
|
|
|
|
# Try to sync in background (opportunistic)
|
|
if [ -n "$JWT_TOKEN" ]; then
|
|
bash "$(dirname "${BASH_SOURCE[0]}")/sync-contexts" >/dev/null 2>&1 &
|
|
fi
|
|
else
|
|
echo "✓ Context saved to database" >&2
|
|
|
|
# Trigger background sync of any queued items
|
|
if [ -n "$JWT_TOKEN" ]; then
|
|
bash "$(dirname "${BASH_SOURCE[0]}")/sync-contexts" >/dev/null 2>&1 &
|
|
fi
|
|
fi
|
|
|
|
exit 0
|