From 3b22c0e9d9ee271b22ca6008c0b6f7963b012b35 Mon Sep 17 00:00:00 2001 From: Test User Date: Tue, 17 Feb 2026 08:09:24 -0700 Subject: [PATCH 1/4] fix(setup): generate .gitignore for new projects (#174) setup.sh and ralph-enable now create a .gitignore that excludes Ralph runtime files (.call_count, status.json, logs, etc.) from git tracking. Uses a template in templates/.gitignore as the single source of truth. --- CLAUDE.md | 8 +-- lib/enable_core.sh | 11 ++++ setup.sh | 5 ++ templates/.gitignore | 47 ++++++++++++++++ tests/integration/test_project_setup.bats | 68 +++++++++++++++++++++++ tests/unit/test_enable_core.bats | 66 ++++++++++++++++++++++ 6 files changed, 201 insertions(+), 4 deletions(-) create mode 100644 templates/.gitignore diff --git a/CLAUDE.md b/CLAUDE.md index a65f2eb8..68f8af18 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 (507 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 (507 tests total) | File | Tests | Description | |------|-------|-------------| @@ -473,9 +473,9 @@ Ralph uses advanced error detection with two-stage filtering to eliminate false | `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_project_setup.bats` | 49 | 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` | 35 | 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/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..8aa27e59 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 (if available) +if [[ -f "$TEMPLATES_DIR/.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_project_setup.bats b/tests/integration/test_project_setup.bats index 49e9350d..8e17e63b 100644 --- a/tests/integration/test_project_setup.bats +++ b/tests/integration/test_project_setup.bats @@ -543,3 +543,71 @@ 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" ]] +} diff --git a/tests/unit/test_enable_core.bats b/tests/unit/test_enable_core.bats index 36d61640..e8e3a4d5 100644 --- a/tests/unit/test_enable_core.bats +++ b/tests/unit/test_enable_core.bats @@ -386,3 +386,69 @@ EOF content=$(cat test_file.txt) [[ "$content" == "original content" ]] } + +# ============================================================================= +# .GITIGNORE CREATION (Issue #174) (3 tests) +# ============================================================================= + +@test "enable_ralph_in_directory creates .gitignore when template exists" { + # Create templates directory with .gitignore + local tpl_dir="$HOME/.ralph/templates" + mkdir -p "$tpl_dir" + cat > "$tpl_dir/.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" { + # Create templates directory with .gitignore + local tpl_dir="$HOME/.ralph/templates" + mkdir -p "$tpl_dir" + echo ".ralph/.call_count" > "$tpl_dir/.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" { + # Create templates directory with .gitignore + local tpl_dir="$HOME/.ralph/templates" + mkdir -p "$tpl_dir" + echo ".ralph/.call_count" > "$tpl_dir/.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 +} From 2f5d855c5ad229b729beddb6869940e38b09228f Mon Sep 17 00:00:00 2001 From: Test User Date: Tue, 17 Feb 2026 13:17:31 -0700 Subject: [PATCH 2/4] fix(test): isolate HOME in enable_core tests, add template-missing test Address code review feedback: - Override HOME to temp dir in test setup/teardown to prevent state leaking to developer's real home directory (CodeRabbit) - Add test for graceful degradation when templates dir exists but .gitignore template is missing (Claude review) --- CLAUDE.md | 6 ++--- tests/unit/test_enable_core.bats | 42 ++++++++++++++++++++++---------- 2 files changed, 32 insertions(+), 16 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 68f8af18..250a6d87 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -169,7 +169,7 @@ tmux attach -t ### Running Tests ```bash -# Run all tests (507 tests) +# Run all tests (508 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 (507 tests total) +### Test Files (508 tests total) | File | Tests | Description | |------|-------|-------------| @@ -475,7 +475,7 @@ Ralph uses advanced error detection with two-stage filtering to eliminate false | `test_installation.bats` | 14 | Global installation/uninstall workflows | | `test_project_setup.bats` | 49 | 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` | 35 | Enable core library (idempotency, project detection, template generation, .gitignore #174) | +| `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/tests/unit/test_enable_core.bats b/tests/unit/test_enable_core.bats index e8e3a4d5..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" @@ -388,14 +394,13 @@ EOF } # ============================================================================= -# .GITIGNORE CREATION (Issue #174) (3 tests) +# .GITIGNORE CREATION (Issue #174) (4 tests) # ============================================================================= @test "enable_ralph_in_directory creates .gitignore when template exists" { - # Create templates directory with .gitignore - local tpl_dir="$HOME/.ralph/templates" - mkdir -p "$tpl_dir" - cat > "$tpl_dir/.gitignore" << 'EOF' + # 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 @@ -413,10 +418,8 @@ EOF } @test "enable_ralph_in_directory skips .gitignore when one exists and no force" { - # Create templates directory with .gitignore - local tpl_dir="$HOME/.ralph/templates" - mkdir -p "$tpl_dir" - echo ".ralph/.call_count" > "$tpl_dir/.gitignore" + mkdir -p "$HOME/.ralph/templates" + echo ".ralph/.call_count" > "$HOME/.ralph/templates/.gitignore" # Pre-existing .gitignore echo "my-custom-ignore" > .gitignore @@ -433,10 +436,8 @@ EOF } @test "enable_ralph_in_directory overwrites .gitignore with force" { - # Create templates directory with .gitignore - local tpl_dir="$HOME/.ralph/templates" - mkdir -p "$tpl_dir" - echo ".ralph/.call_count" > "$tpl_dir/.gitignore" + mkdir -p "$HOME/.ralph/templates" + echo ".ralph/.call_count" > "$HOME/.ralph/templates/.gitignore" # Pre-existing .gitignore with different content echo "my-custom-ignore" > .gitignore @@ -452,3 +453,18 @@ EOF 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" ]] +} From 7e4c23eff8c0c0f05e070b93b8ff0886e7853502 Mon Sep 17 00:00:00 2001 From: Test User Date: Tue, 17 Feb 2026 13:30:54 -0700 Subject: [PATCH 3/4] fix(setup): preserve existing .gitignore on rerun Skip copying .gitignore template when one already exists in the project directory. Prevents overwriting user customizations when setup.sh is re-run in an existing directory. --- CLAUDE.md | 6 +++--- setup.sh | 4 ++-- tests/integration/test_project_setup.bats | 15 +++++++++++++++ 3 files changed, 20 insertions(+), 5 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 250a6d87..5cb4e8cf 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -169,7 +169,7 @@ tmux attach -t ### Running Tests ```bash -# Run all tests (508 tests) +# Run all tests (509 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 (508 tests total) +### Test Files (509 tests total) | File | Tests | Description | |------|-------|-------------| @@ -473,7 +473,7 @@ Ralph uses advanced error detection with two-stage filtering to eliminate false | `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` | 49 | Project setup (setup.sh) validation + .ralphrc permissions + .gitignore (#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` | 36 | Enable core library (idempotency, project detection, template generation, .gitignore #174) | | `test_task_sources.bats` | 23 | Task sources (beads, GitHub, PRD extraction, normalization) | diff --git a/setup.sh b/setup.sh index 8aa27e59..1b007a2b 100755 --- a/setup.sh +++ b/setup.sh @@ -47,8 +47,8 @@ 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 (if available) -if [[ -f "$TEMPLATES_DIR/.gitignore" ]]; then +# 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 diff --git a/tests/integration/test_project_setup.bats b/tests/integration/test_project_setup.bats index 8e17e63b..14630b63 100644 --- a/tests/integration/test_project_setup.bats +++ b/tests/integration/test_project_setup.bats @@ -611,3 +611,18 @@ EOF # .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 +} From 28c955dead735d199ce166353b7a028f609b3394 Mon Sep 17 00:00:00 2001 From: Test User Date: Tue, 17 Feb 2026 13:40:23 -0700 Subject: [PATCH 4/4] fix(install): enable dotglob so .gitignore template is copied Bash glob * skips dotfiles by default, so templates/.gitignore was silently excluded during install.sh. Enable dotglob around the templates copy to handle dotfile templates generically. --- CLAUDE.md | 6 +++--- install.sh | 4 +++- tests/integration/test_installation.bats | 8 ++++++++ 3 files changed, 14 insertions(+), 4 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 5cb4e8cf..9ad9e61b 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -169,7 +169,7 @@ tmux attach -t ### Running Tests ```bash -# Run all tests (509 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 (509 tests total) +### Test Files (510 tests total) | File | Tests | Description | |------|-------|-------------| @@ -472,7 +472,7 @@ 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_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` | 36 | Enable core library (idempotency, project detection, template generation, .gitignore #174) | 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/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