feat(structure): migrate Ralph files to .ralph/ subfolder (#109)

* feat(structure): migrate Ralph files to .ralph/ subfolder

BREAKING CHANGE: Ralph configuration files now live in .ralph/ subfolder

This refactoring moves all Ralph-specific files into a hidden .ralph/
directory while keeping src/ at the project root. This improves
compatibility with existing tooling and keeps the project root clean.

Changes:
- Move PROMPT.md, @fix_plan.md, @AGENT.md to .ralph/
- Move specs/, logs/, docs/generated/, examples/ to .ralph/
- Move state files (.response_analysis, .circuit_breaker_state, etc.) to .ralph/
- Keep src/ at project root (unchanged)
- Add RALPH_DIR=".ralph" configuration variable
- Add ralph-migrate command for existing projects
- Create migrate_to_ralph_folder.sh migration script
- Update all path references in scripts and tests
- Update documentation (README.md, CLAUDE.md)

New project structure:
  project/
  ├── .ralph/           # Ralph configuration
  │   ├── PROMPT.md
  │   ├── @fix_plan.md
  │   ├── @AGENT.md
  │   ├── specs/
  │   ├── logs/
  │   └── docs/generated/
  └── src/              # Source code (unchanged)

Migration: Run `ralph-migrate` in existing projects to upgrade.

All 310 tests pass (100% pass rate).

* chore: add .claude/settings.local.json to .gitignore

* fix: address code review feedback for .ralph/ subfolder structure

Fixes multiple path-related issues identified in code review:

Test fixes:
- Fix create_sample_prompt to use $RALPH_DIR/PROMPT.md in test_session_continuity.bats
- Fix result_file path to use $RALPH_DIR/.json_parse_result in test_json_parsing.bats
- Fix @fix_plan.md and .response_analysis paths in test_cli_modern.bats
- Update templates directory missing test to account for global fallback

Template fix:
- Fix @fix_plan.md reference in templates/PROMPT.md to use .ralph/ prefix

Script fixes:
- Fix PROMPT_FILE comparison in ralph_loop.sh to use $RALPH_DIR/PROMPT.md
- Fix examples migration logic in migrate_to_ralph_folder.sh (remove premature mkdir)
- Move templates directory check AFTER cd in setup.sh (was checking wrong location)
- Add template directory validation with fallback to global templates

All 310 tests pass.

* Update migrate_to_ralph_folder.sh

Co-authored-by: macroscopeapp[bot] <170038800+macroscopeapp[bot]@users.noreply.github.com>

* fix: address code review feedback for .ralph/ subfolder structure

Code Review Fixes:
- Fix test_json_parsing.bats: all result_file and session file paths now use $RALPH_DIR prefix
- Fix ralph_loop.sh help text: paths now show .ralph/.ralph_session, .ralph/.call_count, etc.
- Fix migrate_to_ralph_folder.sh:
  - Proper error handling for date command (separate local declaration)
  - Use cp -a source/. dest/ pattern to preserve dotfiles and attributes
  - Remove 2>/dev/null suppression to surface copy errors
- Update create_files.sh to use .ralph/ structure for embedded scripts
- Update .gitignore with all .ralph/ state file paths
- Add old structure detection in ralph_loop.sh with helpful migration message

Version Update:
- Bump to v0.10.0 (breaking change: structural reorganization)
- Update README.md and CLAUDE.md with new version and release notes
- Add ralph-migrate documentation to Key Commands section

All 310 tests pass.

---------

Co-authored-by: macroscopeapp[bot] <170038800+macroscopeapp[bot]@users.noreply.github.com>
This commit is contained in:
Frank Bria 2026-01-20 23:22:30 -07:00 committed by GitHub
parent 0e95f67318
commit 9b19d70e35
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
27 changed files with 1126 additions and 585 deletions

View file

@ -12,8 +12,10 @@ CB_STATE_HALF_OPEN="HALF_OPEN" # Monitoring mode, checking for recovery
CB_STATE_OPEN="OPEN" # Failure detected, execution halted
# Circuit Breaker Configuration
CB_STATE_FILE=".circuit_breaker_state"
CB_HISTORY_FILE=".circuit_breaker_history"
# Use RALPH_DIR if set by main script, otherwise default to .ralph
RALPH_DIR="${RALPH_DIR:-.ralph}"
CB_STATE_FILE="$RALPH_DIR/.circuit_breaker_state"
CB_HISTORY_FILE="$RALPH_DIR/.circuit_breaker_history"
CB_NO_PROGRESS_THRESHOLD=3 # Open circuit after N loops with no progress
CB_SAME_ERROR_THRESHOLD=5 # Open circuit after N loops with same error
CB_OUTPUT_DECLINE_THRESHOLD=70 # Open circuit if output declines by >70%
@ -301,15 +303,15 @@ should_halt_execution() {
echo -e "${YELLOW}Ralph has detected that no progress is being made.${NC}"
echo ""
echo -e "${YELLOW}Possible reasons:${NC}"
echo " • Project may be complete (check @fix_plan.md)"
echo " • Project may be complete (check .ralph/@fix_plan.md)"
echo " • Claude may be stuck on an error"
echo " • PROMPT.md may need clarification"
echo " • .ralph/PROMPT.md may need clarification"
echo " • Manual intervention may be required"
echo ""
echo -e "${YELLOW}To continue:${NC}"
echo " 1. Review recent logs: tail -20 logs/ralph.log"
echo " 2. Check Claude output: ls -lt logs/claude_output_*.log | head -1"
echo " 3. Update @fix_plan.md if needed"
echo " 1. Review recent logs: tail -20 .ralph/logs/ralph.log"
echo " 2. Check Claude output: ls -lt .ralph/logs/claude_output_*.log | head -1"
echo " 3. Update .ralph/@fix_plan.md if needed"
echo " 4. Reset circuit breaker: ralph --reset-circuit"
echo ""
return 0 # Signal to halt

View file

@ -15,6 +15,9 @@ YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m'
# Use RALPH_DIR if set by main script, otherwise default to .ralph
RALPH_DIR="${RALPH_DIR:-.ralph}"
# Analysis configuration
COMPLETION_KEYWORDS=("done" "complete" "finished" "all tasks complete" "project complete" "ready for review")
TEST_ONLY_PATTERNS=("npm test" "bats" "pytest" "jest" "cargo test" "go test" "running tests")
@ -51,13 +54,13 @@ detect_output_format() {
}
# Parse JSON response and extract structured fields
# Creates .json_parse_result with normalized analysis data
# Creates .ralph/.json_parse_result with normalized analysis data
# Supports TWO JSON formats:
# 1. Flat format: { status, exit_signal, work_type, files_modified, ... }
# 2. Claude CLI format: { result, sessionId, metadata: { files_changed, has_errors, completion_status, ... } }
parse_json_response() {
local output_file=$1
local result_file="${2:-.json_parse_result}"
local result_file="${2:-$RALPH_DIR/.json_parse_result}"
if [[ ! -f "$output_file" ]]; then
echo "ERROR: Output file not found: $output_file" >&2
@ -198,7 +201,7 @@ parse_json_response() {
analyze_response() {
local output_file=$1
local loop_number=$2
local analysis_result_file=${3:-".response_analysis"}
local analysis_result_file=${3:-"$RALPH_DIR/.response_analysis"}
# Initialize analysis result
local has_completion_signal=false
@ -224,16 +227,16 @@ analyze_response() {
if [[ "$output_format" == "json" ]]; then
# Try JSON parsing
if parse_json_response "$output_file" ".json_parse_result" 2>/dev/null; then
if parse_json_response "$output_file" "$RALPH_DIR/.json_parse_result" 2>/dev/null; then
# Extract values from JSON parse result
has_completion_signal=$(jq -r '.has_completion_signal' .json_parse_result 2>/dev/null || echo "false")
exit_signal=$(jq -r '.exit_signal' .json_parse_result 2>/dev/null || echo "false")
is_test_only=$(jq -r '.is_test_only' .json_parse_result 2>/dev/null || echo "false")
is_stuck=$(jq -r '.is_stuck' .json_parse_result 2>/dev/null || echo "false")
work_summary=$(jq -r '.summary' .json_parse_result 2>/dev/null || echo "")
files_modified=$(jq -r '.files_modified' .json_parse_result 2>/dev/null || echo "0")
local json_confidence=$(jq -r '.confidence' .json_parse_result 2>/dev/null || echo "0")
local session_id=$(jq -r '.session_id' .json_parse_result 2>/dev/null || echo "")
has_completion_signal=$(jq -r '.has_completion_signal' $RALPH_DIR/.json_parse_result 2>/dev/null || echo "false")
exit_signal=$(jq -r '.exit_signal' $RALPH_DIR/.json_parse_result 2>/dev/null || echo "false")
is_test_only=$(jq -r '.is_test_only' $RALPH_DIR/.json_parse_result 2>/dev/null || echo "false")
is_stuck=$(jq -r '.is_stuck' $RALPH_DIR/.json_parse_result 2>/dev/null || echo "false")
work_summary=$(jq -r '.summary' $RALPH_DIR/.json_parse_result 2>/dev/null || echo "")
files_modified=$(jq -r '.files_modified' $RALPH_DIR/.json_parse_result 2>/dev/null || echo "0")
local json_confidence=$(jq -r '.confidence' $RALPH_DIR/.json_parse_result 2>/dev/null || echo "0")
local session_id=$(jq -r '.session_id' $RALPH_DIR/.json_parse_result 2>/dev/null || echo "")
# Persist session ID if present (for session continuity across loop iterations)
if [[ -n "$session_id" && "$session_id" != "null" ]]; then
@ -289,7 +292,7 @@ analyze_response() {
output_length: $output_length
}
}' > "$analysis_result_file"
rm -f ".json_parse_result"
rm -f "$RALPH_DIR/.json_parse_result"
return 0
fi
# If JSON parsing failed, fall through to text parsing
@ -394,8 +397,8 @@ analyze_response() {
fi
# 7. Analyze output length trends (detect declining engagement)
if [[ -f ".last_output_length" ]]; then
local last_length=$(cat ".last_output_length")
if [[ -f "$RALPH_DIR/.last_output_length" ]]; then
local last_length=$(cat "$RALPH_DIR/.last_output_length")
local length_ratio=$((output_length * 100 / last_length))
if [[ $length_ratio -lt 50 ]]; then
@ -403,7 +406,7 @@ analyze_response() {
((confidence_score+=10))
fi
fi
echo "$output_length" > ".last_output_length"
echo "$output_length" > "$RALPH_DIR/.last_output_length"
# 8. Extract work summary from output
if [[ -z "$work_summary" ]]; then
@ -463,8 +466,8 @@ analyze_response() {
# Update exit signals file based on analysis
update_exit_signals() {
local analysis_file=${1:-".response_analysis"}
local exit_signals_file=${2:-".exit_signals"}
local analysis_file=${1:-"$RALPH_DIR/.response_analysis"}
local exit_signals_file=${2:-"$RALPH_DIR/.exit_signals"}
if [[ ! -f "$analysis_file" ]]; then
echo "ERROR: Analysis file not found: $analysis_file"
@ -514,7 +517,7 @@ update_exit_signals() {
# Log analysis results in human-readable format
log_analysis_summary() {
local analysis_file=${1:-".response_analysis"}
local analysis_file=${1:-"$RALPH_DIR/.response_analysis"}
if [[ ! -f "$analysis_file" ]]; then
return 1
@ -541,7 +544,7 @@ log_analysis_summary() {
# Detect if Claude is stuck (repeating same errors)
detect_stuck_loop() {
local current_output=$1
local history_dir=${2:-"logs"}
local history_dir=${2:-"$RALPH_DIR/logs"}
# Get last 3 output files
local recent_outputs=$(ls -t "$history_dir"/claude_output_*.log 2>/dev/null | head -3)
@ -592,7 +595,7 @@ detect_stuck_loop() {
# =============================================================================
# Session file location - standardized across ralph_loop.sh and response_analyzer.sh
SESSION_FILE=".claude_session_id"
SESSION_FILE="$RALPH_DIR/.claude_session_id"
# Session expiration time in seconds (24 hours)
SESSION_EXPIRATION_SECONDS=86400