#!/bin/bash # Claude Code Unified Integration Hook # Auto-triggers Ralph Orchestrator, ARC Protocol, and manages claude-mem # ALWAYS-ON MODE - Ralph runs continuously and orchestrates all tasks # # Architecture: # Claude Code (User) -> Hook -> Ralph Orchestrator (ALWAYS-ON) # | # +-> ARC Protocol Workers (parallel execution) # +-> claude-mem (persistent memory) # +-> Multiple AI Backends (Claude, Gemini, etc.) # # Environment Variables: # RALPH_MODE - "always" (default), "agents", "off" # RALPH_BACKEND - "claude" (default), "kiro", "gemini", "opencode" # RALPH_PRESET - "claude-code" (default), "feature", "tdd-red-green", etc. # ARC_ENABLED - "true" (default) to enable ARC Protocol workers # CLAUDE_MEM_ENABLED - "true" (default) to enable claude-mem memory set -euo pipefail # ============================================================================ # CONFIGURATION # ============================================================================ CLAUDE_DIR="${HOME}/.claude" RALPH_DIR="${CLAUDE_DIR}/ralph-integration" ARC_DIR="${RALPH_DIR}/arc" LOG_DIR="${RALPH_DIR}/logs" # Ralph State Files RALPH_LOCK_FILE="${RALPH_DIR}/ralph.lock" RALPH_PID_FILE="${RALPH_DIR}/ralph.pid" RALPH_LOG_FILE="${LOG_DIR}/ralph.log" RALPH_CONFIG="${RALPH_DIR}/ralph.yml" # ARC State Files ARC_LOCK_FILE="${RALPH_DIR}/arc.lock" ARC_PID_FILE="${RALPH_DIR}/arc.pid" ARC_LOG_FILE="${LOG_DIR}/arc.log" ARC_CONFIG="${ARC_DIR}/.arc/STATE.md" # Integration Log INTEGRATION_LOG="${LOG_DIR}/integration.log" # Default Settings (override via environment variables) RALPH_MODE="${RALPH_MODE:-always}" RALPH_BACKEND="${RALPH_BACKEND:-claude}" RALPH_PRESET="${RALPH_PRESET:-claude-code}" ARC_ENABLED="${ARC_ENABLED:-true}" CLAUDE_MEM_ENABLED="${CLAUDE_MEM_ENABLED:-true}" # Maximum Ralph iterations RALPH_MAX_ITERATIONS="${RALPH_MAX_ITERATIONS:-100}" # Create directories mkdir -p "${RALPH_DIR}" "${LOG_DIR}" "${ARC_DIR}" # Logging function log() { local level="$1" shift echo "[$(date -u +"%Y-%m-%d %H:%M:%S UTC")] [${level}] $*" | tee -a "${INTEGRATION_LOG}" } # ============================================================================ # INPUT PROCESSING # ============================================================================ # Read hook input from stdin HOOK_INPUT=$(cat) USER_PROMPT=$(echo "$HOOK_INPUT" | jq -r '.prompt // empty' 2>/dev/null || echo "") # Fallback: if no JSON input, use first argument if [[ -z "$USER_PROMPT" && $# -gt 0 ]]; then USER_PROMPT="$1" fi log INFO "Unified integration triggered" log INFO "Mode: ${RALPH_MODE}" log INFO "Prompt: ${USER_PROMPT:0:100}..." # ============================================================================ # RALPH ORCHESTRATOR INTEGRATION # ============================================================================ start_ralph() { local prompt="$1" # Check if Ralph is already running if [[ -f "$RALPH_LOCK_FILE" ]]; then local lock_pid lock_pid=$(cat "$RALPH_LOCK_FILE" 2>/dev/null || echo "") if [[ -n "$lock_pid" ]] && kill -0 "$lock_pid" 2>/dev/null; then log INFO "Ralph already running (PID: ${lock_pid})" return 0 else log WARN "Ralph lock file exists but process dead, cleaning up" rm -f "$RALPH_LOCK_FILE" "$RALPH_PID_FILE" fi fi # Create Ralph configuration create_ralph_config # Start Ralph in background log INFO "Starting Ralph Orchestrator..." nohup ralph run \ -c "$RALPH_CONFIG" \ -p "$prompt" \ --max-iterations "$RALPH_MAX_ITERATIONS" \ --no-tui \ >> "$RALPH_LOG_FILE" 2>&1 & local ralph_pid=$! echo "$ralph_pid" > "$RALPH_PID_FILE" echo "$ralph_pid" > "$RALPH_LOCK_FILE" log INFO "Ralph started (PID: ${ralph_pid})" log INFO "Config: ${RALPH_CONFIG}" log INFO "Log: ${RALPH_LOG_FILE}" # Notify user echo "🤖 Ralph Orchestrator: ACTIVE" >&2 echo " PID: ${ralph_pid}" >&2 echo " Mode: ${RALPH_MODE}" >&2 echo " Monitor: tail -f ${RALPH_LOG_FILE}" >&2 } create_ralph_config() { # Determine preset to use local preset_config preset_config=$(get_ralph_preset "$RALPH_PRESET") cat > "$RALPH_CONFIG" << EOF # Ralph Orchestrator Configuration for Claude Code # Auto-generated: $(date -u +"%Y-%m-%d %H:%M:%S UTC") # Mode: ${RALPH_MODE} # Backend: ${RALPH_BACKEND} ${preset_config} # Claude Code Integration Settings cli: backend: "${RALPH_BACKEND}" prompt_mode: "arg" # Integration with claude-mem memories: enabled: ${CLAUDE_MEM_ENABLED} inject: auto # Integration with ARC Protocol arc_integration: enabled: ${ARC_ENABLED} bridge_path: "${ARC_DIR}/.agent/mcp/arc_mcp_server.py" dashboard_enabled: true # Core Behaviors core: scratchpad: "${RALPH_DIR}/scratchpad.md" specs_dir: "./specs/" guardrails: - "Fresh context each iteration - scratchpad and memories are state" - "Don't assume 'not implemented' - search first" - "Backpressure is law - tests/typecheck/lint must pass" - "Use ARC workers for parallel execution when possible" - "Leverage claude-mem for context from previous sessions" EOF log INFO "Ralph config created: ${RALPH_CONFIG}" } get_ralph_preset() { local preset="$1" case "$preset" in "claude-code") # Optimized preset for Claude Code integration cat << 'PRESET_EOF' # Claude Code Preset - Planner-Builder-Verifier Workflow event_loop: completion_promise: "TASK_COMPLETE" max_iterations: 100 starting_event: "task.start" hats: planner: name: "📋 Planner" description: "Break down tasks into actionable steps, identify dependencies" triggers: ["task.start", "plan.failed"] publishes: ["plan.ready", "task.complete"] instructions: | You are the Planner. Analyze the user's request and: 1. Break down the task into clear, actionable steps 2. Identify dependencies and parallelizable work 3. Consider using ARC workers for parallel execution 4. Search claude-mem for relevant context from past sessions 5. Create a detailed plan before proceeding builder: name: "🔨 Builder" description: "Implement code following the plan and best practices" triggers: ["plan.ready", "build.failed"] publishes: ["build.done", "build.blocked"] instructions: | You are the Builder. Implement the plan: 1. Follow the plan created by the Planner 2. Write clean, well-documented code 3. Use ARC workers for parallel tasks when beneficial 4. Run tests, linting, and type checking 5. Only publish build.done when all quality gates pass verifier: name: "✅ Verifier" description: "Verify implementation meets requirements and quality standards" triggers: ["build.done"] publishes: ["verification.passed", "verification.failed", "task.complete"] instructions: | You are the Verifier. Ensure quality: 1. Review the implementation against the plan 2. Verify all tests pass 3. Check for edge cases and error handling 4. Ensure code follows best practices 5. Store lessons learned in memories for future sessions PRESET_EOF ;; "autonomous") # Fully autonomous mode cat << 'PRESET_EOF' # Autonomous Mode - Single Hat with Full Autonomy event_loop: completion_promise: "TASK_COMPLETE" max_iterations: 150 core: guardrails: - "You are fully autonomous - plan, execute, and verify independently" - "Use ARC workers for all parallelizable tasks" - "Leverage memories extensively for context" - "Iterate until the task is completely done" PRESET_EOF ;; *) # Default/feature preset cat << 'PRESET_EOF' # Default Feature Development Preset event_loop: completion_promise: "TASK_COMPLETE" max_iterations: 100 PRESET_EOF ;; esac } # ============================================================================ # ARC PROTOCOL INTEGRATION # ============================================================================ start_arc() { if [[ "$ARC_ENABLED" != "true" ]]; then log INFO "ARC Protocol disabled" return 0 fi # Check if ARC is already running if [[ -f "$ARC_LOCK_FILE" ]]; then local lock_pid lock_pid=$(cat "$ARC_LOCK_FILE" 2>/dev/null || echo "") if [[ -n "$lock_pid" ]] && kill -0 "$lock_pid" 2>/dev/null; then log INFO "ARC already running (PID: ${lock_pid})" return 0 else log WARN "ARC lock file exists but process dead, cleaning up" rm -f "$ARC_LOCK_FILE" "$ARC_PID_FILE" fi fi # Setup ARC directory setup_arc_directory # Start ARC dashboard log INFO "Starting ARC Protocol..." cd "$ARC_DIR" # Start the ARC dashboard in background nohup python3 ./dash >> "$ARC_LOG_FILE" 2>&1 & local arc_pid=$! echo "$arc_pid" > "$ARC_PID_FILE" echo "$arc_pid" > "$ARC_LOCK_FILE" log INFO "ARC started (PID: ${arc_pid})" log INFO "Dashboard: http://localhost:37373" echo "🚀 ARC Protocol: ACTIVE" >&2 echo " PID: ${arc_pid}" >&2 echo " Dashboard: http://localhost:37373" >&2 } setup_arc_directory() { # Create .arc structure if it doesn't exist mkdir -p "${ARC_DIR}/.arc/"{planning,archive,templates} # Initialize ARC state cat > "$ARC_CONFIG" << EOF # ARC Protocol State # Initialized: $(date -u +"%Y-%m-%d %H:%M:%S UTC") **Status:** Active **Mode:** Integrated with Ralph Orchestrator **Workspace:** ${ARC_DIR} ## Integration This ARC instance is managed by Ralph Orchestrator. All worker dispatch and coordination happens through Ralph. ## Available Commands - Check status: cat ${ARC_CONFIG} - View logs: tail -f ${ARC_LOG_FILE} - Stop integration: kill \$(cat ${ARC_LOCK_FILE}) EOF log INFO "ARC directory initialized: ${ARC_DIR}" } # ============================================================================ # CLAUDE-MEM INTEGRATION # ============================================================================ check_claude_mem() { if [[ "$CLAUDE_MEM_ENABLED" != "true" ]]; then return 0 fi # Check if claude-mem plugin is installed local mem_plugin="${CLAUDE_DIR}/plugins/cache/thedotmack/claude-mem" if [[ -d "$mem_plugin" ]]; then log INFO "claude-mem plugin found" return 0 else log WARN "claude-mem not installed" echo "📦 claude-mem: NOT INSTALLED" >&2 echo " Install: /plugin marketplace add thedotmack/claude-mem" >&2 return 1 fi } # ============================================================================ # MAIN EXECUTION # ============================================================================ main() { # Exit if disabled if [[ "$RALPH_MODE" == "off" ]]; then log INFO "Integration disabled (RALPH_MODE=off)" exit 0 fi # Determine if we should trigger local should_trigger=false case "$RALPH_MODE" in "always") should_trigger=true ;; "agents") # Detect agent or development keywords if echo "$USER_PROMPT" | grep -qiE "build|create|implement|develop|fix|add|refactor|optimize|write|generate|delegate|autonomous|agent|task|feature|pr"; then should_trigger=true fi ;; esac if [[ "$should_trigger" == true ]]; then # Start components check_claude_mem start_arc start_ralph "$USER_PROMPT" log INFO "Integration complete" else log INFO "Skipped (no trigger keywords)" fi # Always exit immediately (non-blocking) exit 0 } # Run main function main "$@"