From 9b19d70e3551135ea3f3eb5cab79ebdb245b866e Mon Sep 17 00:00:00 2001 From: Frank Bria Date: Tue, 20 Jan 2026 23:22:30 -0700 Subject: [PATCH] feat(structure): migrate Ralph files to .ralph/ subfolder (#109) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 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> --- .claude/settings.local.json | 3 +- .gitignore | 41 ++- CLAUDE.md | 92 +++--- README.md | 79 ++--- create_files.sh | 107 ++++--- install.sh | 50 ++-- lib/circuit_breaker.sh | 16 +- lib/response_analyzer.sh | 45 +-- migrate_to_ralph_folder.sh | 317 +++++++++++++++++++++ ralph_import.sh | 22 +- ralph_loop.sh | 95 +++--- setup.sh | 42 ++- templates/AGENT.md | 12 +- templates/PROMPT.md | 34 ++- tests/helpers/fixtures.bash | 19 +- tests/helpers/test_helper.bash | 37 ++- tests/integration/test_edge_cases.bats | 83 +++--- tests/integration/test_installation.bats | 16 +- tests/integration/test_loop_execution.bats | 61 ++-- tests/integration/test_prd_import.bats | 196 +++++++------ tests/integration/test_project_setup.bats | 134 +++++---- tests/unit/test_cli_modern.bats | 45 +-- tests/unit/test_cli_parsing.bats | 17 +- tests/unit/test_exit_detection.bats | 22 +- tests/unit/test_json_parsing.bats | 87 +++--- tests/unit/test_rate_limiting.bats | 8 +- tests/unit/test_session_continuity.bats | 31 +- 27 files changed, 1126 insertions(+), 585 deletions(-) create mode 100755 migrate_to_ralph_folder.sh diff --git a/.claude/settings.local.json b/.claude/settings.local.json index 2cd6d33..2cef399 100644 --- a/.claude/settings.local.json +++ b/.claude/settings.local.json @@ -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": [] diff --git a/.gitignore b/.gitignore index cb3ffae..4bd2ecf 100644 --- a/.gitignore +++ b/.gitignore @@ -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_* diff --git a/CLAUDE.md b/CLAUDE.md index 6f4e46f..0dc6971 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -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) diff --git a/README.md b/README.md index b2bac75..4681324 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/create_files.sh b/create_files.sh index 69e6154..26ca9fd 100755 --- a/create_files.sh +++ b/create_files.sh @@ -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 diff --git a/install.sh b/install.sh index 516bd91..a5dc65a 100755 --- a/install.sh +++ b/install.sh @@ -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" ;; diff --git a/lib/circuit_breaker.sh b/lib/circuit_breaker.sh index 3e11460..24093cf 100644 --- a/lib/circuit_breaker.sh +++ b/lib/circuit_breaker.sh @@ -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 diff --git a/lib/response_analyzer.sh b/lib/response_analyzer.sh index b451d42..caf814b 100644 --- a/lib/response_analyzer.sh +++ b/lib/response_analyzer.sh @@ -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 diff --git a/migrate_to_ralph_folder.sh b/migrate_to_ralph_folder.sh new file mode 100755 index 0000000..8548142 --- /dev/null +++ b/migrate_to_ralph_folder.sh @@ -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 "$@" diff --git a/ralph_import.sh b/ralph_import.sh index dd615d6..4154781 100755 --- a/ralph_import.sh +++ b/ralph_import.sh @@ -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 "" diff --git a/ralph_loop.sh b/ralph_loop.sh index 197651e..3e48d38 100755 --- a/ralph_loop.sh +++ b/ralph_loop.sh @@ -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 diff --git a/setup.sh b/setup.sh index f860be9..f7f3010 100755 --- a/setup.sh +++ b/setup.sh @@ -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" diff --git a/templates/AGENT.md b/templates/AGENT.md index ab4e07e..6d92b58 100644 --- a/templates/AGENT.md +++ b/templates/AGENT.md @@ -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 diff --git a/templates/PROMPT.md b/templates/PROMPT.md index 383c482..36402e9 100644 --- a/templates/PROMPT.md +++ b/templates/PROMPT.md @@ -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. diff --git a/tests/helpers/fixtures.bash b/tests/helpers/fixtures.bash index 4a4c341..89f51ac 100644 --- a/tests/helpers/fixtures.bash +++ b/tests/helpers/fixtures.bash @@ -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 } diff --git a/tests/helpers/test_helper.bash b/tests/helpers/test_helper.bash index 5d87835..a59d7c3 100644 --- a/tests/helpers/test_helper.bash +++ b/tests/helpers/test_helper.bash @@ -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 } diff --git a/tests/integration/test_edge_cases.bats b/tests/integration/test_edge_cases.bats index d8b17e1..d09fcfe 100644 --- a/tests/integration/test_edge_cases.bats +++ b/tests/integration/test_edge_cases.bats @@ -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" } diff --git a/tests/integration/test_installation.bats b/tests/integration/test_installation.bats index 6d1188e..9c45b1b 100644 --- a/tests/integration/test_installation.bats +++ b/tests/integration/test_installation.bats @@ -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" ]] diff --git a/tests/integration/test_loop_execution.bats b/tests/integration/test_loop_execution.bats index f054393..b799426 100644 --- a/tests/integration/test_loop_execution.bats +++ b/tests/integration/test_loop_execution.bats @@ -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 ]] } diff --git a/tests/integration/test_prd_import.bats b/tests/integration/test_prd_import.bats index 2787020..515c9bd 100644 --- a/tests/integration/test_prd_import.bats +++ b/tests/integration/test_prd_import.bats @@ -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 diff --git a/tests/integration/test_project_setup.bats b/tests/integration/test_project_setup.bats index 17cb17d..94e5fd6 100644 --- a/tests/integration/test_project_setup.bats +++ b/tests/integration/test_project_setup.bats @@ -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 } diff --git a/tests/unit/test_cli_modern.bats b/tests/unit/test_cli_modern.bats index 602e207..8efdd51 100644 --- a/tests/unit/test_cli_modern.bats +++ b/tests/unit/test_cli_modern.bats @@ -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 diff --git a/tests/unit/test_cli_parsing.bats b/tests/unit/test_cli_parsing.bats index b8c4215..b36c900 100644 --- a/tests/unit/test_cli_parsing.bats +++ b/tests/unit/test_cli_parsing.bats @@ -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 diff --git a/tests/unit/test_exit_detection.bats b/tests/unit/test_exit_detection.bats index 8ab7c3d..2d29cd2 100644 --- a/tests/unit/test_exit_detection.bats +++ b/tests/unit/test_exit_detection.bats @@ -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 diff --git a/tests/unit/test_json_parsing.bats b/tests/unit/test_json_parsing.bats index e29c94e..98526ea 100644 --- a/tests/unit/test_json_parsing.bats +++ b/tests/unit/test_json_parsing.bats @@ -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 diff --git a/tests/unit/test_rate_limiting.bats b/tests/unit/test_rate_limiting.bats index e0e3cfb..7836ad5 100755 --- a/tests/unit/test_rate_limiting.bats +++ b/tests/unit/test_rate_limiting.bats @@ -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" diff --git a/tests/unit/test_session_continuity.bats b/tests/unit/test_session_continuity.bats index 7ded613..dc7e13b 100644 --- a/tests/unit/test_session_continuity.bats +++ b/tests/unit/test_session_continuity.bats @@ -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" ]]