agentic-loop 3.13.0 → 3.14.2

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.
Files changed (57) hide show
  1. package/.claude/skills/idea/SKILL.md +56 -0
  2. package/.claude/skills/loopgram/SKILL.md +19 -0
  3. package/.claude/skills/prd/SKILL.md +2 -0
  4. package/README.md +1 -0
  5. package/bin/ralph.sh +17 -11
  6. package/dist/loopgram/claude.d.ts +18 -0
  7. package/dist/loopgram/claude.d.ts.map +1 -0
  8. package/dist/loopgram/claude.js +89 -0
  9. package/dist/loopgram/claude.js.map +1 -0
  10. package/dist/loopgram/context-search.d.ts +26 -0
  11. package/dist/loopgram/context-search.d.ts.map +1 -0
  12. package/dist/loopgram/context-search.js +175 -0
  13. package/dist/loopgram/context-search.js.map +1 -0
  14. package/dist/loopgram/conversation.d.ts +39 -0
  15. package/dist/loopgram/conversation.d.ts.map +1 -0
  16. package/dist/loopgram/conversation.js +158 -0
  17. package/dist/loopgram/conversation.js.map +1 -0
  18. package/dist/loopgram/index.d.ts +3 -0
  19. package/dist/loopgram/index.d.ts.map +1 -0
  20. package/dist/loopgram/index.js +246 -0
  21. package/dist/loopgram/index.js.map +1 -0
  22. package/dist/loopgram/loop-monitor.d.ts +16 -0
  23. package/dist/loopgram/loop-monitor.d.ts.map +1 -0
  24. package/dist/loopgram/loop-monitor.js +149 -0
  25. package/dist/loopgram/loop-monitor.js.map +1 -0
  26. package/dist/loopgram/loop-runner.d.ts +28 -0
  27. package/dist/loopgram/loop-runner.d.ts.map +1 -0
  28. package/dist/loopgram/loop-runner.js +157 -0
  29. package/dist/loopgram/loop-runner.js.map +1 -0
  30. package/dist/loopgram/prd-generator.d.ts +37 -0
  31. package/dist/loopgram/prd-generator.d.ts.map +1 -0
  32. package/dist/loopgram/prd-generator.js +134 -0
  33. package/dist/loopgram/prd-generator.js.map +1 -0
  34. package/dist/loopgram/saver.d.ts +9 -0
  35. package/dist/loopgram/saver.d.ts.map +1 -0
  36. package/dist/loopgram/saver.js +35 -0
  37. package/dist/loopgram/saver.js.map +1 -0
  38. package/dist/loopgram/types.d.ts +37 -0
  39. package/dist/loopgram/types.d.ts.map +1 -0
  40. package/dist/loopgram/types.js +5 -0
  41. package/dist/loopgram/types.js.map +1 -0
  42. package/package.json +6 -2
  43. package/ralph/hooks/common.sh +89 -0
  44. package/ralph/hooks/warn-debug.sh +14 -32
  45. package/ralph/hooks/warn-empty-catch.sh +13 -29
  46. package/ralph/hooks/warn-secrets.sh +19 -37
  47. package/ralph/hooks/warn-urls.sh +17 -33
  48. package/ralph/loop.sh +5 -2
  49. package/ralph/prd-check.sh +35 -8
  50. package/ralph/setup/quick-setup.sh +25 -12
  51. package/ralph/setup/ui.sh +0 -42
  52. package/ralph/setup.sh +71 -46
  53. package/ralph/utils.sh +167 -31
  54. package/templates/config/fastmcp.json +6 -1
  55. package/templates/config/fullstack.json +8 -0
  56. package/templates/config/node.json +8 -0
  57. package/templates/config/python.json +8 -0
@@ -1,63 +1,47 @@
1
1
  #!/usr/bin/env bash
2
+ # shellcheck shell=bash
2
3
  # warn-empty-catch.sh - Warn about empty catch blocks that silently swallow errors
3
4
  # Hook: PostToolUse matcher: "Edit|Write"
4
5
 
5
- set -euo pipefail
6
+ source "$(dirname "$0")/common.sh"
6
7
 
7
- INPUT=$(cat)
8
- TOOL_NAME=$(echo "$INPUT" | jq -r '.tool_name // ""')
9
- FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path // .tool_input.path // ""')
8
+ parse_hook_input
10
9
 
11
10
  # Only check code files
12
- case "$FILE_PATH" in
13
- *.ts|*.tsx|*.js|*.jsx|*.py)
14
- ;;
15
- *)
16
- echo '{"continue": true}'
17
- exit 0
18
- ;;
19
- esac
20
-
21
- # Get the content that was written
22
- NEW_CONTENT=""
23
- if [[ "$TOOL_NAME" == "Write" ]]; then
24
- NEW_CONTENT=$(echo "$INPUT" | jq -r '.tool_input.content // ""')
25
- elif [[ "$TOOL_NAME" == "Edit" ]]; then
26
- NEW_CONTENT=$(echo "$INPUT" | jq -r '.tool_input.new_string // ""')
11
+ if ! is_code_file "ts,tsx,js,jsx,py"; then
12
+ hook_allow
13
+ exit 0
27
14
  fi
