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

@ -23,7 +23,8 @@
"Bash(timeout 30 bats:*)",
"mcp__sequential-thinking__sequentialthinking",
"Bash(git fetch:*)",
"Bash(npm run test:unit:*)"
"Bash(npm run test:unit:*)",
"Bash(node_modules/.bin/bats:*)"
],
"deny": [],
"ask": []

41
.gitignore vendored
View file

@ -1,21 +1,26 @@
# Ralph generated files
.call_count
.last_reset
.exit_signals
status.json
.ralph_session
.ralph_session_history
.claude_session_id
# Ralph generated files (inside .ralph/ subfolder)
.ralph/.call_count
.ralph/.last_reset
.ralph/.exit_signals
.ralph/status.json
.ralph/.ralph_session
.ralph/.ralph_session_history
.ralph/.claude_session_id
.ralph/.response_analysis
.ralph/.circuit_breaker_state
.ralph/.circuit_breaker_history
.ralph/.json_parse_result
.ralph/.last_output_length
# Logs
logs/*
!logs/.gitkeep
# Ralph logs and generated docs
.ralph/logs/*
!.ralph/logs/.gitkeep
.ralph/docs/generated/*
!.ralph/docs/generated/.gitkeep
# General logs
*.log
# Generated documentation
docs/generated/*
!docs/generated/.gitkeep
# OS files
.DS_Store
Thumbs.db
@ -39,3 +44,9 @@ target/
.idea/
*.swp
*.swo
# Claude Code local settings
.claude/settings.local.json
# Ralph backup directories (created by migration)
.ralph_backup_*

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.9.8 | **Tests**: 276 passing (100% pass rate) | **CI/CD**: GitHub Actions
**Version**: v0.10.0 | **Tests**: 310 passing (100% pass rate) | **CI/CD**: GitHub Actions
## Core Architecture
@ -39,9 +39,9 @@ The system uses a modular architecture with reusable components in the `lib/` di
- Supports both flat JSON format and Claude CLI format (`result`, `sessionId`, `metadata`)
- Extracts structured fields: status, exit_signal, work_type, files_modified
- **Session management**: `store_session_id()`, `get_last_session_id()`, `should_resume_session()`
- Automatic session persistence to `.claude_session_id` file with 24-hour expiration
- Automatic session persistence to `.ralph/.claude_session_id` file with 24-hour expiration
- Session lifecycle: `get_session_id()`, `reset_session()`, `log_session_transition()`, `init_session_tracking()`
- Session history tracked in `.ralph_session_history` (last 50 transitions)
- Session history tracked in `.ralph/.ralph_session_history` (last 50 transitions)
- Session auto-reset on: circuit breaker open, manual interrupt, project completion
- Detects test-only loops and stuck error patterns
- Two-stage error filtering to eliminate false positives
@ -77,6 +77,13 @@ ralph-setup my-project-name
cd my-project-name
```
### Migrating Existing Projects
```bash
# Migrate from flat structure to .ralph/ subfolder (v0.10.0+)
cd existing-project
ralph-migrate
```
### Running the Ralph Loop
```bash
# Start with integrated tmux monitoring (recommended)
@ -129,13 +136,13 @@ bats tests/unit/test_cli_modern.bats
## Ralph Loop Configuration
The loop is controlled by several key files and environment variables:
The loop is controlled by several key files and environment variables within the `.ralph/` subfolder:
- **PROMPT.md** - Main prompt file that drives each loop iteration
- **@fix_plan.md** - Prioritized task list that Ralph follows
- **@AGENT.md** - Build and run instructions maintained by Ralph
- **status.json** - Real-time status tracking (JSON format)
- **logs/** - Execution logs for each loop iteration
- **.ralph/PROMPT.md** - Main prompt file that drives each loop iteration
- **.ralph/@fix_plan.md** - Prioritized task list that Ralph follows
- **.ralph/@AGENT.md** - Build and run instructions maintained by Ralph
- **.ralph/status.json** - Real-time status tracking (JSON format)
- **.ralph/logs/** - Execution logs for each loop iteration
### Rate Limiting
- Default: 100 API calls per hour (configurable via `--calls` flag)
@ -167,7 +174,7 @@ Each loop iteration injects context via `build_loop_context()`:
- Previous loop work summary
**Session Continuity:**
- Sessions are preserved in `.claude_session_id`
- Sessions are preserved in `.ralph/.claude_session_id`
- Use `--continue` flag to maintain context across loops
- Disable with `--no-continue` for isolated iterations
@ -178,12 +185,12 @@ The loop uses a dual-condition check to prevent premature exits during productiv
1. `recent_completion_indicators >= 2` (heuristic-based detection from natural language patterns)
2. Claude's explicit `EXIT_SIGNAL: true` in the RALPH_STATUS block
The `EXIT_SIGNAL` value is read from `.response_analysis` (at `.analysis.exit_signal`) which is populated by `response_analyzer.sh` from Claude's RALPH_STATUS output block.
The `EXIT_SIGNAL` value is read from `.ralph/.response_analysis` (at `.analysis.exit_signal`) which is populated by `response_analyzer.sh` from Claude's RALPH_STATUS output block.
**Other exit conditions (checked before completion indicators):**
- Multiple consecutive "done" signals from Claude Code (`done_signals >= 2`)
- Too many test-only loops indicating feature completeness (`test_loops >= 3`)
- All items in @fix_plan.md marked as completed
- All items in .ralph/@fix_plan.md marked as completed
**Example behavior when EXIT_SIGNAL is false:**
```
@ -223,19 +230,22 @@ Bash code coverage measurement with kcov has fundamental limitations when tracin
## Project Structure for Ralph-Managed Projects
Each project created with `./setup.sh` follows this structure:
Each project created with `./setup.sh` follows this structure with a `.ralph/` subfolder:
```
project-name/
├── PROMPT.md # Main development instructions
├── @fix_plan.md # Prioritized TODO list
├── @AGENT.md # Build/run instructions
├── specs/ # Project specifications
├── src/ # Source code
├── examples/ # Usage examples
├── logs/ # Loop execution logs
└── docs/generated/ # Auto-generated documentation
├── .ralph/ # Ralph configuration and state (hidden folder)
│ ├── PROMPT.md # Main development instructions
│ ├── @fix_plan.md # Prioritized TODO list
│ ├── @AGENT.md # Build/run instructions
│ ├── specs/ # Project specifications
│ ├── examples/ # Usage examples
│ ├── logs/ # Loop execution logs
│ └── docs/generated/ # Auto-generated documentation
└── src/ # Source code (at project root)
```
> **Migration**: Existing projects can be migrated with `ralph-migrate`.
## Template System
Templates in `templates/` provide starting points for new projects:
@ -245,25 +255,26 @@ Templates in `templates/` provide starting points for new projects:
## File Naming Conventions
- Files prefixed with `@` (e.g., `@fix_plan.md`) are Ralph-specific control files
- Hidden files (e.g., `.call_count`, `.exit_signals`) track loop state
- `logs/` contains timestamped execution logs
- `docs/generated/` for Ralph-created documentation
- `docs/code-review/` for code review reports
- Files prefixed with `@` (e.g., `.ralph/@fix_plan.md`) are Ralph-specific control files
- Hidden files within `.ralph/` (e.g., `.ralph/.call_count`, `.ralph/.exit_signals`) track loop state
- `.ralph/logs/` contains timestamped execution logs
- `.ralph/docs/generated/` for Ralph-created documentation
- `docs/code-review/` for code review reports (at project root)
## Global Installation
Ralph installs to:
- **Commands**: `~/.local/bin/` (ralph, ralph-monitor, ralph-setup, ralph-import)
- **Commands**: `~/.local/bin/` (ralph, ralph-monitor, ralph-setup, ralph-import, ralph-migrate)
- **Templates**: `~/.ralph/templates/`
- **Scripts**: `~/.ralph/` (ralph_loop.sh, ralph_monitor.sh, setup.sh, ralph_import.sh)
- **Libraries**: `~/.ralph/lib/` (circuit_breaker.sh, response_analyzer.sh, date_utils.sh)
- **Scripts**: `~/.ralph/` (ralph_loop.sh, ralph_monitor.sh, setup.sh, ralph_import.sh, migrate_to_ralph_folder.sh)
- **Libraries**: `~/.ralph/lib/` (circuit_breaker.sh, response_analyzer.sh, date_utils.sh, timeout_utils.sh)
After installation, the following global commands are available:
- `ralph` - Start the autonomous development loop
- `ralph-monitor` - Launch the monitoring dashboard
- `ralph-setup` - Create a new Ralph-managed project
- `ralph-import` - Import PRD/specification documents to Ralph format
- `ralph-migrate` - Migrate existing projects from flat structure to `.ralph/` subfolder
## Integration Points
@ -283,7 +294,7 @@ Ralph uses multiple mechanisms to detect when to exit:
- `MAX_CONSECUTIVE_TEST_LOOPS=3` - Exit if too many test-only iterations
- `MAX_CONSECUTIVE_DONE_SIGNALS=2` - Exit on repeated completion signals
- `TEST_PERCENTAGE_THRESHOLD=30%` - Flag if testing dominates recent loops
- Completion detection via @fix_plan.md checklist items
- Completion detection via .ralph/@fix_plan.md checklist items
### Completion Indicators with EXIT_SIGNAL Gate
@ -300,8 +311,8 @@ The `completion_indicators` exit condition requires dual verification:
**Implementation** (`ralph_loop.sh:312-327`):
```bash
local claude_exit_signal="false"
if [[ -f ".response_analysis" ]]; then
claude_exit_signal=$(jq -r '.analysis.exit_signal // false' ".response_analysis" 2>/dev/null || echo "false")
if [[ -f "$RALPH_DIR/.response_analysis" ]]; then
claude_exit_signal=$(jq -r '.analysis.exit_signal // false' "$RALPH_DIR/.response_analysis" 2>/dev/null || echo "false")
fi
if [[ $recent_completion_indicators -ge 2 ]] && [[ "$claude_exit_signal" == "true" ]]; then
@ -370,6 +381,15 @@ bats tests/unit/test_cli_parsing.bats
## Recent Improvements
### .ralph/ Subfolder Structure (v0.10.0) - BREAKING CHANGE
- **Breaking**: Moved all Ralph-specific files to `.ralph/` subfolder
- Project root stays clean: only `src/`, `README.md`, and user files remain
- Added `ralph-migrate` command for upgrading existing projects
- Migration script with fail-safe copy pattern (`cp -a source/. dest/`)
- Auto-detection of old structure with upgrade guidance
- Updated all configuration variables to use `$RALPH_DIR` prefix
- Test count: 310 (up from 308)
### Modern CLI for PRD Import (v0.9.8)
- Modernized `ralph_import.sh` to use Claude Code CLI JSON output format
- Added `--output-format json` flag for structured responses
@ -566,9 +586,9 @@ Before moving to the next feature, ALL changes must be:
- Create pull requests for all significant changes
4. **Ralph Integration**:
- Update @fix_plan.md with new tasks before starting work
- Mark items complete in @fix_plan.md upon completion
- Update PROMPT.md if Ralph's behavior needs modification
- Update .ralph/@fix_plan.md with new tasks before starting work
- Mark items complete in .ralph/@fix_plan.md upon completion
- Update .ralph/PROMPT.md if Ralph's behavior needs modification
- Test Ralph loop with new features before completion
### Documentation Requirements
@ -613,7 +633,7 @@ Before marking ANY feature as complete, verify:
- [ ] All changes committed with conventional commit messages
- [ ] All commits pushed to remote repository
- [ ] CI/CD pipeline passes
- [ ] @fix_plan.md task marked as complete
- [ ] .ralph/@fix_plan.md task marked as complete
- [ ] Implementation documentation updated
- [ ] Inline code comments updated or added
- [ ] CLAUDE.md updated (if new patterns introduced)

View file

@ -2,7 +2,7 @@
[![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.9.9-blue)
![Version](https://img.shields.io/badge/version-0.10.0-blue)
![Tests](https://img.shields.io/badge/tests-308%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)
@ -16,7 +16,7 @@ Ralph is an implementation of the Geoffrey Huntley's technique for Claude Code t
## Project Status
**Version**: v0.9.9 - Active Development
**Version**: v0.10.0 - Active Development
**Core Features**: Working and tested
**Test Coverage**: 308 tests, 100% pass rate
@ -40,6 +40,15 @@ Ralph is an implementation of the Geoffrey Huntley's technique for Claude Code t
### Recent Improvements
**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
- Added `ralph-migrate` command for upgrading existing projects
- Migration script creates backup before moving files
- Fail-safe migration: preserves dotfiles with `cp -a source/. dest/` pattern
- Auto-detection of old structure with helpful upgrade guidance
- Updated all tests for new structure (310 tests)
**v0.9.9 - EXIT_SIGNAL Gate & Uninstall Script**
- Fixed premature exit bug: completion indicators now require Claude's explicit `EXIT_SIGNAL: true`
- Added dual-condition check preventing exits when Claude reports work in progress
@ -152,7 +161,7 @@ cd ralph-claude-code
./install.sh
```
This adds `ralph`, `ralph-monitor`, and `ralph-setup` commands to your PATH.
This adds `ralph`, `ralph-monitor`, `ralph-setup`, `ralph-import`, and `ralph-migrate` commands to your PATH.
> **Note**: You only need to do this once per system. After installation, you can delete the cloned repository if desired.
@ -167,9 +176,9 @@ ralph-import my-requirements.md my-project
cd my-project
# Review and adjust the generated files:
# - PROMPT.md (Ralph instructions)
# - @fix_plan.md (task priorities)
# - specs/requirements.md (technical specs)
# - .ralph/PROMPT.md (Ralph instructions)
# - .ralph/@fix_plan.md (task priorities)
# - .ralph/specs/requirements.md (technical specs)
# Start autonomous development
ralph --monitor
@ -182,9 +191,9 @@ ralph-setup my-awesome-project
cd my-awesome-project
# Configure your project requirements manually
# Edit PROMPT.md with your project goals
# Edit specs/ with detailed specifications
# Edit @fix_plan.md with initial priorities
# Edit .ralph/PROMPT.md with your project goals
# Edit .ralph/specs/ with detailed specifications
# Edit .ralph/@fix_plan.md with initial priorities
# Start autonomous development
ralph --monitor
@ -247,7 +256,7 @@ Loop 8: Claude outputs "All tasks complete, project ready"
```
**Other exit conditions:**
- All tasks in `@fix_plan.md` marked complete
- All tasks in `.ralph/@fix_plan.md` marked complete
- Multiple consecutive "done" signals from Claude Code
- Too many test-focused loops (indicating feature completeness)
- Claude API 5-hour usage limit reached (with user prompt to wait or exit)
@ -284,10 +293,10 @@ ralph-import design-doc.pdf
Ralph-import creates a complete project with:
- **PROMPT.md** - Converted into Ralph development instructions
- **@fix_plan.md** - Requirements broken down into prioritized tasks
- **specs/requirements.md** - Technical specifications extracted from your document
- **Standard Ralph structure** - All necessary directories and template files
- **.ralph/PROMPT.md** - Converted into Ralph development instructions
- **.ralph/@fix_plan.md** - Requirements broken down into prioritized tasks
- **.ralph/specs/requirements.md** - Technical specifications extracted from your document
- **Standard Ralph structure** - All necessary directories and template files in `.ralph/`
The conversion is intelligent and preserves your original requirements while making them actionable for autonomous development.
@ -384,8 +393,8 @@ ralph --no-continue # Isolated iterations
ralph --reset-session # Clears current session
# Check session status
cat .ralph_session # View current session file
cat .ralph_session_history # View session transition history
cat .ralph/.ralph_session # View current session file
cat .ralph/.ralph_session_history # View session transition history
```
**Session Auto-Reset Triggers:**
@ -395,7 +404,7 @@ cat .ralph_session_history # View session transition history
- Manual circuit breaker reset (`--reset-circuit`)
- Session expiration (default: 24 hours)
Sessions are persisted to `.ralph_session` with a configurable expiration (default: 24 hours). The last 50 session transitions are logged to `.ralph_session_history` for debugging.
Sessions are persisted to `.ralph/.ralph_session` with a configurable expiration (default: 24 hours). The last 50 session transitions are logged to `.ralph/.ralph_session_history` for debugging.
### Exit Thresholds
@ -426,42 +435,45 @@ CB_OUTPUT_DECLINE_THRESHOLD=70 # Open circuit if output declines by >70%
## Project Structure
Ralph creates a standardized structure for each project:
Ralph creates a standardized structure for each project with a `.ralph/` subfolder for configuration:
```
my-project/
├── PROMPT.md # Main development instructions for Ralph
├── @fix_plan.md # Prioritized task list (@ prefix = Ralph control file)
├── @AGENT.md # Build and run instructions
├── specs/ # Project specifications and requirements
│ └── stdlib/ # Standard library specifications
├── src/ # Source code implementation
├── examples/ # Usage examples and test cases
├── logs/ # Ralph execution logs
└── docs/generated/ # Auto-generated documentation
├── .ralph/ # Ralph configuration and state (hidden folder)
│ ├── PROMPT.md # Main development instructions for Ralph
│ ├── @fix_plan.md # Prioritized task list (@ prefix = Ralph control file)
│ ├── @AGENT.md # Build and run instructions
│ ├── specs/ # Project specifications and requirements
│ │ └── stdlib/ # Standard library specifications
│ ├── examples/ # Usage examples and test cases
│ ├── logs/ # Ralph execution logs
│ └── docs/generated/ # Auto-generated documentation
└── src/ # Source code implementation (at project root)
```
> **Migration**: If you have existing Ralph projects using the old flat structure, run `ralph-migrate` to automatically move files to the `.ralph/` subfolder.
## Best Practices
### Writing Effective Prompts
1. **Be Specific** - Clear requirements lead to better results
2. **Prioritize** - Use `@fix_plan.md` to guide Ralph's focus
2. **Prioritize** - Use `.ralph/@fix_plan.md` to guide Ralph's focus
3. **Set Boundaries** - Define what's in/out of scope
4. **Include Examples** - Show expected inputs/outputs
### Project Specifications
- Place detailed requirements in `specs/`
- Use `@fix_plan.md` for prioritized task tracking
- Keep `@AGENT.md` updated with build instructions
- Place detailed requirements in `.ralph/specs/`
- Use `.ralph/@fix_plan.md` for prioritized task tracking
- Keep `.ralph/@AGENT.md` updated with build instructions
- Document key decisions and architecture
### Monitoring Progress
- Use `ralph-monitor` for live status updates
- Check logs in `logs/` for detailed execution history
- Monitor `status.json` for programmatic access
- Check logs in `.ralph/logs/` for detailed execution history
- Monitor `.ralph/status.json` for programmatic access
- Watch for exit condition signals
## System Requirements
@ -646,6 +658,7 @@ This project is licensed under the MIT License - see the [LICENSE](LICENSE) file
./uninstall.sh # Remove Ralph from system (dedicated script)
./install.sh uninstall # Alternative: Remove Ralph from system
./install.sh --help # Show installation help
ralph-migrate # Migrate existing project to .ralph/ structure
```
### Ralph Loop Options

View file

@ -6,7 +6,9 @@ set -e
echo "🚀 Creating Ralph for Claude Code repository structure..."
# Create directories
mkdir -p {logs,docs/generated,specs/stdlib,src,examples,templates/specs}
# Note: Project structure uses .ralph/ subfolder for Ralph-specific files
# src/ stays at root for compatibility with existing tooling
mkdir -p {src,templates/specs}
# Create main scripts
cat > ralph_loop.sh << 'EOF'
@ -17,19 +19,20 @@ cat > ralph_loop.sh << 'EOF'
set -e # Exit on any error
# Configuration
PROMPT_FILE="PROMPT.md"
LOG_DIR="logs"
DOCS_DIR="docs/generated"
STATUS_FILE="status.json"
# Configuration - Ralph files live in .ralph/ subfolder
RALPH_DIR="${RALPH_DIR:-.ralph}"
PROMPT_FILE="$RALPH_DIR/PROMPT.md"
LOG_DIR="$RALPH_DIR/logs"
DOCS_DIR="$RALPH_DIR/docs/generated"
STATUS_FILE="$RALPH_DIR/status.json"
CLAUDE_CODE_CMD="npx @anthropic/claude-code"
MAX_CALLS_PER_HOUR=100 # Adjust based on your plan
SLEEP_DURATION=3600 # 1 hour in seconds
CALL_COUNT_FILE=".call_count"
TIMESTAMP_FILE=".last_reset"
CALL_COUNT_FILE="$RALPH_DIR/.call_count"
TIMESTAMP_FILE="$RALPH_DIR/.last_reset"
# Exit detection configuration
EXIT_SIGNALS_FILE=".exit_signals"
EXIT_SIGNALS_FILE="$RALPH_DIR/.exit_signals"
MAX_CONSECUTIVE_TEST_LOOPS=3
MAX_CONSECUTIVE_DONE_SIGNALS=2
TEST_PERCENTAGE_THRESHOLD=30 # If more than 30% of recent loops are test-only, flag it
@ -201,9 +204,9 @@ should_exit_gracefully() {
fi
# 4. Check fix_plan.md for completion
if [[ -f "@fix_plan.md" ]]; then
local total_items=$(grep -c "^- \[" "@fix_plan.md" 2>/dev/null || echo "0")
local completed_items=$(grep -c "^- \[x\]" "@fix_plan.md" 2>/dev/null || echo "0")
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")
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)"
@ -377,8 +380,9 @@ cat > ralph_monitor.sh << 'EOF'
# Ralph Status Monitor - Live terminal dashboard for the Ralph loop
set -e
STATUS_FILE="status.json"
LOG_FILE="logs/ralph.log"
RALPH_DIR="${RALPH_DIR:-.ralph}"
STATUS_FILE="$RALPH_DIR/status.json"
LOG_FILE="$RALPH_DIR/logs/ralph.log"
REFRESH_INTERVAL=2
# Colors
@ -482,6 +486,7 @@ cat > setup.sh << 'EOF'
#!/bin/bash
# Ralph Project Setup Script
# Creates project structure with Ralph-specific files in .ralph/ subfolder
set -e
PROJECT_NAME=${1:-"my-project"}
@ -492,14 +497,17 @@ echo "🚀 Setting up Ralph project: $PROJECT_NAME"
mkdir -p "$PROJECT_NAME"
cd "$PROJECT_NAME"
# Create structure
mkdir -p {specs/stdlib,src,examples,logs,docs/generated}
# Create structure:
# - src/ stays at root for compatibility with existing tooling
# - All Ralph-specific files go in .ralph/ subfolder
mkdir -p src
mkdir -p .ralph/{specs/stdlib,examples,logs,docs/generated}
# Copy templates
cp ../templates/PROMPT.md .
cp ../templates/fix_plan.md @fix_plan.md
cp ../templates/AGENT.md @AGENT.md
cp -r ../templates/specs/* specs/ 2>/dev/null || true
# Copy templates to .ralph/
cp ../templates/PROMPT.md .ralph/
cp ../templates/fix_plan.md .ralph/@fix_plan.md
cp ../templates/AGENT.md .ralph/@AGENT.md
cp -r ../templates/specs/* .ralph/specs/ 2>/dev/null || true
# Initialize git
git init
@ -509,8 +517,8 @@ git commit -m "Initial Ralph project setup"
echo "✅ Project $PROJECT_NAME created!"
echo "Next steps:"
echo " 1. Edit PROMPT.md with your project requirements"
echo " 2. Update specs/ with your project specifications"
echo " 1. Edit .ralph/PROMPT.md with your project requirements"
echo " 2. Update .ralph/specs/ with your project specifications"
echo " 3. Run: ../ralph_loop.sh"
echo " 4. Monitor: ../ralph_monitor.sh"
EOF
@ -525,19 +533,19 @@ cat > templates/PROMPT.md << 'EOF'
You are Ralph, an autonomous AI development agent working on a [YOUR PROJECT NAME] project.
## Current Objectives
1. Study specs/* to learn about the project specifications
2. Review @fix_plan.md for current priorities
1. Study .ralph/specs/* to learn about the project specifications
2. Review .ralph/@fix_plan.md for current priorities
3. Implement the highest priority item using best practices
4. Use parallel subagents for complex tasks (max 100 concurrent)
5. Run tests after each implementation
6. Update documentation and fix_plan.md
6. Update documentation and .ralph/@fix_plan.md
## Key Principles
- ONE task per loop - focus on the most important thing
- Search the codebase before assuming something isn't implemented
- Use subagents for expensive operations (file searching, analysis)
- Write comprehensive tests with clear documentation
- Update @fix_plan.md with your learnings
- Update .ralph/@fix_plan.md with your learnings
- Commit working changes with descriptive messages
## 🧪 Testing Guidelines (CRITICAL)
@ -558,21 +566,21 @@ You are Ralph, an autonomous AI development agent working on a [YOUR PROJECT NAM
## Completion Awareness
If you believe the project is complete or nearly complete:
- Update @fix_plan.md to reflect completion status
- Update .ralph/@fix_plan.md to reflect completion status
- Summarize what has been accomplished
- Note any remaining minor tasks
- Do NOT continue with busy work like extensive testing
- Do NOT implement features not in the specifications
## File Structure
- specs/: Project specifications and requirements
- src/: Source code implementation
- examples/: Example usage and test cases
- @fix_plan.md: Prioritized TODO list
- @AGENT.md: Project build and run instructions
- .ralph/specs/: Project specifications and requirements
- src/: Source code implementation
- .ralph/examples/: Example usage and test cases
- .ralph/@fix_plan.md: Prioritized TODO list
- .ralph/@AGENT.md: Project build and run instructions
## Current Task
Follow @fix_plan.md and choose the most important item to implement next.
Follow .ralph/@fix_plan.md and choose the most important item to implement next.
Use your judgment to prioritize what will have the biggest impact on project progress.
Remember: Quality over speed. Build it right the first time. Know when you're done.
@ -659,19 +667,27 @@ EOF
# Create gitignore
cat > .gitignore << 'EOF'
# Ralph generated files
.call_count
.last_reset
.exit_signals
status.json
# Ralph generated files (inside .ralph/ subfolder)
.ralph/.call_count
.ralph/.last_reset
.ralph/.exit_signals
.ralph/status.json
.ralph/.ralph_session
.ralph/.ralph_session_history
.ralph/.claude_session_id
.ralph/.response_analysis
.ralph/.circuit_breaker_state
.ralph/.circuit_breaker_history
# Logs
logs/
# Ralph logs and generated docs
.ralph/logs/*
!.ralph/logs/.gitkeep
.ralph/docs/generated/*
!.ralph/docs/generated/.gitkeep
# General logs
*.log
# Generated documentation
docs/generated/
# OS files
.DS_Store
Thumbs.db
@ -695,6 +711,9 @@ target/
.idea/
*.swp
*.swo
# Ralph backup directories (created by migration)
.ralph_backup_*
EOF
# Make scripts executable

View file

@ -154,21 +154,37 @@ EOF
RALPH_HOME="$HOME/.ralph"
exec "$RALPH_HOME/ralph_import.sh" "$@"
EOF
# Create ralph-migrate command
cat > "$INSTALL_DIR/ralph-migrate" << 'EOF'
#!/bin/bash
# Ralph Migration - Global Command
# Migrates existing projects from flat structure to .ralph/ subfolder
RALPH_HOME="$HOME/.ralph"
exec "$RALPH_HOME/migrate_to_ralph_folder.sh" "$@"
EOF
# Copy actual script files to Ralph home with modifications for global operation
cp "$SCRIPT_DIR/ralph_monitor.sh" "$RALPH_HOME/"
# Copy PRD import script to Ralph home
cp "$SCRIPT_DIR/ralph_import.sh" "$RALPH_HOME/"
# Copy migration script to Ralph home
cp "$SCRIPT_DIR/migrate_to_ralph_folder.sh" "$RALPH_HOME/"
# Make all commands executable
chmod +x "$INSTALL_DIR/ralph"
chmod +x "$INSTALL_DIR/ralph-monitor"
chmod +x "$INSTALL_DIR/ralph-monitor"
chmod +x "$INSTALL_DIR/ralph-setup"
chmod +x "$INSTALL_DIR/ralph-import"
chmod +x "$INSTALL_DIR/ralph-migrate"
chmod +x "$RALPH_HOME/ralph_monitor.sh"
chmod +x "$RALPH_HOME/ralph_import.sh"
chmod +x "$RALPH_HOME/migrate_to_ralph_folder.sh"
chmod +x "$RALPH_HOME/lib/"*.sh
log "SUCCESS" "Ralph scripts installed to $INSTALL_DIR"
@ -193,7 +209,7 @@ install_ralph_loop() {
# Install global setup.sh
install_setup() {
log "INFO" "Installing global setup script..."
# Create modified setup.sh for global operation
cat > "$RALPH_HOME/setup.sh" << 'EOF'
#!/bin/bash
@ -210,14 +226,15 @@ echo "🚀 Setting up Ralph project: $PROJECT_NAME"
mkdir -p "$PROJECT_NAME"
cd "$PROJECT_NAME"
# Create structure
mkdir -p {specs/stdlib,src,examples,logs,docs/generated}
# Create structure with .ralph/ subfolder
mkdir -p src
mkdir -p .ralph/{specs/stdlib,examples,logs,docs/generated}
# Copy templates from Ralph home
cp "$RALPH_HOME/templates/PROMPT.md" .
cp "$RALPH_HOME/templates/fix_plan.md" @fix_plan.md
cp "$RALPH_HOME/templates/AGENT.md" @AGENT.md
cp -r "$RALPH_HOME/templates/specs/"* specs/ 2>/dev/null || true
# Copy templates from Ralph home to .ralph/ subfolder
cp "$RALPH_HOME/templates/PROMPT.md" .ralph/
cp "$RALPH_HOME/templates/fix_plan.md" .ralph/@fix_plan.md
cp "$RALPH_HOME/templates/AGENT.md" .ralph/@AGENT.md
cp -r "$RALPH_HOME/templates/specs/"* .ralph/specs/ 2>/dev/null || true
# Initialize git
git init
@ -227,14 +244,14 @@ git commit -m "Initial Ralph project setup"
echo "✅ Project $PROJECT_NAME created!"
echo "Next steps:"
echo " 1. Edit PROMPT.md with your project requirements"
echo " 2. Update specs/ with your project specifications"
echo " 1. Edit .ralph/PROMPT.md with your project requirements"
echo " 2. Update .ralph/specs/ with your project specifications"
echo " 3. Run: ralph --monitor"
echo " 4. Monitor: ralph-monitor (if running manually)"
EOF
chmod +x "$RALPH_HOME/setup.sh"
log "SUCCESS" "Global setup script installed"
}
@ -275,12 +292,13 @@ main() {
echo " ralph --help # Show Ralph options"
echo " ralph-setup my-project # Create new Ralph project"
echo " ralph-import prd.md # Convert PRD to Ralph project"
echo " ralph-migrate # Migrate existing project to .ralph/ structure"
echo " ralph-monitor # Manual monitoring dashboard"
echo ""
echo "Quick start:"
echo " 1. ralph-setup my-awesome-project"
echo " 2. cd my-awesome-project"
echo " 3. # Edit PROMPT.md with your requirements"
echo " 3. # Edit .ralph/PROMPT.md with your requirements"
echo " 4. ralph --monitor"
echo ""
@ -296,7 +314,7 @@ case "${1:-install}" in
;;
uninstall)
log "INFO" "Uninstalling Ralph for Claude Code..."
rm -f "$INSTALL_DIR/ralph" "$INSTALL_DIR/ralph-monitor" "$INSTALL_DIR/ralph-setup" "$INSTALL_DIR/ralph-import"
rm -f "$INSTALL_DIR/ralph" "$INSTALL_DIR/ralph-monitor" "$INSTALL_DIR/ralph-setup" "$INSTALL_DIR/ralph-import" "$INSTALL_DIR/ralph-migrate"
rm -rf "$RALPH_HOME"
log "SUCCESS" "Ralph for Claude Code uninstalled"
;;

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

317
migrate_to_ralph_folder.sh Executable file
View file

@ -0,0 +1,317 @@
#!/bin/bash
# Migration script for Ralph projects from flat structure to .ralph/ subfolder
# Version: 1.0.0
#
# This script migrates existing Ralph projects from the old flat structure:
# PROMPT.md, @fix_plan.md, @AGENT.md, specs/, logs/, docs/generated/
# To the new .ralph/ subfolder structure:
# .ralph/PROMPT.md, .ralph/@fix_plan.md, .ralph/@AGENT.md, .ralph/specs/, etc.
#
# Usage: ./migrate_to_ralph_folder.sh [project-directory]
#
# If no project directory is specified, the current directory is used.
set -e
# Colors
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m'
log() {
local level=$1
local message=$2
local color=""
case $level in
"INFO") color=$BLUE ;;
"WARN") color=$YELLOW ;;
"ERROR") color=$RED ;;
"SUCCESS") color=$GREEN ;;
esac
echo -e "${color}[$(date '+%H:%M:%S')] [$level] $message${NC}"
}
# Check if project is already migrated
is_already_migrated() {
local project_dir=$1
# Check if .ralph/ directory exists with key files
if [[ -d "$project_dir/.ralph" ]] && \
[[ -f "$project_dir/.ralph/PROMPT.md" ]] && \
[[ -f "$project_dir/.ralph/@fix_plan.md" ]]; then
return 0 # Already migrated
fi
return 1 # Not migrated
}
# Check if project needs migration (has old-style structure)
needs_migration() {
local project_dir=$1
# Check for old-style structure (files in root)
if [[ -f "$project_dir/PROMPT.md" ]] || \
[[ -f "$project_dir/@fix_plan.md" ]] || \
[[ -f "$project_dir/@AGENT.md" ]] || \
[[ -d "$project_dir/specs" && ! -d "$project_dir/.ralph/specs" ]] || \
[[ -d "$project_dir/logs" && ! -d "$project_dir/.ralph/logs" ]]; then
return 0 # Needs migration
fi
return 1 # Doesn't need migration
}
# Backup function
create_backup() {
local project_dir=$1
local backup_dir
local backup_ts
# Get timestamp with proper error handling
backup_ts="$(date +%Y%m%d_%H%M%S)" || {
log "ERROR" "Failed to get timestamp for backup"
return 1
}
backup_dir="$project_dir/.ralph_backup_${backup_ts}"
log "INFO" "Creating backup at $backup_dir" >&2
mkdir -p "$backup_dir"
# Backup files that will be moved
[[ -f "$project_dir/PROMPT.md" ]] && cp "$project_dir/PROMPT.md" "$backup_dir/"
[[ -f "$project_dir/@fix_plan.md" ]] && cp "$project_dir/@fix_plan.md" "$backup_dir/"
[[ -f "$project_dir/@AGENT.md" ]] && cp "$project_dir/@AGENT.md" "$backup_dir/"
[[ -d "$project_dir/specs" ]] && cp -r "$project_dir/specs" "$backup_dir/"
[[ -d "$project_dir/logs" ]] && cp -r "$project_dir/logs" "$backup_dir/"
[[ -d "$project_dir/docs/generated" ]] && cp -r "$project_dir/docs/generated" "$backup_dir/docs_generated"
[[ -d "$project_dir/examples" ]] && cp -r "$project_dir/examples" "$backup_dir/"
# Backup hidden state files
[[ -f "$project_dir/.call_count" ]] && cp "$project_dir/.call_count" "$backup_dir/"
[[ -f "$project_dir/.last_reset" ]] && cp "$project_dir/.last_reset" "$backup_dir/"
[[ -f "$project_dir/.exit_signals" ]] && cp "$project_dir/.exit_signals" "$backup_dir/"
[[ -f "$project_dir/.response_analysis" ]] && cp "$project_dir/.response_analysis" "$backup_dir/"
[[ -f "$project_dir/.circuit_breaker_state" ]] && cp "$project_dir/.circuit_breaker_state" "$backup_dir/"
[[ -f "$project_dir/.circuit_breaker_history" ]] && cp "$project_dir/.circuit_breaker_history" "$backup_dir/"
[[ -f "$project_dir/.claude_session_id" ]] && cp "$project_dir/.claude_session_id" "$backup_dir/"
[[ -f "$project_dir/.ralph_session" ]] && cp "$project_dir/.ralph_session" "$backup_dir/"
[[ -f "$project_dir/status.json" ]] && cp "$project_dir/status.json" "$backup_dir/"
echo "$backup_dir"
}
# Migrate project to new structure
migrate_project() {
local project_dir=$1
local backup_dir=$2
log "INFO" "Starting migration..."
# Create .ralph directory structure (examples created only if source exists)
mkdir -p "$project_dir/.ralph/specs/stdlib"
mkdir -p "$project_dir/.ralph/logs"
mkdir -p "$project_dir/.ralph/docs/generated"
# Move main configuration files
if [[ -f "$project_dir/PROMPT.md" ]]; then
log "INFO" "Moving PROMPT.md to .ralph/"
mv "$project_dir/PROMPT.md" "$project_dir/.ralph/PROMPT.md"
fi
if [[ -f "$project_dir/@fix_plan.md" ]]; then
log "INFO" "Moving @fix_plan.md to .ralph/"
mv "$project_dir/@fix_plan.md" "$project_dir/.ralph/@fix_plan.md"
fi
if [[ -f "$project_dir/@AGENT.md" ]]; then
log "INFO" "Moving @AGENT.md to .ralph/"
mv "$project_dir/@AGENT.md" "$project_dir/.ralph/@AGENT.md"
fi
# Move specs directory contents (fail-safe: preserve dotfiles, verify copy before delete)
if [[ -d "$project_dir/specs" ]]; then
log "INFO" "Moving specs/ to .ralph/specs/"
if [[ "$(ls -A "$project_dir/specs" 2>/dev/null)" ]]; then
# Use cp -a with /. pattern to preserve dotfiles and attributes
if cp -a "$project_dir/specs/." "$project_dir/.ralph/specs/"; then
rm -rf "$project_dir/specs"
else
log "WARN" "Failed to copy specs/, keeping original (backup available)"
fi
else
rm -rf "$project_dir/specs"
fi
fi
# Move logs directory contents (fail-safe: preserve dotfiles, verify copy before delete)
if [[ -d "$project_dir/logs" ]]; then
log "INFO" "Moving logs/ to .ralph/logs/"
if [[ "$(ls -A "$project_dir/logs" 2>/dev/null)" ]]; then
# Use cp -a with /. pattern to preserve dotfiles and attributes
if cp -a "$project_dir/logs/." "$project_dir/.ralph/logs/"; then
rm -rf "$project_dir/logs"
else
log "WARN" "Failed to copy logs/, keeping original (backup available)"
fi
else
rm -rf "$project_dir/logs"
fi
fi
# Move docs/generated contents (fail-safe: preserve dotfiles, verify copy before delete)
if [[ -d "$project_dir/docs/generated" ]]; then
log "INFO" "Moving docs/generated/ to .ralph/docs/generated/"
if [[ "$(ls -A "$project_dir/docs/generated" 2>/dev/null)" ]]; then
# Use cp -a with /. pattern to preserve dotfiles and attributes
if cp -a "$project_dir/docs/generated/." "$project_dir/.ralph/docs/generated/"; then
rm -rf "$project_dir/docs/generated"
# Remove docs directory if empty
rmdir "$project_dir/docs" 2>/dev/null || true
else
log "WARN" "Failed to copy docs/generated/, keeping original (backup available)"
fi
else
rm -rf "$project_dir/docs/generated"
rmdir "$project_dir/docs" 2>/dev/null || true
fi
fi
# Move hidden state files
local state_files=(
".call_count"
".last_reset"
".exit_signals"
".response_analysis"
".circuit_breaker_state"
".circuit_breaker_history"
".claude_session_id"
".ralph_session"
".ralph_session_history"
".json_parse_result"
".last_output_length"
"status.json"
)
for file in "${state_files[@]}"; do
if [[ -f "$project_dir/$file" ]]; then
log "INFO" "Moving $file to .ralph/"
mv "$project_dir/$file" "$project_dir/.ralph/$file"
fi
done
# Move examples if source exists (fail-safe: preserve dotfiles, verify copy before delete)
if [[ -d "$project_dir/examples" ]]; then
# Only move if target doesn't exist or is empty
if [[ ! -d "$project_dir/.ralph/examples" ]] || [[ -z "$(ls -A "$project_dir/.ralph/examples" 2>/dev/null)" ]]; then
log "INFO" "Moving examples/ to .ralph/examples/"
mkdir -p "$project_dir/.ralph/examples"
if [[ "$(ls -A "$project_dir/examples" 2>/dev/null)" ]]; then
# Use cp -a with /. pattern to preserve dotfiles and attributes
if cp -a "$project_dir/examples/." "$project_dir/.ralph/examples/"; then
rm -rf "$project_dir/examples"
else
log "WARN" "Failed to copy examples/, keeping original (backup available)"
fi
else
rm -rf "$project_dir/examples"
fi
fi
fi
log "SUCCESS" "Migration completed successfully!"
}
# Main function
main() {
local project_dir="${1:-.}"
# Convert to absolute path
project_dir=$(cd "$project_dir" && pwd)
log "INFO" "Checking project directory: $project_dir"
# Check if already migrated
if is_already_migrated "$project_dir"; then
log "SUCCESS" "Project is already using the new .ralph/ structure"
exit 0
fi
# Check if needs migration
if ! needs_migration "$project_dir"; then
log "WARN" "No Ralph project files found. Nothing to migrate."
log "INFO" "Expected files: PROMPT.md, @fix_plan.md, @AGENT.md, specs/, logs/"
exit 0
fi
# Create backup
backup_dir=$(create_backup "$project_dir")
log "SUCCESS" "Backup created at: $backup_dir"
# Perform migration
migrate_project "$project_dir" "$backup_dir"
echo ""
log "INFO" "Migration summary:"
echo " - Project files moved to .ralph/ subfolder"
echo " - Backup saved at: $backup_dir"
echo " - src/ directory preserved at project root"
echo ""
log "INFO" "Next steps:"
echo " 1. Verify the migration by checking .ralph/ contents"
echo " 2. Run 'ralph --status' to verify Ralph can read the new structure"
echo " 3. If everything works, you can delete the backup directory"
echo ""
}
# Show help
if [[ "$1" == "-h" || "$1" == "--help" ]]; then
cat << 'HELPEOF'
Ralph Migration Script - Migrate to .ralph/ subfolder structure
Usage: migrate_to_ralph_folder.sh [project-directory]
Arguments:
project-directory Path to the Ralph project to migrate (default: current directory)
Description:
This script migrates existing Ralph projects from the old flat structure to the
new .ralph/ subfolder structure. This change keeps source code clean by moving
Ralph-specific files into a dedicated subfolder.
Old structure:
project/
├── PROMPT.md
├── @fix_plan.md
├── @AGENT.md
├── specs/
├── logs/
└── src/
New structure:
project/
├── .ralph/
│ ├── PROMPT.md
│ ├── @fix_plan.md
│ ├── @AGENT.md
│ ├── specs/
│ ├── logs/
│ └── docs/generated/
└── src/
Features:
- Automatically detects if migration is needed
- Creates backup before migration
- Moves all Ralph-specific files and state
- Preserves src/ at project root
Examples:
migrate_to_ralph_folder.sh # Migrate current directory
migrate_to_ralph_folder.sh ./my-project # Migrate specific project
HELPEOF
exit 0
fi
main "$@"

View file

@ -260,9 +260,9 @@ Supported formats:
The command will:
1. Create a new Ralph project
2. Use Claude Code to intelligently convert your PRD into:
- PROMPT.md (Ralph instructions)
- @fix_plan.md (prioritized tasks)
- specs/ (technical specifications)
- .ralph/PROMPT.md (Ralph instructions)
- .ralph/@fix_plan.md (prioritized tasks)
- .ralph/specs/ (technical specifications)
HELPEOF
}
@ -312,9 +312,9 @@ Analyze the provided specification file and extract:
## Required Outputs
Create these files in the current directory:
Create these files in the .ralph/ subdirectory:
### 1. PROMPT.md
### 1. .ralph/PROMPT.md
Transform the PRD into Ralph development instructions:
```markdown
# Ralph Development Instructions
@ -353,7 +353,7 @@ You are Ralph, an autonomous AI development agent working on a [PROJECT NAME] pr
Follow @fix_plan.md and choose the most important item to implement next.
```
### 2. @fix_plan.md
### 2. .ralph/@fix_plan.md
Convert requirements into a prioritized task list:
```markdown
# Ralph Fix Plan
@ -374,7 +374,7 @@ Convert requirements into a prioritized task list:
[Any important context from the original PRD]
```
### 3. specs/requirements.md
### 3. .ralph/specs/requirements.md
Create detailed technical specifications:
```markdown
# Technical Specifications
@ -505,7 +505,7 @@ PROMPTEOF
# Use PARSED_FILES_CREATED from JSON if available, otherwise check filesystem
local missing_files=()
local created_files=()
local expected_files=("PROMPT.md" "@fix_plan.md" "specs/requirements.md")
local expected_files=(".ralph/PROMPT.md" ".ralph/@fix_plan.md" ".ralph/specs/requirements.md")
# If JSON provided files_created, use that to inform verification
if [[ "$json_parsed" == "true" && -n "$PARSED_FILES_CREATED" && "$PARSED_FILES_CREATED" != "[]" ]]; then
@ -605,9 +605,9 @@ main() {
echo ""
echo "Next steps:"
echo " 1. Review and edit the generated files:"
echo " - PROMPT.md (Ralph instructions)"
echo " - @fix_plan.md (task priorities)"
echo " - specs/requirements.md (technical specs)"
echo " - .ralph/PROMPT.md (Ralph instructions)"
echo " - .ralph/@fix_plan.md (task priorities)"
echo " - .ralph/specs/requirements.md (technical specs)"
echo " 2. Start autonomous development:"
echo " ralph --monitor"
echo ""

View file

@ -13,31 +13,33 @@ source "$SCRIPT_DIR/lib/response_analyzer.sh"
source "$SCRIPT_DIR/lib/circuit_breaker.sh"
# Configuration
PROMPT_FILE="PROMPT.md"
LOG_DIR="logs"
DOCS_DIR="docs/generated"
STATUS_FILE="status.json"
PROGRESS_FILE="progress.json"
# Ralph-specific files live in .ralph/ subfolder
RALPH_DIR=".ralph"
PROMPT_FILE="$RALPH_DIR/PROMPT.md"
LOG_DIR="$RALPH_DIR/logs"
DOCS_DIR="$RALPH_DIR/docs/generated"
STATUS_FILE="$RALPH_DIR/status.json"
PROGRESS_FILE="$RALPH_DIR/progress.json"
CLAUDE_CODE_CMD="claude"
MAX_CALLS_PER_HOUR=100 # Adjust based on your plan
VERBOSE_PROGRESS=false # Default: no verbose progress updates
CLAUDE_TIMEOUT_MINUTES=15 # Default: 15 minutes timeout for Claude Code execution
SLEEP_DURATION=3600 # 1 hour in seconds
CALL_COUNT_FILE=".call_count"
TIMESTAMP_FILE=".last_reset"
CALL_COUNT_FILE="$RALPH_DIR/.call_count"
TIMESTAMP_FILE="$RALPH_DIR/.last_reset"
USE_TMUX=false
# Modern Claude CLI configuration (Phase 1.1)
CLAUDE_OUTPUT_FORMAT="json" # Options: json, text
CLAUDE_ALLOWED_TOOLS="Write,Bash(git *),Read" # Comma-separated list of allowed tools
CLAUDE_USE_CONTINUE=true # Enable session continuity
CLAUDE_SESSION_FILE=".claude_session_id" # Session ID persistence file
CLAUDE_SESSION_FILE="$RALPH_DIR/.claude_session_id" # Session ID persistence file
CLAUDE_MIN_VERSION="2.0.76" # Minimum required Claude CLI version
# Session management configuration (Phase 1.2)
# Note: SESSION_EXPIRATION_SECONDS is defined in lib/response_analyzer.sh (86400 = 24 hours)
RALPH_SESSION_FILE=".ralph_session" # Ralph-specific session tracking (lifecycle)
RALPH_SESSION_HISTORY_FILE=".ralph_session_history" # Session transition history
RALPH_SESSION_FILE="$RALPH_DIR/.ralph_session" # Ralph-specific session tracking (lifecycle)
RALPH_SESSION_HISTORY_FILE="$RALPH_DIR/.ralph_session_history" # Session transition history
# Session expiration: 24 hours default balances project continuity with fresh context
# Too short = frequent context loss; Too long = stale context causes unpredictable behavior
CLAUDE_SESSION_EXPIRY_HOURS=${CLAUDE_SESSION_EXPIRY_HOURS:-24}
@ -65,7 +67,8 @@ VALID_TOOL_PATTERNS=(
)
# Exit detection configuration
EXIT_SIGNALS_FILE=".exit_signals"
EXIT_SIGNALS_FILE="$RALPH_DIR/.exit_signals"
RESPONSE_ANALYSIS_FILE="$RALPH_DIR/.response_analysis"
MAX_CONSECUTIVE_TEST_LOOPS=3
MAX_CONSECUTIVE_DONE_SIGNALS=2
TEST_PERCENTAGE_THRESHOLD=30 # If more than 30% of recent loops are test-only, flag it
@ -124,7 +127,7 @@ setup_tmux_session() {
if [[ "$MAX_CALLS_PER_HOUR" != "100" ]]; then
ralph_cmd="$ralph_cmd --calls $MAX_CALLS_PER_HOUR"
fi
if [[ "$PROMPT_FILE" != "PROMPT.md" ]]; then
if [[ "$PROMPT_FILE" != "$RALPH_DIR/PROMPT.md" ]]; then
ralph_cmd="$ralph_cmd --prompt '$PROMPT_FILE'"
fi
@ -315,8 +318,8 @@ should_exit_gracefully() {
# but Claude explicitly indicates work is still in progress via RALPH_STATUS block.
# The exit_signal in .response_analysis represents Claude's explicit intent.
local claude_exit_signal="false"
if [[ -f ".response_analysis" ]]; then
claude_exit_signal=$(jq -r '.analysis.exit_signal // false' ".response_analysis" 2>/dev/null || echo "false")
if [[ -f "$RESPONSE_ANALYSIS_FILE" ]]; then
claude_exit_signal=$(jq -r '.analysis.exit_signal // false' "$RESPONSE_ANALYSIS_FILE" 2>/dev/null || echo "false")
fi
if [[ $recent_completion_indicators -ge 2 ]] && [[ "$claude_exit_signal" == "true" ]]; then
@ -328,23 +331,23 @@ should_exit_gracefully() {
fi
# 4. Check fix_plan.md for completion
if [[ -f "@fix_plan.md" ]]; then
local total_items=$(grep -c "^- \[" "@fix_plan.md" 2>/dev/null)
local completed_items=$(grep -c "^- \[x\]" "@fix_plan.md" 2>/dev/null)
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)
# Handle case where grep returns no matches (exit code 1)
[[ -z "$total_items" ]] && total_items=0
[[ -z "$completed_items" ]] && completed_items=0
log_status "INFO" "DEBUG: @fix_plan.md check - total_items:$total_items, completed_items:$completed_items" >&2
log_status "INFO" "DEBUG: .ralph/@fix_plan.md check - total_items:$total_items, completed_items:$completed_items" >&2
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)" >&2
echo "plan_complete"
return 0
fi
else
log_status "INFO" "DEBUG: @fix_plan.md file not found" >&2
log_status "INFO" "DEBUG: .ralph/@fix_plan.md file not found" >&2
fi
log_status "INFO" "DEBUG: No exit conditions met, continuing loop" >&2
@ -442,22 +445,22 @@ build_loop_context() {
context="Loop #${loop_count}. "
# Extract incomplete tasks from @fix_plan.md
if [[ -f "@fix_plan.md" ]]; then
local incomplete_tasks=$(grep -c "^- \[ \]" "@fix_plan.md" 2>/dev/null || echo "0")
if [[ -f "$RALPH_DIR/@fix_plan.md" ]]; then
local incomplete_tasks=$(grep -c "^- \[ \]" "$RALPH_DIR/@fix_plan.md" 2>/dev/null || echo "0")
context+="Remaining tasks: ${incomplete_tasks}. "
fi
# Add circuit breaker state
if [[ -f ".circuit_breaker_state" ]]; then
local cb_state=$(jq -r '.state // "UNKNOWN"' .circuit_breaker_state 2>/dev/null)
if [[ -f "$RALPH_DIR/.circuit_breaker_state" ]]; then
local cb_state=$(jq -r '.state // "UNKNOWN"' "$RALPH_DIR/.circuit_breaker_state" 2>/dev/null)
if [[ "$cb_state" != "CLOSED" && "$cb_state" != "null" && -n "$cb_state" ]]; then
context+="Circuit breaker: ${cb_state}. "
fi
fi
# Add previous loop summary (truncated)
if [[ -f ".response_analysis" ]]; then
local prev_summary=$(jq -r '.analysis.work_summary // ""' .response_analysis 2>/dev/null | head -c 200)
if [[ -f "$RESPONSE_ANALYSIS_FILE" ]]; then
local prev_summary=$(jq -r '.analysis.work_summary // ""' "$RESPONSE_ANALYSIS_FILE" 2>/dev/null | head -c 200)
if [[ -n "$prev_summary" && "$prev_summary" != "null" ]]; then
context+="Previous: ${prev_summary}"
fi
@ -1028,32 +1031,46 @@ loop_count=0
# Main loop
main() {
log_status "SUCCESS" "🚀 Ralph loop starting with Claude Code"
log_status "INFO" "Max calls per hour: $MAX_CALLS_PER_HOUR"
log_status "INFO" "Logs: $LOG_DIR/ | Docs: $DOCS_DIR/ | Status: $STATUS_FILE"
# Check if project uses old flat structure and needs migration
if [[ -f "PROMPT.md" ]] && [[ ! -d ".ralph" ]]; then
log_status "ERROR" "This project uses the old flat structure."
echo ""
echo "Ralph v0.10.0+ uses a .ralph/ subfolder to keep your project root clean."
echo ""
echo "To upgrade your project, run:"
echo " ralph-migrate"
echo ""
echo "This will move Ralph-specific files to .ralph/ while preserving src/ at root."
echo "A backup will be created before migration."
exit 1
fi
# Check if this is a Ralph project directory
if [[ ! -f "$PROMPT_FILE" ]]; then
log_status "ERROR" "Prompt file '$PROMPT_FILE' not found!"
echo ""
# Check if this looks like a partial Ralph project
if [[ -f "@fix_plan.md" ]] || [[ -d "specs" ]] || [[ -f "@AGENT.md" ]]; then
echo "This appears to be a Ralph project but is missing PROMPT.md."
if [[ -f "$RALPH_DIR/@fix_plan.md" ]] || [[ -d "$RALPH_DIR/specs" ]] || [[ -f "$RALPH_DIR/@AGENT.md" ]]; then
echo "This appears to be a Ralph project but is missing .ralph/PROMPT.md."
echo "You may need to create or restore the PROMPT.md file."
else
echo "This directory is not a Ralph project."
fi
echo ""
echo "To fix this:"
echo " 1. Create a new project: ralph-setup my-project"
echo " 2. Import existing requirements: ralph-import requirements.md"
echo " 3. Navigate to an existing Ralph project directory"
echo " 4. Or create PROMPT.md manually in this directory"
echo " 4. Or create .ralph/PROMPT.md manually in this directory"
echo ""
echo "Ralph projects should contain: PROMPT.md, @fix_plan.md, specs/, src/, etc."
echo "Ralph projects should contain: .ralph/PROMPT.md, .ralph/@fix_plan.md, .ralph/specs/, src/, etc."
exit 1
fi
@ -1203,10 +1220,10 @@ Files created:
- $LOG_DIR/: All execution logs
- $DOCS_DIR/: Generated documentation
- $STATUS_FILE: Current status (JSON)
- .ralph_session: Session lifecycle tracking
- .ralph_session_history: Session transition history (last 50)
- .call_count: API call counter for rate limiting
- .last_reset: Timestamp of last rate limit reset
- .ralph/.ralph_session: Session lifecycle tracking
- .ralph/.ralph_session_history: Session transition history (last 50)
- .ralph/.call_count: API call counter for rate limiting
- .ralph/.last_reset: Timestamp of last rate limit reset
Example workflow:
ralph-setup my-project # Create project

View file

@ -1,6 +1,7 @@
#!/bin/bash
# Ralph Project Setup Script
# Creates project structure with Ralph-specific files in .ralph/ subfolder
set -e
PROJECT_NAME=${1:-"my-project"}
@ -11,14 +12,37 @@ echo "🚀 Setting up Ralph project: $PROJECT_NAME"
mkdir -p "$PROJECT_NAME"
cd "$PROJECT_NAME"
# Create structure
mkdir -p {specs/stdlib,src,examples,logs,docs/generated}
# Determine templates directory location (checked AFTER cd into project)
# Check local ../templates first, then global ~/.ralph/templates
TEMPLATES_DIR=""
if [[ -d "../templates" ]]; then
TEMPLATES_DIR="../templates"
elif [[ -d "$HOME/.ralph/templates" ]]; then
TEMPLATES_DIR="$HOME/.ralph/templates"
else
echo "❌ Error: Templates directory not found."
echo " Expected at: ../templates or ~/.ralph/templates"
echo " Please run ./install.sh first to install Ralph globally."
exit 1
fi
# Copy templates
cp ../templates/PROMPT.md .
cp ../templates/fix_plan.md @fix_plan.md
cp ../templates/AGENT.md @AGENT.md
cp -r ../templates/specs/* specs/ 2>/dev/null || true
# Verify required template files exist
if [[ ! -f "$TEMPLATES_DIR/PROMPT.md" ]]; then
echo "❌ Error: Required template file PROMPT.md not found in $TEMPLATES_DIR"
exit 1
fi
# Create structure:
# - src/ stays at root for compatibility with existing tooling
# - All Ralph-specific files go in .ralph/ subfolder
mkdir -p src
mkdir -p .ralph/{specs/stdlib,examples,logs,docs/generated}
# Copy templates to .ralph/
cp "$TEMPLATES_DIR/PROMPT.md" .ralph/
cp "$TEMPLATES_DIR/fix_plan.md" .ralph/@fix_plan.md
cp "$TEMPLATES_DIR/AGENT.md" .ralph/@AGENT.md
cp -r "$TEMPLATES_DIR/specs"/* .ralph/specs/ 2>/dev/null || true
# Initialize git
git init
@ -28,7 +52,7 @@ git commit -m "Initial Ralph project setup"
echo "✅ Project $PROJECT_NAME created!"
echo "Next steps:"
echo " 1. Edit PROMPT.md with your project requirements"
echo " 2. Update specs/ with your project specifications"
echo " 1. Edit .ralph/PROMPT.md with your project requirements"
echo " 2. Update .ralph/specs/ with your project specifications"
echo " 3. Run: ../ralph_loop.sh"
echo " 4. Monitor: ../ralph_monitor.sh"

View file

@ -94,9 +94,9 @@ Before moving to the next feature, ALL changes must be:
- Create pull requests for all significant changes
4. **Ralph Integration**:
- Update @fix_plan.md with new tasks before starting work
- Mark items complete in @fix_plan.md upon completion
- Update PROMPT.md if development patterns change
- Update .ralph/@fix_plan.md with new tasks before starting work
- Mark items complete in .ralph/@fix_plan.md upon completion
- Update .ralph/PROMPT.md if development patterns change
- Test features work within Ralph's autonomous loop
### Documentation Requirements
@ -137,10 +137,10 @@ Before marking ANY feature as complete, verify:
- [ ] Type checking passes (if applicable)
- [ ] All changes committed with conventional commit messages
- [ ] All commits pushed to remote repository
- [ ] @fix_plan.md task marked as complete
- [ ] .ralph/@fix_plan.md task marked as complete
- [ ] Implementation documentation updated
- [ ] Inline code comments updated or added
- [ ] AGENT.md updated (if new patterns introduced)
- [ ] .ralph/@AGENT.md updated (if new patterns introduced)
- [ ] Breaking changes documented
- [ ] Features tested within Ralph loop (if applicable)
- [ ] CI/CD pipeline passes
@ -149,7 +149,7 @@ Before marking ANY feature as complete, verify:
These standards ensure:
- **Quality**: High test coverage and pass rates prevent regressions
- **Traceability**: Git commits and @fix_plan.md provide clear history of changes
- **Traceability**: Git commits and .ralph/@fix_plan.md provide clear history of changes
- **Maintainability**: Current documentation reduces onboarding time and prevents knowledge loss
- **Collaboration**: Pushed changes enable team visibility and code review
- **Reliability**: Consistent quality gates maintain production stability

View file

@ -4,8 +4,8 @@
You are Ralph, an autonomous AI development agent working on a [YOUR PROJECT NAME] project.
## Current Objectives
1. Study specs/* to learn about the project specifications
2. Review @fix_plan.md for current priorities
1. Study .ralph/specs/* to learn about the project specifications
2. Review .ralph/@fix_plan.md for current priorities
3. Implement the highest priority item using best practices
4. Use parallel subagents for complex tasks (max 100 concurrent)
5. Run tests after each implementation
@ -16,7 +16,7 @@ You are Ralph, an autonomous AI development agent working on a [YOUR PROJECT NAM
- Search the codebase before assuming something isn't implemented
- Use subagents for expensive operations (file searching, analysis)
- Write comprehensive tests with clear documentation
- Update @fix_plan.md with your learnings
- Update .ralph/@fix_plan.md with your learnings
- Commit working changes with descriptive messages
## 🧪 Testing Guidelines (CRITICAL)
@ -31,7 +31,7 @@ You are Ralph, an autonomous AI development agent working on a [YOUR PROJECT NAM
- Before making changes: search codebase using subagents
- After implementation: run ESSENTIAL tests for the modified code only
- If tests fail: fix them as part of your current work
- Keep @AGENT.md updated with build/run instructions
- Keep .ralph/@AGENT.md updated with build/run instructions
- Document the WHY behind tests and implementations
- No placeholder implementations - build it properly
@ -115,10 +115,10 @@ Each scenario shows the exact conditions and expected behavior.
### Scenario 1: Successful Project Completion
**Given**:
- All items in @fix_plan.md are marked [x]
- All items in .ralph/@fix_plan.md are marked [x]
- Last test run shows all tests passing
- No errors in recent logs/
- All requirements from specs/ are implemented
- All requirements from .ralph/specs/ are implemented
**When**: You evaluate project status at end of loop
@ -193,7 +193,7 @@ RECOMMENDATION: Stuck on [error description] - human intervention needed
### Scenario 4: No Work Remaining
**Given**:
- All tasks in @fix_plan.md are complete
- You analyze specs/ and find nothing new to implement
- You analyze .ralph/specs/ and find nothing new to implement
- Code quality is acceptable
- Tests are passing
@ -208,7 +208,7 @@ FILES_MODIFIED: 0
TESTS_STATUS: PASSING
WORK_TYPE: DOCUMENTATION
EXIT_SIGNAL: true
RECOMMENDATION: No remaining work, all specs implemented
RECOMMENDATION: No remaining work, all .ralph/specs implemented
---END_RALPH_STATUS---
```
@ -218,7 +218,7 @@ RECOMMENDATION: No remaining work, all specs implemented
### Scenario 5: Making Progress
**Given**:
- Tasks remain in @fix_plan.md
- Tasks remain in .ralph/@fix_plan.md
- Implementation is underway
- Files are being modified
- Tests are passing or being fixed
@ -234,7 +234,7 @@ FILES_MODIFIED: 7
TESTS_STATUS: PASSING
WORK_TYPE: IMPLEMENTATION
EXIT_SIGNAL: false
RECOMMENDATION: Continue with next task from @fix_plan.md
RECOMMENDATION: Continue with next task from .ralph/@fix_plan.md
---END_RALPH_STATUS---
```
@ -268,14 +268,18 @@ RECOMMENDATION: Blocked on [specific dependency] - need [what's needed]
---
## File Structure
- specs/: Project specifications and requirements
- src/: Source code implementation
- .ralph/: Ralph-specific configuration and documentation
- specs/: Project specifications and requirements
- @fix_plan.md: Prioritized TODO list
- @AGENT.md: Project build and run instructions
- PROMPT.md: This file - Ralph development instructions
- logs/: Loop execution logs
- docs/generated/: Auto-generated documentation
- src/: Source code implementation
- examples/: Example usage and test cases
- @fix_plan.md: Prioritized TODO list
- @AGENT.md: Project build and run instructions
## Current Task
Follow @fix_plan.md and choose the most important item to implement next.
Follow .ralph/@fix_plan.md and choose the most important item to implement next.
Use your judgment to prioritize what will have the biggest impact on project progress.
Remember: Quality over speed. Build it right the first time. Know when you're done.

View file

@ -353,20 +353,25 @@ EOF
}
# Create complete test project structure
# Creates .ralph/ subfolder structure for Ralph-specific files
create_test_project() {
local project_dir=${1:-"test_project"}
mkdir -p "$project_dir"/{specs/stdlib,src,examples,logs,docs/generated}
# Create project with .ralph/ subfolder structure
mkdir -p "$project_dir"/src
mkdir -p "$project_dir"/.ralph/{specs/stdlib,examples,logs,docs/generated}
cd "$project_dir" || return 1
create_sample_prompt "PROMPT.md"
create_sample_fix_plan "@fix_plan.md" 10 3
create_sample_agent_md "@AGENT.md"
# Create Ralph files in .ralph/ subdirectory
create_sample_prompt ".ralph/PROMPT.md"
create_sample_fix_plan ".ralph/@fix_plan.md" 10 3
create_sample_agent_md ".ralph/@AGENT.md"
echo "0" > .call_count
echo "$(date +%Y%m%d%H)" > .last_reset
echo '{"test_only_loops": [], "done_signals": [], "completion_indicators": []}' > .exit_signals
# Create state files in .ralph/
echo "0" > .ralph/.call_count
echo "$(date +%Y%m%d%H)" > .ralph/.last_reset
echo '{"test_only_loops": [], "done_signals": [], "completion_indicators": []}' > .ralph/.exit_signals
cd - > /dev/null || return 1
}

View file

@ -1,6 +1,12 @@
#!/usr/bin/env bash
# Test Helper Utilities for Ralph Test Suite
# Helper: Fail with message (for use in assertions)
fail() {
echo "$1"
return 1
}
# Simple assertion functions (replacing bats-assert)
assert_success() {
if [ "$status" -ne 0 ]; then
@ -43,18 +49,20 @@ setup() {
export TEST_TEMP_DIR="$(mktemp -d "${BATS_TEST_TMPDIR}/test.XXXXXX")"
cd "$TEST_TEMP_DIR"
# Set up test environment variables
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"
# Set up test environment variables with .ralph/ subfolder structure
export RALPH_DIR=".ralph"
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 RESPONSE_ANALYSIS_FILE="$RALPH_DIR/.response_analysis"
# Create necessary directories
mkdir -p "$LOG_DIR" "$DOCS_DIR"
mkdir -p "$LOG_DIR" "$DOCS_DIR" "$RALPH_DIR"
# Initialize files
echo "0" > "$CALL_COUNT_FILE"
@ -77,6 +85,7 @@ strip_colors() {
# Helper: Create a mock PROMPT.md file
create_mock_prompt() {
mkdir -p "$RALPH_DIR"
cat > "$PROMPT_FILE" << 'EOF'
# Test Prompt
This is a test prompt for Ralph.
@ -90,19 +99,21 @@ EOF
create_mock_fix_plan() {
local total=${1:-5}
local completed=${2:-0}
local fix_plan_file="$RALPH_DIR/@fix_plan.md"
cat > "@fix_plan.md" << EOF
mkdir -p "$RALPH_DIR"
cat > "$fix_plan_file" << EOF
# Fix Plan
## High Priority
EOF
for ((i=1; i<=completed; i++)); do
echo "- [x] Completed task $i" >> "@fix_plan.md"
echo "- [x] Completed task $i" >> "$fix_plan_file"
done
for ((i=completed+1; i<=total; i++)); do
echo "- [ ] Pending task $i" >> "@fix_plan.md"
echo "- [ ] Pending task $i" >> "$fix_plan_file"
done
}

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
}

View file

@ -15,15 +15,16 @@ setup() {
git config user.email "test@example.com"
git config user.name "Test User"
# Set up environment
export PROMPT_FILE="PROMPT.md"
export LOG_DIR="logs"
export DOCS_DIR="docs/generated"
export STATUS_FILE="status.json"
export EXIT_SIGNALS_FILE=".exit_signals"
export CALL_COUNT_FILE=".call_count"
export TIMESTAMP_FILE=".last_reset"
export CLAUDE_SESSION_FILE=".claude_session_id"
# Set up environment with .ralph/ subfolder structure
export RALPH_DIR=".ralph"
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 EXIT_SIGNALS_FILE="$RALPH_DIR/.exit_signals"
export CALL_COUNT_FILE="$RALPH_DIR/.call_count"
export TIMESTAMP_FILE="$RALPH_DIR/.last_reset"
export CLAUDE_SESSION_FILE="$RALPH_DIR/.claude_session_id"
export CLAUDE_MIN_VERSION="2.0.76"
export CLAUDE_CODE_CMD="claude"
@ -34,7 +35,7 @@ setup() {
# Create sample project files
create_sample_prompt
create_sample_fix_plan "@fix_plan.md" 10 3
create_sample_fix_plan "$RALPH_DIR/@fix_plan.md" 10 3
# Source library components
source "${BATS_TEST_DIRNAME}/../../lib/date_utils.sh"
@ -92,20 +93,20 @@ setup() {
context="Loop #${loop_count}. "
if [[ -f "@fix_plan.md" ]]; then
local incomplete_tasks=$(grep -c "^- \[ \]" "@fix_plan.md" 2>/dev/null || echo "0")
if [[ -f "$RALPH_DIR/@fix_plan.md" ]]; then
local incomplete_tasks=$(grep -c "^- \[ \]" "$RALPH_DIR/@fix_plan.md" 2>/dev/null || echo "0")
context+="Remaining tasks: ${incomplete_tasks}. "
fi
if [[ -f ".circuit_breaker_state" ]]; then
local cb_state=$(jq -r '.state // "UNKNOWN"' .circuit_breaker_state 2>/dev/null)
if [[ -f "$RALPH_DIR/.circuit_breaker_state" ]]; then
local cb_state=$(jq -r '.state // "UNKNOWN"' "$RALPH_DIR/.circuit_breaker_state" 2>/dev/null)
if [[ "$cb_state" != "CLOSED" && "$cb_state" != "null" && -n "$cb_state" ]]; then
context+="Circuit breaker: ${cb_state}. "
fi
fi
if [[ -f ".response_analysis" ]]; then
local prev_summary=$(jq -r '.analysis.work_summary // ""' .response_analysis 2>/dev/null | head -c 200)
if [[ -f "$RALPH_DIR/.response_analysis" ]]; then
local prev_summary=$(jq -r '.analysis.work_summary // ""' "$RALPH_DIR/.response_analysis" 2>/dev/null | head -c 200)
if [[ -n "$prev_summary" && "$prev_summary" != "null" ]]; then
context+="Previous: ${prev_summary}"
fi
@ -217,8 +218,8 @@ teardown() {
}
@test "build_loop_context counts remaining tasks from @fix_plan.md" {
# Create fix plan with 7 incomplete tasks
cat > "@fix_plan.md" << 'EOF'
# Create fix plan with 7 incomplete tasks in .ralph/ directory
cat > "$RALPH_DIR/@fix_plan.md" << 'EOF'
# Fix Plan
- [x] Task 1 done
- [x] Task 2 done
@ -252,7 +253,7 @@ EOF
@test "build_loop_context includes previous loop summary" {
# Create previous response analysis
cat > ".response_analysis" << 'EOF'
cat > "$RALPH_DIR/.response_analysis" << 'EOF'
{
"loop_number": 1,
"analysis": {
@ -270,7 +271,7 @@ EOF
@test "build_loop_context limits output length to 500 chars" {
# Create very long work summary
local long_summary=$(printf 'x%.0s' {1..1000})
cat > ".response_analysis" << EOF
cat > "$RALPH_DIR/.response_analysis" << EOF
{
"loop_number": 1,
"analysis": {
@ -286,7 +287,7 @@ EOF
}
@test "build_loop_context handles missing @fix_plan.md gracefully" {
rm -f "@fix_plan.md"
rm -f "$RALPH_DIR/@fix_plan.md"
run build_loop_context 1
@ -295,7 +296,7 @@ EOF
}
@test "build_loop_context handles missing .response_analysis gracefully" {
rm -f ".response_analysis"
rm -f "$RALPH_DIR/.response_analysis"
run build_loop_context 1

View file

@ -19,13 +19,14 @@ setup() {
git config user.email "test@example.com"
git config user.name "Test User"
# Set up required environment
export PROMPT_FILE="PROMPT.md"
export LOG_DIR="logs"
export STATUS_FILE="status.json"
export EXIT_SIGNALS_FILE=".exit_signals"
export CALL_COUNT_FILE=".call_count"
export TIMESTAMP_FILE=".last_reset"
# Set up required environment with .ralph/ subfolder structure
export RALPH_DIR=".ralph"
export PROMPT_FILE="$RALPH_DIR/PROMPT.md"
export LOG_DIR="$RALPH_DIR/logs"
export STATUS_FILE="$RALPH_DIR/status.json"
export EXIT_SIGNALS_FILE="$RALPH_DIR/.exit_signals"
export CALL_COUNT_FILE="$RALPH_DIR/.call_count"
export TIMESTAMP_FILE="$RALPH_DIR/.last_reset"
mkdir -p "$LOG_DIR"
@ -38,6 +39,7 @@ setup() {
# Create lib directory with circuit breaker stub
mkdir -p lib
cat > lib/circuit_breaker.sh << 'EOF'
RALPH_DIR="${RALPH_DIR:-.ralph}"
reset_circuit_breaker() { echo "Circuit breaker reset: $1"; }
show_circuit_status() { echo "Circuit breaker status: CLOSED"; }
init_circuit_breaker() { :; }
@ -45,6 +47,7 @@ record_loop_result() { :; }
EOF
cat > lib/response_analyzer.sh << 'EOF'
RALPH_DIR="${RALPH_DIR:-.ralph}"
analyze_response() { :; }
detect_output_format() { echo "text"; }
EOF

View file

@ -7,15 +7,17 @@ setup() {
# Source helper functions
source "$(dirname "$BATS_TEST_FILENAME")/../helpers/test_helper.bash"
# Set up environment
export EXIT_SIGNALS_FILE=".exit_signals"
export RESPONSE_ANALYSIS_FILE=".response_analysis"
# Set up environment with .ralph/ subfolder structure
export RALPH_DIR=".ralph"
export EXIT_SIGNALS_FILE="$RALPH_DIR/.exit_signals"
export RESPONSE_ANALYSIS_FILE="$RALPH_DIR/.response_analysis"
export MAX_CONSECUTIVE_TEST_LOOPS=3
export MAX_CONSECUTIVE_DONE_SIGNALS=2
# Create temp test directory
export TEST_TEMP_DIR="$(mktemp -d /tmp/ralph-test.XXXXXX)"
cd "$TEST_TEMP_DIR"
mkdir -p "$RALPH_DIR"
# Initialize exit signals file
echo '{"test_only_loops": [], "done_signals": [], "completion_indicators": []}' > "$EXIT_SIGNALS_FILE"
@ -73,9 +75,9 @@ should_exit_gracefully() {
fi
# 4. Check fix_plan.md for completion
if [[ -f "@fix_plan.md" ]]; then
local total_items=$(grep -c "^- \[" "@fix_plan.md" 2>/dev/null)
local completed_items=$(grep -c "^- \[x\]" "@fix_plan.md" 2>/dev/null)
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)
# Handle case where grep returns no matches (exit code 1)
[[ -z "$total_items" ]] && total_items=0
@ -176,7 +178,7 @@ EOF
# Test 10: Exit when @fix_plan.md all items complete
@test "should_exit_gracefully exits when all fix_plan items complete" {
cat > "@fix_plan.md" << 'EOF'
cat > "$RALPH_DIR/@fix_plan.md" << 'EOF'
# Fix Plan
- [x] Task 1
- [x] Task 2
@ -189,7 +191,7 @@ EOF
# Test 11: No exit when @fix_plan.md partially complete
@test "should_exit_gracefully continues when fix_plan partially complete" {
cat > "@fix_plan.md" << 'EOF'
cat > "$RALPH_DIR/@fix_plan.md" << 'EOF'
# Fix Plan
- [x] Task 1
- [ ] Task 2
@ -236,7 +238,7 @@ EOF
# Test 16: @fix_plan.md with no checkboxes
@test "should_exit_gracefully handles fix_plan with no checkboxes" {
cat > "@fix_plan.md" << 'EOF'
cat > "$RALPH_DIR/@fix_plan.md" << 'EOF'
# Fix Plan
This is just text, no tasks yet.
EOF
@ -247,7 +249,7 @@ EOF
# Test 17: @fix_plan.md with mixed checkbox formats
@test "should_exit_gracefully handles mixed checkbox formats" {
cat > "@fix_plan.md" << 'EOF'
cat > "$RALPH_DIR/@fix_plan.md" << 'EOF'
# Fix Plan
- [x] Task 1 completed
- [ ] Task 2 pending

View file

@ -15,12 +15,13 @@ setup() {
git config user.email "test@example.com"
git config user.name "Test User"
# Set up environment
export PROMPT_FILE="PROMPT.md"
export LOG_DIR="logs"
export DOCS_DIR="docs/generated"
export STATUS_FILE="status.json"
export EXIT_SIGNALS_FILE=".exit_signals"
# Set up environment with .ralph/ subfolder structure
export RALPH_DIR=".ralph"
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 EXIT_SIGNALS_FILE="$RALPH_DIR/.exit_signals"
mkdir -p "$LOG_DIR" "$DOCS_DIR"
echo '{"test_only_loops": [], "done_signals": [], "completion_indicators": []}' > "$EXIT_SIGNALS_FILE"
@ -124,7 +125,7 @@ EOF
EOF
run parse_json_response "$output_file"
local result_file=".json_parse_result"
local result_file="$RALPH_DIR/.json_parse_result"
# Should create result file with parsed values
[[ -f "$result_file" ]] || skip "parse_json_response not yet implemented"
@ -145,7 +146,7 @@ EOF
EOF
run parse_json_response "$output_file"
local result_file=".json_parse_result"
local result_file="$RALPH_DIR/.json_parse_result"
[[ -f "$result_file" ]] || skip "parse_json_response not yet implemented"
@ -166,7 +167,7 @@ EOF
EOF
run parse_json_response "$output_file"
local result_file=".json_parse_result"
local result_file="$RALPH_DIR/.json_parse_result"
[[ -f "$result_file" ]] || skip "parse_json_response not yet implemented"
@ -187,7 +188,7 @@ EOF
EOF
run parse_json_response "$output_file"
local result_file=".json_parse_result"
local result_file="$RALPH_DIR/.json_parse_result"
[[ -f "$result_file" ]] || skip "parse_json_response not yet implemented"
@ -207,7 +208,7 @@ EOF
EOF
run parse_json_response "$output_file"
local result_file=".json_parse_result"
local result_file="$RALPH_DIR/.json_parse_result"
[[ -f "$result_file" ]] || skip "parse_json_response not yet implemented"
@ -228,7 +229,7 @@ EOF
EOF
run parse_json_response "$output_file"
local result_file=".json_parse_result"
local result_file="$RALPH_DIR/.json_parse_result"
[[ -f "$result_file" ]] || skip "parse_json_response not yet implemented"
@ -248,7 +249,7 @@ EOF
EOF
run parse_json_response "$output_file"
local result_file=".json_parse_result"
local result_file="$RALPH_DIR/.json_parse_result"
[[ -f "$result_file" ]] || skip "parse_json_response not yet implemented"
@ -271,7 +272,7 @@ EOF
EOF
run parse_json_response "$output_file"
local result_file=".json_parse_result"
local result_file="$RALPH_DIR/.json_parse_result"
[[ -f "$result_file" ]] || skip "parse_json_response not yet implemented"
@ -312,7 +313,7 @@ EOF
EOF
run parse_json_response "$output_file"
local result_file=".json_parse_result"
local result_file="$RALPH_DIR/.json_parse_result"
[[ -f "$result_file" ]] || skip "parse_json_response not yet implemented"
@ -342,9 +343,9 @@ EOF
local result=$?
assert_equal "$result" "0"
assert_file_exists ".response_analysis"
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"
}
@ -361,10 +362,10 @@ EOF
local result=$?
assert_equal "$result" "0"
assert_file_exists ".response_analysis"
assert_file_exists "$RALPH_DIR/.response_analysis"
# Should still detect completion via text parsing
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"
}
@ -382,7 +383,7 @@ EOF
analyze_response "$output_file" 1
# JSON with explicit exit_signal should have high confidence
local confidence=$(jq -r '.analysis.confidence_score' .response_analysis)
local confidence=$(jq -r '.analysis.confidence_score' "$RALPH_DIR/.response_analysis")
[[ "$confidence" -ge 50 ]]
}
@ -405,10 +406,10 @@ EOF
analyze_response "$output_file" 1
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" -ge 100 ]]
}
@ -423,7 +424,7 @@ EOF
analyze_response "$output_file" 1
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"
}
@ -438,7 +439,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"
}
@ -480,7 +481,7 @@ EOF
EOF
run parse_json_response "$output_file"
local result_file=".json_parse_result"
local result_file="$RALPH_DIR/.json_parse_result"
[[ -f "$result_file" ]] || skip "parse_json_response not yet implemented"
@ -500,7 +501,7 @@ EOF
EOF
run parse_json_response "$output_file"
local result_file=".json_parse_result"
local result_file="$RALPH_DIR/.json_parse_result"
[[ -f "$result_file" ]] || skip "parse_json_response not yet implemented"
@ -523,7 +524,7 @@ EOF
EOF
run parse_json_response "$output_file"
local result_file=".json_parse_result"
local result_file="$RALPH_DIR/.json_parse_result"
[[ -f "$result_file" ]] || skip "parse_json_response not yet implemented"
@ -546,7 +547,7 @@ EOF
EOF
run parse_json_response "$output_file"
local result_file=".json_parse_result"
local result_file="$RALPH_DIR/.json_parse_result"
[[ -f "$result_file" ]] || skip "parse_json_response not yet implemented"
@ -573,7 +574,7 @@ EOF
EOF
run parse_json_response "$output_file"
local result_file=".json_parse_result"
local result_file="$RALPH_DIR/.json_parse_result"
[[ -f "$result_file" ]] || skip "parse_json_response not yet implemented"
@ -597,7 +598,7 @@ EOF
EOF
run parse_json_response "$output_file"
local result_file=".json_parse_result"
local result_file="$RALPH_DIR/.json_parse_result"
[[ -f "$result_file" ]] || skip "parse_json_response not yet implemented"
@ -623,7 +624,7 @@ EOF
EOF
run parse_json_response "$output_file"
local result_file=".json_parse_result"
local result_file="$RALPH_DIR/.json_parse_result"
[[ -f "$result_file" ]] || skip "parse_json_response not yet implemented"
@ -648,12 +649,12 @@ EOF
analyze_response "$output_file" 1
assert_file_exists ".response_analysis"
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 output_format=$(jq -r '.output_format' .response_analysis)
local output_format=$(jq -r '.output_format' "$RALPH_DIR/.response_analysis")
assert_equal "$output_format" "json"
}
@ -670,9 +671,9 @@ EOF
analyze_response "$output_file" 1
# Session ID should be persisted for continuity
[[ -f ".claude_session_id" ]] || skip "Session persistence not yet implemented"
[[ -f "$RALPH_DIR/.claude_session_id" ]] || skip "Session persistence not yet implemented"
local stored_session=$(cat .claude_session_id)
local stored_session=$(cat "$RALPH_DIR/.claude_session_id")
[[ "$stored_session" == *"session-persist-test-123"* ]]
}
@ -683,15 +684,15 @@ EOF
@test "store_session_id writes session to file with timestamp" {
run store_session_id "session-test-abc"
[[ -f ".claude_session_id" ]] || skip "store_session_id not yet implemented"
[[ -f "$RALPH_DIR/.claude_session_id" ]] || skip "store_session_id not yet implemented"
local content=$(cat .claude_session_id)
local content=$(cat "$RALPH_DIR/.claude_session_id")
[[ "$content" == *"session-test-abc"* ]]
}
@test "get_last_session_id retrieves stored session" {
# First store a session
echo '{"session_id": "session-retrieve-test", "timestamp": "2026-01-09T10:00:00Z"}' > .claude_session_id
echo '{"session_id": "session-retrieve-test", "timestamp": "2026-01-09T10:00:00Z"}' > "$RALPH_DIR/.claude_session_id"
run get_last_session_id
@ -699,7 +700,7 @@ EOF
}
@test "get_last_session_id returns empty when no session file" {
rm -f .claude_session_id
rm -f "$RALPH_DIR/.claude_session_id"
run get_last_session_id
@ -711,7 +712,7 @@ EOF
@test "should_resume_session returns true for recent session" {
# Store a recent session (simulated as current timestamp)
local now=$(date +%s)
echo "{\"session_id\": \"session-recent\", \"timestamp\": \"$(date -Iseconds)\"}" > .claude_session_id
echo "{\"session_id\": \"session-recent\", \"timestamp\": \"$(date -Iseconds)\"}" > "$RALPH_DIR/.claude_session_id"
run should_resume_session
@ -721,7 +722,7 @@ EOF
@test "should_resume_session returns false for old session" {
# Store an old session (24+ hours ago)
echo '{"session_id": "session-old", "timestamp": "2020-01-01T00:00:00Z"}' > .claude_session_id
echo '{"session_id": "session-old", "timestamp": "2020-01-01T00:00:00Z"}' > "$RALPH_DIR/.claude_session_id"
run should_resume_session
@ -730,7 +731,7 @@ EOF
}
@test "should_resume_session returns false when no session file" {
rm -f .claude_session_id
rm -f "$RALPH_DIR/.claude_session_id"
run should_resume_session

View file

@ -8,14 +8,16 @@ setup() {
# Source helper functions
source "$(dirname "$BATS_TEST_FILENAME")/../helpers/test_helper.bash"
# Set up environment
# Set up environment with .ralph/ subfolder structure
export RALPH_DIR=".ralph"
export MAX_CALLS_PER_HOUR=100
export CALL_COUNT_FILE=".call_count"
export TIMESTAMP_FILE=".last_reset"
export CALL_COUNT_FILE="$RALPH_DIR/.call_count"
export TIMESTAMP_FILE="$RALPH_DIR/.last_reset"
# Create temp test directory
export TEST_TEMP_DIR="$(mktemp -d /tmp/ralph-test.XXXXXX)"
cd "$TEST_TEMP_DIR"
mkdir -p "$RALPH_DIR"
# Initialize files
echo "0" > "$CALL_COUNT_FILE"

View file

@ -15,17 +15,18 @@ setup() {
git config user.email "test@example.com"
git config user.name "Test User"
# Set up environment
export PROMPT_FILE="PROMPT.md"
export LOG_DIR="logs"
export DOCS_DIR="docs/generated"
export STATUS_FILE="status.json"
export EXIT_SIGNALS_FILE=".exit_signals"
export CALL_COUNT_FILE=".call_count"
export TIMESTAMP_FILE=".last_reset"
export CLAUDE_SESSION_FILE=".claude_session_id"
export RALPH_SESSION_FILE=".ralph_session"
export RALPH_SESSION_HISTORY_FILE=".ralph_session_history"
# Set up environment with .ralph/ subfolder structure
export RALPH_DIR=".ralph"
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 EXIT_SIGNALS_FILE="$RALPH_DIR/.exit_signals"
export CALL_COUNT_FILE="$RALPH_DIR/.call_count"
export TIMESTAMP_FILE="$RALPH_DIR/.last_reset"
export CLAUDE_SESSION_FILE="$RALPH_DIR/.claude_session_id"
export RALPH_SESSION_FILE="$RALPH_DIR/.ralph_session"
export RALPH_SESSION_HISTORY_FILE="$RALPH_DIR/.ralph_session_history"
export CLAUDE_MIN_VERSION="2.0.76"
export CLAUDE_CODE_CMD="claude"
export CLAUDE_USE_CONTINUE="true"
@ -35,9 +36,9 @@ setup() {
echo "$(date +%Y%m%d%H)" > "$TIMESTAMP_FILE"
echo '{"test_only_loops": [], "done_signals": [], "completion_indicators": []}' > "$EXIT_SIGNALS_FILE"
# Create sample project files
create_sample_prompt
create_sample_fix_plan "@fix_plan.md" 10 3
# Create sample project files in .ralph/ directory
create_sample_prompt "$RALPH_DIR/PROMPT.md"
create_sample_fix_plan "$RALPH_DIR/@fix_plan.md" 10 3
# Source library components
source "${BATS_TEST_DIRNAME}/../../lib/date_utils.sh"
@ -238,7 +239,7 @@ function_exists_in_ralph() {
EOF
run parse_json_response "$output_file"
local result_file=".json_parse_result"
local result_file="$RALPH_DIR/.json_parse_result"
[[ -f "$result_file" ]]