Merge main and apply code review fixes
Merged origin/main into PR branch and applied review feedback: Review fixes: - Guard against empty result_obj if jq fails (Macroscope) - Prioritize result object's session_id over init message (CodeRabbit) - Add regression test for arrays with session_id only in result element Resolved conflicts: - CLAUDE.md: Updated test count to 319 All 319 tests pass.
This commit is contained in:
commit
761db2f67d
7 changed files with 111 additions and 26 deletions
|
|
@ -6,7 +6,7 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co
|
||||||
|
|
||||||
This is the Ralph for Claude Code repository - an autonomous AI development loop system that enables continuous development cycles with intelligent exit detection and rate limiting.
|
This is the Ralph for Claude Code repository - an autonomous AI development loop system that enables continuous development cycles with intelligent exit detection and rate limiting.
|
||||||
|
|
||||||
**Version**: v0.10.1 | **Tests**: 318 passing (100% pass rate) | **CI/CD**: GitHub Actions
|
**Version**: v0.10.1 | **Tests**: 319 passing (100% pass rate) | **CI/CD**: GitHub Actions
|
||||||
|
|
||||||
## Core Architecture
|
## Core Architecture
|
||||||
|
|
||||||
|
|
@ -357,7 +357,7 @@ Ralph uses advanced error detection with two-stage filtering to eliminate false
|
||||||
|------|-------|-------------|
|
|------|-------|-------------|
|
||||||
| `test_cli_parsing.bats` | 27 | CLI argument parsing for all 12 flags |
|
| `test_cli_parsing.bats` | 27 | CLI argument parsing for all 12 flags |
|
||||||
| `test_cli_modern.bats` | 29 | Modern CLI commands (Phase 1.1) + build_claude_command fix |
|
| `test_cli_modern.bats` | 29 | Modern CLI commands (Phase 1.1) + build_claude_command fix |
|
||||||
| `test_json_parsing.bats` | 44 | JSON output format parsing + Claude CLI format + session management + array format |
|
| `test_json_parsing.bats` | 45 | JSON output format parsing + Claude CLI format + session management + array format |
|
||||||
| `test_session_continuity.bats` | 26 | Session lifecycle management + circuit breaker integration |
|
| `test_session_continuity.bats` | 26 | Session lifecycle management + circuit breaker integration |
|
||||||
| `test_exit_detection.bats` | 20 | Exit signal detection |
|
| `test_exit_detection.bats` | 20 | Exit signal detection |
|
||||||
| `test_rate_limiting.bats` | 15 | Rate limiting behavior |
|
| `test_rate_limiting.bats` | 15 | Rate limiting behavior |
|
||||||
|
|
@ -390,8 +390,9 @@ bats tests/unit/test_cli_parsing.bats
|
||||||
3. Claude CLI array format: `[ {type: "result", ...}, ... ]`
|
3. Claude CLI array format: `[ {type: "result", ...}, ... ]`
|
||||||
- Extracts `result` type message from array and normalizes to object format
|
- Extracts `result` type message from array and normalizes to object format
|
||||||
- Preserves `session_id` from init message for session continuity
|
- Preserves `session_id` from init message for session continuity
|
||||||
- Added 8 new tests for JSON array format handling
|
- Added 9 new tests for JSON array format handling (including session_id-in-result regression test)
|
||||||
- Test count: 318 (up from 310)
|
- Review fixes: guard against empty result_obj, prioritize result object's session_id
|
||||||
|
- Test count: 319 (up from 310)
|
||||||
|
|
||||||
### .ralph/ Subfolder Structure (v0.10.0) - BREAKING CHANGE
|
### .ralph/ Subfolder Structure (v0.10.0) - BREAKING CHANGE
|
||||||
- **Breaking**: Moved all Ralph-specific files to `.ralph/` subfolder
|
- **Breaking**: Moved all Ralph-specific files to `.ralph/` subfolder
|
||||||
|
|
|
||||||
28
README.md
28
README.md
|
|
@ -2,8 +2,8 @@
|
||||||
|
|
||||||
[](https://github.com/frankbria/ralph-claude-code/actions/workflows/test.yml)
|
[](https://github.com/frankbria/ralph-claude-code/actions/workflows/test.yml)
|
||||||
[](LICENSE)
|
[](LICENSE)
|
||||||

|

|
||||||

|

|
||||||
[](https://github.com/frankbria/ralph-claude-code/issues)
|
[](https://github.com/frankbria/ralph-claude-code/issues)
|
||||||
[](https://github.com/hesreallyhim/awesome-claude-code)
|
[](https://github.com/hesreallyhim/awesome-claude-code)
|
||||||
[](https://x.com/FrankBria18044)
|
[](https://x.com/FrankBria18044)
|
||||||
|
|
@ -16,9 +16,9 @@ Ralph is an implementation of the Geoffrey Huntley's technique for Claude Code t
|
||||||
|
|
||||||
## Project Status
|
## Project Status
|
||||||
|
|
||||||
**Version**: v0.10.0 - Active Development
|
**Version**: v0.10.1 - Active Development
|
||||||
**Core Features**: Working and tested
|
**Core Features**: Working and tested
|
||||||
**Test Coverage**: 308 tests, 100% pass rate
|
**Test Coverage**: 310 tests, 100% pass rate
|
||||||
|
|
||||||
### What's Working Now
|
### What's Working Now
|
||||||
- Autonomous development loops with intelligent exit detection
|
- Autonomous development loops with intelligent exit detection
|
||||||
|
|
@ -36,10 +36,26 @@ Ralph is an implementation of the Geoffrey Huntley's technique for Claude Code t
|
||||||
- PRD import functionality
|
- PRD import functionality
|
||||||
- **CI/CD pipeline with GitHub Actions**
|
- **CI/CD pipeline with GitHub Actions**
|
||||||
- **Dedicated uninstall script for clean removal**
|
- **Dedicated uninstall script for clean removal**
|
||||||
- 308 passing tests across 11 test files
|
- 310 passing tests across 11 test files
|
||||||
|
|
||||||
### Recent Improvements
|
### Recent Improvements
|
||||||
|
|
||||||
|
**v0.10.1 - Bug Fixes & Monitor Path Corrections**
|
||||||
|
- Fixed `ralph_monitor.sh` hardcoded paths for v0.10.0 compatibility:
|
||||||
|
- `STATUS_FILE`: `status.json` → `.ralph/status.json`
|
||||||
|
- `LOG_FILE`: `logs/ralph.log` → `.ralph/logs/ralph.log`
|
||||||
|
- `progress.json` → `.ralph/progress.json`
|
||||||
|
- Fixed EXIT_SIGNAL parsing in JSON format (Bug #1):
|
||||||
|
- Now extracts `EXIT_SIGNAL` from `.result` field when Claude CLI returns JSON
|
||||||
|
- Properly detects RALPH_STATUS blocks embedded in JSON response text
|
||||||
|
- Added safety circuit breaker (Bug #2):
|
||||||
|
- Force exit after 5 consecutive completion indicators (prevents infinite loops)
|
||||||
|
- Higher threshold than normal (2) to avoid false positives while preventing API waste
|
||||||
|
- Fixed checkbox parsing for indented markdown (Bug #3):
|
||||||
|
- Changed patterns from `^- \[` to `^[[:space:]]*- \[` (POSIX-compliant)
|
||||||
|
- Supports indented checkboxes in `@fix_plan.md`
|
||||||
|
- Updated README.md documentation example for new log path
|
||||||
|
|
||||||
**v0.10.0 - .ralph/ Subfolder Structure (BREAKING CHANGE)**
|
**v0.10.0 - .ralph/ Subfolder Structure (BREAKING CHANGE)**
|
||||||
- **Breaking**: Moved all Ralph-specific files to `.ralph/` subfolder
|
- **Breaking**: Moved all Ralph-specific files to `.ralph/` subfolder
|
||||||
- Project root stays clean: only `src/`, `README.md`, and user files remain
|
- Project root stays clean: only `src/`, `README.md`, and user files remain
|
||||||
|
|
@ -584,7 +600,7 @@ Shows real-time:
|
||||||
ralph --status
|
ralph --status
|
||||||
|
|
||||||
# Manual log inspection
|
# Manual log inspection
|
||||||
tail -f logs/ralph.log
|
tail -f .ralph/logs/ralph.log
|
||||||
```
|
```
|
||||||
|
|
||||||
### Common Issues
|
### Common Issues
|
||||||
|
|
|
||||||
|
|
@ -204,9 +204,10 @@ should_exit_gracefully() {
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# 4. Check fix_plan.md for completion
|
# 4. Check fix_plan.md for completion
|
||||||
|
# Bug #3 Fix: Support indented markdown checkboxes with [[:space:]]* pattern
|
||||||
if [[ -f "$RALPH_DIR/@fix_plan.md" ]]; then
|
if [[ -f "$RALPH_DIR/@fix_plan.md" ]]; then
|
||||||
local total_items=$(grep -c "^- \[" "$RALPH_DIR/@fix_plan.md" 2>/dev/null || echo "0")
|
local total_items=$(grep -cE "^[[:space:]]*- \[" "$RALPH_DIR/@fix_plan.md" 2>/dev/null || echo "0")
|
||||||
local completed_items=$(grep -c "^- \[x\]" "$RALPH_DIR/@fix_plan.md" 2>/dev/null || echo "0")
|
local completed_items=$(grep -cE "^[[:space:]]*- \[x\]" "$RALPH_DIR/@fix_plan.md" 2>/dev/null || echo "0")
|
||||||
|
|
||||||
if [[ $total_items -gt 0 ]] && [[ $completed_items -eq $total_items ]]; then
|
if [[ $total_items -gt 0 ]] && [[ $completed_items -eq $total_items ]]; then
|
||||||
log_status "WARN" "Exit condition: All fix_plan.md items completed ($completed_items/$total_items)"
|
log_status "WARN" "Exit condition: All fix_plan.md items completed ($completed_items/$total_items)"
|
||||||
|
|
|
||||||
|
|
@ -84,14 +84,25 @@ parse_json_response() {
|
||||||
# This contains: result, session_id, is_error, duration_ms, etc.
|
# This contains: result, session_id, is_error, duration_ms, etc.
|
||||||
local result_obj=$(jq '[.[] | select(.type == "result")] | .[-1] // {}' "$output_file" 2>/dev/null)
|
local result_obj=$(jq '[.[] | select(.type == "result")] | .[-1] // {}' "$output_file" 2>/dev/null)
|
||||||
|
|
||||||
# Also extract session_id from init message if not in result object
|
# Guard against empty result_obj if jq fails (review fix: Macroscope)
|
||||||
|
[[ -z "$result_obj" ]] && result_obj="{}"
|
||||||
|
|
||||||
|
# Extract session_id from init message as fallback
|
||||||
local init_session_id=$(jq -r '.[] | select(.type == "system" and .subtype == "init") | .session_id // empty' "$output_file" 2>/dev/null | head -1)
|
local init_session_id=$(jq -r '.[] | select(.type == "system" and .subtype == "init") | .session_id // empty' "$output_file" 2>/dev/null | head -1)
|
||||||
|
|
||||||
# Build normalized object merging result with session_id
|
# Prioritize result object's own session_id, then fall back to init message (review fix: CodeRabbit)
|
||||||
if [[ -n "$init_session_id" && "$init_session_id" != "null" ]]; then
|
# This prevents session ID loss when arrays lack an init message with session_id
|
||||||
echo "$result_obj" | jq --arg sid "$init_session_id" '. + {sessionId: $sid}' > "$normalized_file"
|
local effective_session_id
|
||||||
|
effective_session_id=$(echo "$result_obj" | jq -r '.sessionId // .session_id // empty' 2>/dev/null)
|
||||||
|
if [[ -z "$effective_session_id" || "$effective_session_id" == "null" ]]; then
|
||||||
|
effective_session_id="$init_session_id"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Build normalized object merging result with effective session_id
|
||||||
|
if [[ -n "$effective_session_id" && "$effective_session_id" != "null" ]]; then
|
||||||
|
echo "$result_obj" | jq --arg sid "$effective_session_id" '. + {sessionId: $sid} | del(.session_id)' > "$normalized_file"
|
||||||
else
|
else
|
||||||
echo "$result_obj" > "$normalized_file"
|
echo "$result_obj" | jq 'del(.session_id)' > "$normalized_file"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Use normalized file for subsequent parsing
|
# Use normalized file for subsequent parsing
|
||||||
|
|
@ -114,6 +125,27 @@ parse_json_response() {
|
||||||
# Exit signal: from flat format OR derived from completion_status
|
# Exit signal: from flat format OR derived from completion_status
|
||||||
local exit_signal=$(jq -r '.exit_signal // false' "$output_file" 2>/dev/null)
|
local exit_signal=$(jq -r '.exit_signal // false' "$output_file" 2>/dev/null)
|
||||||
|
|
||||||
|
# Bug #1 Fix: If exit_signal is still false, check for RALPH_STATUS block in .result field
|
||||||
|
# Claude CLI JSON format embeds the RALPH_STATUS block within the .result text field
|
||||||
|
if [[ "$exit_signal" == "false" && "$has_result_field" == "true" ]]; then
|
||||||
|
local result_text=$(jq -r '.result // ""' "$output_file" 2>/dev/null)
|
||||||
|
if [[ -n "$result_text" ]] && echo "$result_text" | grep -q -- "---RALPH_STATUS---"; then
|
||||||
|
# Extract EXIT_SIGNAL value from RALPH_STATUS block within result text
|
||||||
|
local embedded_exit_sig=$(echo "$result_text" | grep "EXIT_SIGNAL:" | cut -d: -f2 | xargs)
|
||||||
|
if [[ "$embedded_exit_sig" == "true" ]]; then
|
||||||
|
exit_signal="true"
|
||||||
|
[[ "${VERBOSE_PROGRESS:-}" == "true" ]] && echo "DEBUG: Extracted EXIT_SIGNAL=true from .result RALPH_STATUS block" >&2
|
||||||
|
fi
|
||||||
|
# Also check STATUS field as fallback
|
||||||
|
local embedded_status=$(echo "$result_text" | grep "STATUS:" | cut -d: -f2 | xargs)
|
||||||
|
if [[ "$embedded_status" == "COMPLETE" && "$exit_signal" != "true" ]]; then
|
||||||
|
# STATUS: COMPLETE without explicit EXIT_SIGNAL implies completion
|
||||||
|
exit_signal="true"
|
||||||
|
[[ "${VERBOSE_PROGRESS:-}" == "true" ]] && echo "DEBUG: Inferred EXIT_SIGNAL=true from .result STATUS=COMPLETE" >&2
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
# Work type: from flat format
|
# Work type: from flat format
|
||||||
local work_type=$(jq -r '.work_type // "UNKNOWN"' "$output_file" 2>/dev/null)
|
local work_type=$(jq -r '.work_type // "UNKNOWN"' "$output_file" 2>/dev/null)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -313,7 +313,17 @@ should_exit_gracefully() {
|
||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# 3. Strong completion indicators (only if Claude's EXIT_SIGNAL is true)
|
# 3. Safety circuit breaker - force exit after 5 consecutive completion indicators
|
||||||
|
# Bug #2 Fix: Prevents infinite loops when EXIT_SIGNAL is not explicitly set
|
||||||
|
# but completion patterns clearly indicate work is done. Threshold of 5 is higher
|
||||||
|
# than normal threshold (2) to avoid false positives while preventing API waste.
|
||||||
|
if [[ $recent_completion_indicators -ge 5 ]]; then
|
||||||
|
log_status "WARN" "🚨 SAFETY CIRCUIT BREAKER: Force exit after 5 consecutive completion indicators ($recent_completion_indicators)" >&2
|
||||||
|
echo "safety_circuit_breaker"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# 4. Strong completion indicators (only if Claude's EXIT_SIGNAL is true)
|
||||||
# This prevents premature exits when heuristics detect completion patterns
|
# This prevents premature exits when heuristics detect completion patterns
|
||||||
# but Claude explicitly indicates work is still in progress via RALPH_STATUS block.
|
# but Claude explicitly indicates work is still in progress via RALPH_STATUS block.
|
||||||
# The exit_signal in .response_analysis represents Claude's explicit intent.
|
# The exit_signal in .response_analysis represents Claude's explicit intent.
|
||||||
|
|
@ -330,10 +340,11 @@ should_exit_gracefully() {
|
||||||
log_status "INFO" "DEBUG: Completion indicators ($recent_completion_indicators) present but EXIT_SIGNAL=false, continuing..." >&2
|
log_status "INFO" "DEBUG: Completion indicators ($recent_completion_indicators) present but EXIT_SIGNAL=false, continuing..." >&2
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# 4. Check fix_plan.md for completion
|
# 5. Check fix_plan.md for completion
|
||||||
|
# Bug #3 Fix: Support indented markdown checkboxes with [[:space:]]* pattern
|
||||||
if [[ -f "$RALPH_DIR/@fix_plan.md" ]]; then
|
if [[ -f "$RALPH_DIR/@fix_plan.md" ]]; then
|
||||||
local total_items=$(grep -c "^- \[" "$RALPH_DIR/@fix_plan.md" 2>/dev/null)
|
local total_items=$(grep -cE "^[[:space:]]*- \[" "$RALPH_DIR/@fix_plan.md" 2>/dev/null)
|
||||||
local completed_items=$(grep -c "^- \[x\]" "$RALPH_DIR/@fix_plan.md" 2>/dev/null)
|
local completed_items=$(grep -cE "^[[:space:]]*- \[x\]" "$RALPH_DIR/@fix_plan.md" 2>/dev/null)
|
||||||
|
|
||||||
# Handle case where grep returns no matches (exit code 1)
|
# Handle case where grep returns no matches (exit code 1)
|
||||||
[[ -z "$total_items" ]] && total_items=0
|
[[ -z "$total_items" ]] && total_items=0
|
||||||
|
|
@ -445,8 +456,9 @@ build_loop_context() {
|
||||||
context="Loop #${loop_count}. "
|
context="Loop #${loop_count}. "
|
||||||
|
|
||||||
# Extract incomplete tasks from @fix_plan.md
|
# Extract incomplete tasks from @fix_plan.md
|
||||||
|
# Bug #3 Fix: Support indented markdown checkboxes with [[:space:]]* pattern
|
||||||
if [[ -f "$RALPH_DIR/@fix_plan.md" ]]; then
|
if [[ -f "$RALPH_DIR/@fix_plan.md" ]]; then
|
||||||
local incomplete_tasks=$(grep -c "^- \[ \]" "$RALPH_DIR/@fix_plan.md" 2>/dev/null || echo "0")
|
local incomplete_tasks=$(grep -cE "^[[:space:]]*- \[ \]" "$RALPH_DIR/@fix_plan.md" 2>/dev/null || echo "0")
|
||||||
context+="Remaining tasks: ${incomplete_tasks}. "
|
context+="Remaining tasks: ${incomplete_tasks}. "
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,8 +3,8 @@
|
||||||
# Ralph Status Monitor - Live terminal dashboard for the Ralph loop
|
# Ralph Status Monitor - Live terminal dashboard for the Ralph loop
|
||||||
set -e
|
set -e
|
||||||
|
|
||||||
STATUS_FILE="status.json"
|
STATUS_FILE=".ralph/status.json"
|
||||||
LOG_FILE="logs/ralph.log"
|
LOG_FILE=".ralph/logs/ralph.log"
|
||||||
REFRESH_INTERVAL=2
|
REFRESH_INTERVAL=2
|
||||||
|
|
||||||
# Colors
|
# Colors
|
||||||
|
|
@ -74,8 +74,8 @@ display_status() {
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Claude Code Progress section
|
# Claude Code Progress section
|
||||||
if [[ -f "progress.json" ]]; then
|
if [[ -f ".ralph/progress.json" ]]; then
|
||||||
local progress_data=$(cat "progress.json" 2>/dev/null)
|
local progress_data=$(cat ".ralph/progress.json" 2>/dev/null)
|
||||||
local progress_status=$(echo "$progress_data" | jq -r '.status // "idle"' 2>/dev/null || echo "idle")
|
local progress_status=$(echo "$progress_data" | jq -r '.status // "idle"' 2>/dev/null || echo "idle")
|
||||||
|
|
||||||
if [[ "$progress_status" == "executing" ]]; then
|
if [[ "$progress_status" == "executing" ]]; then
|
||||||
|
|
|
||||||
|
|
@ -896,3 +896,26 @@ EOF
|
||||||
local stored_session=$(cat "$RALPH_DIR/.claude_session_id")
|
local stored_session=$(cat "$RALPH_DIR/.claude_session_id")
|
||||||
[[ "$stored_session" == *"session-persist-array-test"* ]]
|
[[ "$stored_session" == *"session-persist-array-test"* ]]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Regression test: arrays where only result element carries session_id (review fix: CodeRabbit)
|
||||||
|
@test "parse_json_response extracts session_id from result object when no init message" {
|
||||||
|
local output_file="$LOG_DIR/test_output.log"
|
||||||
|
|
||||||
|
# Array with session_id only in result object, no init message
|
||||||
|
cat > "$output_file" << 'EOF'
|
||||||
|
[
|
||||||
|
{"type": "assistant", "message": {"content": [{"type": "text", "text": "Working..."}]}},
|
||||||
|
{"type": "result", "subtype": "success", "result": "Task complete.", "session_id": "session-in-result-only"}
|
||||||
|
]
|
||||||
|
EOF
|
||||||
|
|
||||||
|
run parse_json_response "$output_file"
|
||||||
|
assert_equal "$status" "0"
|
||||||
|
|
||||||
|
local result_file="$RALPH_DIR/.json_parse_result"
|
||||||
|
[[ -f "$result_file" ]]
|
||||||
|
|
||||||
|
# Session ID should be extracted from result object
|
||||||
|
local session_id=$(jq -r '.session_id' "$result_file")
|
||||||
|
assert_equal "$session_id" "session-in-result-only"
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue