agentic-loop 3.3.0 → 3.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/bin/ralph.sh CHANGED
@@ -56,6 +56,8 @@ source "$RALPH_LIB/loop.sh"
56
56
  source "$RALPH_LIB/verify.sh"
57
57
  source "$RALPH_LIB/prd.sh"
58
58
  source "$RALPH_LIB/signs.sh"
59
+ source "$RALPH_LIB/test.sh"
60
+ source "$RALPH_LIB/ci.sh"
59
61
 
60
62
  # Run auto-config if config.json was just created
61
63
  if [[ "${_ralph_needs_autoconfig:-}" == "true" ]]; then
@@ -104,6 +106,15 @@ main() {
104
106
  fi
105
107
  run_verification "$1"
106
108
  ;;
109
+ test)
110
+ ralph_test "$@"
111
+ ;;
112
+ coverage)
113
+ ralph_test_coverage "$@"
114
+ ;;
115
+ ci)
116
+ ralph_ci "$@"
117
+ ;;
107
118
  sign)
108
119
  ralph_sign "$@"
109
120
  ;;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agentic-loop",
3
- "version": "3.3.0",
3
+ "version": "3.4.1",
4
4
  "description": "Autonomous AI coding loop - PRD-driven development with Claude Code",
5
5
  "author": "Allie Jones <allie@allthrive.ai>",
6
6
  "license": "MIT",
