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:
Test User 2026-01-21 21:42:29 -07:00
commit 761db2f67d
7 changed files with 111 additions and 26 deletions

View file

@ -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.
**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
@ -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_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_exit_detection.bats` | 20 | Exit signal detection |
| `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", ...}, ... ]`
- Extracts `result` type message from array and normalizes to object format
- Preserves `session_id` from init message for session continuity
- Added 8 new tests for JSON array format handling
- Test count: 318 (up from 310)
- Added 9 new tests for JSON array format handling (including session_id-in-result regression test)
- 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
- **Breaking**: Moved all Ralph-specific files to `.ralph/` subfolder

View file

@ -2,8 +2,8 @@
[![CI](https://github.com/frankbria/ralph-claude-code/actions/workflows/test.yml/badge.svg)](https://github.com/frankbria/ralph-claude-code/actions/workflows/test.yml)
[![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)
![Version](https://img.shields.io/badge/version-0.10.0-blue)
![Tests](https://img.shields.io/badge/tests-308%20passing-green)
![Version](https://img.shields.io/badge/version-0.10.1-blue)
![Tests](https://img.shields.io/badge/tests-310%20passing-green)
[![GitHub Issues](https://img.shields.io/github/issues/frankbria/ralph-claude-code)](https://github.com/frankbria/ralph-claude-code/issues)
[![Mentioned in Awesome Claude Code](https://awesome.re/mentioned-badge.svg)](https://github.com/hesreallyhim/awesome-claude-code)
[![Follow on X](https://img.shields.io/twitter/follow/FrankBria18044?style=social)](https://x.com/FrankBria18044)
@ -16,9 +16,9 @@ Ralph is an implementation of the Geoffrey Huntley's technique for Claude Code t
## Project Status
**Version**: v0.10.0 - Active Development
**Version**: v0.10.1 - Active Development
**Core Features**: Working and tested
**Test Coverage**: 308 tests, 100% pass rate
**Test Coverage**: 310 tests, 100% pass rate
### What's Working Now
- 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
- **CI/CD pipeline with GitHub Actions**
- **Dedicated uninstall script for clean removal**
- 308 passing tests across 11 test files
- 310 passing tests across 11 test files
### 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)**
- **Breaking**: Moved all Ralph-specific files to `.ralph/` subfolder
- Project root stays clean: only `src/`, `README.md`, and user files remain
@ -584,7 +600,7 @@ Shows real-time:
ralph --status
# Manual log inspection
tail -f logs/ralph.log
tail -f .ralph/logs/ralph.log
```
### Common Issues

View file

@ -204,9 +204,10 @@ should_exit_gracefully() {
fi
# 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
local total_items=$(grep -c "^- \[" "$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 total_items=$(grep -cE "^[[:space:]]*- \[" "$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
log_status "WARN" "Exit condition: All fix_plan.md items completed ($completed_items/$total_items)"

View file

@ -84,14 +84,25 @@ parse_json_response() {
# This contains: result, session_id, is_error, duration_ms, etc.
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)
# Build normalized object merging result with session_id
if [[ -n "$init_session_id" && "$init_session_id" != "null" ]]; then
echo "$result_obj" | jq --arg sid "$init_session_id" '. + {sessionId: $sid}' > "$normalized_file"
# Prioritize result object's own session_id, then fall back to init message (review fix: CodeRabbit)
# This prevents session ID loss when arrays lack an init message with session_id
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
echo "$result_obj" > "$normalized_file"
echo "$result_obj" | jq 'del(.session_id)' > "$normalized_file"
fi
# Use normalized file for subsequent parsing
@ -114,6 +125,27 @@ parse_json_response() {
# Exit signal: from flat format OR derived from completion_status
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
local work_type=$(jq -r '.work_type // "UNKNOWN"' "$output_file" 2>/dev/null)

View file

@ -313,7 +313,17 @@ should_exit_gracefully() {
return 0
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
# but Claude explicitly indicates work is still in progress via RALPH_STATUS block.
# 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
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
local total_items=$(grep -c "^- \[" "$RALPH_DIR/@fix_plan.md" 2>/dev/null)
local completed_items=$(grep -c "^- \[x\]" "$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 -cE "^[[:space:]]*- \[x\]" "$RALPH_DIR/@fix_plan.md" 2>/dev/null)
# Handle case where grep returns no matches (exit code 1)
[[ -z "$total_items" ]] && total_items=0
@ -445,8 +456,9 @@ build_loop_context() {
context="Loop #${loop_count}. "
# 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
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}. "
fi

View file

@ -3,8 +3,8 @@
# Ralph Status Monitor - Live terminal dashboard for the Ralph loop
set -e
STATUS_FILE="status.json"
LOG_FILE="logs/ralph.log"
STATUS_FILE=".ralph/status.json"
LOG_FILE=".ralph/logs/ralph.log"
REFRESH_INTERVAL=2
# Colors
@ -74,8 +74,8 @@ display_status() {
fi
# Claude Code Progress section
if [[ -f "progress.json" ]]; then
local progress_data=$(cat "progress.json" 2>/dev/null)
if [[ -f ".ralph/progress.json" ]]; then
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")
if [[ "$progress_status" == "executing" ]]; then

View file

@ -896,3 +896,26 @@ EOF
local stored_session=$(cat "$RALPH_DIR/.claude_session_id")
[[ "$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"
}