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

@ -16,16 +16,19 @@ setup() {
git config user.email "test@example.com"
git config user.name "Test User"
# Set up environment with .ralph/ subfolder structure
export RALPH_DIR=".ralph"
# Create necessary files
create_sample_prd_md
create_sample_fix_plan
# Set up environment
export PROMPT_FILE="PROMPT.md"
export LOG_DIR="logs"
export EXIT_SIGNALS_FILE=".exit_signals"
export PROMPT_FILE="$RALPH_DIR/PROMPT.md"
export LOG_DIR="$RALPH_DIR/logs"
export EXIT_SIGNALS_FILE="$RALPH_DIR/.exit_signals"
mkdir -p "$LOG_DIR"
mkdir -p "$RALPH_DIR" "$LOG_DIR"
echo '{"test_only_loops": [], "done_signals": [], "completion_indicators": []}' > "$EXIT_SIGNALS_FILE"
# Source library components
@ -48,8 +51,8 @@ teardown() {
analyze_response "$output_file" 1
# Should not crash, should create analysis file
assert_file_exists ".response_analysis"
local exit_signal=$(jq -r '.analysis.exit_signal' .response_analysis)
assert_file_exists "$RALPH_DIR/.response_analysis"
local exit_signal=$(jq -r '.analysis.exit_signal' "$RALPH_DIR/.response_analysis")
# Empty output shouldn't trigger exit
assert_equal "$exit_signal" "false"
}
@ -66,8 +69,8 @@ teardown() {
analyze_response "$output_file" 1
# Should handle without error
assert_file_exists ".response_analysis"
local output_length=$(jq -r '.analysis.output_length' .response_analysis)
assert_file_exists "$RALPH_DIR/.response_analysis"
local output_length=$(jq -r '.analysis.output_length' "$RALPH_DIR/.response_analysis")
[[ "$output_length" -gt 50000 ]]
}
@ -86,7 +89,7 @@ EOF
analyze_response "$output_file" 1
# Should not crash, may not detect structured output
assert_file_exists ".response_analysis"
assert_file_exists "$RALPH_DIR/.response_analysis"
}
# Edge Case 4: Missing exit signals file
@ -117,7 +120,7 @@ EOF
record_loop_result 1 -1 "false" 1000 || true
# Should not crash
local state=$(jq -r '.state' .circuit_breaker_state)
local state=$(jq -r '.state' "$RALPH_DIR/.circuit_breaker_state")
# Should still be valid state
[[ "$state" == "CLOSED" || "$state" == "HALF_OPEN" ]]
}
@ -129,7 +132,7 @@ EOF
# Simulate loop 9999
record_loop_result 9999 5 "false" 1000
local current_loop=$(jq -r '.current_loop' .circuit_breaker_state)
local current_loop=$(jq -r '.current_loop' "$RALPH_DIR/.circuit_breaker_state")
assert_equal "$current_loop" "9999"
}
@ -145,10 +148,10 @@ EOF
analyze_response "$output_file" 1
assert_file_exists ".response_analysis"
assert_file_exists "$RALPH_DIR/.response_analysis"
# Should detect "Done" as completion keyword
local has_completion=$(jq -r '.analysis.has_completion_signal' .response_analysis)
local has_completion=$(jq -r '.analysis.has_completion_signal' "$RALPH_DIR/.response_analysis")
assert_equal "$has_completion" "true"
}
@ -173,7 +176,7 @@ EOF
analyze_response "$output_file" 1
# Should detect structured output (picks first or last block)
local exit_signal=$(jq -r '.analysis.exit_signal' .response_analysis)
local exit_signal=$(jq -r '.analysis.exit_signal' "$RALPH_DIR/.response_analysis")
# Should detect completion somehow
[[ "$exit_signal" == "true" || "$exit_signal" == "false" ]]
}
@ -183,13 +186,13 @@ EOF
init_circuit_breaker
# Corrupt the state file
echo "invalid json{" > .circuit_breaker_state
echo "invalid json{" > "$RALPH_DIR/.circuit_breaker_state"
# Should recover gracefully
init_circuit_breaker
# Should have valid state now
local state=$(jq -r '.state' .circuit_breaker_state)
local state=$(jq -r '.state' "$RALPH_DIR/.circuit_breaker_state")
assert_equal "$state" "CLOSED"
}
@ -205,7 +208,7 @@ EOF
analyze_response "$output_file" 1 || true
# File should exist even if analysis struggled
[[ -f ".response_analysis" ]]
[[ -f "$RALPH_DIR/.response_analysis" ]]
}
# Edge Case 11: Simultaneous test-only and completion signals
@ -222,8 +225,8 @@ EOF
analyze_response "$output_file" 1
local is_test_only=$(jq -r '.analysis.is_test_only' .response_analysis)
local has_completion=$(jq -r '.analysis.has_completion_signal' .response_analysis)
local is_test_only=$(jq -r '.analysis.is_test_only' "$RALPH_DIR/.response_analysis")
local has_completion=$(jq -r '.analysis.has_completion_signal' "$RALPH_DIR/.response_analysis")
# Both can be true - completion signal should take precedence
assert_equal "$has_completion" "true"
@ -241,7 +244,7 @@ EOF
record_loop_result 3 5 "false" 2000
# Should recover to CLOSED
local state=$(jq -r '.state' .circuit_breaker_state)
local state=$(jq -r '.state' "$RALPH_DIR/.circuit_breaker_state")
assert_equal "$state" "CLOSED"
}
@ -264,7 +267,7 @@ EOF
analyze_response "$output_file" 2
# Should be at boundary
assert_file_exists ".response_analysis"
assert_file_exists "$RALPH_DIR/.response_analysis"
}
# Edge Case 14: Missing git repository
@ -278,10 +281,10 @@ EOF
# Should not crash when git commands fail
analyze_response "$output_file" 1
assert_file_exists ".response_analysis"
assert_file_exists "$RALPH_DIR/.response_analysis"
# files_modified should be 0 (can't detect without git)
local files_modified=$(jq -r '.analysis.files_modified' .response_analysis)
local files_modified=$(jq -r '.analysis.files_modified' "$RALPH_DIR/.response_analysis")
assert_equal "$files_modified" "0"
}
@ -318,7 +321,7 @@ EOF
record_loop_result 3 1 "false" 1000
# Should track all 3 correctly
local current_loop=$(jq -r '.current_loop' .circuit_breaker_state)
local current_loop=$(jq -r '.current_loop' "$RALPH_DIR/.circuit_breaker_state")
assert_equal "$current_loop" "3"
}
@ -344,7 +347,7 @@ EOF
analyze_response "$output_file" 1
# Confidence should be very high (100 + bonuses)
local confidence=$(jq -r '.analysis.confidence_score' .response_analysis)
local confidence=$(jq -r '.analysis.confidence_score' "$RALPH_DIR/.response_analysis")
[[ "$confidence" -ge 100 ]]
}
@ -353,7 +356,7 @@ EOF
init_circuit_breaker
# Corrupt history
echo "not valid json" > .circuit_breaker_history
echo "not valid json" > "$RALPH_DIR/.circuit_breaker_history"
# Should handle gracefully on next transition
record_loop_result 1 0 "false" 1000 || true
@ -361,7 +364,7 @@ EOF
# Depending on implementation, may recreate or skip history logging
# Just verify no crash
[[ -f .circuit_breaker_state ]]
[[ -f "$RALPH_DIR/.circuit_breaker_state" ]]
}
# Edge Case 19: Status block with extra fields
@ -380,7 +383,7 @@ EOF
analyze_response "$output_file" 1
# Should successfully parse known fields
local exit_signal=$(jq -r '.analysis.exit_signal' .response_analysis)
local exit_signal=$(jq -r '.analysis.exit_signal' "$RALPH_DIR/.response_analysis")
assert_equal "$exit_signal" "true"
}
@ -440,18 +443,18 @@ EOF
update_exit_signals
# After each loop, check that exit_signal is correctly captured as false
local exit_signal=$(jq -r '.analysis.exit_signal' .response_analysis)
local exit_signal=$(jq -r '.analysis.exit_signal' "$RALPH_DIR/.response_analysis")
assert_equal "$exit_signal" "false"
done
# Verify that analyze_response correctly captures EXIT_SIGNAL=false
local final_exit_signal=$(jq -r '.analysis.exit_signal' .response_analysis)
local final_exit_signal=$(jq -r '.analysis.exit_signal' "$RALPH_DIR/.response_analysis")
assert_equal "$final_exit_signal" "false"
# Key test: Even with high completion indicators set externally,
# the exit_signal should still be false (respecting Claude's explicit intent)
echo '{"test_only_loops": [], "done_signals": [], "completion_indicators": [1,2,3]}' > "$EXIT_SIGNALS_FILE"
local last_exit_signal=$(jq -r '.analysis.exit_signal' .response_analysis)
local last_exit_signal=$(jq -r '.analysis.exit_signal' "$RALPH_DIR/.response_analysis")
assert_equal "$last_exit_signal" "false"
}
@ -473,7 +476,7 @@ EOF
analyze_response "$output_file" $i
update_exit_signals
local exit_signal=$(jq -r '.analysis.exit_signal' .response_analysis)
local exit_signal=$(jq -r '.analysis.exit_signal' "$RALPH_DIR/.response_analysis")
assert_equal "$exit_signal" "false"
done
@ -491,11 +494,11 @@ EOF
update_exit_signals
# Exit signal should now be true
local exit_signal=$(jq -r '.analysis.exit_signal' .response_analysis)
local exit_signal=$(jq -r '.analysis.exit_signal' "$RALPH_DIR/.response_analysis")
assert_equal "$exit_signal" "true"
# Confidence should be >= 100 (100 from EXIT_SIGNAL: true, plus any natural language bonuses)
local confidence=$(jq -r '.analysis.confidence_score' .response_analysis)
local confidence=$(jq -r '.analysis.confidence_score' "$RALPH_DIR/.response_analysis")
[[ "$confidence" -ge 100 ]]
}
@ -517,10 +520,10 @@ EOF
update_exit_signals
# Verify file exists
assert_file_exists ".response_analysis"
assert_file_exists "$RALPH_DIR/.response_analysis"
# Simulate file deletion (e.g., cleanup script ran)
rm -f ".response_analysis"
rm -f "$RALPH_DIR/.response_analysis"
# Add more completion indicators
cat > "$output_file" << 'EOF'
@ -531,7 +534,7 @@ EOF
update_exit_signals
# File should be recreated
assert_file_exists ".response_analysis"
assert_file_exists "$RALPH_DIR/.response_analysis"
}
# Edge Case 24: STATUS=COMPLETE but EXIT_SIGNAL=false conflict in RALPH_STATUS
@ -554,7 +557,7 @@ EOF
analyze_response "$output_file" 1
# EXIT_SIGNAL: false should take precedence over STATUS: COMPLETE
local exit_signal=$(jq -r '.analysis.exit_signal' .response_analysis)
local exit_signal=$(jq -r '.analysis.exit_signal' "$RALPH_DIR/.response_analysis")
assert_equal "$exit_signal" "false"
# has_completion_signal can still be true (STATUS was COMPLETE)
@ -582,7 +585,7 @@ EOF
update_exit_signals
# Exit signal should be false (completion_status is in_progress)
local exit_signal=$(jq -r '.analysis.exit_signal' .response_analysis)
local exit_signal=$(jq -r '.analysis.exit_signal' "$RALPH_DIR/.response_analysis")
assert_equal "$exit_signal" "false"
# Now test with complete status
@ -602,6 +605,6 @@ EOF
update_exit_signals
# Exit signal should be true (completion_status is complete)
local exit_signal=$(jq -r '.analysis.exit_signal' .response_analysis)
local exit_signal=$(jq -r '.analysis.exit_signal' "$RALPH_DIR/.response_analysis")
assert_equal "$exit_signal" "true"
}

