Logo
热心市民王先生

实施方案详解

方案 A(原生集成)与方案 B(外部循环控制)的详细实施步骤、代码示例、跨平台适配策略及各平台具体实施指南

1. 方案概述

方案适用平台实施周期复杂度可控性
方案 A:原生集成Claude Code(确认支持 Stop Hook)
OpenCode(验证后)
1-2 天
方案 B:外部循环所有平台(通用方案)5-7 天

2. 方案 A:原生集成

2.1 适用条件

必须满足

  1. 平台原生支持 Stop Hook 机制
  2. 支持 --continue 或等效会话保持功能
  3. 支持 JSON 或结构化输出(可选但推荐)

2.2 实施步骤

步骤 1:下载并适配 Ralph

cd ~
git clone https://github.com/frankbria/ralph-claude-code.git
cd ralph-claude-code
git checkout -b platform-adaptation
cp ralph_loop.sh ralph_loop.sh.original

步骤 2:修改 Ralph 配置

# 修改 ralph_loop.sh
build_opencode_command() {
    local prompt="$1"
    local cmd="opencode"
    cmd="$cmd --continue-session"
    cmd="$cmd -p \"$prompt\""
    echo "$cmd"
}

步骤 3:创建 Stop Hook 钩子

mkdir -p hooks
cat > hooks/stop-hook.sh << 'EOF'
#!/bin/bash
set -euo pipefail

OUTPUT_FILE="$1"
MIN_COMPLETION_INDICATORS=2
DEBUG=${DEBUG:-false}

log() {
    if [ "$DEBUG" = "true" ]; then
        echo "[$(date)] [HOOK] $*" >&2
    fi
}

main() {
    local output_file="$1"
    log "Stop Hook triggered"

    if [ ! -f "$output_file" ]; then
        log "Output file not found"
        exit 0
    fi

    local output_content=$(cat "$output_file")
    local completion_indicators=$(echo "$output_content" | \
        grep -oEi "complete|done|finished|ready|implemented" | wc -l)
    
    local exit_signal="false"
    if echo "$output_content" | grep -qE '"EXIT_SIGNAL":\s*true'; then
        exit_signal="true"
    fi

    log "Indicators: $completion_indicators, Signal: $exit_signal"

    if [ "$completion_indicators" -ge "$MIN_COMPLETION_INDICATORS" ] && \
       [ "$exit_signal" = "true" ]; then
        log "✅ Completion met, allowing exit"
        exit 0
    else
        log "❌ Completion not met, blocking exit"
        exit 2
    fi
}

main "$@"
EOF
chmod +x hooks/stop-hook.sh

步骤 4-7:初始化项目、配置、启动循环

# 4. 初始化项目
ralph-setup my-research-project
cd my-research-project

# 5. 配置 PROMPT.md、@fix_plan.md、@AGENT.md
# (详见下文配置示例)

# 6. 启动循环
ralph --monitor

# 7. 监控与调试
ralph-monitor
tail -f logs/ralph.log

2.3 项目结构

my-research-project/
├── PROMPT.md              # 项目需求文档
├── @fix_plan.md          # 任务优先级列表
├── @AGENT.md             # 构建和运行说明
├── .ralph_session        # 当前会话 ID
├── .ralph_session_history # 会话转换历史
├── status.json           # 实时状态
├── hooks/
│   └── stop-hook.sh      # Stop Hook 钩子
├── src/                  # 项目源文件
├── logs/                 # 执行日志
└── docs/generated/       # 生成的文档

3. 方案 B:外部循环控制

3.1 适用场景

  • 平台不支持 Stop Hook 机制
  • 需要完全控制循环逻辑
  • 需要深度定制和集成

3.2 核心组件实现

组件 1:Shell 循环控制器

#!/bin/bash
# ralph-external-loop.sh

set -euo pipefail

MAX_ITERATIONS=${MAX_ITERATIONS:-50}
MAX_API_CALLS=${MAX_API_CALLS:-100}
HOURLY_LIMIT_DURATION=${HOURLY_LIMIT_DURATION:-3600}
LOOP_DELAY=${LOOP_DELAY:-5}

# 颜色定义
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
RED='\033[0;31m'
NC='\033[0m'

init() {
    echo -e "${GREEN}初始化 Ralph 外部循环...${NC}"
    
    [ ! -f .loop_count ] && echo 0 > .loop_count
    [ ! -f .api_calls ] && echo 0 > .api_calls
    [ ! -f .hour_limit_reset ] && echo $(($(date +%s) + HOURLY_LIMIT_DURATION)) > .hour_limit_reset
    
    mkdir -p outputs logs src
    
    if [ ! -f .current_status.json ]; then
        cat > .current_status.json << 'INIT'
{
  "loop_count": 0,
  "progress": 0,
  "current_task": "初始化项目",
  "completed_tasks": [],
  "pending_tasks": ["创建目录", "撰写背景", "文献综述"],
  "api_calls_this_hour": 0,
  "no_progress_count": 0
}
INIT
    fi
    
    echo -e "${GREEN}✅ 初始化完成${NC}"
}

