diff --git a/CLAUDE.md b/CLAUDE.md index a65f2eb8..9ad9e61b 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -169,7 +169,7 @@ tmux attach -t ### Running Tests ```bash -# Run all tests (499 tests) +# Run all tests (510 tests) npm test # Run specific test suites @@ -459,7 +459,7 @@ Ralph uses advanced error detection with two-stage filtering to eliminate false ## Test Suite -### Test Files (499 tests total) +### Test Files (510 tests total) | File | Tests | Description | |------|-------|-------------| @@ -472,10 +472,10 @@ Ralph uses advanced error detection with two-stage filtering to eliminate false | `test_rate_limiting.bats` | 15 | Rate limiting behavior | | `test_loop_execution.bats` | 20 | Integration tests | | `test_edge_cases.bats` | 25 | Edge case handling | -| `test_installation.bats` | 14 | Global installation/uninstall workflows | -| `test_project_setup.bats` | 44 | Project setup (setup.sh) validation + .ralphrc permissions | +| `test_installation.bats` | 15 | Global installation/uninstall workflows + dotfile template copying (#174) | +| `test_project_setup.bats` | 50 | Project setup (setup.sh) validation + .ralphrc permissions + .gitignore (#174) | | `test_prd_import.bats` | 33 | PRD import (ralph_import.sh) workflows + modern CLI tests | -| `test_enable_core.bats` | 32 | Enable core library (idempotency, project detection, template generation) | +| `test_enable_core.bats` | 36 | Enable core library (idempotency, project detection, template generation, .gitignore #174) | | `test_task_sources.bats` | 23 | Task sources (beads, GitHub, PRD extraction, normalization) | | `test_ralph_enable.bats` | 22 | Ralph enable integration tests (wizard, CI version, JSON output) | | `test_wizard_utils.bats` | 20 | Wizard utility functions (stdout/stderr separation, prompt functions) | diff --git a/install.sh b/install.sh index 82adee93..b4737e40 100755 --- a/install.sh +++ b/install.sh @@ -108,8 +108,10 @@ create_install_dirs() { install_scripts() { log "INFO" "Installing Ralph scripts..." - # Copy templates to Ralph home + # Copy templates to Ralph home (dotglob needed for dotfiles like .gitignore) + shopt -s dotglob cp -r "$SCRIPT_DIR/templates/"* "$RALPH_HOME/templates/" + shopt -u dotglob # Copy lib scripts (response_analyzer.sh, circuit_breaker.sh) cp -r "$SCRIPT_DIR/lib/"* "$RALPH_HOME/lib/" diff --git a/lib/enable_core.sh b/lib/enable_core.sh index f276a3fd..c73fd8fb 100755 --- a/lib/enable_core.sh +++ b/lib/enable_core.sh @@ -777,6 +777,17 @@ enable_ralph_in_directory() { fix_plan_content=$(generate_fix_plan_md "$task_content") safe_create_file ".ralph/fix_plan.md" "$fix_plan_content" + # Copy .gitignore template to project root (if available) + local templates_dir + templates_dir=$(get_templates_dir 2>/dev/null) || true + if [[ -n "$templates_dir" ]] && [[ -f "$templates_dir/.gitignore" ]]; then + local gitignore_content + gitignore_content=$(<"$templates_dir/.gitignore") + safe_create_file ".gitignore" "$gitignore_content" + else + enable_log "WARN" ".gitignore template not found, skipping" + fi + # Detect task sources for .ralphrc detect_task_sources local task_sources="local" diff --git a/setup.sh b/setup.sh index 420c0384..1b007a2b 100755 --- a/setup.sh +++ b/setup.sh @@ -47,6 +47,11 @@ 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 +# Copy .gitignore template to project root (skip if one already exists) +if [[ -f "$TEMPLATES_DIR/.gitignore" ]] && [[ ! -f ".gitignore" ]]; then + cp "$TEMPLATES_DIR/.gitignore" .gitignore +fi + # Generate .ralphrc configuration file # Source enable_core.sh if available for generate_ralphrc(), otherwise create inline if [[ -f "$LIB_DIR/enable_core.sh" ]]; then diff --git a/templates/.gitignore b/templates/.gitignore new file mode 100644 index 00000000..b345356c --- /dev/null +++ b/templates/.gitignore @@ -0,0 +1,47 @@ +# 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 logs and generated docs +.ralph/logs/* +!.ralph/logs/.gitkeep +.ralph/docs/generated/* +!.ralph/docs/generated/.gitkeep + +# General logs +*.log + +# OS files +.DS_Store +Thumbs.db + +# Temporary files +*.tmp +.temp/ + +# Node modules (if using Node.js projects) +node_modules/ + +# Python cache (if using Python projects) +__pycache__/ +*.pyc + +# Rust build (if using Rust projects) +target/ + +# IDE files +.vscode/ +.idea/ +*.swp +*.swo + +# Ralph backup directories (created by migration) +.ralph_backup_* diff --git a/tests/integration/test_installation.bats b/tests/integration/test_installation.bats index 4dda2178..2f21d54c 100644 --- a/tests/integration/test_installation.bats +++ b/tests/integration/test_installation.bats @@ -31,6 +31,7 @@ setup() { echo "# Mock PROMPT.md" > "$MOCK_SOURCE_DIR/templates/PROMPT.md" echo "# Mock fix_plan.md" > "$MOCK_SOURCE_DIR/templates/fix_plan.md" echo "# Mock AGENT.md" > "$MOCK_SOURCE_DIR/templates/AGENT.md" + echo ".ralph/.call_count" > "$MOCK_SOURCE_DIR/templates/.gitignore" # Create mock lib files cat > "$MOCK_SOURCE_DIR/lib/circuit_breaker.sh" << 'EOF' @@ -254,6 +255,13 @@ run_install() { diff -q "$MOCK_SOURCE_DIR/templates/AGENT.md" "$TEST_RALPH_HOME/templates/AGENT.md" } +@test "install.sh copies dotfile templates like .gitignore" { + run run_install + + assert_file_exists "$TEST_RALPH_HOME/templates/.gitignore" + diff -q "$MOCK_SOURCE_DIR/templates/.gitignore" "$TEST_RALPH_HOME/templates/.gitignore" +} + @test "install.sh copies lib/ directory" { run run_install diff --git a/tests/integration/test_project_setup.bats b/tests/integration/test_project_setup.bats index 49e9350d..14630b63 100644 --- a/tests/integration/test_project_setup.bats +++ b/tests/integration/test_project_setup.bats @@ -543,3 +543,86 @@ teardown() { # .ralphrc should reference the project name grep -q "my-custom-project" my-custom-project/.ralphrc } + +# ============================================================================= +# Test: .gitignore Generation (Issue #174) +# ============================================================================= + +@test "setup.sh creates .gitignore file" { + # Create .gitignore template + cat > templates/.gitignore << 'EOF' +# Ralph generated files +.ralph/.call_count +.ralph/.last_reset +.ralph/status.json +EOF + + run bash "$SETUP_SCRIPT" test-project + + assert_success + assert_file_exists "test-project/.gitignore" +} + +@test "setup.sh .gitignore contains Ralph runtime patterns" { + cat > templates/.gitignore << 'EOF' +.ralph/.call_count +.ralph/.last_reset +.ralph/status.json +.ralph/.circuit_breaker_state +EOF + + bash "$SETUP_SCRIPT" test-project + + grep -q ".ralph/.call_count" test-project/.gitignore + grep -q ".ralph/.circuit_breaker_state" test-project/.gitignore +} + +@test "setup.sh .gitignore is committed in initial git commit" { + cat > templates/.gitignore << 'EOF' +.ralph/.call_count +EOF + + bash "$SETUP_SCRIPT" test-project + + cd test-project + run command git ls-files .gitignore + + assert_success + assert_equal "$output" ".gitignore" +} + +@test "setup.sh .gitignore content matches template" { + cat > templates/.gitignore << 'EOF' +# Ralph generated files +.ralph/.call_count +.ralph/.last_reset +EOF + + bash "$SETUP_SCRIPT" test-project + + diff templates/.gitignore test-project/.gitignore +} + +@test "setup.sh succeeds when .gitignore template is missing" { + # Do NOT create templates/.gitignore — should still succeed + run bash "$SETUP_SCRIPT" test-project + + assert_success + # .gitignore should not exist since template was missing + [[ ! -f "test-project/.gitignore" ]] +} + +@test "setup.sh preserves existing .gitignore on rerun" { + echo ".ralph/.call_count" > templates/.gitignore + + # First run creates the project with .gitignore + bash "$SETUP_SCRIPT" test-project + + # User customizes the .gitignore + echo "my-custom-pattern" >> test-project/.gitignore + + # Second run (rerun in existing directory) should not overwrite + bash "$SETUP_SCRIPT" test-project + + grep -q "my-custom-pattern" test-project/.gitignore +} diff --git a/tests/unit/test_enable_core.bats b/tests/unit/test_enable_core.bats index 36d61640..bc3b8e9a 100644 --- a/tests/unit/test_enable_core.bats +++ b/tests/unit/test_enable_core.bats @@ -7,12 +7,17 @@ load '../helpers/fixtures' # Path to enable_core.sh ENABLE_CORE="${BATS_TEST_DIRNAME}/../../lib/enable_core.sh" +ORIGINAL_HOME="$HOME" setup() { # Create temporary test directory TEST_DIR="$(mktemp -d)" cd "$TEST_DIR" + # Isolate HOME so tests that write to ~/.ralph don't leak to real home dir + export HOME="$TEST_DIR/home" + mkdir -p "$HOME" + # Source the library (disable set -e for testing) set +e source "$ENABLE_CORE" @@ -20,6 +25,7 @@ setup() { } teardown() { + export HOME="$ORIGINAL_HOME" if [[ -n "$TEST_DIR" ]] && [[ -d "$TEST_DIR" ]]; then cd / rm -rf "$TEST_DIR" @@ -386,3 +392,79 @@ EOF content=$(cat test_file.txt) [[ "$content" == "original content" ]] } + +# ============================================================================= +# .GITIGNORE CREATION (Issue #174) (4 tests) +# ============================================================================= + +@test "enable_ralph_in_directory creates .gitignore when template exists" { + # HOME is already isolated to TEST_DIR/home by setup() + mkdir -p "$HOME/.ralph/templates" + cat > "$HOME/.ralph/templates/.gitignore" << 'EOF' +.ralph/.call_count +.ralph/.last_reset +.ralph/status.json +EOF + + export ENABLE_FORCE="false" + export ENABLE_SKIP_TASKS="true" + export ENABLE_PROJECT_NAME="test-project" + + run enable_ralph_in_directory + + assert_success + [[ -f ".gitignore" ]] + grep -q ".ralph/.call_count" .gitignore +} + +@test "enable_ralph_in_directory skips .gitignore when one exists and no force" { + mkdir -p "$HOME/.ralph/templates" + echo ".ralph/.call_count" > "$HOME/.ralph/templates/.gitignore" + + # Pre-existing .gitignore + echo "my-custom-ignore" > .gitignore + + export ENABLE_FORCE="false" + export ENABLE_SKIP_TASKS="true" + export ENABLE_PROJECT_NAME="test-project" + + run enable_ralph_in_directory + + assert_success + # Should preserve existing .gitignore content + grep -q "my-custom-ignore" .gitignore +} + +@test "enable_ralph_in_directory overwrites .gitignore with force" { + mkdir -p "$HOME/.ralph/templates" + echo ".ralph/.call_count" > "$HOME/.ralph/templates/.gitignore" + + # Pre-existing .gitignore with different content + echo "my-custom-ignore" > .gitignore + + export ENABLE_FORCE="true" + export ENABLE_SKIP_TASKS="true" + export ENABLE_PROJECT_NAME="test-project" + + run enable_ralph_in_directory + + assert_success + # Should have template content, not old content + grep -q ".ralph/.call_count" .gitignore + ! grep -q "my-custom-ignore" .gitignore +} + +@test "enable_ralph_in_directory succeeds when templates dir exists but .gitignore is missing" { + # Templates dir exists but no .gitignore template inside + mkdir -p "$HOME/.ralph/templates" + + export ENABLE_FORCE="false" + export ENABLE_SKIP_TASKS="true" + export ENABLE_PROJECT_NAME="test-project" + + run enable_ralph_in_directory + + assert_success + # .gitignore should not be created + [[ ! -f ".gitignore" ]] +}