28
15
 
29
16
  WARNINGS=""
30
17
 
31
18
  # JavaScript/TypeScript: catch (e) { } or catch { }
32
- if echo "$NEW_CONTENT" | grep -qE 'catch\s*\([^)]*\)\s*\{\s*\}'; then
19
+ if echo "$NEW_CONTENT" | grep -qE 'catch[[:space:]]*\([^)]*\)[[:space:]]*\{[[:space:]]*\}'; then
33
20
  WARNINGS="⚠️ Empty catch block detected. Handle the error or add a comment explaining why it's ignored."
34
21
  fi
35
22
 
36
23
  # Also check for catch with only a comment (no actual handling)
37
- if echo "$NEW_CONTENT" | grep -qE 'catch\s*\([^)]*\)\s*\{\s*(//[^\n]*)?\s*\}'; then
24
+ if echo "$NEW_CONTENT" | grep -qE 'catch[[:space:]]*\([^)]*\)[[:space:]]*\{[[:space:]]*(//[^}]*)?\}'; then
38
25
  if [[ -z "$WARNINGS" ]]; then
39
26
  WARNINGS="⚠️ Catch block with no error handling. Consider logging or rethrowing the error."
40
27
  fi
41
28
  fi
42
29
 
43
30
  # Python: except: pass or except Exception: pass
44
- if echo "$NEW_CONTENT" | grep -qE 'except.*:\s*pass\s*$'; then
31
+ if echo "$NEW_CONTENT" | grep -qE 'except.*:[[:space:]]*pass[[:space:]]*$'; then
45
32
  WARNINGS="⚠️ Empty except block (pass). Handle the exception or add logging."
46
33
  fi
47
34
 
48
35
  # Python: bare except with just pass on next line
49
- if echo "$NEW_CONTENT" | grep -qE 'except.*:\s*$' && echo "$NEW_CONTENT" | grep -qE '^\s*pass\s*$'; then
36
+ if echo "$NEW_CONTENT" | grep -qE 'except.*:[[:space:]]*$' && echo "$NEW_CONTENT" | grep -qE '^[[:space:]]*pass[[:space:]]*$'; then
50
37
  if [[ -z "$WARNINGS" ]]; then
51
38
  WARNINGS="⚠️ Except block with only 'pass'. Consider logging or reraising the exception."
52
39
  fi
53
40
  fi
54
41
 
55
- # Block if empty catch detected
42
+ # Block if empty catch detected (this hook blocks, doesn't just warn)
56
43
  if [[ -n "$WARNINGS" ]]; then
57
- jq -n --arg warn "$WARNINGS" '{
58
- "continue": false,
59
- "message": $warn
60
- }'
44
+ hook_block "$WARNINGS"
61
45
  else
62
- echo '{"continue": true}'
46
+ hook_allow
63
47
  fi
@@ -1,31 +1,18 @@
1
1
  #!/usr/bin/env bash
2
+ # shellcheck shell=bash
2
3
  # warn-secrets.sh - Warn about potential secrets in written code
3
4
  # Hook: PostToolUse matcher: "Edit|Write"
4
5
  #
5
6
  # Mirrors the pre-commit check-secrets patterns for consistency
6
7
 
7
- set -euo pipefail
8
+ source "$(dirname "$0")/common.sh"
8
9
 
9
- INPUT=$(cat)
10
- TOOL_NAME=$(echo "$INPUT" | jq -r '.tool_name // ""')
11
- FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path // .tool_input.path // ""')
10
+ parse_hook_input
12
11
 
13
12
  # Only check code files
14
- case "$FILE_PATH" in
15
- *.ts|*.tsx|*.js|*.jsx|*.py|*.json|*.yaml|*.yml|*.env*)
16
- ;;
17
- *)
18
- echo '{"continue": true}'
19
- exit 0
20
- ;;
21
- esac
22
-
23
- # Get the content that was written
24
- NEW_CONTENT=""
25
- if [[ "$TOOL_NAME" == "Write" ]]; then
26
- NEW_CONTENT=$(echo "$INPUT" | jq -r '.tool_input.content // ""')
27
- elif [[ "$TOOL_NAME" == "Edit" ]]; then
28
- NEW_CONTENT=$(echo "$INPUT" | jq -r '.tool_input.new_string // ""')
13
+ if ! is_code_file "ts,tsx,js,jsx,py,json,yaml,yml,env,env.local,env.development,env.production"; then
14
+ hook_allow
15
+ exit 0
29
16
  fi
30
17
 
31
18
  WARNINGS=""
@@ -37,53 +24,48 @@ fi
37
24
 
38
25
  # Stripe keys (sk_live_* or sk_test_*)
39
26
  if echo "$NEW_CONTENT" | grep -qE 'sk_(live|test)_[0-9a-zA-Z]{24,}'; then
40
- WARNINGS="${WARNINGS}\n🚨 SECURITY: Stripe API key detected! Use environment variables."
27
+ WARNINGS="${WARNINGS}${WARNINGS:+\\n}🚨 SECURITY: Stripe API key detected! Use environment variables."
41
28
  fi