package/ralph/ci.sh ADDED
@@ -0,0 +1,130 @@
1
+ #!/usr/bin/env bash
2
+ # shellcheck shell=bash
3
+ # ci.sh - Set up GitHub Actions CI/CD workflows
4
+
5
+ # Install GitHub Actions workflows
6
+ ralph_ci() {
7
+ local cmd="${1:-install}"
8
+
9
+ case "$cmd" in
10
+ install)
11
+ install_github_workflows
12
+ ;;
13
+ status)
14
+ check_ci_status
15
+ ;;
16
+ *)
17
+ echo "Usage: ralph ci [install|status]"
18
+ echo ""
19
+ echo "Commands:"
20
+ echo " install - Install GitHub Actions workflows"
21
+ echo " status - Check CI status"
22
+ ;;
23
+ esac
24
+ }
25
+
26
+ install_github_workflows() {
27
+ echo ""
28
+ print_info "=== Setting up GitHub Actions CI/CD ==="
29
+ echo ""
30
+
31
+ # Check if this is a git repo
32
+ if [[ ! -d ".git" ]]; then
33
+ print_error "Not a git repository. Run 'git init' first."
34
+ return 1
35
+ fi
36
+
37
+ # Create workflows directory
38
+ mkdir -p .github/workflows
39
+
40
+ # Copy workflow templates
41
+ local template_dir="$RALPH_TEMPLATES/github/workflows"
42
+
43
+ if [[ ! -d "$template_dir" ]]; then
44
+ print_error "Workflow templates not found at $template_dir"
45
+ return 1
46
+ fi
47
+
48
+ # Install PR workflow
49
+ if [[ -f ".github/workflows/pr.yml" ]]; then
50
+ echo " PR workflow already exists, skipping..."
51
+ else
52
+ cp "$template_dir/pr.yml" .github/workflows/pr.yml
53
+ print_success "Created .github/workflows/pr.yml (fast lint checks)"
54
+ fi
55
+
56
+ # Install nightly workflow
57
+ if [[ -f ".github/workflows/nightly.yml" ]]; then
58
+ echo " Nightly workflow already exists, skipping..."
59
+ else
60
+ cp "$template_dir/nightly.yml" .github/workflows/nightly.yml
61
+ print_success "Created .github/workflows/nightly.yml (full test suite)"
62
+ fi
63
+
64
+ echo ""
65
+ echo "Workflows installed:"
66
+ echo ""
67
+ echo " 📋 PR Check (.github/workflows/pr.yml)"
68
+ echo " Runs on: Pull requests to main/master"
69
+ echo " Checks: Lint, TypeScript, Build"
70
+ echo " Speed: Fast (~1-2 min)"
71
+ echo ""
72
+ echo " 🌙 Nightly Tests (.github/workflows/nightly.yml)"
73
+ echo " Runs on: Daily at 3am UTC + manual trigger"
74
+ echo " Checks: Full test suite + PRD testSteps + Coverage"
75
+ echo " Speed: Comprehensive (~5-10 min)"
76
+ echo ""
77
+
78
+ # Check if we need to customize for monorepo
79
+ local backend_dir frontend_dir
80
+ backend_dir=$(get_config '.directories.backend' "")
81
+ frontend_dir=$(get_config '.directories.frontend' "")
82
+
83
+ if [[ -n "$backend_dir" ]] || [[ -n "$frontend_dir" ]]; then
84
+ print_warning "Monorepo detected. You may need to customize workflow paths."
85
+ echo ""
86
+ echo " Edit .github/workflows/*.yml to add:"
87
+ [[ -n "$backend_dir" ]] && echo " - working-directory: $backend_dir"
88
+ [[ -n "$frontend_dir" ]] && echo " - working-directory: $frontend_dir"
89
+ echo ""
90
+ fi
91
+
92
+ # Remind about secrets
93
+ echo "Next steps:"
94
+ echo " 1. Review and customize the workflows if needed"
95
+ echo " 2. Commit and push: git add .github && git commit -m 'ci: Add GitHub Actions workflows'"
96
+ echo " 3. Set up any required secrets in GitHub repo settings"
97
+ echo ""
98
+
99
+ return 0
100
+ }
101
+
102
+ check_ci_status() {
103
+ echo ""
104
+ print_info "=== CI/CD Status ==="
105
+ echo ""
106
+
107
+ # Check for workflow files
108
+ if [[ -f ".github/workflows/pr.yml" ]]; then
109
+ print_success "PR workflow: installed"
110
+ else
111
+ print_warning "PR workflow: not installed"
112
+ fi
113
+
114
+ if [[ -f ".github/workflows/nightly.yml" ]]; then
115
+ print_success "Nightly workflow: installed"
116
+ else
117
+ print_warning "Nightly workflow: not installed"
118
+ fi
119
+
120
+ # Check GitHub CLI
121
+ if command -v gh &>/dev/null; then
122
+ echo ""
123
+ echo "Recent workflow runs:"
124
+ gh run list --limit 5 2>/dev/null || echo " (unable to fetch - check 'gh auth login')"
125
+ else
126
+ echo ""
127
+ echo "Install GitHub CLI (gh) to see workflow status:"
128
+ echo " brew install gh && gh auth login"
129
+ fi
130
+ }
package/ralph/test.sh ADDED
@@ -0,0 +1,181 @@
1
+ #!/usr/bin/env bash
2
+ # shellcheck shell=bash
3
+ # test.sh - Comprehensive test runner for nightly builds
4
+ #
5
+ # Runs full test suite + all PRD testSteps from completed stories.
6
+ # Use this in nightly CI jobs, not on every PR.
7
+
8
+ # Run comprehensive tests (for nightly CI)
9
+ ralph_test() {
10
+ local mode="${1:-all}"
11
+
12
+ echo ""
13
+ print_info "=== Ralph Nightly Test Suite ==="
14
+ echo ""
15
+
16
+ local failed=0
17
+ local total=0
18
+ local passed=0
19
+
20
+ case "$mode" in
21
+ all)
22
+ run_full_test_suite || failed=1
23
+ run_all_prd_tests || failed=1
24
+ ;;
25
+ unit)
26
+ run_full_test_suite || failed=1
27
+ ;;
28
+ prd)
29
+ run_all_prd_tests || failed=1
30
+ ;;
31
+ *)
32
+ echo "Usage: ralph test [all|unit|prd]"
33
+ echo ""
34
+ echo "Modes:"
35
+ echo " all - Run unit tests + all PRD testSteps (default)"
36
+ echo " unit - Run only unit tests"
37
+ echo " prd - Run only PRD testSteps from completed stories"
38
+ return 1
39
+ ;;
40
+ esac
41
+
42
+ echo ""
43
+ if [[ $failed -eq 0 ]]; then
44
+ print_success "=== All nightly tests passed ==="
45
+ return 0
46
+ else
47
+ print_error "=== Nightly tests failed ==="
48
+ return 1
49
+ fi
50
+ }
51
+
52
+ # Run the full test suite
53
+ run_full_test_suite() {
54
+ echo "--- Unit Tests ---"
55
+ echo ""
56
+
57
+ local test_cmd
58
+ test_cmd=$(get_config '.checks.testCommand' "")
59
+
60
+ if [[ -z "$test_cmd" ]]; then
61
+ # Auto-detect test command
62
+ if [[ -f "package.json" ]] && grep -q '"test"' package.json; then
63
+ test_cmd="npm test"
64
+ elif [[ -f "pytest.ini" ]] || [[ -f "pyproject.toml" ]]; then
65
+ test_cmd="pytest -v"
66
+ elif [[ -f "Cargo.toml" ]]; then
67
+ test_cmd="cargo test"
68
+ elif [[ -f "go.mod" ]]; then
69
+ test_cmd="go test -v ./..."
70
+ else
71
+ print_warning "No test command found, skipping unit tests"
72
+ return 0
73
+ fi
74
+ fi
75
+
76
+ echo "Running: $test_cmd"
77
+ echo ""
78
+
79
+ if eval "$test_cmd"; then
80
+ print_success "Unit tests passed"
81
+ return 0
82
+ else
83
+ print_error "Unit tests failed"
84
+ return 1
85
+ fi
86
+ }
87
+
88
+ # Run all PRD testSteps from all stories (completed and incomplete)
89
+ run_all_prd_tests() {
90
+ echo ""
91
+ echo "--- PRD Test Steps ---"
92
+ echo ""
93
+
94
+ if [[ ! -f "$RALPH_DIR/prd.json" ]]; then
95
+ print_warning "No PRD found, skipping PRD tests"
96
+ return 0
97
+ fi
98
+
99
+ local failed=0
100
+ local total=0
101
+ local passed=0
102
+
103
+ # Get all stories
104
+ local stories
105
+ stories=$(jq -r '.stories[].id' "$RALPH_DIR/prd.json" 2>/dev/null)
106
+
107
+ if [[ -z "$stories" ]]; then
108
+ echo "No stories found in PRD"
109
+ return 0
110
+ fi
111
+
112
+ while IFS= read -r story_id; do
113
+ [[ -z "$story_id" ]] && continue
114
+
115
+ local story_title
116
+ story_title=$(jq -r --arg id "$story_id" '.stories[] | select(.id==$id) | .title' "$RALPH_DIR/prd.json")
117
+
118
+ echo "[$story_id] $story_title"
119
+
120
+ local test_steps
121
+ test_steps=$(jq -r --arg id "$story_id" '.stories[] | select(.id==$id) | .testSteps[]?' "$RALPH_DIR/prd.json" 2>/dev/null)
122
+
123
+ if [[ -z "$test_steps" ]]; then
124
+ echo " (no testSteps)"
125
+ continue
126
+ fi
127
+
128
+ while IFS= read -r step; do
129
+ [[ -z "$step" ]] && continue
130
+ ((total++))
131
+
132
+ echo -n " $step... "
133
+
134
+ if eval "$step" >/dev/null 2>&1; then
135
+ print_success "passed"
136
+ ((passed++))
137
+ else
138
+ print_error "failed"
139
+ ((failed++))
140
+ fi
141
+ done <<< "$test_steps"
142
+
143
+ echo ""
144
+ done <<< "$stories"
145
+
146
+ echo "PRD Tests: $passed/$total passed"
147
+
148
+ [[ $failed -gt 0 ]] && return 1
149
+ return 0
150
+ }
151
+
152
+ # Generate test coverage report
153
+ ralph_test_coverage() {
154
+ echo ""
155
+ print_info "=== Test Coverage Report ==="
156
+ echo ""
157
+
158
+ # Python coverage
159
+ if [[ -f "pytest.ini" ]] || [[ -f "pyproject.toml" ]]; then
160
+ local backend_dir
161
+ backend_dir=$(get_config '.directories.backend' ".")
162
+
163
+ echo "Running pytest with coverage..."
164
+ if (cd "$backend_dir" && pytest --cov --cov-report=term-missing 2>/dev/null); then
165
+ return 0
166
+ else
167
+ print_warning "Coverage report failed (pytest-cov may not be installed)"
168
+ return 1
169
+ fi
170
+ fi
171
+
172
+ # JS/TS coverage
173
+ if [[ -f "package.json" ]] && grep -q '"test:coverage"' package.json; then
174
+ echo "Running npm test:coverage..."
175
+ npm run test:coverage
176
+ return $?
177
+ fi
178
+
179
+ print_warning "No coverage tool detected"
180
+ return 0
181
+ }
@@ -2,6 +2,106 @@
2
2
  # shellcheck shell=bash