read_status() {
    if [ -f .current_status.json ]; then
        cat .current_status.json
    else
        echo '{}'
    fi
}

update_status() {
    local status="$1"
    echo "$status" | jq '.last_update = "'"$(date -Iseconds)"'"' > .current_status.json
}

generate_prompt() {
    local status="$1"
    local loop_count=$(echo "$status" | jq -r '.loop_count // 0')
    local progress=$(echo "$status" | jq -r '.progress // 0')
    local current_task=$(echo "$status" | jq -r '.current_task // ""')
    local completed_tasks=$(echo "$status" | jq -r '.completed_tasks // []')
    local pending_tasks=$(echo "$status" | jq -r '.pending_tasks // []')

    cat << PROMPT
# 自动化任务 - 第 $((loop_count + 1)) 次迭代

## 当前状态
- 进度: ${progress}%
- 循环次数: $loop_count
- 当前任务: $current_task

## 已完成任务
$(if [ "$completed_tasks" != "[]" ]; then
    echo "$completed_tasks" | jq -r '.[]' | sed 's/^/  - /'
  else
    echo "  (无)"
fi)

## 待办任务(前 5 项)
$(if [ "$pending_tasks" != "[]" ]; then
    echo "$pending_tasks" | jq -r '.[]' | head -5 | sed 's/^/  - /'
  else
    echo "  (无)"
fi)

完成后使用以下格式报告进展:
\`\`\`
RALPH_UPDATE:
{
  "completed_task": "完成的任务名称",
  "next_task": "建议的下一个任务",
  "progress": 新的进度百分比(0-100)
}
\`\`\`

如果所有任务完成,添加: <<COMPLETED>>

## 原始需求
$(cat PROMPT.md 2>/dev/null || echo "请查看 PROMPT.md")
PROMPT
}

check_completion() {
    local output="$1"
    if echo "$output" | grep -q "<<COMPLETED>>"; then
        return 0
    fi
    local pending_count=$(read_status | jq '.pending_tasks | length')
    if [ "$pending_count" -eq 0 ]; then
        return 0
    fi
    return 1
}

parse_and_update_status() {
    local output="$1"
    local current_status="$2"
    
    local update_block=$(echo "$output" | grep -A 10 "RALPH_UPDATE:" | tail -n +2)
    
    if [ -n "$update_block" ]; then
        local completed_task=$(echo "$update_block" | jq -r '.completed_task // ""' 2>/dev/null)
        local next_task=$(echo "$update_block" | jq -r '.next_task // ""' 2>/dev/null)
        local new_progress=$(echo "$update_block" | jq -r '.progress // 0' 2>/dev/null)

        local updated_status="$current_status"

        if [ -n "$completed_task" ] && [ "$completed_task" != "null" ]; then
            updated_status=$(echo "$updated_status" | \
                jq --arg ct "$completed_task" '.completed_tasks += [$ct]' | \
                jq --arg ct "$completed_task" '.pending_tasks -= [$ct]')
        fi

        if [ -n "$next_task" ] && [ "$next_task" != "null" ]; then
            updated_status=$(echo "$updated_status" | \
                jq --arg nt "$next_task" '.current_task = $nt')
        fi

        if [ "$new_progress" -gt 0 ] 2>/dev/null; then
            updated_status=$(echo "$updated_status" | \
                jq --argjson lp "$new_progress" '.progress = $lp')
        fi

        local current_loop=$(echo "$updated_status" | jq -r '.loop_count // 0')
        updated_status=$(echo "$updated_status" | \
            jq --argjson lc "$((current_loop + 1))" '.loop_count = $lc')

        echo "$updated_status"
    else
        echo "$current_status" | jq '.loop_count += 1'
    fi
}

check_rate_limit() {
    local api_calls=$(cat .api_calls)
    local hour_reset=$(cat .hour_limit_reset)
    local current_time=$(date +%s)

    if [ "$api_calls" -ge "$MAX_API_CALLS" ]; then
        if [ "$current_time" -lt "$hour_reset" ]; then
            local wait_seconds=$((hour_reset - current_time))
            echo -e "${YELLOW}达到 API 速率限制,等待 $wait_seconds 秒...${NC}"
            sleep "$wait_seconds"
            echo 0 > .api_calls
            echo $(($(date +%s) + HOURLY_LIMIT_DURATION)) > .hour_limit_reset
        fi
    fi
}

main_loop() {
    init
    
    echo -e "${GREEN}═══════════════════════════════════════${NC}"
    echo -e "${GREEN}       Ralph 外部循环启动${NC}"
    echo -e "${GREEN}═══════════════════════════════════════${NC}"
    echo ""

    while true; do
        local loop_count=$(cat .loop_count)

        if [ "$loop_count" -ge "$MAX_ITERATIONS" ]; then
            echo -e "${RED}达到最大循环次数 ($MAX_ITERATIONS),退出${NC}"
            break
        fi

        check_rate_limit

        local current_status=$(read_status)
        local prompt=$(generate_prompt "$current_status")
        
        local current_task=$(echo "$current_status" | jq -r '.current_task // "未知"')
        local progress=$(echo "$current_status" | jq -r '.progress // 0')
        
        echo -e "${GREEN}[循环 $((loop_count + 1))/$MAX_ITERATIONS]${NC} 进度: ${progress}% | 任务: $current_task"

        # 执行 AI 调用
        local output
        if command -v opencode &> /dev/null; then
            output=$(opencode -p "$prompt" 2>&1)
        elif command -v claude &> /dev/null; then
            output=$(claude -p "$prompt" 2>&1)
        else
            echo -e "${RED}错误:未找到支持的 AI 工具${NC}"
            exit 1
        fi

        echo "$output" > "outputs/loop_$(printf '%04d' $loop_count).txt"

        local api_calls=$(cat .api_calls)
        ((api_calls++))
        echo "$api_calls" > .api_calls

        local updated_status=$(parse_and_update_status "$output" "$(read_status)")
        update_status "$updated_status"

        if check_completion "$output"; then
            echo ""
            echo -e "${GREEN}✅ 任务完成!${NC}"
            break
        fi

        ((loop_count++))
        echo "$loop_count" > .loop_count

        echo -e "${YELLOW}等待 $LOOP_DELAY 秒...${NC}"
        sleep "$LOOP_DELAY"
        echo ""
    done

    echo ""
    echo -e "${GREEN}循环结束,总迭代次数: $loop_count${NC}"
    cat .current_status.json | jq '.'
}

main

4. 跨平台适配策略

4.1 平台检测脚本

#!/bin/bash
# detect_platform.sh

detect_platform() {
    if command -v claude &> /dev/null; then
        echo "claude-code"
        return 0
    fi
    
    if command -v opencode &> /dev/null; then
        echo "opencode"
        return 0
    fi
    
    if command -v codebuddy &> /dev/null; then
        echo "codebuddy"
        return 0
    fi
    
    echo "unknown"
    return 1
}

detect_stop_hook() {
    local platform="$1"
    
    case "$platform" in
        "claude-code")
            echo "yes"
            ;;
        "opencode")
            echo "unknown"
            ;;
        *)
            echo "no"
            ;;
    esac
}

main() {
    local platform=$(detect_platform)
    echo "检测到的平台: $platform"
    
    if [ "$platform" != "unknown" ]; then
        local stop_hook=$(detect_stop_hook "$platform")
        echo "Stop Hook 支持: $stop_hook"
        
        if [ "$stop_hook" = "yes" ]; then
            echo "推荐方案: A(原生集成)"
        else
            echo "推荐方案: B(外部循环)"
        fi
    fi
}

main

4.2 统一的 AI 调用接口

#!/bin/bash
# ai_adapter.sh

call_ai() {
    local prompt="$1"
    local platform="${AI_PLATFORM:-$(detect_platform)}"
    
    case "$platform" in
        "claude-code")
            claude -p "$prompt"
            ;;
        "opencode")
            opencode -p "$prompt"
            ;;
        "codebuddy")
            codebuddy --prompt "$prompt"
            ;;
        *)
            echo "错误:未知的平台 '$platform'" >&2
            return 1
            ;;
    esac
}

detect_platform() {
    if command -v claude &> /dev/null; then
        echo "claude-code"
    elif command -v opencode &> /dev/null; then
        echo "opencode"
    elif command -v codebuddy &> /dev/null; then
        echo "codebuddy"
    else
        echo "unknown"
    fi
}

5. 方案对比

维度方案 A(原生集成)方案 B(外部循环)
开发工作量低(1-2 天)高(5-7 天)
可靠性高(已验证)中(需测试)
依赖条件OpenCode 支持 Stop Hook无特殊依赖
功能完整性完整(所有 Ralph 特性)部分(需自行实现)
维护成本低(跟随上游更新)高(自主维护)
灵活性中(受限于 Ralph 设计)高(完全自定义)

6. 推荐实施路径

阶段 1:验证(1 天)

  • 执行 Blackbox 测试验证平台能力
  • 确认 Stop Hook 和会话管理支持情况

阶段 2:MVP(2-3 天)

  • 根据验证结果选择方案 A 或 B
  • 实现核心循环功能

阶段 3:生产化(1-2 周)

  • 添加监控和日志分析
  • 优化提示词
  • 集成到现有工作流