42
29
 
43
30
  # GitHub tokens (ghp_, gho_, ghu_, ghs_, ghr_)
44
31
  if echo "$NEW_CONTENT" | grep -qE 'gh[pousr]_[A-Za-z0-9_]{36,}'; then
45
- WARNINGS="${WARNINGS}\n🚨 SECURITY: GitHub token detected! Use environment variables."
32
+ WARNINGS="${WARNINGS}${WARNINGS:+\\n}🚨 SECURITY: GitHub token detected! Use environment variables."
46
33
  fi
47
34
 
48
35
  # Slack tokens (xoxb-, xoxp-, xoxa-, xoxr-, xoxs-)
49
36
  if echo "$NEW_CONTENT" | grep -qE 'xox[baprs]-[0-9]{10,}-[0-9a-zA-Z]{24,}'; then
50
- WARNINGS="${WARNINGS}\n🚨 SECURITY: Slack token detected! Use environment variables."
37
+ WARNINGS="${WARNINGS}${WARNINGS:+\\n}🚨 SECURITY: Slack token detected! Use environment variables."
51
38
  fi
52
39
 
53
40
  # SendGrid keys (SG.*)
54
41
  if echo "$NEW_CONTENT" | grep -qE 'SG\.[a-zA-Z0-9_-]{22}\.[a-zA-Z0-9_-]{43}'; then
55
- WARNINGS="${WARNINGS}\n🚨 SECURITY: SendGrid API key detected! Use environment variables."
42
+ WARNINGS="${WARNINGS}${WARNINGS:+\\n}🚨 SECURITY: SendGrid API key detected! Use environment variables."
56
43
  fi
57
44
 
58
45
  # Private keys
59
- if echo "$NEW_CONTENT" | grep -qE '-----BEGIN (RSA |EC |DSA |OPENSSH )?PRIVATE KEY-----'; then
60
- WARNINGS="${WARNINGS}\n🚨 SECURITY: Private key detected! Never commit private keys."
46
+ if echo "$NEW_CONTENT" | grep -q -- '-----BEGIN.*PRIVATE KEY-----'; then
47
+ WARNINGS="${WARNINGS}${WARNINGS:+\\n}🚨 SECURITY: Private key detected! Never commit private keys."
61
48
  fi
62
49
 
63
50
  # Generic API key patterns (api_key = "...", apikey: "...", etc.)
64
- if echo "$NEW_CONTENT" | grep -qiE '(api[_-]?key|api[_-]?secret)\s*[:=]\s*['"'"'"][a-zA-Z0-9_\-]{20,}['"'"'"]'; then
51
+ if echo "$NEW_CONTENT" | grep -qiE '(api[_-]?key|api[_-]?secret)[[:space:]]*[:=][[:space:]]*['"'"'"][a-zA-Z0-9_-]{20,}['"'"'"]'; then
65
52
  # Check it's not a placeholder
66
53
  if ! echo "$NEW_CONTENT" | grep -qiE '(example|placeholder|your[_-]?key|xxx|test|dummy|fake|sample|demo)'; then
67
- WARNINGS="${WARNINGS}\n⚠️ Possible hardcoded API key - use environment variables."
54
+ WARNINGS="${WARNINGS}${WARNINGS:+\\n}⚠️ Possible hardcoded API key - use environment variables."
68
55
  fi
69
56
  fi
70
57
 
71
58
  # Generic secrets (password = "...", token = "...", etc.)
72
- if echo "$NEW_CONTENT" | grep -qiE '(password|passwd|pwd|secret|token)\s*[:=]\s*['"'"'"][^'"'"'"]{8,}['"'"'"]'; then
59
+ if echo "$NEW_CONTENT" | grep -qiE '(password|passwd|pwd|secret|token)[[:space:]]*[:=][[:space:]]*['"'"'"][^'"'"'"]{8,}['"'"'"]'; then
73
60
  # Check it's not a placeholder or type annotation
74
61
  if ! echo "$NEW_CONTENT" | grep -qiE '(example|placeholder|xxx|test|dummy|type|interface|password:)'; then
75
- WARNINGS="${WARNINGS}\n⚠️ Possible hardcoded secret - use environment variables."
62
+ WARNINGS="${WARNINGS}${WARNINGS:+\\n}⚠️ Possible hardcoded secret - use environment variables."
76
63
  fi
77
64
  fi
78
65
 
79
- # Output warning as additional context (non-blocking)
66
+ # Output warning or allow
80
67
  if [[ -n "$WARNINGS" ]]; then
81
- jq -n --arg warn "$WARNINGS" '{
82
- "continue": true,
83
- "hookSpecificOutput": {
84
- "additionalContext": $warn
85
- }
86
- }'
68
+ hook_warn "$WARNINGS"
87
69
  else
88
- echo '{"continue": true}'
70
+ hook_allow
89
71
  fi
@@ -1,35 +1,24 @@
1
1
  #!/usr/bin/env bash
2
+ # shellcheck shell=bash
2
3
  # warn-urls.sh - Warn about hardcoded URLs in written code