3
3
  # tests.sh - Test verification module for ralph
4
4
 
5
+ # Check that new/modified source files have corresponding test files
6
+ # This catches the case where Claude writes code but forgets tests
7
+ verify_test_files_exist() {
8
+ local story_type="${RALPH_STORY_TYPE:-general}"
9
+
10
+ # Skip for frontend stories (handled differently with .test.tsx pattern)
11
+ [[ "$story_type" == "frontend" ]] && return 0
12
+
13
+ echo -n " Test files exist for new code... "
14
+
15
+ # Get list of modified Python files (excluding tests themselves)
16
+ local modified_files
17
+ modified_files=$(git diff --name-only HEAD~1 2>/dev/null | grep '\.py$' | grep -v 'test_' | grep -v '_test\.py' | grep -v '/tests/' || true)
18
+
19
+ # If no Python files modified, skip
20
+ if [[ -z "$modified_files" ]]; then
21
+ print_success "skipped (no new Python files)"
22
+ return 0
23
+ fi
24
+
25
+ local missing_tests=()
26
+ local checked=0
27
+
28
+ while IFS= read -r src_file; do
29
+ [[ -z "$src_file" ]] && continue
30
+ [[ ! -f "$src_file" ]] && continue
31
+
32
+ # Skip __init__.py, migrations, config files
33
+ [[ "$src_file" == *"__init__.py" ]] && continue
34
+ [[ "$src_file" == *"/migrations/"* ]] && continue
35
+ [[ "$src_file" == *"/alembic/"* ]] && continue
36
+ [[ "$src_file" == *"config"* ]] && continue
37
+ [[ "$src_file" == *"settings"* ]] && continue
38
+
39
+ ((checked++))
40
+
41
+ # Determine expected test file location
42
+ local base_name dir_name test_file
43
+ base_name=$(basename "$src_file" .py)
44
+ dir_name=$(dirname "$src_file")
45
+
46
+ # Common patterns: tests/test_foo.py or foo_test.py
47
+ local possible_tests=(
48
+ "$dir_name/tests/test_${base_name}.py"
49
+ "$dir_name/test_${base_name}.py"
50
+ "${dir_name}/tests/${base_name}_test.py"
51
+ "tests/test_${base_name}.py"
52
+ "tests/${base_name}_test.py"
53
+ )
54
+
55
+ # Check for backend dir patterns
56
+ local backend_dir
57
+ backend_dir=$(get_config '.directories.backend' "")
58
+ if [[ -n "$backend_dir" ]]; then
59
+ possible_tests+=(
60
+ "$backend_dir/tests/test_${base_name}.py"
61
+ "$backend_dir/tests/${base_name}_test.py"
62
+ )
63
+ fi
64
+
65
+ local found=false
66
+ for test_path in "${possible_tests[@]}"; do
67
+ if [[ -f "$test_path" ]]; then
68
+ found=true
69
+ break
70
+ fi
71
+ done
72
+
73
+ if [[ "$found" == "false" ]]; then
74
+ missing_tests+=("$src_file")
75
+ fi
76
+ done <<< "$modified_files"
77
+
78
+ if [[ ${#missing_tests[@]} -eq 0 ]]; then
79
+ print_success "passed ($checked files checked)"
80
+ return 0
81
+ else
82
+ print_error "missing tests"
83
+ echo ""
84
+ echo " The following files need test files:"
85
+ for file in "${missing_tests[@]}"; do
86
+ local base_name
87
+ base_name=$(basename "$file" .py)
88
+ echo " $file → test_${base_name}.py"
89
+ done
90
+ echo ""
91
+ echo " Create test files for new code before completing the story."
92
+
93
+ # Save for failure context
94
+ {
95
+ echo "Missing test files for new code:"
96
+ for file in "${missing_tests[@]}"; do
97
+ echo " $file"
98
+ done
99
+ } > "$RALPH_DIR/last_test_existence_failure.log"
100
+
101
+ return 1
102
+ fi
103
+ }
104
+
5
105
  # Run unit tests
6
106
  run_unit_tests() {
7
107
  local log_file
package/ralph/verify.sh CHANGED
@@ -33,12 +33,15 @@ run_verification() {
33
33
  fi
34
34
 
35
35
  # ========================================
36
- # STEP 2: Run unit tests
36
+ # STEP 2: Verify tests exist + run them
37
37
  # ========================================
38
38
  if [[ $failed -eq 0 ]]; then
39
39
  echo ""
40
40
  echo " [2/3] Running tests..."
41
- if ! run_unit_tests; then
41
+ # First check that test files exist for new code
42
+ if ! verify_test_files_exist; then
43
+ failed=1
44
+ elif ! run_unit_tests; then
42
45
  failed=1
43
46
  fi
44
47
  fi
@@ -22,9 +22,21 @@ For each story, you must:
22
22
 
23
23
  ### 2. Write Tests
24
24
 
25
- - Write unit tests for the business logic
26
- - Write tests that validate acceptance criteria
27
- - Test error cases and edge cases
25
+ **Every new code file MUST have a corresponding test file.**
26
+
27
+ For **backend** stories (Python/API):
28
+ - New file `foo.py` → create `tests/test_foo.py`
29
+ - Test each public function/method
30
+ - Test error cases (invalid input, missing data, API failures)
31
+ - Test edge cases (empty lists, None values, boundary conditions)
32
+ - Use pytest fixtures for database/API mocking
33
+
34
+ For **frontend** stories (TypeScript/React):
35
+ - New component `Foo.tsx` → create `Foo.test.tsx`
36
+ - Test rendering, user interactions, error states
37
+ - Test loading states and empty states
38
+
39
+ **Do NOT skip tests.** If you create code without tests, verification will fail.
28
40
 
29
41
  ### 3. Verify It Actually Works
30
42
 
@@ -0,0 +1,105 @@
1
+ # Nightly comprehensive test suite
2
+ # Runs full tests + all PRD testSteps
3
+
4
+ name: Nightly Tests
5
+
6
+ on:
7
+ schedule:
8
+ # Run at 3am UTC every day
9
+ - cron: '0 3 * * *'
10
+ workflow_dispatch: # Allow manual trigger
11
+
12
+ jobs:
13
+ test:
14
+ runs-on: ubuntu-latest
15
+
16
+ services:
17
+ # Add postgres if your project needs it
18
+ postgres:
19
+ image: postgres:15
20
+ env:
21
+ POSTGRES_USER: test
22
+ POSTGRES_PASSWORD: test
23
+ POSTGRES_DB: test
24
+ ports:
25
+ - 5432:5432
26
+ options: >-
27
+ --health-cmd pg_isready
28
+ --health-interval 10s
29
+ --health-timeout 5s
30
+ --health-retries 5
31
+
32
+ env:
33
+ DATABASE_URL: postgresql://test:test@localhost:5432/test
34
+
35
+ steps:
36
+ - uses: actions/checkout@v4
37
+
38
+ # Python setup
39
+ - name: Set up Python
40
+ if: hashFiles('pyproject.toml') != '' || hashFiles('requirements.txt') != ''
41
+ uses: actions/setup-python@v5
42
+ with:
43
+ python-version: '3.11'
44
+
45
+ - name: Install Python dependencies
46
+ if: hashFiles('pyproject.toml') != ''
47
+ run: |
48
+ pip install uv
49
+ uv pip install -e ".[dev]" --system 2>/dev/null || pip install -e ".[dev]" 2>/dev/null || pip install -e . 2>/dev/null || true
50
+
51
+ # Node.js setup
52
+ - name: Set up Node.js
53
+ if: hashFiles('package.json') != ''
54
+ uses: actions/setup-node@v4
55
+ with:
56
+ node-version: '20'
57
+ cache: 'npm'
58
+
59
+ - name: Install Node dependencies
60
+ if: hashFiles('package.json') != ''
61
+ run: npm ci
62
+
63
+ # Run database migrations if needed
64
+ - name: Run migrations
65
+ if: hashFiles('alembic.ini') != ''
66
+ run: alembic upgrade head
67
+ continue-on-error: true
68
+
69
+ # Python tests
70
+ - name: Python tests
71
+ if: hashFiles('pyproject.toml') != '' || hashFiles('pytest.ini') != ''
72
+ run: |
73
+ pytest -v --tb=short 2>/dev/null || python -m pytest -v --tb=short 2>/dev/null || true
74
+
75
+ # Node tests
76
+ - name: Node tests
77
+ if: hashFiles('package.json') != ''
78
+ run: npm test 2>/dev/null || true
79
+
80
+ # PRD testSteps (if ralph is set up)
81
+ - name: Install ralph
82
+ run: npm install -g thrivekit 2>/dev/null || true
83
+
84
+ - name: Run PRD tests
85
+ if: hashFiles('.ralph/prd.json') != ''
86
+ run: ralph test prd 2>/dev/null || true
87
+ continue-on-error: true
88
+
89
+ # Coverage report
90
+ - name: Coverage report
91
+ if: hashFiles('pyproject.toml') != ''
92
+ run: |
93
+ pip install pytest-cov
94
+ pytest --cov --cov-report=term-missing 2>/dev/null || true
95
+ continue-on-error: true
96
+
97
+ notify:
98
+ needs: test
99
+ runs-on: ubuntu-latest
100
+ if: failure()
101
+ steps:
102
+ - name: Notify on failure
103
+ run: |
104
+ echo "Nightly tests failed! Check the workflow run for details."
105
+ # Add Slack/Discord notification here if desired
@@ -0,0 +1,56 @@
1
+ # Fast PR checks - lint only, no tests
2
+ # Tests run in nightly workflow to keep PRs fast
3
+
4
+ name: PR Check
5
+
6
+ on:
7
+ pull_request:
8
+ branches: [main, master]
9
+
10
+ jobs:
11
+ lint:
12
+ runs-on: ubuntu-latest
13
+ steps:
14
+ - uses: actions/checkout@v4
15
+
16
+ # Python linting
17
+ - name: Set up Python
18
+ if: hashFiles('pyproject.toml') != '' || hashFiles('requirements.txt') != ''
19
+ uses: actions/setup-python@v5
20
+ with:
21
+ python-version: '3.11'
22
+
23
+ - name: Install Python dependencies
24
+ if: hashFiles('pyproject.toml') != ''
25
+ run: |
26
+ pip install ruff
27
+ pip install -e . 2>/dev/null || true
28
+
29
+ - name: Ruff lint
30
+ if: hashFiles('pyproject.toml') != '' || hashFiles('ruff.toml') != ''
31
+ run: ruff check .
32
+
33
+ # Node.js linting
34
+ - name: Set up Node.js
35
+ if: hashFiles('package.json') != ''
36
+ uses: actions/setup-node@v4
37
+ with:
38
+ node-version: '20'
39
+ cache: 'npm'
40
+
41
+ - name: Install Node dependencies
42
+ if: hashFiles('package.json') != ''
43
+ run: npm ci
44
+
45
+ - name: ESLint
46
+ if: hashFiles('package.json') != ''
47
+ run: npm run lint 2>/dev/null || npx eslint . 2>/dev/null || true
48
+
49
+ - name: TypeScript check
50
+ if: hashFiles('tsconfig.json') != ''
51
+ run: npx tsc --noEmit
52
+
53
+ # Build check (catches import/bundling errors)
54
+ - name: Build
55
+ if: hashFiles('package.json') != ''
56
+ run: npm run build 2>/dev/null || true