agentic-loop 3.10.2 → 3.11.0
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/api.md +496 -0
- package/.claude/commands/aws.md +408 -0
- package/bin/ralph.sh +2 -1
- package/package.json +2 -1
- package/ralph/code-check.sh +307 -0
- package/ralph/loop.sh +80 -27
- package/ralph/prd-check.sh +498 -0
- package/ralph/utils.sh +66 -351
- package/templates/config/elixir.json +1 -1
- package/templates/config/fastmcp.json +1 -1
- package/templates/config/fullstack.json +1 -1
- package/templates/config/go.json +1 -1
- package/templates/config/minimal.json +1 -1
- package/templates/config/node.json +1 -1
- package/templates/config/python.json +1 -1
- package/templates/config/rust.json +1 -1
- package/ralph/verify.sh +0 -106
package/ralph/utils.sh
CHANGED
|
@@ -429,367 +429,82 @@ send_notification() {
|
|
|
429
429
|
return 0
|
|
430
430
|
}
|
|
431
431
|
|
|
432
|
-
#
|
|
433
|
-
#
|
|
434
|
-
|
|
432
|
+
# Replace hardcoded paths/URLs with config placeholders
|
|
433
|
+
# Makes PRDs portable across machines and environments
|
|
434
|
+
fix_hardcoded_paths() {
|
|
435
435
|
local prd_file="$1"
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
#
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
echo ""
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
echo ""
|
|
469
|
-
echo "
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
print_error "Some stories are missing required fields (id, title):"
|
|
490
|
-
echo "$invalid_stories" | head -5
|
|
491
|
-
echo ""
|
|
492
|
-
echo "Fix the PRD or regenerate with: /idea 'your feature'"
|
|
493
|
-
echo ""
|
|
494
|
-
return 1
|
|
495
|
-
fi
|
|
496
|
-
|
|
497
|
-
# Check stories have passes field (initialize if missing)
|
|
498
|
-
local missing_passes
|
|
499
|
-
missing_passes=$(jq '[.stories[] | select(.passes == null)] | length' "$prd_file" 2>/dev/null || echo "0")
|
|
500
|
-
if [[ "$missing_passes" != "0" ]]; then
|
|
501
|
-
print_info "Initializing $missing_passes stories with passes=false..."
|
|
502
|
-
update_json "$prd_file" '(.stories[] | select(.passes == null) | .passes) = false'
|
|
503
|
-
fi
|
|
504
|
-
|
|
505
|
-
# Check feature name exists
|
|
506
|
-
local feature_name
|
|
507
|
-
feature_name=$(jq -r '.feature.name // empty' "$prd_file" 2>/dev/null)
|
|
508
|
-
if [[ -z "$feature_name" ]]; then
|
|
509
|
-
print_warning "PRD is missing feature name (will show as 'unnamed')"
|
|
510
|
-
fi
|
|
511
|
-
|
|
512
|
-
# Check if project has tests (from config)
|
|
513
|
-
local config="$RALPH_DIR/config.json"
|
|
514
|
-
if [[ -f "$config" ]]; then
|
|
515
|
-
local require_tests
|
|
516
|
-
require_tests=$(jq -r '.checks.requireTests // true' "$config" 2>/dev/null)
|
|
517
|
-
local test_dir
|
|
518
|
-
test_dir=$(jq -r '.tests.directory // empty' "$config" 2>/dev/null)
|
|
519
|
-
|
|
520
|
-
if [[ "$require_tests" == "true" && -z "$test_dir" ]]; then
|
|
521
|
-
echo ""
|
|
522
|
-
print_warning "No test directory configured in .ralph/config.json"
|
|
523
|
-
echo " Without tests, Ralph can only verify syntax and API responses."
|
|
524
|
-
echo " Import errors and integration issues won't be caught."
|
|
525
|
-
echo ""
|
|
526
|
-
echo " To fix: Add tests, or set in .ralph/config.json:"
|
|
527
|
-
echo " {\"tests\": {\"directory\": \"src\", \"patterns\": \"*.test.ts\"}}"
|
|
528
|
-
echo " To silence: {\"checks\": {\"requireTests\": false}}"
|
|
529
|
-
echo ""
|
|
436
|
+
local config_file="$2"
|
|
437
|
+
local modified=false
|
|
438
|
+
|
|
439
|
+
# Read current PRD
|
|
440
|
+
local prd_content
|
|
441
|
+
prd_content=$(cat "$prd_file")
|
|
442
|
+
|
|
443
|
+
# Get URLs from config (if available)
|
|
444
|
+
local backend_url="" frontend_url=""
|
|
445
|
+
if [[ -f "$config_file" ]]; then
|
|
446
|
+
backend_url=$(jq -r '.urls.backend // .api.baseUrl // empty' "$config_file" 2>/dev/null)
|
|
447
|
+
frontend_url=$(jq -r '.urls.frontend // .playwright.baseUrl // empty' "$config_file" 2>/dev/null)
|
|
448
|
+
fi
|
|
449
|
+
|
|
450
|
+
# Check for hardcoded absolute paths (non-portable)
|
|
451
|
+
if echo "$prd_content" | grep -qE '"/Users/|"/home/|"C:\\|"/var/|"/opt/' ; then
|
|
452
|
+
echo " Removing hardcoded absolute paths..."
|
|
453
|
+
# Remove common absolute path prefixes, keep relative path
|
|
454
|
+
prd_content=$(echo "$prd_content" | sed -E 's|"/Users/[^"]*/([^"]+)"|"\1"|g')
|
|
455
|
+
prd_content=$(echo "$prd_content" | sed -E 's|"/home/[^"]*/([^"]+)"|"\1"|g')
|
|
456
|
+
modified=true
|
|
457
|
+
fi
|
|
458
|
+
|
|
459
|
+
# Replace hardcoded backend URLs with {config.urls.backend}
|
|
460
|
+
if [[ -n "$backend_url" ]] && echo "$prd_content" | grep -qF "\"$backend_url" ; then
|
|
461
|
+
echo " Replacing hardcoded backend URL with {config.urls.backend}..."
|
|
462
|
+
prd_content=$(echo "$prd_content" | sed "s|$backend_url|{config.urls.backend}|g")
|
|
463
|
+
modified=true
|
|
464
|
+
fi
|
|
465
|
+
|
|
466
|
+
# Replace hardcoded frontend URLs with {config.urls.frontend}
|
|
467
|
+
if [[ -n "$frontend_url" ]] && echo "$prd_content" | grep -qF "\"$frontend_url" ; then
|
|
468
|
+
echo " Replacing hardcoded frontend URL with {config.urls.frontend}..."
|
|
469
|
+
prd_content=$(echo "$prd_content" | sed "s|$frontend_url|{config.urls.frontend}|g")
|
|
470
|
+
modified=true
|
|
471
|
+
fi
|
|
472
|
+
|
|
473
|
+
# Replace hardcoded health endpoints with config placeholder
|
|
474
|
+
if echo "$prd_content" | grep -qE '/api(/v[0-9]+)?/health|/health' ; then
|
|
475
|
+
echo " Replacing hardcoded health endpoints with {config.api.healthEndpoint}..."
|
|
476
|
+
prd_content=$(echo "$prd_content" | sed -E 's|/api/v[0-9]+/health|{config.api.healthEndpoint}|g')
|
|
477
|
+
prd_content=$(echo "$prd_content" | sed -E 's|/api/health|{config.api.healthEndpoint}|g')
|
|
478
|
+
prd_content=$(echo "$prd_content" | sed -E 's|"/health"|"{config.api.healthEndpoint}"|g')
|
|
479
|
+
modified=true
|
|
480
|
+
fi
|
|
481
|
+
|
|
482
|
+
# Replace common localhost patterns if no config URLs set
|
|
483
|
+
if [[ -z "$backend_url" ]]; then
|
|
484
|
+
# Common backend ports: 8000, 8001, 8080, 3001, 4000, 5000
|
|
485
|
+
if echo "$prd_content" | grep -qE 'http://localhost:(8000|8001|8080|3001|4000|5000)' ; then
|
|
486
|
+
echo " Replacing hardcoded localhost backend URLs with {config.urls.backend}..."
|
|
487
|
+
prd_content=$(echo "$prd_content" | sed -E 's|http://localhost:(8000|8001|8080|3001|4000|5000)|{config.urls.backend}|g')
|
|
488
|
+
modified=true
|
|
530
489
|
fi
|
|
531
490
|
fi
|
|
532
491
|
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
}
|
|
538
|
-
|
|
539
|
-
# Validate individual stories and auto-fix with Claude if needed
|
|
540
|
-
# Checks: testSteps quality, apiContract, testUrl, contextFiles, security, scale
|
|
541
|
-
validate_and_fix_stories() {
|
|
542
|
-
local prd_file="$1"
|
|
543
|
-
local needs_fix=false
|
|
544
|
-
local issues=""
|
|
545
|
-
|
|
546
|
-
echo " Validating story quality..."
|
|
547
|
-
|
|
548
|
-
# Get all story IDs
|
|
549
|
-
local story_ids
|
|
550
|
-
story_ids=$(jq -r '.stories[].id' "$prd_file" 2>/dev/null)
|
|
551
|
-
|
|
552
|
-
while IFS= read -r story_id; do
|
|
553
|
-
[[ -z "$story_id" ]] && continue
|
|
554
|
-
|
|
555
|
-
local story_issues=""
|
|
556
|
-
local story_type
|
|
557
|
-
story_type=$(jq -r --arg id "$story_id" '.stories[] | select(.id==$id) | .type // "unknown"' "$prd_file")
|
|
558
|
-
local story_title
|
|
559
|
-
story_title=$(jq -r --arg id "$story_id" '.stories[] | select(.id==$id) | .title // ""' "$prd_file")
|
|
560
|
-
|
|
561
|
-
# Check 1: testSteps quality
|
|
562
|
-
local test_steps
|
|
563
|
-
test_steps=$(jq -r --arg id "$story_id" '.stories[] | select(.id==$id) | .testSteps // [] | join(" ")' "$prd_file")
|
|
564
|
-
|
|
565
|
-
if [[ -z "$test_steps" ]]; then
|
|
566
|
-
story_issues+="no testSteps, "
|
|
567
|
-
elif [[ "$story_type" == "backend" ]]; then
|
|
568
|
-
# Backend must have curl, not just npm test/pytest
|
|
569
|
-
if ! echo "$test_steps" | grep -q "curl "; then
|
|
570
|
-
story_issues+="backend needs curl tests (npm test alone uses mocks), "
|
|
571
|
-
fi
|
|
572
|
-
elif [[ "$story_type" == "frontend" ]]; then
|
|
573
|
-
# Frontend must have tsc or playwright
|
|
574
|
-
if ! echo "$test_steps" | grep -qE "(tsc --noEmit|playwright)"; then
|
|
575
|
-
story_issues+="frontend needs tsc --noEmit or playwright tests, "
|
|
576
|
-
fi
|
|
577
|
-
fi
|
|
578
|
-
|
|
579
|
-
# Check 2: Backend needs apiContract
|
|
580
|
-
if [[ "$story_type" == "backend" ]]; then
|
|
581
|
-
local has_contract
|
|
582
|
-
has_contract=$(jq -r --arg id "$story_id" '.stories[] | select(.id==$id) | .apiContract // empty' "$prd_file")
|
|
583
|
-
if [[ -z "$has_contract" || "$has_contract" == "null" ]]; then
|
|
584
|
-
story_issues+="backend missing apiContract, "
|
|
585
|
-
fi
|
|
492
|
+
if [[ -z "$frontend_url" ]]; then
|
|
493
|
+
# Common frontend ports: 3000, 5173, 4200, 8080
|
|
494
|
+
if echo "$prd_content" | grep -qE 'http://localhost:(3000|5173|4200)' ; then
|
|
495
|
+
echo " Replacing hardcoded localhost frontend URLs with {config.urls.frontend}..."
|
|
496
|
+
prd_content=$(echo "$prd_content" | sed -E 's|http://localhost:(3000|5173|4200)|{config.urls.frontend}|g')
|
|
497
|
+
modified=true
|
|
586
498
|
fi
|
|
587
|
-
|
|
588
|
-
# Check 3: Frontend needs testUrl and contextFiles
|
|
589
|
-
if [[ "$story_type" == "frontend" ]]; then
|
|
590
|
-
local has_url
|
|
591
|
-
has_url=$(jq -r --arg id "$story_id" '.stories[] | select(.id==$id) | .testUrl // empty' "$prd_file")
|
|
592
|
-
if [[ -z "$has_url" || "$has_url" == "null" ]]; then
|
|
593
|
-
story_issues+="frontend missing testUrl, "
|
|
594
|
-
fi
|
|
595
|
-
|
|
596
|
-
local context_files
|
|
597
|
-
context_files=$(jq -r --arg id "$story_id" '.stories[] | select(.id==$id) | .contextFiles // [] | length' "$prd_file")
|
|
598
|
-
if [[ "$context_files" == "0" ]]; then
|
|
599
|
-
story_issues+="frontend missing contextFiles (idea file + styleguide), "
|
|
600
|
-
fi
|
|
601
|
-
fi
|
|
602
|
-
|
|
603
|
-
# Check 4: Auth stories need security criteria
|
|
604
|
-
if echo "$story_title" | grep -qiE "(login|auth|password|register|signup|sign.?up)"; then
|
|
605
|
-
local criteria
|
|
606
|
-
criteria=$(jq -r --arg id "$story_id" '.stories[] | select(.id==$id) | .acceptanceCriteria // [] | join(" ")' "$prd_file")
|
|
607
|
-
if ! echo "$criteria" | grep -qiE "(hash|bcrypt|sanitiz|inject|rate.?limit)"; then
|
|
608
|
-
story_issues+="auth story missing security criteria (password hashing/rate limiting), "
|
|
609
|
-
fi
|
|
610
|
-
fi
|
|
611
|
-
|
|
612
|
-
# Check 5: List endpoints need scale criteria
|
|
613
|
-
# Note: "search" excluded - search endpoints often return single/filtered results
|
|
614
|
-
if echo "$story_title" | grep -qiE "(list|get all|fetch all|index)"; then
|
|
615
|
-
local criteria
|
|
616
|
-
criteria=$(jq -r --arg id "$story_id" '.stories[] | select(.id==$id) | .acceptanceCriteria // [] | join(" ")' "$prd_file")
|
|
617
|
-
if ! echo "$criteria" | grep -qiE "(pagina|limit|page=|per.?page)"; then
|
|
618
|
-
story_issues+="list endpoint missing pagination criteria, "
|
|
619
|
-
fi
|
|
620
|
-
fi
|
|
621
|
-
|
|
622
|
-
# Report issues for this story
|
|
623
|
-
if [[ -n "$story_issues" ]]; then
|
|
624
|
-
needs_fix=true
|
|
625
|
-
issues+="$story_id: ${story_issues%%, }
|
|
626
|
-
"
|
|
627
|
-
fi
|
|
628
|
-
done <<< "$story_ids"
|
|
629
|
-
|
|
630
|
-
# If issues found, attempt to fix with Claude
|
|
631
|
-
if [[ "$needs_fix" == "true" ]]; then
|
|
632
|
-
print_warning "Story quality issues found:"
|
|
633
|
-
echo "$issues" | while IFS= read -r line; do
|
|
634
|
-
[[ -n "$line" ]] && echo " $line"
|
|
635
|
-
done
|
|
636
|
-
echo ""
|
|
637
|
-
|
|
638
|
-
# Check if Claude is available for auto-fix
|
|
639
|
-
if command -v claude &>/dev/null; then
|
|
640
|
-
echo " Attempting auto-fix with Claude..."
|
|
641
|
-
fix_stories_with_claude "$prd_file" "$issues"
|
|
642
|
-
else
|
|
643
|
-
echo " Claude CLI not found - fix these issues manually or regenerate PRD."
|
|
644
|
-
echo ""
|
|
645
|
-
return 1
|
|
646
|
-
fi
|
|
647
|
-
else
|
|
648
|
-
print_success "All stories validated"
|
|
649
499
|
fi
|
|
650
500
|
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
fix_stories_with_claude() {
|
|
656
|
-
local prd_file="$1"
|
|
657
|
-
local issues="$2"
|
|
658
|
-
|
|
659
|
-
local fix_prompt="Fix the following issues in this PRD. Output the COMPLETE fixed prd.json.
|
|
660
|
-
|
|
661
|
-
ISSUES FOUND:
|
|
662
|
-
$issues
|
|
663
|
-
|
|
664
|
-
RULES FOR FIXING:
|
|
665
|
-
1. Backend stories MUST have testSteps with curl commands that hit real endpoints
|
|
666
|
-
Example: curl -s -X POST {config.urls.backend}/api/users -d '...' | jq -e '.id'
|
|
667
|
-
2. Backend stories MUST have apiContract with endpoint, request, response
|
|
668
|
-
3. Frontend stories MUST have testUrl set to {config.urls.frontend}/page
|
|
669
|
-
4. Frontend stories MUST have contextFiles array (include idea file path from originalContext)
|
|
670
|
-
5. Auth stories MUST have security acceptanceCriteria:
|
|
671
|
-
- Passwords hashed with bcrypt (cost 10+)
|
|
672
|
-
- Passwords NEVER in API responses
|
|
673
|
-
- Rate limiting on login attempts
|
|
674
|
-
6. List endpoints MUST have pagination acceptanceCriteria:
|
|
675
|
-
- Returns paginated results (max 100 per page)
|
|
676
|
-
- Accepts ?page=N&limit=N query params
|
|
677
|
-
|
|
678
|
-
CURRENT PRD:
|
|
679
|
-
$(cat "$prd_file")
|
|
680
|
-
|
|
681
|
-
Output ONLY the fixed JSON, no explanation. Start with { and end with }."
|
|
682
|
-
|
|
683
|
-
local raw_response
|
|
684
|
-
raw_response=$(echo "$fix_prompt" | run_with_timeout "$CODE_REVIEW_TIMEOUT_SECONDS" claude -p 2>/dev/null)
|
|
685
|
-
|
|
686
|
-
# Extract JSON from response (Claude sometimes adds text before/after)
|
|
687
|
-
local fixed_prd
|
|
688
|
-
fixed_prd=$(echo "$raw_response" | sed -n '/^[[:space:]]*{/,/^[[:space:]]*}[[:space:]]*$/p' | head -1000)
|
|
689
|
-
|
|
690
|
-
# If sed extraction failed, try the raw response
|
|
691
|
-
if [[ -z "$fixed_prd" ]]; then
|
|
692
|
-
fixed_prd="$raw_response"
|
|
693
|
-
fi
|
|
694
|
-
|
|
695
|
-
# Validate the response is valid JSON with required structure
|
|
696
|
-
if echo "$fixed_prd" | jq -e '.stories' >/dev/null 2>&1; then
|
|
697
|
-
# Timestamped backup (preserves history across multiple fixes)
|
|
698
|
-
local backup_file="${prd_file}.$(date +%Y%m%d-%H%M%S).bak"
|
|
699
|
-
cp "$prd_file" "$backup_file"
|
|
700
|
-
|
|
701
|
-
# Write fixed PRD
|
|
702
|
-
echo "$fixed_prd" > "$prd_file"
|
|
703
|
-
print_success "PRD auto-fixed (backup at $backup_file)"
|
|
704
|
-
|
|
705
|
-
# Re-validate to confirm fixes
|
|
706
|
-
echo " Re-validating..."
|
|
707
|
-
local remaining_issues
|
|
708
|
-
remaining_issues=$(validate_stories_quick "$prd_file")
|
|
709
|
-
if [[ -n "$remaining_issues" ]]; then
|
|
710
|
-
print_warning "Some issues remain - may need manual fixes:"
|
|
711
|
-
echo "$remaining_issues" | tr ',' '\n' | while IFS= read -r line; do
|
|
712
|
-
[[ -n "$line" ]] && echo " $line"
|
|
713
|
-
done
|
|
714
|
-
else
|
|
715
|
-
print_success "All issues resolved"
|
|
716
|
-
fi
|
|
717
|
-
else
|
|
718
|
-
print_error "Claude returned invalid JSON - fix manually"
|
|
719
|
-
echo " Response preview: $(echo "$raw_response" | head -3)"
|
|
720
|
-
return 1
|
|
501
|
+
# Write back if modified
|
|
502
|
+
if [[ "$modified" == "true" ]]; then
|
|
503
|
+
echo "$prd_content" > "$prd_file"
|
|
504
|
+
print_success "Paths updated to use config placeholders"
|
|
721
505
|
fi
|
|
722
506
|
}
|
|
723
507
|
|
|
724
|
-
# Quick validation without auto-fix (for re-checking after fix)
|
|
725
|
-
# Checks all the same things as validate_and_fix_stories() but returns issues string
|
|
726
|
-
validate_stories_quick() {
|
|
727
|
-
local prd_file="$1"
|
|
728
|
-
local issues=""
|
|
729
|
-
|
|
730
|
-
local story_ids
|
|
731
|
-
story_ids=$(jq -r '.stories[].id' "$prd_file" 2>/dev/null)
|
|
732
|
-
|
|
733
|
-
while IFS= read -r story_id; do
|
|
734
|
-
[[ -z "$story_id" ]] && continue
|
|
735
|
-
|
|
736
|
-
local story_type
|
|
737
|
-
story_type=$(jq -r --arg id "$story_id" '.stories[] | select(.id==$id) | .type // "unknown"' "$prd_file")
|
|
738
|
-
local story_title
|
|
739
|
-
story_title=$(jq -r --arg id "$story_id" '.stories[] | select(.id==$id) | .title // ""' "$prd_file")
|
|
740
|
-
local test_steps
|
|
741
|
-
test_steps=$(jq -r --arg id "$story_id" '.stories[] | select(.id==$id) | .testSteps // [] | join(" ")' "$prd_file")
|
|
742
|
-
|
|
743
|
-
# Check 1: testSteps quality
|
|
744
|
-
if [[ "$story_type" == "backend" ]] && ! echo "$test_steps" | grep -q "curl "; then
|
|
745
|
-
issues+="$story_id: missing curl tests, "
|
|
746
|
-
fi
|
|
747
|
-
if [[ "$story_type" == "frontend" ]] && ! echo "$test_steps" | grep -qE "(tsc --noEmit|playwright)"; then
|
|
748
|
-
issues+="$story_id: missing tsc/playwright tests, "
|
|
749
|
-
fi
|
|
750
|
-
|
|
751
|
-
# Check 2: Backend needs apiContract
|
|
752
|
-
if [[ "$story_type" == "backend" ]]; then
|
|
753
|
-
local has_contract
|
|
754
|
-
has_contract=$(jq -r --arg id "$story_id" '.stories[] | select(.id==$id) | .apiContract // empty' "$prd_file")
|
|
755
|
-
if [[ -z "$has_contract" || "$has_contract" == "null" ]]; then
|
|
756
|
-
issues+="$story_id: missing apiContract, "
|
|
757
|
-
fi
|
|
758
|
-
fi
|
|
759
|
-
|
|
760
|
-
# Check 3: Frontend needs testUrl and contextFiles
|
|
761
|
-
if [[ "$story_type" == "frontend" ]]; then
|
|
762
|
-
local has_url
|
|
763
|
-
has_url=$(jq -r --arg id "$story_id" '.stories[] | select(.id==$id) | .testUrl // empty' "$prd_file")
|
|
764
|
-
[[ -z "$has_url" || "$has_url" == "null" ]] && issues+="$story_id: missing testUrl, "
|
|
765
|
-
|
|
766
|
-
local context_files
|
|
767
|
-
context_files=$(jq -r --arg id "$story_id" '.stories[] | select(.id==$id) | .contextFiles // [] | length' "$prd_file")
|
|
768
|
-
[[ "$context_files" == "0" ]] && issues+="$story_id: missing contextFiles, "
|
|
769
|
-
fi
|
|
770
|
-
|
|
771
|
-
# Check 4: Auth stories need security criteria
|
|
772
|
-
if echo "$story_title" | grep -qiE "(login|auth|password|register|signup|sign.?up)"; then
|
|
773
|
-
local criteria
|
|
774
|
-
criteria=$(jq -r --arg id "$story_id" '.stories[] | select(.id==$id) | .acceptanceCriteria // [] | join(" ")' "$prd_file")
|
|
775
|
-
if ! echo "$criteria" | grep -qiE "(hash|bcrypt|sanitiz|inject|rate.?limit)"; then
|
|
776
|
-
issues+="$story_id: missing security criteria, "
|
|
777
|
-
fi
|
|
778
|
-
fi
|
|
779
|
-
|
|
780
|
-
# Check 5: List endpoints need scale criteria
|
|
781
|
-
if echo "$story_title" | grep -qiE "(list|get all|fetch all|index)"; then
|
|
782
|
-
local criteria
|
|
783
|
-
criteria=$(jq -r --arg id "$story_id" '.stories[] | select(.id==$id) | .acceptanceCriteria // [] | join(" ")' "$prd_file")
|
|
784
|
-
if ! echo "$criteria" | grep -qiE "(pagina|limit|page=|per.?page)"; then
|
|
785
|
-
issues+="$story_id: missing pagination criteria, "
|
|
786
|
-
fi
|
|
787
|
-
fi
|
|
788
|
-
done <<< "$story_ids"
|
|
789
|
-
|
|
790
|
-
echo "$issues"
|
|
791
|
-
}
|
|
792
|
-
|
|
793
508
|
# Detect Python runner (uv, poetry, pipenv, or plain python)
|
|
794
509
|
detect_python_runner() {
|
|
795
510
|
local search_dir="${1:-.}"
|
package/templates/config/go.json
CHANGED
package/ralph/verify.sh
DELETED
|
@@ -1,106 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env bash
|
|
2
|
-
# shellcheck shell=bash
|
|
3
|
-
# verify.sh - Lightweight verification for ralph
|
|
4
|
-
#
|
|
5
|
-
# Philosophy: Claude verifies its own work using MCP browser tools.
|
|
6
|
-
# Ralph just runs lint, tests, and testSteps from the PRD.
|
|
7
|
-
|
|
8
|
-
# Source verification modules
|
|
9
|
-
VERIFY_DIR="${RALPH_LIB:-$(dirname "${BASH_SOURCE[0]}")}"
|
|
10
|
-
source "$VERIFY_DIR/verify/lint.sh"
|
|
11
|
-
source "$VERIFY_DIR/verify/tests.sh"
|
|
12
|
-
source "$VERIFY_DIR/verify/api.sh"
|
|
13
|
-
|
|
14
|
-
run_verification() {
|
|
15
|
-
local story="$1"
|
|
16
|
-
|
|
17
|
-
echo ""
|
|
18
|
-
print_info "=== Verification: $story ==="
|
|
19
|
-
echo ""
|
|
20
|
-
|
|
21
|
-
# Get story type for targeted checks
|
|
22
|
-
local story_type
|
|
23
|
-
story_type=$(jq -r --arg id "$story" '.stories[] | select(.id==$id) | .type // "general"' "$RALPH_DIR/prd.json" 2>/dev/null)
|
|
24
|
-
export RALPH_STORY_TYPE="$story_type"
|
|
25
|
-
|
|
26
|
-
local failed=0
|
|
27
|
-
|
|
28
|
-
# ========================================
|
|
29
|
-
# STEP 1: Run lint checks
|
|
30
|
-
# ========================================
|
|
31
|
-
echo " [1/5] Running lint checks..."
|
|
32
|
-
if ! run_configured_checks "$story_type"; then
|
|
33
|
-
failed=1
|
|
34
|
-
fi
|
|
35
|
-
|
|
36
|
-
# ========================================
|
|
37
|
-
# STEP 2: Verify tests exist + run them
|
|
38
|
-
# ========================================
|
|
39
|
-
if [[ $failed -eq 0 ]]; then
|
|
40
|
-
echo ""
|
|
41
|
-
echo " [2/5] Running tests..."
|
|
42
|
-
# First check that test files exist for new code
|
|
43
|
-
if ! verify_test_files_exist; then
|
|
44
|
-
failed=1
|
|
45
|
-
elif ! run_unit_tests; then
|
|
46
|
-
failed=1
|
|
47
|
-
fi
|
|
48
|
-
fi
|
|
49
|
-
|
|
50
|
-
# ========================================
|
|
51
|
-
# STEP 3: Run PRD test steps
|
|
52
|
-
# ========================================
|
|
53
|
-
if [[ $failed -eq 0 ]]; then
|
|
54
|
-
echo ""
|
|
55
|
-
echo " [3/5] Running PRD test steps..."
|
|
56
|
-
if ! verify_prd_criteria "$story"; then
|
|
57
|
-
failed=1
|
|
58
|
-
fi
|
|
59
|
-
fi
|
|
60
|
-
|
|
61
|
-
# ========================================
|
|
62
|
-
# STEP 4: API smoke test (if configured)
|
|
63
|
-
# ========================================
|
|
64
|
-
if [[ $failed -eq 0 ]]; then
|
|
65
|
-
if ! run_api_smoke_test "$story"; then
|
|
66
|
-
failed=1
|
|
67
|
-
fi
|
|
68
|
-
fi
|
|
69
|
-
|
|
70
|
-
# ========================================
|
|
71
|
-
# STEP 5: Frontend smoke test (if configured)
|
|
72
|
-
# ========================================
|
|
73
|
-
if [[ $failed -eq 0 ]]; then
|
|
74
|
-
if ! run_frontend_smoke_test "$story"; then
|
|
75
|
-
failed=1
|
|
76
|
-
fi
|
|
77
|
-
fi
|
|
78
|
-
|
|
79
|
-
# ========================================
|
|
80
|
-
# Final result
|
|
81
|
-
# ========================================
|
|
82
|
-
echo ""
|
|
83
|
-
if [[ $failed -eq 0 ]]; then
|
|
84
|
-
print_success "=== All verification passed ==="
|
|
85
|
-
return 0
|
|
86
|
-
else
|
|
87
|
-
print_error "=== Verification failed ==="
|
|
88
|
-
save_failure_context "$story"
|
|
89
|
-
return 1
|
|
90
|
-
fi
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
# Save failure context for next iteration
|
|
94
|
-
save_failure_context() {
|
|
95
|
-
local story="$1"
|
|
96
|
-
local context_file="$RALPH_DIR/last_failure.txt"
|
|
97
|
-
|
|
98
|
-
{
|
|
99
|
-
echo "=== Verification failed for $story ==="
|
|
100
|
-
echo ""
|
|
101
|
-
# Just include the verification output - Claude can figure out what went wrong
|
|
102
|
-
if [[ -f "$RALPH_DIR/last_verification.log" ]]; then
|
|
103
|
-
tail -100 "$RALPH_DIR/last_verification.log"
|
|
104
|
-
fi
|
|
105
|
-
} > "$context_file"
|
|
106
|
-
}
|