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 +11 -0
- package/package.json +1 -1
- package/ralph/ci.sh +130 -0
- package/ralph/test.sh +181 -0
- package/ralph/verify/tests.sh +100 -0
- package/ralph/verify.sh +5 -2
- package/templates/PROMPT.md +15 -3
- package/templates/github/workflows/nightly.yml +105 -0
- package/templates/github/workflows/pr.yml +56 -0
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
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
|
+
}
|
package/ralph/verify/tests.sh
CHANGED
|
@@ -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:
|
|
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
|
-
|
|
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
|
package/templates/PROMPT.md
CHANGED
|
@@ -22,9 +22,21 @@ For each story, you must:
|
|
|
22
22
|
|
|
23
23
|
### 2. Write Tests
|
|
24
24
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
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
|