3
4
  # Hook: PostToolUse matcher: "Edit|Write"
4
5
  #
5
6
  # Mirrors the pre-commit check-hardcoded-urls patterns for consistency
6
7
 
7
- set -euo pipefail
8
+ source "$(dirname "$0")/common.sh"
8
9
 
9
- INPUT=$(cat)
10
- TOOL_NAME=$(echo "$INPUT" | jq -r '.tool_name // ""')
11
- FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path // .tool_input.path // ""')
10
+ parse_hook_input
12
11
 
13
- # Only check code files (skip test files)
14
- case "$FILE_PATH" in
15
- *.test.*|*.spec.*|*/__tests__/*|*/test/*|*/fixtures/*)
16
- echo '{"continue": true}'
17
- exit 0
18
- ;;
19
- *.ts|*.tsx|*.js|*.jsx|*.py)
20
- ;;
21
- *)
22
- echo '{"continue": true}'
23
- exit 0
24
- ;;
25
- esac
12
+ # Skip test files
13
+ if is_test_file; then
14
+ hook_allow
15
+ exit 0
16
+ fi
26
17
 
27
- # Get the content that was written
28
- NEW_CONTENT=""
29
- if [[ "$TOOL_NAME" == "Write" ]]; then
30
- NEW_CONTENT=$(echo "$INPUT" | jq -r '.tool_input.content // ""')
31
- elif [[ "$TOOL_NAME" == "Edit" ]]; then
32
- NEW_CONTENT=$(echo "$INPUT" | jq -r '.tool_input.new_string // ""')
18
+ # Only check code files
19
+ if ! is_code_file "ts,tsx,js,jsx,py"; then
20
+ hook_allow
21
+ exit 0
33
22
  fi
34
23
 
35
24
  WARNINGS=""
@@ -45,7 +34,7 @@ fi
45
34
  # Check for 127.0.0.1 URLs
46
35
  if echo "$NEW_CONTENT" | grep -qE 'https?://127\.0\.0\.1(:[0-9]+)?'; then
47
36
  if ! echo "$NEW_CONTENT" | grep -qE '(\|\||\?\?|default|fallback).*127\.0\.0\.1'; then
48
- WARNINGS="${WARNINGS}\n⚠️ Hardcoded 127.0.0.1 URL detected - use environment variable"
37
+ WARNINGS="${WARNINGS}${WARNINGS:+\\n}⚠️ Hardcoded 127.0.0.1 URL detected - use environment variable"
49
38
  fi
50
39
  fi
51
40
 
@@ -60,18 +49,13 @@ if [[ -n "$PROD_URLS" ]]; then
60
49
  PROD_URLS=$(echo "$PROD_URLS" | grep -v -E '(example|placeholder|test|mock)' || true)
61
50
  if [[ -n "$PROD_URLS" ]]; then
62
51
  FIRST_URL=$(echo "$PROD_URLS" | head -1)
63
- WARNINGS="${WARNINGS}\n⚠️ Hardcoded URL ($FIRST_URL) - consider using environment variable"
52
+ WARNINGS="${WARNINGS}${WARNINGS:+\\n}⚠️ Hardcoded URL ($FIRST_URL) - consider using environment variable"
64
53
  fi
65
54
  fi
66
55
 
67
- # Output warning as additional context (non-blocking)
56
+ # Output warning or allow
68
57
  if [[ -n "$WARNINGS" ]]; then
69
- jq -n --arg warn "$WARNINGS" '{
70
- "continue": true,
71
- "hookSpecificOutput": {
72
- "additionalContext": $warn
73
- }
74
- }'
58
+ hook_warn "$WARNINGS"
75
59
  else
76
- echo '{"continue": true}'
60
+ hook_allow
77
61
  fi
package/ralph/loop.sh CHANGED
@@ -478,10 +478,13 @@ run_loop() {
478
478
  rm -f "$RALPH_DIR/last_failure.txt"
479
479
  rm -f "$RALPH_DIR/last_verification.log"
480
480
 
481
+ # Get story title for commit message and completion display
482
+ local title
483
+ title=$(jq -r --arg id "$story" '.stories[] | select(.id==$id) | .title' "$RALPH_DIR/prd.json")
484
+
481
485
  # Auto-commit if git is available
482
486
  if command -v git &>/dev/null && [[ -d ".git" ]]; then
483
- local title commit_log commit_success
484
- title=$(jq -r --arg id "$story" '.stories[] | select(.id==$id) | .title' "$RALPH_DIR/prd.json")
487
+ local commit_log commit_success
485
488
  commit_log=$(mktemp)
486
489
  commit_success=false
487
490
 
@@ -282,7 +282,7 @@ _validate_and_fix_stories() {
282
282
 
283
283
  # Issue counters (bash 3.2 compatible - no associative arrays)
284
284
  local cnt_no_tests=0 cnt_backend_curl=0 cnt_backend_contract=0
285
- local cnt_frontend_tsc=0 cnt_frontend_url=0 cnt_frontend_context=0
285
+ local cnt_frontend_tsc=0 cnt_frontend_url=0 cnt_frontend_context=0 cnt_frontend_mcp=0
286
286
  local cnt_auth_security=0 cnt_list_pagination=0 cnt_prose_steps=0
287
287
  local cnt_migration_prereq=0 cnt_naming_convention=0 cnt_bare_pytest=0
288
288
 
@@ -355,7 +355,7 @@ _validate_and_fix_stories() {
355
355
  fi
356
356
  fi
357
357
 
358
- # Check 3: Frontend needs testUrl and contextFiles
358
+ # Check 3: Frontend needs testUrl, contextFiles, and mcp
359
359
  if [[ "$story_type" == "frontend" ]]; then
360
360
  local has_url
361
361
  has_url=$(jq -r --arg id "$story_id" '.stories[] | select(.id==$id) | .testUrl // empty' "$prd_file")
@@ -370,6 +370,14 @@ _validate_and_fix_stories() {
370
370
  story_issues+="missing contextFiles, "
371
371
  cnt_frontend_context=$((cnt_frontend_context + 1))
372
372
  fi
373
+
374
+ # Frontend must have mcp tools for browser verification
375
+ local mcp_tools
376
+ mcp_tools=$(jq -r --arg id "$story_id" '.stories[] | select(.id==$id) | .mcp // [] | length' "$prd_file")
377
+ if [[ "$mcp_tools" == "0" ]]; then
378
+ story_issues+="missing mcp (browser tools), "
379
+ cnt_frontend_mcp=$((cnt_frontend_mcp + 1))
380
+ fi
373
381
  fi
374
382
 
375
383
  # Check 4: Auth stories need security criteria
@@ -442,6 +450,7 @@ _validate_and_fix_stories() {
442
450
  [[ $cnt_frontend_tsc -gt 0 ]] && echo " ${cnt_frontend_tsc}x frontend: add tsc/playwright"
443
451
  [[ $cnt_frontend_url -gt 0 ]] && echo " ${cnt_frontend_url}x frontend: add testUrl"
444
452
  [[ $cnt_frontend_context -gt 0 ]] && echo " ${cnt_frontend_context}x frontend: add contextFiles"
453
+ [[ $cnt_frontend_mcp -gt 0 ]] && echo " ${cnt_frontend_mcp}x frontend: add mcp browser tools"
445
454
  [[ $cnt_auth_security -gt 0 ]] && echo " ${cnt_auth_security}x auth: add security criteria"
446
455
  [[ $cnt_list_pagination -gt 0 ]] && echo " ${cnt_list_pagination}x list: add pagination"
447
456
  [[ $cnt_migration_prereq -gt 0 ]] && echo " ${cnt_migration_prereq}x migration: add prerequisites (DB reset)"
@@ -467,27 +476,41 @@ _fix_stories_with_claude() {
467
476
  local prd_file="$1"
468
477
  local issues="$2"
469
478
 
479
+ # Read config values for context
480
+ local config_file="$RALPH_DIR/config.json"
481
+ local backend_url="" frontend_url=""
482
+ if [[ -f "$config_file" ]]; then
483
+ backend_url=$(jq -r '.urls.backend // .api.baseUrl // "http://localhost:8000"' "$config_file" 2>/dev/null)
484
+ frontend_url=$(jq -r '.urls.frontend // .playwright.baseUrl // "http://localhost:3000"' "$config_file" 2>/dev/null)
485
+ fi
486
+
470
487
  local fix_prompt="Enhance test coverage for these stories. Output the COMPLETE updated prd.json.
471
488
 
472
489
  STORIES TO OPTIMIZE:
473
490
  $issues
474
491
 
492
+ CONFIG VALUES (use these):
493
+ - Backend URL: $backend_url (use as {config.urls.backend} in testSteps)
494
+ - Frontend URL: $frontend_url (use as {config.urls.frontend} in testUrl)
495
+
475
496
  RULES:
476
497
  1. Backend stories MUST have testSteps with curl commands that hit real endpoints
477
498
  Example: curl -s -X POST {config.urls.backend}/api/users -d '...' | jq -e '.id'
478
499
  2. Backend stories MUST have apiContract with endpoint, request, response
479
- 3. Frontend stories MUST have testUrl set to {config.urls.frontend}/page
500
+ 3. Frontend stories MUST have testUrl set to {config.urls.frontend}/[page-path]
501
+ - Derive page path from story title (e.g., 'login form' → '/login', 'dashboard' → '/dashboard')
480
502
  4. Frontend stories MUST have contextFiles array (include idea file path from originalContext)
481
- 5. Auth stories MUST have security acceptanceCriteria:
503
+ 5. Frontend stories MUST have mcp array with browser tools: [\"playwright\", \"devtools\"]
504
+ 6. Auth stories MUST have security acceptanceCriteria:
482
505
  - Passwords hashed with bcrypt (cost 10+)
483
506
  - Passwords NEVER in API responses
484
507
  - Rate limiting on login attempts
485
- 6. List endpoints MUST have pagination acceptanceCriteria:
508
+ 7. List endpoints MUST have pagination acceptanceCriteria:
486
509
  - Returns paginated results (max 100 per page)
487
510
  - Accepts ?page=N&limit=N query params
488
- 7. Migration stories (creating alembic/versions, migrations/, or modifying models) MUST have prerequisites:
511
+ 8. Migration stories (creating alembic/versions, migrations/, or modifying models) MUST have prerequisites:
489
512
  Example: \"prerequisites\": [{\"name\": \"Reset test DB\", \"command\": \"npm run db:reset:test\", \"when\": \"schema changes\"}]
490
- 8. Frontend/general stories that consume APIs MUST have notes about naming conventions:
513
+ 9. Frontend/general stories that consume APIs MUST have notes about naming conventions:
491
514
  Example: \"notes\": \"Transform API responses from snake_case to camelCase. Create typed interfaces with camelCase properties and map: const user = { userName: data.user_name }\"
492
515
 
493
516
  CURRENT PRD:
@@ -591,7 +614,7 @@ validate_stories_quick() {
591
614
  fi
592
615
  fi
593
616
 
594
- # Check 3: Frontend needs testUrl and contextFiles
617
+ # Check 3: Frontend needs testUrl, contextFiles, and mcp
595
618
  if [[ "$story_type" == "frontend" ]]; then
596
619
  local has_url
597
620
  has_url=$(jq -r --arg id "$story_id" '.stories[] | select(.id==$id) | .testUrl // empty' "$prd_file")
@@ -600,6 +623,10 @@ validate_stories_quick() {
600
623
  local context_files
601
624
  context_files=$(jq -r --arg id "$story_id" '.stories[] | select(.id==$id) | .contextFiles // [] | length' "$prd_file")
602
625
  [[ "$context_files" == "0" ]] && issues+="$story_id: missing contextFiles, "
626
+
627
+ local mcp_tools
628
+ mcp_tools=$(jq -r --arg id "$story_id" '.stories[] | select(.id==$id) | .mcp // [] | length' "$prd_file")
629
+ [[ "$mcp_tools" == "0" ]] && issues+="$story_id: missing mcp, "
603
630
  fi
604
631
 
605
632
  # Check 4: Auth stories need security criteria
@@ -202,20 +202,33 @@ configure_mcp() {
202
202
  # Create claude.json if it doesn't exist
203
203
  [[ ! -f "$claude_json" ]] && echo '{}' > "$claude_json"
204
204
 
205
- # Check if chrome-devtools is already configured
206
- if jq -e '.mcpServers["chrome-devtools"]' "$claude_json" > /dev/null 2>&1; then
207
- echo -e " ${DIM}Skipped: Chrome DevTools MCP already configured${NC}"
208
- return
209
- fi
205
+ local added_any=false
210
206
 
211
- # Add Chrome DevTools MCP
212
- local tmp=$(mktemp)
213
- jq '.mcpServers["chrome-devtools"] = {
214
- "command": "npx",
215
- "args": ["-y", "@anthropic-ai/mcp-server-chrome-devtools@0.0.5"]
216
- }' "$claude_json" > "$tmp" && mv "$tmp" "$claude_json"
207
+ # Add Playwright MCP if not configured (uses chromium + headless to avoid Chrome conflicts)
208
+ if ! jq -e '.mcpServers["playwright"]' "$claude_json" > /dev/null 2>&1; then
209
+ local tmp=$(mktemp)
210
+ jq '.mcpServers["playwright"] = {
211
+ "command": "npx",
212
+ "args": ["-y", "@playwright/mcp@latest", "--browser", "chromium", "--headless"]
213
+ }' "$claude_json" > "$tmp" && mv "$tmp" "$claude_json"
214
+ echo -e " ${GREEN}${EMOJI_CHECK}${NC} Playwright MCP configured"
215
+ added_any=true
216
+ else
217
+ echo -e " ${DIM}Skipped: Playwright MCP already configured${NC}"
218
+ fi
217
219
 
218
- echo -e " ${GREEN}${EMOJI_CHECK}${NC} Chrome DevTools MCP configured"
220
+ # Add Chrome DevTools MCP if not configured
221
+ if ! jq -e '.mcpServers["chrome-devtools"]' "$claude_json" > /dev/null 2>&1; then
222
+ local tmp=$(mktemp)
223
+ jq '.mcpServers["chrome-devtools"] = {
224
+ "command": "npx",
225
+ "args": ["-y", "@anthropic-ai/mcp-server-chrome-devtools@0.0.5"]
226
+ }' "$claude_json" > "$tmp" && mv "$tmp" "$claude_json"
227
+ echo -e " ${GREEN}${EMOJI_CHECK}${NC} Chrome DevTools MCP configured"
228
+ added_any=true
229
+ else
230
+ echo -e " ${DIM}Skipped: Chrome DevTools MCP already configured${NC}"
231
+ fi
219
232
  }
220
233
 
221
234
  show_completion() {
package/ralph/setup/ui.sh CHANGED
@@ -37,33 +37,6 @@ EOF
37
37
  echo ""
38
38
  }
39
39
 
40
- show_welcome() {
41
- echo -e " ${BOLD}Welcome!${NC} How would you like to get started?"
42
- echo ""
43
- echo -e " ${GREEN}[1]${NC} ${EMOJI_ROCKET} ${BOLD}Quick Setup${NC}"
44
- echo -e " ${DIM}I know what I'm doing, just configure my project${NC}"
45
- echo ""
46
- echo -e " ${GREEN}[2]${NC} ${EMOJI_MOVIE} ${BOLD}Feature Tour${NC}"
47
- echo -e " ${DIM}Show me what agentic-loop can do (auto-demo)${NC}"
48
- echo ""
49
- echo -e " ${GREEN}[3]${NC} ${EMOJI_BOOK} ${BOLD}New to Claude Code${NC}"
50
- echo -e " ${DIM}Teach me the basics (interactive tutorial)${NC}"
51
- echo ""
52
- }
53
-
54
- prompt_menu() {
55
- local choice
56
- while true; do
57
- echo -ne " Press ${GREEN}1${NC}, ${GREEN}2${NC}, or ${GREEN}3${NC} (or ${DIM}q${NC} to quit): "
58
- read -r -n 1 choice
59
- echo ""
60
- case "$choice" in
61
- 1|2|3|q|Q) echo "$choice"; return ;;
62
- *) echo -e " ${RED}Invalid choice${NC}" ;;
63
- esac
64
- done
65
- }
66
-
67
40
  # Typewriter effect for demos
68
41
  typewrite() {
69
42
  local text="$1" delay="${2:-0.02}"
@@ -111,21 +84,6 @@ print_info() {
111
84
  echo -e " ${CYAN}→${NC} $1"
112
85
  }
113
86
 
114
- # Spinner for long operations
115
- spin() {
116
- local pid=$1
117
- local delay=0.1
118
- local spinstr='|/-\'
119
- while [ "$(ps a | awk '{print $1}' | grep $pid)" ]; do
120
- local temp=${spinstr#?}
121
- printf " [%c] " "$spinstr"
122
- local spinstr=$temp${spinstr%"$temp"}
123
- sleep $delay
124
- printf "\b\b\b\b\b\b"
125
- done
126
- printf " \b\b\b\b\b\b"
127
- }
128
-
129
87
  # Confirmation prompt
130
88
  confirm() {
131
89
  local prompt="${1:-Proceed?}"
package/ralph/setup.sh CHANGED
@@ -285,6 +285,7 @@ setup_claude_hooks() {
285
285
  local settings_file=".claude/settings.json"
286
286
  local src_hooks_dir="$pkg_root/ralph/hooks"
287
287
  local project_hooks_dir=".ralph/hooks"
288
+ local global_hooks_dir="$HOME/.config/ralph/hooks"
288
289
 
289
290
  echo "Installing Claude Code hooks..."
290
291
 
@@ -307,6 +308,11 @@ setup_claude_hooks() {
307
308
  chmod +x "$project_hooks_dir"/*.sh 2>/dev/null || true
308
309
  echo " Copied hooks to $project_hooks_dir/"
309
310
 
311
+ # Note if global hooks exist
312
+ if [[ -d "$global_hooks_dir" ]] && ls -1 "$global_hooks_dir"/*.sh &>/dev/null; then
313
+ echo " Found global hooks in $global_hooks_dir/"
314
+ fi
315
+
310
316
  # Get absolute path to project hooks
311
317
  local hooks_dir
312
318
  hooks_dir="$(cd "$project_hooks_dir" && pwd)"
@@ -317,52 +323,70 @@ setup_claude_hooks() {
317
323
  # Create settings file if it doesn't exist
318
324
  [[ ! -f "$settings_file" ]] && echo '{}' > "$settings_file"
319
325
 
320
- # Build hooks config with absolute paths to project hooks
326
+ # Helper to resolve hook path (project takes priority over global)
327
+ _resolve_hook() {
328
+ local hook_name="$1"
329
+ if [[ -f "$project_hooks_dir/$hook_name" ]]; then
330
+ echo "$hooks_dir/$hook_name"
331
+ elif [[ -f "$global_hooks_dir/$hook_name" ]]; then
332
+ echo "$global_hooks_dir/$hook_name"
333
+ fi
334
+ }
335
+
336
+ # Resolve each hook path (project hooks take priority over global)
337
+ local protect_prd warn_debug warn_secrets warn_urls warn_empty_catch log_tools inject_context save_learnings
338
+ protect_prd=$(_resolve_hook "protect-prd.sh")
339
+ warn_debug=$(_resolve_hook "warn-debug.sh")
340
+ warn_secrets=$(_resolve_hook "warn-secrets.sh")
341
+ warn_urls=$(_resolve_hook "warn-urls.sh")
342
+ warn_empty_catch=$(_resolve_hook "warn-empty-catch.sh")
343
+ log_tools=$(_resolve_hook "log-tools.sh")
344
+ inject_context=$(_resolve_hook "inject-context.sh")
345
+ save_learnings=$(_resolve_hook "save-learnings.sh")
346
+
347
+ # Build hooks arrays using jq for proper JSON
348
+ local pre_tool_hooks post_edit_hooks post_all_hooks session_start_hooks stop_hooks
349
+
350
+ # PreToolUse: protect-prd on Edit|Write
351
+ pre_tool_hooks="[]"
352
+ [[ -n "$protect_prd" ]] && pre_tool_hooks=$(jq -n --arg cmd "$protect_prd" '[{"type": "command", "command": $cmd, "timeout": 5}]')
353
+
354
+ # PostToolUse: warn-* hooks on Edit|Write
355
+ post_edit_hooks="[]"
356
+ for hook_path in "$warn_debug" "$warn_secrets" "$warn_urls" "$warn_empty_catch"; do
357
+ [[ -n "$hook_path" ]] && post_edit_hooks=$(echo "$post_edit_hooks" | jq --arg cmd "$hook_path" '. + [{"type": "command", "command": $cmd, "timeout": 5}]')
358
+ done
359
+
360
+ # PostToolUse: log-tools on all
361
+ post_all_hooks="[]"
362
+ [[ -n "$log_tools" ]] && post_all_hooks=$(jq -n --arg cmd "$log_tools" '[{"type": "command", "command": $cmd, "timeout": 3}]')
363
+
364
+ # SessionStart: inject-context
365
+ session_start_hooks="[]"
366
+ [[ -n "$inject_context" ]] && session_start_hooks=$(jq -n --arg cmd "$inject_context" '[{"type": "command", "command": $cmd, "timeout": 5}]')
367
+
368
+ # Stop: save-learnings
369
+ stop_hooks="[]"
370
+ [[ -n "$save_learnings" ]] && stop_hooks=$(jq -n --arg cmd "$save_learnings" '[{"type": "command", "command": $cmd, "timeout": 10}]')
371
+
372
+ # Build the complete hooks config
321
373
  local hooks_config
322
- hooks_config=$(cat <<EOF
323
- {
324
- "PreToolUse": [
325
- {
326
- "matcher": "Edit|Write",
327
- "hooks": [
328
- {"type": "command", "command": "$hooks_dir/protect-prd.sh", "timeout": 5}
329
- ]
330
- }
331
- ],
332
- "PostToolUse": [
333
- {
334
- "matcher": "Edit|Write",
335
- "hooks": [
336
- {"type": "command", "command": "$hooks_dir/warn-debug.sh", "timeout": 5},
337
- {"type": "command", "command": "$hooks_dir/warn-secrets.sh", "timeout": 5},
338
- {"type": "command", "command": "$hooks_dir/warn-urls.sh", "timeout": 5},
339
- {"type": "command", "command": "$hooks_dir/warn-empty-catch.sh", "timeout": 5}
340
- ]
341
- },
342
- {
343
- "matcher": "*",
344
- "hooks": [
345
- {"type": "command", "command": "$hooks_dir/log-tools.sh", "timeout": 3}
346
- ]
347
- }
348
- ],
349
- "SessionStart": [
350
- {
351
- "hooks": [
352
- {"type": "command", "command": "$hooks_dir/inject-context.sh", "timeout": 5}
353
- ]
354
- }
355
- ],
356
- "Stop": [
357
- {
358
- "hooks": [
359
- {"type": "command", "command": "$hooks_dir/save-learnings.sh", "timeout": 10}
360
- ]
361
- }
362
- ]
363
- }
364
- EOF
365
- )
374
+ hooks_config=$(jq -n \
375
+ --argjson pre_tool "$pre_tool_hooks" \
376
+ --argjson post_edit "$post_edit_hooks" \
377
+ --argjson post_all "$post_all_hooks" \
378
+ --argjson session_start "$session_start_hooks" \
379
+ --argjson stop "$stop_hooks" \
380
+ '{
381
+ "PreToolUse": [{"matcher": "Edit|Write", "hooks": $pre_tool}],
382
+ "PostToolUse": [
383
+ {"matcher": "Edit|Write", "hooks": $post_edit},
384
+ {"matcher": "*", "hooks": $post_all}
385
+ ],
386
+ "SessionStart": [{"hooks": $session_start}],
387
+ "Stop": [{"hooks": $stop}]
388
+ }'
389
+ )
366
390
 
367
391
  # Merge hooks into settings
368
392
  local tmp
@@ -562,13 +586,14 @@ setup_mcp() {
562
586
  local added_any=false
563
587
 
564
588
  # Add Playwright MCP if not configured
589
+ # Uses chromium + headless to avoid conflicts with user's Chrome session
565
590
  if ! jq -e '.mcpServers["playwright"]' "$claude_json" > /dev/null 2>&1; then
566
591
  echo "Configuring MCP servers..."
567
592
  local tmp
568
593
  tmp=$(mktemp)
569
594
  jq '.mcpServers["playwright"] = {
570
595
  "command": "npx",
571
- "args": ["-y", "@playwright/mcp@latest"]
596
+ "args": ["-y", "@playwright/mcp@latest", "--browser", "chromium", "--headless"]
572
597
  }' "$claude_json" > "$tmp" && mv "$tmp" "$claude_json"
573
598
  echo " Added playwright MCP server (browser automation & testing)"
574
599
  added_any=true