View file

@ -74,6 +74,12 @@ EOF
#!/bin/bash
# Mock setup.sh
echo "Setup running"
EOF
cat > "$MOCK_SOURCE_DIR/migrate_to_ralph_folder.sh" << 'EOF'
#!/bin/bash
# Mock migrate_to_ralph_folder.sh
echo "Migration running"
EOF
chmod +x "$MOCK_SOURCE_DIR"/*.sh
@ -155,17 +161,19 @@ run_install() {
@test "install.sh creates ~/.local/bin commands" {
run run_install
# Check all four wrapper commands exist
# Check all five wrapper commands exist
assert_file_exists "$TEST_INSTALL_DIR/ralph"
assert_file_exists "$TEST_INSTALL_DIR/ralph-monitor"
assert_file_exists "$TEST_INSTALL_DIR/ralph-setup"
assert_file_exists "$TEST_INSTALL_DIR/ralph-import"
assert_file_exists "$TEST_INSTALL_DIR/ralph-migrate"
# Verify each command contains proper shebang
grep -q "#!/bin/bash" "$TEST_INSTALL_DIR/ralph"
grep -q "#!/bin/bash" "$TEST_INSTALL_DIR/ralph-monitor"
grep -q "#!/bin/bash" "$TEST_INSTALL_DIR/ralph-setup"
grep -q "#!/bin/bash" "$TEST_INSTALL_DIR/ralph-import"
grep -q "#!/bin/bash" "$TEST_INSTALL_DIR/ralph-migrate"
}
@test "install.sh sets executable permissions" {
@ -176,12 +184,14 @@ run_install() {
[[ -x "$TEST_INSTALL_DIR/ralph-monitor" ]]
[[ -x "$TEST_INSTALL_DIR/ralph-setup" ]]
[[ -x "$TEST_INSTALL_DIR/ralph-import" ]]
[[ -x "$TEST_INSTALL_DIR/ralph-migrate" ]]
# Verify executable bit on main scripts
[[ -x "$TEST_RALPH_HOME/ralph_loop.sh" ]]
[[ -x "$TEST_RALPH_HOME/ralph_monitor.sh" ]]
[[ -x "$TEST_RALPH_HOME/setup.sh" ]]
[[ -x "$TEST_RALPH_HOME/ralph_import.sh" ]]
[[ -x "$TEST_RALPH_HOME/migrate_to_ralph_folder.sh" ]]
# Verify lib scripts are executable
[[ -x "$TEST_RALPH_HOME/lib/circuit_breaker.sh" ]]
@ -428,6 +438,7 @@ EOF
assert_file_exists "$TEST_INSTALL_DIR/ralph-monitor"
assert_file_exists "$TEST_INSTALL_DIR/ralph-setup"
assert_file_exists "$TEST_INSTALL_DIR/ralph-import"
assert_file_exists "$TEST_INSTALL_DIR/ralph-migrate"
# Run uninstall
run run_install uninstall
@ -438,6 +449,7 @@ EOF
assert_file_not_exists "$TEST_INSTALL_DIR/ralph-monitor"
assert_file_not_exists "$TEST_INSTALL_DIR/ralph-setup"
assert_file_not_exists "$TEST_INSTALL_DIR/ralph-import"
assert_file_not_exists "$TEST_INSTALL_DIR/ralph-migrate"
}
@test "install.sh uninstall cleans up directories" {
@ -511,6 +523,7 @@ EOF
assert_file_exists "$TEST_INSTALL_DIR/ralph-monitor"
assert_file_exists "$TEST_INSTALL_DIR/ralph-setup"
assert_file_exists "$TEST_INSTALL_DIR/ralph-import"
assert_file_exists "$TEST_INSTALL_DIR/ralph-migrate"
# Verify all templates copied
assert_file_exists "$TEST_RALPH_HOME/templates/PROMPT.md"
@ -527,6 +540,7 @@ EOF
assert_file_exists "$TEST_RALPH_HOME/ralph_monitor.sh"
assert_file_exists "$TEST_RALPH_HOME/setup.sh"
assert_file_exists "$TEST_RALPH_HOME/ralph_import.sh"
assert_file_exists "$TEST_RALPH_HOME/migrate_to_ralph_folder.sh"
# Verify all permissions correct
[[ -x "$TEST_INSTALL_DIR/ralph" ]]

View file

@ -15,24 +15,27 @@ setup() {
git config user.email "test@example.com"
git config user.name "Test User"
# Set up environment with .ralph/ subfolder structure
export RALPH_DIR=".ralph"
# Create necessary files
create_sample_prd_md
create_sample_fix_plan
# Source the main ralph_loop.sh functions
export PROMPT_FILE="PROMPT.md"
export LOG_DIR="logs"
export DOCS_DIR="docs/generated"
export STATUS_FILE="status.json"
export PROGRESS_FILE="progress.json"
export CALL_COUNT_FILE=".call_count"
export TIMESTAMP_FILE=".last_reset"
export EXIT_SIGNALS_FILE=".exit_signals"
export PROMPT_FILE="$RALPH_DIR/PROMPT.md"
export LOG_DIR="$RALPH_DIR/logs"
export DOCS_DIR="$RALPH_DIR/docs/generated"
export STATUS_FILE="$RALPH_DIR/status.json"
export PROGRESS_FILE="$RALPH_DIR/progress.json"
export CALL_COUNT_FILE="$RALPH_DIR/.call_count"
export TIMESTAMP_FILE="$RALPH_DIR/.last_reset"
export EXIT_SIGNALS_FILE="$RALPH_DIR/.exit_signals"
export MAX_CALLS_PER_HOUR=100
export MAX_CONSECUTIVE_TEST_LOOPS=3
export MAX_CONSECUTIVE_DONE_SIGNALS=2
mkdir -p "$LOG_DIR" "$DOCS_DIR"
mkdir -p "$RALPH_DIR" "$LOG_DIR" "$DOCS_DIR"
# Initialize tracking files
echo "0" > "$CALL_COUNT_FILE"
@ -78,13 +81,13 @@ EOF
# Should return 0 (success)
assert_equal "$result" "0"
# Check analysis file
assert_file_exists ".response_analysis"
# Check analysis file in .ralph/ subfolder
assert_file_exists "$RALPH_DIR/.response_analysis"
local exit_signal=$(jq -r '.analysis.exit_signal' .response_analysis)
local exit_signal=$(jq -r '.analysis.exit_signal' "$RALPH_DIR/.response_analysis")
assert_equal "$exit_signal" "true"
local confidence=$(jq -r '.analysis.confidence_score' .response_analysis)
local confidence=$(jq -r '.analysis.confidence_score' "$RALPH_DIR/.response_analysis")
# Confidence may be >= 100 due to multiple bonus points
[[ "$confidence" -ge 100 ]]
}
@ -103,7 +106,7 @@ EOF
local result=$?
# Check analysis result
local has_completion=$(jq -r '.analysis.has_completion_signal' .response_analysis)
local has_completion=$(jq -r '.analysis.has_completion_signal' "$RALPH_DIR/.response_analysis")
assert_equal "$has_completion" "true"
}
@ -120,7 +123,7 @@ EOF
analyze_response "$output_file" 1
local is_test_only=$(jq -r '.analysis.is_test_only' .response_analysis)
local is_test_only=$(jq -r '.analysis.is_test_only' "$RALPH_DIR/.response_analysis")
assert_equal "$is_test_only" "true"
}
@ -137,7 +140,7 @@ EOF
analyze_response "$output_file" 1
local files_modified=$(jq -r '.analysis.files_modified' .response_analysis)
local files_modified=$(jq -r '.analysis.files_modified' "$RALPH_DIR/.response_analysis")
# files_modified should be > 0 because test_file.txt is untracked
[[ "$files_modified" -ge 0 ]] # Relaxed: >= 0 instead of > 0 (git diff doesn't show untracked)
}
@ -167,9 +170,9 @@ EOF
@test "init_circuit_breaker creates state file" {
init_circuit_breaker
assert_file_exists ".circuit_breaker_state"
assert_file_exists "$RALPH_DIR/.circuit_breaker_state"
local state=$(jq -r '.state' .circuit_breaker_state)
local state=$(jq -r '.state' "$RALPH_DIR/.circuit_breaker_state")
assert_equal "$state" "CLOSED"
}
@ -183,7 +186,7 @@ EOF
record_loop_result $i 0 "false" 1000 || true
done
local state=$(jq -r '.state' .circuit_breaker_state)
local state=$(jq -r '.state' "$RALPH_DIR/.circuit_breaker_state")
assert_equal "$state" "OPEN"
}
@ -195,7 +198,7 @@ EOF
record_loop_result 1 0 "false" 1000
record_loop_result 2 0 "false" 1000
local state=$(jq -r '.state' .circuit_breaker_state)
local state=$(jq -r '.state' "$RALPH_DIR/.circuit_breaker_state")
assert_equal "$state" "HALF_OPEN"
}
@ -210,7 +213,7 @@ EOF
# Now make progress
record_loop_result 3 5 "false" 1000
local state=$(jq -r '.state' .circuit_breaker_state)
local state=$(jq -r '.state' "$RALPH_DIR/.circuit_breaker_state")
assert_equal "$state" "CLOSED"
}
@ -223,9 +226,9 @@ EOF
record_loop_result $i 1 "true" 1000 || true
done
local state=$(jq -r '.state' .circuit_breaker_state)
local state=$(jq -r '.state' "$RALPH_DIR/.circuit_breaker_state")
# Should eventually open due to consecutive errors
local same_error_count=$(jq -r '.consecutive_same_error' .circuit_breaker_state)
local same_error_count=$(jq -r '.consecutive_same_error' "$RALPH_DIR/.circuit_breaker_state")
[[ "$same_error_count" -ge 5 ]]
}
@ -260,7 +263,7 @@ EOF
# Reset
reset_circuit_breaker "Test reset"
local state=$(jq -r '.state' .circuit_breaker_state)
local state=$(jq -r '.state' "$RALPH_DIR/.circuit_breaker_state")
assert_equal "$state" "CLOSED"
}
@ -343,7 +346,7 @@ EOF
done
# Circuit should be OPEN
local state=$(jq -r '.state' .circuit_breaker_state)
local state=$(jq -r '.state' "$RALPH_DIR/.circuit_breaker_state")
assert_equal "$state" "OPEN"
# Verify should_halt_execution returns true
@ -374,7 +377,7 @@ EOF
analyze_response "$output_file" 1
local confidence=$(jq -r '.analysis.confidence_score' .response_analysis)
local confidence=$(jq -r '.analysis.confidence_score' "$RALPH_DIR/.response_analysis")
# Should be very high (100 from structured + bonuses)
[[ "$confidence" -ge 100 ]]
}
@ -412,10 +415,10 @@ EOF
record_loop_result 2 0 "false" 1000
# Check history file exists
assert_file_exists ".circuit_breaker_history"
assert_file_exists "$RALPH_DIR/.circuit_breaker_history"
# Verify it's valid JSON
jq '.' .circuit_breaker_history > /dev/null
jq '.' "$RALPH_DIR/.circuit_breaker_history" > /dev/null
}
# Test 19: Rolling window for exit signals
@ -458,7 +461,7 @@ EOF
analyze_response "$output_file" 2
# Should detect declining output
local confidence=$(jq -r '.analysis.confidence_score' .response_analysis)
local confidence=$(jq -r '.analysis.confidence_score' "$RALPH_DIR/.response_analysis")
# Short output after long one should increase confidence of completion
[[ "$confidence" -gt 0 ]]
}

View file

@ -25,18 +25,19 @@ setup() {
mkdir -p "$MOCK_BIN_DIR"
export PATH="$MOCK_BIN_DIR:$PATH"
# Create mock ralph-setup command
# Create mock ralph-setup command (with .ralph/ subfolder structure)
cat > "$MOCK_BIN_DIR/ralph-setup" << 'MOCK_SETUP_EOF'
#!/bin/bash
# Mock ralph-setup that creates project structure
# Mock ralph-setup that creates project structure with .ralph/ subfolder
project_name="${1:-test-project}"
mkdir -p "$project_name"/{specs,src,logs,docs/generated}
mkdir -p "$project_name"/src
mkdir -p "$project_name"/.ralph/{specs/stdlib,examples,logs,docs/generated}
cd "$project_name"
git init > /dev/null 2>&1
git config user.email "test@example.com"
git config user.name "Test User"
# Create basic template files
cat > PROMPT.md << 'EOF'
# Create basic template files in .ralph/ subfolder
cat > .ralph/PROMPT.md << 'EOF'
# Ralph Development Instructions
## Context
@ -53,7 +54,7 @@ You are Ralph, an autonomous AI development agent.
- LIMIT testing to ~20% of your total effort
EOF
cat > "@fix_plan.md" << 'EOF'
cat > ".ralph/@fix_plan.md" << 'EOF'
# Ralph Fix Plan
## High Priority
@ -69,7 +70,7 @@ cat > "@fix_plan.md" << 'EOF'
- [x] Project initialization
EOF
cat > "@AGENT.md" << 'EOF'
cat > ".ralph/@AGENT.md" << 'EOF'
# Agent Build Instructions
## Project Setup
@ -104,12 +105,15 @@ teardown() {
create_mock_claude_success() {
cat > "$MOCK_BIN_DIR/claude" << 'MOCK_CLAUDE_EOF'
#!/bin/bash
# Mock Claude Code CLI that creates expected output files
# Mock Claude Code CLI that creates expected output files in .ralph/ subfolder
# Read from stdin (conversion prompt)
cat > /dev/null
# Create PROMPT.md with Ralph format
cat > PROMPT.md << 'EOF'
# Ensure .ralph directory exists
mkdir -p .ralph/specs
# Create PROMPT.md with Ralph format in .ralph/
cat > .ralph/PROMPT.md << 'EOF'
# Ralph Development Instructions
## Context
@ -158,8 +162,8 @@ You are Ralph, an autonomous AI development agent working on a Task Management A
Follow @fix_plan.md and choose the most important item to implement next.
EOF
# Create @fix_plan.md
cat > "@fix_plan.md" << 'EOF'
# Create @fix_plan.md in .ralph/
cat > ".ralph/@fix_plan.md" << 'EOF'
# Ralph Fix Plan
## High Priority
@ -186,9 +190,8 @@ cat > "@fix_plan.md" << 'EOF'
- Update this file after each major milestone
EOF
# Create specs/requirements.md
mkdir -p specs
cat > specs/requirements.md << 'EOF'
# Create specs/requirements.md in .ralph/specs/
cat > .ralph/specs/requirements.md << 'EOF'
# Technical Specifications
## System Architecture
@ -336,23 +339,23 @@ remove_ralph_setup_mock() {
assert_success
# PROMPT.md should exist
assert_file_exists "test-app/PROMPT.md"
# PROMPT.md should exist in .ralph/ subfolder
assert_file_exists "test-app/.ralph/PROMPT.md"
# Check key sections exist
run grep -c "Ralph Development Instructions" "test-app/PROMPT.md"
run grep -c "Ralph Development Instructions" "test-app/.ralph/PROMPT.md"
assert_success
[[ "$output" -ge 1 ]]
run grep -c "Current Objectives" "test-app/PROMPT.md"
run grep -c "Current Objectives" "test-app/.ralph/PROMPT.md"
assert_success
[[ "$output" -ge 1 ]]
run grep -c "Key Principles" "test-app/PROMPT.md"
run grep -c "Key Principles" "test-app/.ralph/PROMPT.md"
assert_success
[[ "$output" -ge 1 ]]
run grep -c "Testing Guidelines" "test-app/PROMPT.md"
run grep -c "Testing Guidelines" "test-app/.ralph/PROMPT.md"
assert_success
[[ "$output" -ge 1 ]]
}
@ -365,28 +368,28 @@ remove_ralph_setup_mock() {
assert_success
# @fix_plan.md should exist
assert_file_exists "test-app/@fix_plan.md"
# @fix_plan.md should exist in .ralph/ subfolder
assert_file_exists "test-app/.ralph/@fix_plan.md"
# Check structure includes priority sections
run grep -c "High Priority" "test-app/@fix_plan.md"
run grep -c "High Priority" "test-app/.ralph/@fix_plan.md"
assert_success
[[ "$output" -ge 1 ]]
run grep -c "Medium Priority" "test-app/@fix_plan.md"
run grep -c "Medium Priority" "test-app/.ralph/@fix_plan.md"
assert_success
[[ "$output" -ge 1 ]]
run grep -c "Low Priority" "test-app/@fix_plan.md"
run grep -c "Low Priority" "test-app/.ralph/@fix_plan.md"
assert_success
[[ "$output" -ge 1 ]]
run grep -c "Completed" "test-app/@fix_plan.md"
run grep -c "Completed" "test-app/.ralph/@fix_plan.md"
assert_success
[[ "$output" -ge 1 ]]
# Check checkbox format
run grep -E "^\- \[[ x]\]" "test-app/@fix_plan.md"
run grep -E "^\- \[[ x]\]" "test-app/.ralph/@fix_plan.md"
assert_success
}
@ -398,14 +401,14 @@ remove_ralph_setup_mock() {
assert_success
# specs directory should exist
assert_dir_exists "test-app/specs"
# specs directory should exist in .ralph/ subfolder
assert_dir_exists "test-app/.ralph/specs"
# requirements.md should exist
assert_file_exists "test-app/specs/requirements.md"
# requirements.md should exist in .ralph/specs/
assert_file_exists "test-app/.ralph/specs/requirements.md"
# Check technical specification content
run grep -c "Technical Specifications" "test-app/specs/requirements.md"
run grep -c "Technical Specifications" "test-app/.ralph/specs/requirements.md"
assert_success
[[ "$output" -ge 1 ]]
}
@ -426,10 +429,10 @@ remove_ralph_setup_mock() {
# Custom project directory should be created
assert_dir_exists "my-custom-project"
# Files should be in custom-named directory
assert_file_exists "my-custom-project/PROMPT.md"
assert_file_exists "my-custom-project/@fix_plan.md"
assert_file_exists "my-custom-project/specs/requirements.md"
# Files should be in custom-named directory under .ralph/ subfolder
assert_file_exists "my-custom-project/.ralph/PROMPT.md"
assert_file_exists "my-custom-project/.ralph/@fix_plan.md"
assert_file_exists "my-custom-project/.ralph/specs/requirements.md"
# Default name directory should NOT exist
[[ ! -d "generic-prd" ]]
@ -447,8 +450,8 @@ remove_ralph_setup_mock() {
# Project name should be extracted from filename (without extension)
assert_dir_exists "awesome-app-requirements"
# Files should be in auto-named directory
assert_file_exists "awesome-app-requirements/PROMPT.md"
# Files should be in auto-named directory under .ralph/ subfolder
assert_file_exists "awesome-app-requirements/.ralph/PROMPT.md"
}
# =============================================================================
@ -590,18 +593,18 @@ remove_ralph_setup_mock() {
assert_success
# Verify complete project structure
# Verify complete project structure with .ralph/ subfolder
assert_dir_exists "my-app"
assert_dir_exists "my-app/specs"
assert_dir_exists "my-app/.ralph/specs"
assert_dir_exists "my-app/src"
assert_dir_exists "my-app/logs"
assert_dir_exists "my-app/docs/generated"
assert_dir_exists "my-app/.ralph/logs"
assert_dir_exists "my-app/.ralph/docs/generated"
# Verify all required files
assert_file_exists "my-app/PROMPT.md"
assert_file_exists "my-app/@fix_plan.md"
assert_file_exists "my-app/@AGENT.md"
assert_file_exists "my-app/specs/requirements.md"
# Verify all required files in .ralph/ subfolder
assert_file_exists "my-app/.ralph/PROMPT.md"
assert_file_exists "my-app/.ralph/@fix_plan.md"
assert_file_exists "my-app/.ralph/@AGENT.md"
assert_file_exists "my-app/.ralph/specs/requirements.md"
# Verify source PRD was copied
assert_file_exists "my-app/my-app.md"
@ -698,12 +701,15 @@ EOF
create_mock_claude_json_success() {
cat > "$MOCK_BIN_DIR/claude" << 'MOCK_CLAUDE_JSON_EOF'
#!/bin/bash
# Mock Claude Code CLI that outputs JSON format and creates expected files
# Mock Claude Code CLI that outputs JSON format and creates expected files in .ralph/
# Read from stdin (conversion prompt)
cat > /dev/null
# Create PROMPT.md with Ralph format
cat > PROMPT.md << 'EOF'
# Ensure .ralph directory exists
mkdir -p .ralph/specs
# Create PROMPT.md with Ralph format in .ralph/
cat > .ralph/PROMPT.md << 'EOF'
# Ralph Development Instructions
## Context
@ -720,8 +726,8 @@ You are Ralph, an autonomous AI development agent working on a Task Management A
- LIMIT testing to ~20% of your total effort
EOF
# Create @fix_plan.md
cat > "@fix_plan.md" << 'EOF'
# Create @fix_plan.md in .ralph/
cat > ".ralph/@fix_plan.md" << 'EOF'
# Ralph Fix Plan
## High Priority
@ -737,9 +743,8 @@ cat > "@fix_plan.md" << 'EOF'
- [x] Project initialization
EOF
# Create specs/requirements.md
mkdir -p specs
cat > specs/requirements.md << 'EOF'
# Create specs/requirements.md in .ralph/specs/
cat > .ralph/specs/requirements.md << 'EOF'
# Technical Specifications
## System Architecture
@ -755,13 +760,13 @@ EOF
# Output JSON response to stdout (mimicking --output-format json)
cat << 'JSON_OUTPUT'
{
"result": "Successfully converted PRD to Ralph format. Created PROMPT.md, @fix_plan.md, and specs/requirements.md",
"result": "Successfully converted PRD to Ralph format. Created .ralph/PROMPT.md, .ralph/@fix_plan.md, and .ralph/specs/requirements.md",
"sessionId": "session-prd-convert-123",
"metadata": {
"files_changed": 3,
"has_errors": false,
"completion_status": "complete",
"files_created": ["PROMPT.md", "@fix_plan.md", "specs/requirements.md"]
"files_created": [".ralph/PROMPT.md", ".ralph/@fix_plan.md", ".ralph/specs/requirements.md"]
}
}
JSON_OUTPUT
@ -775,11 +780,14 @@ MOCK_CLAUDE_JSON_EOF
create_mock_claude_json_partial() {
cat > "$MOCK_BIN_DIR/claude" << 'MOCK_CLAUDE_PARTIAL_EOF'
#!/bin/bash
# Mock Claude Code CLI that outputs JSON but only creates some files
# Mock Claude Code CLI that outputs JSON but only creates some files in .ralph/
cat > /dev/null
# Ensure .ralph directory exists
mkdir -p .ralph
# Only create PROMPT.md (missing @fix_plan.md and specs/requirements.md)
cat > PROMPT.md << 'EOF'
cat > .ralph/PROMPT.md << 'EOF'
# Ralph Development Instructions
## Context
@ -795,8 +803,8 @@ cat << 'JSON_OUTPUT'
"files_changed": 1,
"has_errors": true,
"completion_status": "partial",
"files_created": ["PROMPT.md"],
"missing_files": ["@fix_plan.md", "specs/requirements.md"]
"files_created": [".ralph/PROMPT.md"],
"missing_files": [".ralph/@fix_plan.md", ".ralph/specs/requirements.md"]
}
}
JSON_OUTPUT
@ -837,18 +845,21 @@ MOCK_CLAUDE_JSON_ERROR_EOF
create_mock_claude_text_output() {
cat > "$MOCK_BIN_DIR/claude" << 'MOCK_CLAUDE_TEXT_EOF'
#!/bin/bash
# Mock Claude Code CLI that outputs text (older CLI version)
# Mock Claude Code CLI that outputs text (older CLI version) - files in .ralph/
cat > /dev/null
# Create files
cat > PROMPT.md << 'EOF'
# Ensure .ralph directory exists
mkdir -p .ralph/specs
# Create files in .ralph/
cat > .ralph/PROMPT.md << 'EOF'
# Ralph Development Instructions
## Context
You are Ralph, an autonomous AI development agent.
EOF
cat > "@fix_plan.md" << 'EOF'
cat > ".ralph/@fix_plan.md" << 'EOF'
# Ralph Fix Plan
## High Priority
@ -858,8 +869,7 @@ cat > "@fix_plan.md" << 'EOF'
- [x] Project initialization
EOF
mkdir -p specs
cat > specs/requirements.md << 'EOF'
cat > .ralph/specs/requirements.md << 'EOF'
# Technical Specifications
## Overview
@ -868,7 +878,7 @@ EOF
# Output plain text (no JSON)
echo "Mock: Claude Code conversion completed successfully"
echo "Created: PROMPT.md, @fix_plan.md, specs/requirements.md"
echo "Created: .ralph/PROMPT.md, .ralph/@fix_plan.md, .ralph/specs/requirements.md"
exit 0
MOCK_CLAUDE_TEXT_EOF
chmod +x "$MOCK_BIN_DIR/claude"
@ -883,10 +893,10 @@ MOCK_CLAUDE_TEXT_EOF
assert_success
# All files should be created
assert_file_exists "json-test/PROMPT.md"
assert_file_exists "json-test/@fix_plan.md"
assert_file_exists "json-test/specs/requirements.md"
# All files should be created in .ralph/ subfolder
assert_file_exists "json-test/.ralph/PROMPT.md"
assert_file_exists "json-test/.ralph/@fix_plan.md"
assert_file_exists "json-test/.ralph/specs/requirements.md"
}
# Test 24: ralph-import handles JSON partial success response
@ -899,8 +909,8 @@ MOCK_CLAUDE_TEXT_EOF
# Should succeed but with warnings
assert_success
# PROMPT.md should exist
assert_file_exists "partial-test/PROMPT.md"
# PROMPT.md should exist in .ralph/ subfolder
assert_file_exists "partial-test/.ralph/PROMPT.md"
# Warning should mention missing files
[[ "$output" == *"WARN"* ]] || [[ "$output" == *"not created"* ]] || [[ "$output" == *"missing"* ]]
@ -929,10 +939,10 @@ MOCK_CLAUDE_TEXT_EOF
assert_success
# All files should be created
assert_file_exists "text-test/PROMPT.md"
assert_file_exists "text-test/@fix_plan.md"
assert_file_exists "text-test/specs/requirements.md"
# All files should be created in .ralph/ subfolder
assert_file_exists "text-test/.ralph/PROMPT.md"
assert_file_exists "text-test/.ralph/@fix_plan.md"
assert_file_exists "text-test/.ralph/specs/requirements.md"
}
# Test 27: ralph-import cleans up JSON output file after processing
@ -999,19 +1009,21 @@ MOCK_CLAUDE_TEXT_EOF
# Capture invocation arguments for testing
echo "INVOCATION_ARGS: $*" >> /tmp/claude_invocation.log
# Create expected files
cat > PROMPT.md << 'EOF'
# Ensure .ralph directory exists
mkdir -p .ralph/specs
# Create expected files in .ralph/
cat > .ralph/PROMPT.md << 'EOF'
# Ralph Development Instructions
EOF
cat > "@fix_plan.md" << 'EOF'
cat > ".ralph/@fix_plan.md" << 'EOF'
# Ralph Fix Plan
## High Priority
- [ ] Task 1
EOF
mkdir -p specs
cat > specs/requirements.md << 'EOF'
cat > .ralph/specs/requirements.md << 'EOF'
# Technical Specifications
EOF
@ -1059,19 +1071,21 @@ CAPTURE_ARGS_EOF
#!/bin/bash
cat > /dev/null
# Create files
cat > PROMPT.md << 'EOF'
# Ensure .ralph directory exists
mkdir -p .ralph/specs
# Create files in .ralph/
cat > .ralph/PROMPT.md << 'EOF'
# Ralph Development Instructions
EOF
cat > "@fix_plan.md" << 'EOF'
cat > ".ralph/@fix_plan.md" << 'EOF'
# Ralph Fix Plan
## High Priority
- [ ] Task 1
EOF
mkdir -p specs
cat > specs/requirements.md << 'EOF'
cat > .ralph/specs/requirements.md << 'EOF'
# Technical Specifications
EOF
@ -1089,8 +1103,8 @@ MALFORMED_JSON_EOF
# Should still succeed (fallback to text parsing)
assert_success
# Files should exist
assert_file_exists "malformed-test/PROMPT.md"
# Files should exist in .ralph/ subfolder
assert_file_exists "malformed-test/.ralph/PROMPT.md"
}
# Test 33: ralph-import extracts error details from JSON error response

View file

@ -106,77 +106,95 @@ teardown() {
}
# =============================================================================
# Test: Subdirectory Structure
# Test: Subdirectory Structure (.ralph/ subfolder)
# =============================================================================
@test "setup.sh creates all required subdirectories" {
@test "setup.sh creates .ralph subdirectory for Ralph-specific files" {
run bash "$SETUP_SCRIPT" test-project
assert_success
assert_dir_exists "test-project/specs"
assert_dir_exists "test-project/specs/stdlib"
assert_dir_exists "test-project/.ralph"
}
@test "setup.sh creates all required subdirectories in .ralph/" {
run bash "$SETUP_SCRIPT" test-project
assert_success
# Ralph-specific directories go inside .ralph/
assert_dir_exists "test-project/.ralph/specs"
assert_dir_exists "test-project/.ralph/specs/stdlib"
assert_dir_exists "test-project/.ralph/examples"
assert_dir_exists "test-project/.ralph/logs"
assert_dir_exists "test-project/.ralph/docs"
assert_dir_exists "test-project/.ralph/docs/generated"
# src/ stays at root per maintainer decision
assert_dir_exists "test-project/src"
assert_dir_exists "test-project/examples"
assert_dir_exists "test-project/logs"
assert_dir_exists "test-project/docs"
assert_dir_exists "test-project/docs/generated"
}
@test "setup.sh creates nested docs/generated directory" {
@test "setup.sh keeps src directory at project root (not in .ralph/)" {
run bash "$SETUP_SCRIPT" test-project
assert_success
# Verify the nested structure exists
[[ -d "test-project/docs/generated" ]]
# src should be at root, NOT inside .ralph
assert_dir_exists "test-project/src"
[[ ! -d "test-project/.ralph/src" ]]
}
@test "setup.sh creates nested specs/stdlib directory" {
@test "setup.sh creates nested docs/generated directory in .ralph/" {
run bash "$SETUP_SCRIPT" test-project
assert_success
[[ -d "test-project/specs/stdlib" ]]
# Verify the nested structure exists inside .ralph
[[ -d "test-project/.ralph/docs/generated" ]]
}
@test "setup.sh creates nested specs/stdlib directory in .ralph/" {
run bash "$SETUP_SCRIPT" test-project
assert_success
[[ -d "test-project/.ralph/specs/stdlib" ]]
}
# =============================================================================
# Test: Template Copying
# Test: Template Copying (to .ralph/ subfolder)
# =============================================================================
@test "setup.sh copies PROMPT.md template" {
@test "setup.sh copies PROMPT.md template to .ralph/" {
run bash "$SETUP_SCRIPT" test-project
assert_success
assert_file_exists "test-project/PROMPT.md"
assert_file_exists "test-project/.ralph/PROMPT.md"
# Verify content matches source
diff templates/PROMPT.md test-project/PROMPT.md
diff templates/PROMPT.md test-project/.ralph/PROMPT.md
}
@test "setup.sh copies fix_plan.md as @fix_plan.md" {
@test "setup.sh copies fix_plan.md as @fix_plan.md to .ralph/" {
run bash "$SETUP_SCRIPT" test-project
assert_success
assert_file_exists "test-project/@fix_plan.md"
assert_file_exists "test-project/.ralph/@fix_plan.md"
# Verify content matches source
diff templates/fix_plan.md "test-project/@fix_plan.md"
diff templates/fix_plan.md "test-project/.ralph/@fix_plan.md"
}
@test "setup.sh copies AGENT.md as @AGENT.md" {
@test "setup.sh copies AGENT.md as @AGENT.md to .ralph/" {
run bash "$SETUP_SCRIPT" test-project
assert_success
assert_file_exists "test-project/@AGENT.md"
assert_file_exists "test-project/.ralph/@AGENT.md"
# Verify content matches source
diff templates/AGENT.md "test-project/@AGENT.md"
diff templates/AGENT.md "test-project/.ralph/@AGENT.md"
}
@test "setup.sh copies specs templates if they exist" {
@test "setup.sh copies specs templates to .ralph/specs/" {
run bash "$SETUP_SCRIPT" test-project
assert_success
# Verify spec file was copied
assert_file_exists "test-project/specs/sample_spec.md"
# Verify spec file was copied to .ralph/specs/
assert_file_exists "test-project/.ralph/specs/sample_spec.md"
}
@test "setup.sh handles empty specs directory gracefully" {
@ -187,7 +205,7 @@ teardown() {
# Should not fail (|| true in script handles this)
assert_success
assert_dir_exists "test-project/specs"
assert_dir_exists "test-project/.ralph/specs"
}
@test "setup.sh handles missing specs directory gracefully" {
@ -198,7 +216,7 @@ teardown() {
# Should not fail due to || true in script
assert_success
assert_dir_exists "test-project/specs"
assert_dir_exists "test-project/.ralph/specs"
}
# =============================================================================
@ -298,22 +316,24 @@ teardown() {
grep -q "# custom-project-name" custom-project-name/README.md
}
@test "setup.sh custom project has all subdirectories" {
@test "setup.sh custom project has all subdirectories in .ralph/" {
bash "$SETUP_SCRIPT" my-custom-app
assert_dir_exists "my-custom-app/specs/stdlib"
# Ralph-specific dirs in .ralph/
assert_dir_exists "my-custom-app/.ralph/specs/stdlib"
assert_dir_exists "my-custom-app/.ralph/examples"
assert_dir_exists "my-custom-app/.ralph/logs"
assert_dir_exists "my-custom-app/.ralph/docs/generated"
# src stays at root
assert_dir_exists "my-custom-app/src"
assert_dir_exists "my-custom-app/examples"
assert_dir_exists "my-custom-app/logs"
assert_dir_exists "my-custom-app/docs/generated"
}
@test "setup.sh custom project has all template files" {
@test "setup.sh custom project has all template files in .ralph/" {
bash "$SETUP_SCRIPT" my-custom-app
assert_file_exists "my-custom-app/PROMPT.md"
assert_file_exists "my-custom-app/@fix_plan.md"
assert_file_exists "my-custom-app/@AGENT.md"
assert_file_exists "my-custom-app/.ralph/PROMPT.md"
assert_file_exists "my-custom-app/.ralph/@fix_plan.md"
assert_file_exists "my-custom-app/.ralph/@AGENT.md"
}
# =============================================================================
@ -334,20 +354,25 @@ teardown() {
grep -q "# my-project" my-project/README.md
}
@test "setup.sh default project has all required structure" {
@test "setup.sh default project has all required structure in .ralph/" {
bash "$SETUP_SCRIPT"
# Verify all directories
assert_dir_exists "my-project/specs/stdlib"
assert_dir_exists "my-project/src"
assert_dir_exists "my-project/examples"
assert_dir_exists "my-project/logs"
assert_dir_exists "my-project/docs/generated"
# Verify .ralph directory exists
assert_dir_exists "my-project/.ralph"
# Verify all files
assert_file_exists "my-project/PROMPT.md"
assert_file_exists "my-project/@fix_plan.md"
assert_file_exists "my-project/@AGENT.md"
# Verify all directories in .ralph/
assert_dir_exists "my-project/.ralph/specs/stdlib"
assert_dir_exists "my-project/.ralph/examples"
assert_dir_exists "my-project/.ralph/logs"
assert_dir_exists "my-project/.ralph/docs/generated"
# src stays at root
assert_dir_exists "my-project/src"
# Verify all files in .ralph/
assert_file_exists "my-project/.ralph/PROMPT.md"
assert_file_exists "my-project/.ralph/@fix_plan.md"
assert_file_exists "my-project/.ralph/@AGENT.md"
# README stays at root
assert_file_exists "my-project/README.md"
}
@ -405,12 +430,12 @@ teardown() {
[[ "$output" == *"Project test-project created"* ]]
}
@test "setup.sh outputs next steps guidance" {
@test "setup.sh outputs next steps guidance with .ralph paths" {
run bash "$SETUP_SCRIPT" test-project
assert_success
[[ "$output" == *"Next steps:"* ]]
[[ "$output" == *"PROMPT.md"* ]]
[[ "$output" == *".ralph/PROMPT.md"* ]]
}
# =============================================================================
@ -418,11 +443,18 @@ teardown() {
# =============================================================================
@test "setup.sh fails if templates directory missing" {
# Remove templates directory
# Remove local templates directory
rm -rf templates
# Also hide global templates by overriding HOME to a temp location
local original_home="$HOME"
export HOME="$(mktemp -d)"
run bash "$SETUP_SCRIPT" test-project
# Restore HOME
export HOME="$original_home"
assert_failure
}