agentic-loop 3.1.6 → 3.2.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/.claude/commands/idea.md +2 -2
- package/.claude/commands/prd.md +1 -1
- package/README.md +3 -4
- package/package.json +1 -1
- package/ralph/setup.sh +76 -11
- package/ralph/verify.sh +12 -143
- package/templates/PROMPT.md +26 -43
- package/ralph/api.sh +0 -216
- package/ralph/browser-verify/README.md +0 -135
- package/ralph/browser-verify/verify.ts +0 -450
- package/ralph/playwright.sh +0 -238
- package/ralph/verify/browser.sh +0 -324
- package/ralph/verify/review.sh +0 -152
package/.claude/commands/idea.md
CHANGED
|
@@ -306,7 +306,7 @@ For frontend stories, acceptance criteria MUST include:
|
|
|
306
306
|
- "Required elements render" (specify which: header, form, button, etc.)
|
|
307
307
|
- "Works on mobile viewport (375px)"
|
|
308
308
|
|
|
309
|
-
These get verified by Playwright
|
|
309
|
+
These get verified by Playwright tests and MCP browser tools.
|
|
310
310
|
|
|
311
311
|
### Test Steps - CRITICAL
|
|
312
312
|
**Test steps MUST be executable shell commands.** Ralph runs them with bash.
|
|
@@ -352,7 +352,7 @@ The Playwright test file can check:
|
|
|
352
352
|
]
|
|
353
353
|
```
|
|
354
354
|
|
|
355
|
-
**If a step can't be automated**, leave it out of testSteps and put it in acceptanceCriteria instead.
|
|
355
|
+
**If a step can't be automated**, leave it out of testSteps and put it in acceptanceCriteria instead. Claude will verify acceptanceCriteria visually using MCP browser tools.
|
|
356
356
|
|
|
357
357
|
### Architecture Guidelines
|
|
358
358
|
- **Domain-driven directories** - Group by feature (`src/contact/`) not type (`src/components/`)
|
package/.claude/commands/prd.md
CHANGED
|
@@ -283,4 +283,4 @@ The Playwright test file can check:
|
|
|
283
283
|
]
|
|
284
284
|
```
|
|
285
285
|
|
|
286
|
-
**If a step can't be automated**, leave it out of testSteps and put it in acceptanceCriteria instead.
|
|
286
|
+
**If a step can't be automated**, leave it out of testSteps and put it in acceptanceCriteria instead. Claude will verify acceptanceCriteria visually using MCP browser tools.
|
package/README.md
CHANGED
|
@@ -45,8 +45,7 @@ claude --dangerously-skip-permissions
|
|
|
45
45
|
|
|
46
46
|
**Terminal 2 - Ralph Loop:**
|
|
47
47
|
```bash
|
|
48
|
-
npx agentic-loop run
|
|
49
|
-
npx agentic-loop run --fast # Skip code review (~2x faster)
|
|
48
|
+
npx agentic-loop run # Execute PRDs autonomously
|
|
50
49
|
```
|
|
51
50
|
|
|
52
51
|
> **Tip:** Use two terminals. Plan with Claude in one, run Ralph in the other.
|
|
@@ -61,8 +60,8 @@ npx agentic-loop run --fast # Skip code review (~2x faster)
|
|
|
61
60
|
├─────────────────────────────────────────────────────────────┤
|
|
62
61
|
│ 1. Read prd.json → find next story where passes=false │
|
|
63
62
|
│ 2. Build prompt (story + context + failures + signs) │
|
|
64
|
-
│ 3. Spawn Claude with prompt
|
|
65
|
-
│ 4. Run verification (lint, tests,
|
|
63
|
+
│ 3. Spawn Claude with prompt + MCP browser tools │
|
|
64
|
+
│ 4. Run verification (lint, tests, testSteps) │
|
|
66
65
|
│ 5. Pass? → commit, next story │
|
|
67
66
|
│ Fail? → save error, retry with failure context │
|
|
68
67
|
│ 6. Repeat until all stories pass │
|
package/package.json
CHANGED
package/ralph/setup.sh
CHANGED
|
@@ -340,7 +340,7 @@ EOF
|
|
|
340
340
|
fi
|
|
341
341
|
}
|
|
342
342
|
|
|
343
|
-
# Configure MCP (
|
|
343
|
+
# Configure MCP (Browser tools for verification)
|
|
344
344
|
setup_mcp() {
|
|
345
345
|
local claude_json="$HOME/.claude.json"
|
|
346
346
|
|
|
@@ -350,17 +350,82 @@ setup_mcp() {
|
|
|
350
350
|
# Create claude.json if it doesn't exist
|
|
351
351
|
[[ ! -f "$claude_json" ]] && echo '{}' > "$claude_json"
|
|
352
352
|
|
|
353
|
-
|
|
354
|
-
|
|
353
|
+
local added_any=false
|
|
354
|
+
|
|
355
|
+
# Add Playwright MCP if not configured
|
|
356
|
+
if ! jq -e '.mcpServers["playwright"]' "$claude_json" > /dev/null 2>&1; then
|
|
357
|
+
echo "Configuring MCP servers..."
|
|
358
|
+
local tmp
|
|
359
|
+
tmp=$(mktemp)
|
|
360
|
+
jq '.mcpServers["playwright"] = {
|
|
361
|
+
"command": "npx",
|
|
362
|
+
"args": ["-y", "@anthropic-ai/mcp-server-playwright"]
|
|
363
|
+
}' "$claude_json" > "$tmp" && mv "$tmp" "$claude_json"
|
|
364
|
+
echo " Added playwright MCP server (browser automation & testing)"
|
|
365
|
+
added_any=true
|
|
366
|
+
fi
|
|
355
367
|
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
368
|
+
# Add Chrome DevTools MCP if not configured
|
|
369
|
+
if ! jq -e '.mcpServers["chrome-devtools"]' "$claude_json" > /dev/null 2>&1; then
|
|
370
|
+
[[ "$added_any" == "false" ]] && echo "Configuring MCP servers..."
|
|
371
|
+
local tmp
|
|
372
|
+
tmp=$(mktemp)
|
|
373
|
+
jq '.mcpServers["chrome-devtools"] = {
|
|
374
|
+
"command": "npx",
|
|
375
|
+
"args": ["-y", "@anthropic-ai/mcp-server-chrome-devtools@0.0.5"]
|
|
376
|
+
}' "$claude_json" > "$tmp" && mv "$tmp" "$claude_json"
|
|
377
|
+
echo " Added chrome-devtools MCP server (debugging & inspection)"
|
|
378
|
+
added_any=true
|
|
379
|
+
fi
|
|
380
|
+
|
|
381
|
+
# Ask about test credentials
|
|
382
|
+
if [[ "$added_any" == "true" ]]; then
|
|
383
|
+
setup_test_credentials
|
|
384
|
+
fi
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
# Set up test credentials for browser automation
|
|
388
|
+
setup_test_credentials() {
|
|
389
|
+
echo ""
|
|
390
|
+
echo " Browser automation often needs login credentials for testing."
|
|
391
|
+
echo ""
|
|
392
|
+
read -r -p " Do you have test user credentials to configure? [y/N] " response
|
|
393
|
+
|
|
394
|
+
if [[ "$response" =~ ^[Yy]$ ]]; then
|
|
395
|
+
echo ""
|
|
396
|
+
echo " These will be stored in .env (already in .gitignore - never committed)"
|
|
397
|
+
echo ""
|
|
398
|
+
|
|
399
|
+
# Create .env if it doesn't exist
|
|
400
|
+
if [[ ! -f ".env" ]]; then
|
|
401
|
+
touch ".env"
|
|
402
|
+
echo " Created .env file"
|
|
403
|
+
fi
|
|
404
|
+
|
|
405
|
+
# Get credentials
|
|
406
|
+
read -r -p " Test user email: " test_email
|
|
407
|
+
read -r -s -p " Test user password: " test_password
|
|
408
|
+
echo ""
|
|
409
|
+
|
|
410
|
+
# Add to .env (append or update)
|
|
411
|
+
if grep -q "^RALPH_TEST_USER=" .env 2>/dev/null; then
|
|
412
|
+
# Update existing
|
|
413
|
+
local tmp
|
|
414
|
+
tmp=$(mktemp)
|
|
415
|
+
sed "s/^RALPH_TEST_USER=.*/RALPH_TEST_USER=$test_email/" .env > "$tmp" && mv "$tmp" .env
|
|
416
|
+
sed "s/^RALPH_TEST_PASSWORD=.*/RALPH_TEST_PASSWORD=$test_password/" .env > "$tmp" && mv "$tmp" .env
|
|
417
|
+
else
|
|
418
|
+
# Append new
|
|
419
|
+
echo "" >> .env
|
|
420
|
+
echo "# Test credentials for browser automation (auto-added by agentic-loop)" >> .env
|
|
421
|
+
echo "RALPH_TEST_USER=$test_email" >> .env
|
|
422
|
+
echo "RALPH_TEST_PASSWORD=$test_password" >> .env
|
|
423
|
+
fi
|
|
424
|
+
|
|
425
|
+
echo " Saved credentials to .env"
|
|
426
|
+
echo ""
|
|
427
|
+
echo " Note: .env is gitignored - your password will never be committed to git."
|
|
428
|
+
fi
|
|
364
429
|
}
|
|
365
430
|
|
|
366
431
|
# Set up pre-commit hooks
|
package/ralph/verify.sh
CHANGED
|
@@ -1,35 +1,14 @@
|
|
|
1
1
|
#!/usr/bin/env bash
|
|
2
2
|
# shellcheck shell=bash
|
|
3
|
-
# verify.sh -
|
|
3
|
+
# verify.sh - Lightweight verification for ralph
|
|
4
4
|
#
|
|
5
|
-
#
|
|
6
|
-
#
|
|
7
|
-
# - verify/lint.sh - Auto-fix + lint checks
|
|
8
|
-
# - verify/tests.sh - Unit tests + PRD criteria
|
|
9
|
-
# - verify/browser.sh - Browser validation
|
|
10
|
-
|
|
11
|
-
# Validate required source files exist before sourcing
|
|
12
|
-
if [[ ! -f "$RALPH_LIB/playwright.sh" ]]; then
|
|
13
|
-
echo "Error: Missing $RALPH_LIB/playwright.sh" >&2
|
|
14
|
-
exit 1
|
|
15
|
-
fi
|
|
16
|
-
if [[ ! -f "$RALPH_LIB/api.sh" ]]; then
|
|
17
|
-
echo "Error: Missing $RALPH_LIB/api.sh" >&2
|
|
18
|
-
exit 1
|
|
19
|
-
fi
|
|
5
|
+
# Philosophy: Claude verifies its own work using MCP browser tools.
|
|
6
|
+
# Ralph just runs lint, tests, and testSteps from the PRD.
|
|
20
7
|
|
|
21
8
|
# Source verification modules
|
|
22
|
-
source "$RALPH_LIB/playwright.sh"
|
|
23
|
-
source "$RALPH_LIB/api.sh"
|
|
24
|
-
|
|
25
|
-
# Determine the directory where this script lives
|
|
26
9
|
VERIFY_DIR="${RALPH_LIB:-$(dirname "${BASH_SOURCE[0]}")}"
|
|
27
|
-
|
|
28
|
-
# Source modular verification components
|
|
29
|
-
source "$VERIFY_DIR/verify/review.sh"
|
|
30
10
|
source "$VERIFY_DIR/verify/lint.sh"
|
|
31
11
|
source "$VERIFY_DIR/verify/tests.sh"
|
|
32
|
-
source "$VERIFY_DIR/verify/browser.sh"
|
|
33
12
|
|
|
34
13
|
run_verification() {
|
|
35
14
|
local story="$1"
|
|
@@ -38,142 +17,33 @@ run_verification() {
|
|
|
38
17
|
print_info "=== Verification: $story ==="
|
|
39
18
|
echo ""
|
|
40
19
|
|
|
41
|
-
# Clear old failure logs (so smart-skip logic uses fresh state)
|
|
42
|
-
rm -f "$RALPH_DIR/last_precommit_failure.log" \
|
|
43
|
-
"$RALPH_DIR/last_test_failure.log" \
|
|
44
|
-
"$RALPH_DIR/last_review_failure.json" 2>/dev/null
|
|
45
|
-
|
|
46
|
-
# Check for fast mode
|
|
47
|
-
local fast_mode="${RALPH_FAST_MODE:-false}"
|
|
48
|
-
if [[ "$fast_mode" == "true" ]]; then
|
|
49
|
-
echo " (fast mode - skipping code review)"
|
|
50
|
-
fi
|
|
51
|
-
|
|
52
|
-
# Determine story type (single jq call for all story info)
|
|
53
|
-
local story_json
|
|
54
|
-
story_json=$(jq --arg id "$story" '.stories[] | select(.id==$id)' "$RALPH_DIR/prd.json" 2>/dev/null)
|
|
55
|
-
|
|
56
|
-
local story_type has_test_url has_api_endpoints
|
|
57
|
-
story_type=$(echo "$story_json" | jq -r '.type // "frontend"')
|
|
58
|
-
has_test_url=$(echo "$story_json" | jq -r '.testUrl // empty')
|
|
59
|
-
has_api_endpoints=$(echo "$story_json" | jq -r '.apiEndpoints[0] // empty')
|
|
60
|
-
|
|
61
|
-
# Auto-detect type if not specified
|
|
62
|
-
if [[ -n "$has_api_endpoints" && -z "$has_test_url" ]]; then
|
|
63
|
-
story_type="backend"
|
|
64
|
-
fi
|
|
65
|
-
|
|
66
20
|
local failed=0
|
|
67
|
-
local lint_failed=0
|
|
68
|
-
local test_failed=0
|
|
69
21
|
|
|
70
22
|
# ========================================
|
|
71
|
-
# STEP 1:
|
|
23
|
+
# STEP 1: Run lint checks
|
|
72
24
|
# ========================================
|
|
73
|
-
|
|
74
|
-
if
|
|
75
|
-
|
|
76
|
-
elif [[ -f "$RALPH_DIR/last_precommit_failure.log" ]] || [[ -f "$RALPH_DIR/last_test_failure.log" ]]; then
|
|
77
|
-
# Skip review if last failure was lint/test - review won't help
|
|
78
|
-
skip_review=true
|
|
79
|
-
echo " [1/5] Skipping code review (last failure was lint/test)"
|
|
80
|
-
fi
|
|
81
|
-
|
|
82
|
-
if [[ "$skip_review" == "false" ]]; then
|
|
83
|
-
echo " [1/5] Running code review..."
|
|
84
|
-
if ! run_code_review "$story"; then
|
|
85
|
-
failed=1
|
|
86
|
-
fi
|
|
25
|
+
echo " [1/3] Running lint checks..."
|
|
26
|
+
if ! run_configured_checks; then
|
|
27
|
+
failed=1
|
|
87
28
|
fi
|
|
88
29
|
|
|
89
30
|
# ========================================
|
|
90
|
-
# STEP 2
|
|
31
|
+
# STEP 2: Run unit tests
|
|
91
32
|
# ========================================
|
|
92
33
|
if [[ $failed -eq 0 ]]; then
|
|
93
34
|
echo ""
|
|
94
|
-
echo " [2/
|
|
95
|
-
|
|
96
|
-
# Create temp files for results
|
|
97
|
-
local lint_log test_log
|
|
98
|
-
lint_log=$(mktemp)
|
|
99
|
-
test_log=$(mktemp)
|
|
100
|
-
|
|
101
|
-
# Run lint in background
|
|
102
|
-
(run_configured_checks > "$lint_log" 2>&1; echo $? > "${lint_log}.exit") &
|
|
103
|
-
local lint_pid=$!
|
|
104
|
-
|
|
105
|
-
# Run tests in background
|
|
106
|
-
(run_unit_tests > "$test_log" 2>&1; echo $? > "${test_log}.exit") &
|
|
107
|
-
local test_pid=$!
|
|
108
|
-
|
|
109
|
-
# Wait for both
|
|
110
|
-
wait $lint_pid 2>/dev/null
|
|
111
|
-
wait $test_pid 2>/dev/null
|
|
112
|
-
|
|
113
|
-
# Check results
|
|
114
|
-
lint_failed=$(cat "${lint_log}.exit" 2>/dev/null || echo "1")
|
|
115
|
-
test_failed=$(cat "${test_log}.exit" 2>/dev/null || echo "1")
|
|
116
|
-
|
|
117
|
-
# Show lint output
|
|
118
|
-
echo " Lint:"
|
|
119
|
-
cat "$lint_log" | sed 's/^/ /'
|
|
120
|
-
|
|
121
|
-
# Show test output
|
|
122
|
-
echo " Tests:"
|
|
123
|
-
cat "$test_log" | sed 's/^/ /'
|
|
124
|
-
|
|
125
|
-
# Cleanup
|
|
126
|
-
rm -f "$lint_log" "${lint_log}.exit" "$test_log" "${test_log}.exit"
|
|
127
|
-
|
|
128
|
-
if [[ "$lint_failed" != "0" ]] || [[ "$test_failed" != "0" ]]; then
|
|
35
|
+
echo " [2/3] Running tests..."
|
|
36
|
+
if ! run_unit_tests; then
|
|
129
37
|
failed=1
|
|
130
38
|
fi
|
|
131
39
|
fi
|
|
132
40
|
|
|
133
41
|
# ========================================
|
|
134
|
-
# STEP 3: Run
|
|
135
|
-
# ========================================
|
|
136
|
-
if [[ $failed -eq 0 ]]; then
|
|
137
|
-
echo ""
|
|
138
|
-
if [[ "$story_type" == "backend" ]]; then
|
|
139
|
-
echo " [3/5] Running API tests..."
|
|
140
|
-
if ! run_api_validation "$story"; then
|
|
141
|
-
failed=1
|
|
142
|
-
elif ! run_api_error_tests "$story"; then
|
|
143
|
-
failed=1
|
|
144
|
-
fi
|
|
145
|
-
else
|
|
146
|
-
echo " [3/5] Running Playwright tests..."
|
|
147
|
-
if ! run_playwright_tests "$story"; then
|
|
148
|
-
failed=1
|
|
149
|
-
fi
|
|
150
|
-
fi
|
|
151
|
-
fi
|
|
152
|
-
|
|
153
|
-
# ========================================
|
|
154
|
-
# STEP 4: Run browser validation (frontend) or API validation (backend)
|
|
155
|
-
# ========================================
|
|
156
|
-
if [[ $failed -eq 0 ]]; then
|
|
157
|
-
echo ""
|
|
158
|
-
if [[ "$story_type" == "backend" ]]; then
|
|
159
|
-
echo " [4/5] Running API validation..."
|
|
160
|
-
if ! run_api_tests "$story"; then
|
|
161
|
-
failed=1
|
|
162
|
-
fi
|
|
163
|
-
else
|
|
164
|
-
echo " [4/5] Running browser validation..."
|
|
165
|
-
if ! run_browser_validation "$story"; then
|
|
166
|
-
failed=1
|
|
167
|
-
fi
|
|
168
|
-
fi
|
|
169
|
-
fi
|
|
170
|
-
|
|
171
|
-
# ========================================
|
|
172
|
-
# STEP 5: Run PRD test steps
|
|
42
|
+
# STEP 3: Run PRD test steps
|
|
173
43
|
# ========================================
|
|
174
44
|
if [[ $failed -eq 0 ]]; then
|
|
175
45
|
echo ""
|
|
176
|
-
echo " [
|
|
46
|
+
echo " [3/3] Running PRD test steps..."
|
|
177
47
|
if ! verify_prd_criteria "$story"; then
|
|
178
48
|
failed=1
|
|
179
49
|
fi
|
|
@@ -188,7 +58,6 @@ run_verification() {
|
|
|
188
58
|
return 0
|
|
189
59
|
else
|
|
190
60
|
print_error "=== Verification failed ==="
|
|
191
|
-
# Save failure context for next iteration
|
|
192
61
|
save_failure_context "$story"
|
|
193
62
|
return 1
|
|
194
63
|
fi
|
package/templates/PROMPT.md
CHANGED
|
@@ -14,36 +14,38 @@ Before writing any code, verify:
|
|
|
14
14
|
|
|
15
15
|
For each story, you must:
|
|
16
16
|
|
|
17
|
-
### 1.
|
|
17
|
+
### 1. Implement the Feature
|
|
18
18
|
|
|
19
|
-
**For frontend stories:**
|
|
20
|
-
- Write a Playwright test that validates the acceptance criteria
|
|
21
|
-
- Include tests for error handling (API fails, validation errors)
|
|
22
|
-
- Include tests for empty/loading states
|
|
23
|
-
- Include accessibility checks (axe-core)
|
|
24
|
-
- Include mobile viewport test (375px)
|
|
25
|
-
|
|
26
|
-
**For backend stories:**
|
|
27
|
-
- Write unit tests for the business logic
|
|
28
|
-
- Write API tests that validate all endpoints
|
|
29
|
-
- Test error responses (400, 401, 500)
|
|
30
|
-
- Test validation rules
|
|
31
|
-
|
|
32
|
-
### 2. Implement the Feature
|
|
33
|
-
|
|
34
|
-
- Write code to make all tests pass
|
|
35
19
|
- Follow existing patterns in the codebase
|
|
36
20
|
- Handle ALL error cases defined in the story
|
|
37
21
|
- Implement loading states for async operations
|
|
38
22
|
|
|
23
|
+
### 2. Write Tests
|
|
24
|
+
|
|
25
|
+
- Write unit tests for the business logic
|
|
26
|
+
- Write tests that validate acceptance criteria
|
|
27
|
+
- Test error cases and edge cases
|
|
28
|
+
|
|
39
29
|
### 3. Verify It Actually Works
|
|
40
30
|
|
|
31
|
+
**You have browser tools - USE THEM to verify your work:**
|
|
32
|
+
|
|
33
|
+
**Playwright MCP** (testing & automation):
|
|
34
|
+
- `browser_navigate` - Go to a URL and get page content
|
|
35
|
+
- `browser_screenshot` - Take a screenshot to verify UI
|
|
36
|
+
- `browser_click` - Click elements to test interactions
|
|
37
|
+
- `browser_type` - Fill in forms to test inputs
|
|
38
|
+
- `browser_snapshot` - Get accessibility tree for a11y testing
|
|
39
|
+
|
|
40
|
+
**Chrome DevTools MCP** (debugging & inspection):
|
|
41
|
+
- Inspect DOM, check console for errors
|
|
42
|
+
- Debug network requests
|
|
43
|
+
- Check element styles and computed properties
|
|
44
|
+
|
|
41
45
|
**Do NOT say you're done until:**
|
|
42
46
|
- All unit tests pass
|
|
43
|
-
-
|
|
44
|
-
- You've opened the browser via MCP and visually verified
|
|
47
|
+
- You've opened the browser and visually verified the feature works
|
|
45
48
|
- Console has no errors
|
|
46
|
-
- It works on mobile (375px viewport)
|
|
47
49
|
- Error states are handled gracefully
|
|
48
50
|
|
|
49
51
|
## Rules
|
|
@@ -60,33 +62,14 @@ For each story, you must:
|
|
|
60
62
|
|
|
61
63
|
Before considering any story complete:
|
|
62
64
|
|
|
63
|
-
### Code
|
|
64
65
|
- [ ] All acceptance criteria are met
|
|
65
66
|
- [ ] All error handling from story is implemented
|
|
66
|
-
- [ ]
|
|
67
|
-
- [ ] Validation implemented (if backend)
|
|
68
|
-
- [ ] TypeScript compiles without errors
|
|
69
|
-
|
|
70
|
-
### Tests
|
|
67
|
+
- [ ] TypeScript/code compiles without errors
|
|
71
68
|
- [ ] Unit tests written and passing
|
|
72
|
-
- [ ] Playwright
|
|
73
|
-
- [ ]
|
|
74
|
-
- [ ] Error cases tested
|
|
75
|
-
- [ ] Edge cases tested (empty state, etc.)
|
|
76
|
-
|
|
77
|
-
### Browser/API Validation
|
|
78
|
-
- [ ] Browser check passes (frontend) - no console errors
|
|
79
|
-
- [ ] Mobile viewport works (375px)
|
|
80
|
-
- [ ] Accessibility passes (can Tab through, focus visible)
|
|
81
|
-
- [ ] API returns correct responses (backend)
|
|
82
|
-
|
|
83
|
-
### Documentation
|
|
84
|
-
- [ ] Updated `.ralph/progress.txt` with files created/modified
|
|
85
|
-
- [ ] Noted any key decisions or context for next story
|
|
86
|
-
|
|
87
|
-
### Quality
|
|
69
|
+
- [ ] **Browser verified** - used Playwright MCP to visually confirm it works
|
|
70
|
+
- [ ] No console errors
|
|
88
71
|
- [ ] Linting passes
|
|
89
|
-
- [ ]
|
|
72
|
+
- [ ] Updated `.ralph/progress.txt` with files created/modified
|
|
90
73
|
|
|
91
74
|
## If Verification Fails
|
|
92
75
|
|
package/ralph/api.sh
DELETED
|
@@ -1,216 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env bash
|
|
2
|
-
# shellcheck shell=bash
|
|
3
|
-
# api.sh - API validation for backend stories
|
|
4
|
-
|
|
5
|
-
# Parse an endpoint string into method and path
|
|
6
|
-
# Usage: parse_endpoint "POST /api/users" "http://localhost:3000"
|
|
7
|
-
# Sets: ENDPOINT_METHOD, ENDPOINT_PATH, ENDPOINT_URL
|
|
8
|
-
parse_endpoint() {
|
|
9
|
-
local endpoint="$1"
|
|
10
|
-
local base_url="${2:-}"
|
|
11
|
-
|
|
12
|
-
# Defaults
|
|
13
|
-
ENDPOINT_METHOD="GET"
|
|
14
|
-
ENDPOINT_PATH="$endpoint"
|
|
15
|
-
ENDPOINT_URL=""
|
|
16
|
-
|
|
17
|
-
# Parse method if present (e.g., "POST /api/contact")
|
|
18
|
-
if [[ "$endpoint" =~ ^(GET|POST|PUT|PATCH|DELETE)[[:space:]]+(.*) ]]; then
|
|
19
|
-
ENDPOINT_METHOD="${BASH_REMATCH[1]}"
|
|
20
|
-
ENDPOINT_PATH="${BASH_REMATCH[2]}"
|
|
21
|
-
fi
|
|
22
|
-
|
|
23
|
-
# Build full URL
|
|
24
|
-
if [[ "$ENDPOINT_PATH" =~ ^https?:// ]]; then
|
|
25
|
-
ENDPOINT_URL="$ENDPOINT_PATH"
|
|
26
|
-
elif [[ -n "$base_url" ]]; then
|
|
27
|
-
ENDPOINT_URL="${base_url}${ENDPOINT_PATH}"
|
|
28
|
-
fi
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
# Check if endpoint is a WebSocket (can't be tested with HTTP)
|
|
32
|
-
is_websocket_endpoint() {
|
|
33
|
-
local endpoint="$1"
|
|
34
|
-
[[ "$endpoint" =~ ^wss?:// ]] || [[ "$endpoint" =~ ^(GET|POST|PUT|PATCH|DELETE)[[:space:]]+wss?:// ]]
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
# Validate API endpoints for a backend story
|
|
38
|
-
run_api_validation() {
|
|
39
|
-
local story="$1"
|
|
40
|
-
|
|
41
|
-
# Get API endpoints from story
|
|
42
|
-
local endpoints
|
|
43
|
-
endpoints=$(jq -r --arg id "$story" '.stories[] | select(.id==$id) | .apiEndpoints[]?' "$RALPH_DIR/prd.json" 2>/dev/null)
|
|
44
|
-
|
|
45
|
-
if [[ -z "$endpoints" ]]; then
|
|
46
|
-
echo " (no apiEndpoints defined, skipping API validation)"
|
|
47
|
-
return 0
|
|
48
|
-
fi
|
|
49
|
-
|
|
50
|
-
# Get base URL from config or use default
|
|
51
|
-
local base_url
|
|
52
|
-
base_url=$(get_config '.api.baseUrl' "http://localhost:3000")
|
|
53
|
-
|
|
54
|
-
local failed=0
|
|
55
|
-
|
|
56
|
-
echo " Validating API endpoints..."
|
|
57
|
-
|
|
58
|
-
while IFS= read -r endpoint; do
|
|
59
|
-
[[ -z "$endpoint" ]] && continue
|
|
60
|
-
|
|
61
|
-
# Skip WebSocket endpoints - they can't be tested with HTTP curl
|
|
62
|
-
if is_websocket_endpoint "$endpoint"; then
|
|
63
|
-
echo " Skipping WebSocket endpoint: $endpoint (use integration tests)"
|
|
64
|
-
continue
|
|
65
|
-
fi
|
|
66
|
-
|
|
67
|
-
# Parse endpoint into method, path, and full URL
|
|
68
|
-
parse_endpoint "$endpoint" "$base_url"
|
|
69
|
-
|
|
70
|
-
echo -n " $ENDPOINT_METHOD $ENDPOINT_PATH... "
|
|
71
|
-
|
|
72
|
-
# Make the request
|
|
73
|
-
local response_code
|
|
74
|
-
response_code=$(curl -sf -m "$CURL_TIMEOUT_SECONDS" -o /dev/null -w "%{http_code}" -X "$ENDPOINT_METHOD" "$ENDPOINT_URL" 2>/dev/null)
|
|
75
|
-
|
|
76
|
-
if [[ "$response_code" =~ ^2[0-9][0-9]$ ]]; then
|
|
77
|
-
print_success "$response_code"
|
|
78
|
-
elif [[ "$response_code" == "000" ]]; then
|
|
79
|
-
print_error "connection failed"
|
|
80
|
-
failed=1
|
|
81
|
-
elif [[ "$response_code" == "422" || "$response_code" == "400" ]]; then
|
|
82
|
-
# 422/400 = endpoint exists but needs params - don't fail verification
|
|
83
|
-
print_warning "$response_code (needs params)"
|
|
84
|
-
elif [[ "$response_code" == "401" || "$response_code" == "403" ]]; then
|
|
85
|
-
# Auth required - endpoint exists
|
|
86
|
-
print_warning "$response_code (auth required)"
|
|
87
|
-
else
|
|
88
|
-
print_error "$response_code"
|
|
89
|
-
failed=1
|
|
90
|
-
fi
|
|
91
|
-
done <<< "$endpoints"
|
|
92
|
-
|
|
93
|
-
return $failed
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
# Run comprehensive API tests for a story
|
|
97
|
-
run_api_tests() {
|
|
98
|
-
local story="$1"
|
|
99
|
-
|
|
100
|
-
# Get test steps that look like API calls
|
|
101
|
-
local test_steps
|
|
102
|
-
test_steps=$(jq -r --arg id "$story" '.stories[] | select(.id==$id) | .testSteps[]?' "$RALPH_DIR/prd.json" 2>/dev/null)
|
|
103
|
-
|
|
104
|
-
if [[ -z "$test_steps" ]]; then
|
|
105
|
-
return 0
|
|
106
|
-
fi
|
|
107
|
-
|
|
108
|
-
local failed=0
|
|
109
|
-
local log_file
|
|
110
|
-
log_file=$(create_temp_file ".log") || return 1
|
|
111
|
-
|
|
112
|
-
echo " Running API test steps..."
|
|
113
|
-
|
|
114
|
-
while IFS= read -r step; do
|
|
115
|
-
[[ -z "$step" ]] && continue
|
|
116
|
-
|
|
117
|
-
# Check if this looks like a curl command or API test
|
|
118
|
-
if [[ "$step" =~ ^curl ]]; then
|
|
119
|
-
echo -n " $step... "
|
|
120
|
-
|
|
121
|
-
if safe_exec "$step" "$log_file"; then
|
|
122
|
-
print_success "passed"
|
|
123
|
-
else
|
|
124
|
-
print_error "failed"
|
|
125
|
-
echo ""
|
|
126
|
-
echo " Response:"
|
|
127
|
-
tail -"$MAX_OUTPUT_PREVIEW_LINES" "$log_file" | sed 's/^/ /'
|
|
128
|
-
failed=1
|
|
129
|
-
fi
|
|
130
|
-
fi
|
|
131
|
-
done <<< "$test_steps"
|
|
132
|
-
|
|
133
|
-
rm -f "$log_file"
|
|
134
|
-
return $failed
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
# Validate error handling for API
|
|
138
|
-
run_api_error_tests() {
|
|
139
|
-
local story="$1"
|
|
140
|
-
|
|
141
|
-
# Get error handling requirements
|
|
142
|
-
local error_handling
|
|
143
|
-
error_handling=$(jq -r --arg id "$story" '.stories[] | select(.id==$id) | .errorHandling[]?' "$RALPH_DIR/prd.json" 2>/dev/null)
|
|
144
|
-
|
|
145
|
-
if [[ -z "$error_handling" ]]; then
|
|
146
|
-
return 0
|
|
147
|
-
fi
|
|
148
|
-
|
|
149
|
-
# Get base URL and endpoints
|
|
150
|
-
local base_url
|
|
151
|
-
base_url=$(get_config '.api.baseUrl' "http://localhost:3000")
|
|
152
|
-
|
|
153
|
-
local endpoints
|
|
154
|
-
endpoints=$(jq -r --arg id "$story" '.stories[] | select(.id==$id) | .apiEndpoints[0]?' "$RALPH_DIR/prd.json" 2>/dev/null)
|
|
155
|
-
|
|
156
|
-
if [[ -z "$endpoints" ]]; then
|
|
157
|
-
return 0
|
|
158
|
-
fi
|
|
159
|
-
|
|
160
|
-
# Skip WebSocket endpoints
|
|
161
|
-
if is_websocket_endpoint "$endpoints"; then
|
|
162
|
-
echo " Skipping error tests for WebSocket endpoint"
|
|
163
|
-
return 0
|
|
164
|
-
fi
|
|
165
|
-
|
|
166
|
-
# Parse endpoint (default to POST for error tests)
|
|
167
|
-
parse_endpoint "$endpoints" "$base_url"
|
|
168
|
-
[[ "$ENDPOINT_METHOD" == "GET" ]] && ENDPOINT_METHOD="POST"
|
|
169
|
-
|
|
170
|
-
local failed=0
|
|
171
|
-
|
|
172
|
-
echo " Testing API error handling..."
|
|
173
|
-
|
|
174
|
-
# Test common error cases
|
|
175
|
-
while IFS= read -r error_case; do
|
|
176
|
-
[[ -z "$error_case" ]] && continue
|
|
177
|
-
|
|
178
|
-
# Check for 400 tests (bad input)
|
|
179
|
-
if [[ "$error_case" =~ 400 ]]; then
|
|
180
|
-
echo -n " Testing 400 (bad request)... "
|
|
181
|
-
|
|
182
|
-
local response_code
|
|
183
|
-
response_code=$(curl -sf -m "$CURL_TIMEOUT_SECONDS" -o /dev/null -w "%{http_code}" \
|
|
184
|
-
-X "$ENDPOINT_METHOD" \
|
|
185
|
-
-H "Content-Type: application/json" \
|
|
186
|
-
-d '{}' \
|
|
187
|
-
"$ENDPOINT_URL" 2>/dev/null)
|
|
188
|
-
|
|
189
|
-
if [[ "$response_code" == "400" ]]; then
|
|
190
|
-
print_success "correctly returns 400"
|
|
191
|
-
else
|
|
192
|
-
print_warning "got $response_code (expected 400)"
|
|
193
|
-
failed=1
|
|
194
|
-
fi
|
|
195
|
-
fi
|
|
196
|
-
|
|
197
|
-
# Check for 401 tests (unauthorized)
|
|
198
|
-
if [[ "$error_case" =~ 401 ]]; then
|
|
199
|
-
echo -n " Testing 401 (unauthorized)... "
|
|
200
|
-
|
|
201
|
-
local response_code
|
|
202
|
-
response_code=$(curl -sf -m "$CURL_TIMEOUT_SECONDS" -o /dev/null -w "%{http_code}" \
|
|
203
|
-
-X "$ENDPOINT_METHOD" \
|
|
204
|
-
"$ENDPOINT_URL" 2>/dev/null)
|
|
205
|
-
|
|
206
|
-
if [[ "$response_code" == "401" ]]; then
|
|
207
|
-
print_success "correctly returns 401"
|
|
208
|
-
else
|
|
209
|
-
print_warning "got $response_code (expected 401)"
|
|
210
|
-
failed=1
|
|
211
|
-
fi
|
|
212
|
-
fi
|
|
213
|
-
done <<< "$error_handling"
|
|
214
|
-
|
|
215
|
-
return $failed
|
|
216
|
-
}
|