agentic-loop 1.0.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.
Files changed (162) hide show
  1. package/.claude/commands/explain.md +114 -0
  2. package/.claude/commands/idea.md +398 -0
  3. package/.claude/commands/my-dna.md +122 -0
  4. package/.claude/commands/prd.md +286 -0
  5. package/.claude/commands/review.md +167 -0
  6. package/.claude/commands/sign.md +32 -0
  7. package/.claude/commands/styleguide.md +450 -0
  8. package/.claude/commands/tour.md +301 -0
  9. package/.claude/commands/vibe-check.md +116 -0
  10. package/.claude/commands/vibe-help.md +47 -0
  11. package/.claude/commands/vibe-list.md +203 -0
  12. package/.pre-commit-hooks.yaml +102 -0
  13. package/LICENSE +21 -0
  14. package/README.md +238 -0
  15. package/bin/agentic-loop.sh +24 -0
  16. package/bin/postinstall.sh +29 -0
  17. package/bin/ralph.sh +171 -0
  18. package/bin/vibe-check.js +19 -0
  19. package/dist/checks/check-any-types.d.ts +6 -0
  20. package/dist/checks/check-any-types.d.ts.map +1 -0
  21. package/dist/checks/check-any-types.js +73 -0
  22. package/dist/checks/check-any-types.js.map +1 -0
  23. package/dist/checks/check-commented-code.d.ts +6 -0
  24. package/dist/checks/check-commented-code.d.ts.map +1 -0
  25. package/dist/checks/check-commented-code.js +81 -0
  26. package/dist/checks/check-commented-code.js.map +1 -0
  27. package/dist/checks/check-console-error.d.ts +6 -0
  28. package/dist/checks/check-console-error.d.ts.map +1 -0
  29. package/dist/checks/check-console-error.js +41 -0
  30. package/dist/checks/check-console-error.js.map +1 -0
  31. package/dist/checks/check-debug-statements.d.ts +6 -0
  32. package/dist/checks/check-debug-statements.d.ts.map +1 -0
  33. package/dist/checks/check-debug-statements.js +120 -0
  34. package/dist/checks/check-debug-statements.js.map +1 -0
  35. package/dist/checks/check-deep-nesting.d.ts +6 -0
  36. package/dist/checks/check-deep-nesting.d.ts.map +1 -0
  37. package/dist/checks/check-deep-nesting.js +116 -0
  38. package/dist/checks/check-deep-nesting.js.map +1 -0
  39. package/dist/checks/check-docker-platform.d.ts +6 -0
  40. package/dist/checks/check-docker-platform.d.ts.map +1 -0
  41. package/dist/checks/check-docker-platform.js +42 -0
  42. package/dist/checks/check-docker-platform.js.map +1 -0
  43. package/dist/checks/check-dry-violations.d.ts +6 -0
  44. package/dist/checks/check-dry-violations.d.ts.map +1 -0
  45. package/dist/checks/check-dry-violations.js +124 -0
  46. package/dist/checks/check-dry-violations.js.map +1 -0
  47. package/dist/checks/check-empty-catch.d.ts +6 -0
  48. package/dist/checks/check-empty-catch.d.ts.map +1 -0
  49. package/dist/checks/check-empty-catch.js +111 -0
  50. package/dist/checks/check-empty-catch.js.map +1 -0
  51. package/dist/checks/check-function-length.d.ts +6 -0
  52. package/dist/checks/check-function-length.d.ts.map +1 -0
  53. package/dist/checks/check-function-length.js +152 -0
  54. package/dist/checks/check-function-length.js.map +1 -0
  55. package/dist/checks/check-hardcoded-ai-models.d.ts +10 -0
  56. package/dist/checks/check-hardcoded-ai-models.d.ts.map +1 -0
  57. package/dist/checks/check-hardcoded-ai-models.js +102 -0
  58. package/dist/checks/check-hardcoded-ai-models.js.map +1 -0
  59. package/dist/checks/check-hardcoded-urls.d.ts +6 -0
  60. package/dist/checks/check-hardcoded-urls.d.ts.map +1 -0
  61. package/dist/checks/check-hardcoded-urls.js +124 -0
  62. package/dist/checks/check-hardcoded-urls.js.map +1 -0
  63. package/dist/checks/check-magic-numbers.d.ts +6 -0
  64. package/dist/checks/check-magic-numbers.d.ts.map +1 -0
  65. package/dist/checks/check-magic-numbers.js +116 -0
  66. package/dist/checks/check-magic-numbers.js.map +1 -0
  67. package/dist/checks/check-secrets.d.ts +6 -0
  68. package/dist/checks/check-secrets.d.ts.map +1 -0
  69. package/dist/checks/check-secrets.js +138 -0
  70. package/dist/checks/check-secrets.js.map +1 -0
  71. package/dist/checks/check-snake-case-ts.d.ts +6 -0
  72. package/dist/checks/check-snake-case-ts.d.ts.map +1 -0
  73. package/dist/checks/check-snake-case-ts.js +78 -0
  74. package/dist/checks/check-snake-case-ts.js.map +1 -0
  75. package/dist/checks/check-todo-fixme.d.ts +6 -0
  76. package/dist/checks/check-todo-fixme.d.ts.map +1 -0
  77. package/dist/checks/check-todo-fixme.js +41 -0
  78. package/dist/checks/check-todo-fixme.js.map +1 -0
  79. package/dist/checks/check-unsafe-html.d.ts +6 -0
  80. package/dist/checks/check-unsafe-html.d.ts.map +1 -0
  81. package/dist/checks/check-unsafe-html.js +101 -0
  82. package/dist/checks/check-unsafe-html.js.map +1 -0
  83. package/dist/checks/index.d.ts +30 -0
  84. package/dist/checks/index.d.ts.map +1 -0
  85. package/dist/checks/index.js +57 -0
  86. package/dist/checks/index.js.map +1 -0
  87. package/dist/cli.d.ts +13 -0
  88. package/dist/cli.d.ts.map +1 -0
  89. package/dist/cli.js +208 -0
  90. package/dist/cli.js.map +1 -0
  91. package/dist/index.d.ts +9 -0
  92. package/dist/index.d.ts.map +1 -0
  93. package/dist/index.js +10 -0
  94. package/dist/index.js.map +1 -0
  95. package/dist/utils/file-reader.d.ts +24 -0
  96. package/dist/utils/file-reader.d.ts.map +1 -0
  97. package/dist/utils/file-reader.js +146 -0
  98. package/dist/utils/file-reader.js.map +1 -0
  99. package/dist/utils/patterns.d.ts +27 -0
  100. package/dist/utils/patterns.d.ts.map +1 -0
  101. package/dist/utils/patterns.js +84 -0
  102. package/dist/utils/patterns.js.map +1 -0
  103. package/dist/utils/reporters.d.ts +21 -0
  104. package/dist/utils/reporters.d.ts.map +1 -0
  105. package/dist/utils/reporters.js +115 -0
  106. package/dist/utils/reporters.js.map +1 -0
  107. package/dist/utils/types.d.ts +71 -0
  108. package/dist/utils/types.d.ts.map +1 -0
  109. package/dist/utils/types.js +5 -0
  110. package/dist/utils/types.js.map +1 -0
  111. package/package.json +83 -0
  112. package/ralph/api.sh +216 -0
  113. package/ralph/backup.sh +838 -0
  114. package/ralph/browser-verify/README.md +135 -0
  115. package/ralph/browser-verify/verify.ts +450 -0
  116. package/ralph/checks/check-fastapi-responses.py +155 -0
  117. package/ralph/hooks/hooks-config.json +72 -0
  118. package/ralph/hooks/inject-context.sh +44 -0
  119. package/ralph/hooks/install.sh +207 -0
  120. package/ralph/hooks/log-tools.sh +45 -0
  121. package/ralph/hooks/protect-prd.sh +27 -0
  122. package/ralph/hooks/save-learnings.sh +36 -0
  123. package/ralph/hooks/warn-debug.sh +54 -0
  124. package/ralph/hooks/warn-empty-catch.sh +63 -0
  125. package/ralph/hooks/warn-secrets.sh +89 -0
  126. package/ralph/hooks/warn-urls.sh +77 -0
  127. package/ralph/init.sh +515 -0
  128. package/ralph/loop.sh +730 -0
  129. package/ralph/playwright.sh +238 -0
  130. package/ralph/prd.sh +295 -0
  131. package/ralph/setup/feature-tour.sh +155 -0
  132. package/ralph/setup/quick-setup.sh +239 -0
  133. package/ralph/setup/tutorial.sh +159 -0
  134. package/ralph/setup/ui.sh +136 -0
  135. package/ralph/setup.sh +401 -0
  136. package/ralph/signs.sh +150 -0
  137. package/ralph/utils.sh +682 -0
  138. package/ralph/verify/browser.sh +324 -0
  139. package/ralph/verify/lint.sh +363 -0
  140. package/ralph/verify/review.sh +152 -0
  141. package/ralph/verify/tests.sh +81 -0
  142. package/ralph/verify.sh +268 -0
  143. package/templates/PROMPT.md +235 -0
  144. package/templates/config/fullstack.json +86 -0
  145. package/templates/config/go.json +81 -0
  146. package/templates/config/minimal.json +76 -0
  147. package/templates/config/node.json +81 -0
  148. package/templates/config/python.json +81 -0
  149. package/templates/config/rust.json +81 -0
  150. package/templates/examples/CLAUDE-django.md +174 -0
  151. package/templates/examples/CLAUDE-fastapi.md +270 -0
  152. package/templates/examples/CLAUDE-fastmcp.md +352 -0
  153. package/templates/examples/CLAUDE-fullstack.md +256 -0
  154. package/templates/examples/CLAUDE-node.md +246 -0
  155. package/templates/examples/CLAUDE-react.md +138 -0
  156. package/templates/optional/cursorrules.template +147 -0
  157. package/templates/optional/eslint.config.js +34 -0
  158. package/templates/optional/lint-staged.config.js +34 -0
  159. package/templates/optional/ruff.toml +125 -0
  160. package/templates/optional/vibe-check.yml +116 -0
  161. package/templates/optional/vscode-settings.json +127 -0
  162. package/templates/signs.json +46 -0
@@ -0,0 +1,324 @@
1
+ #!/usr/bin/env bash
2
+ # shellcheck shell=bash
3
+ # browser.sh - Browser validation module for ralph
4
+
5
+ # Browser validation for frontend stories using Playwright
6
+ run_browser_validation() {
7
+ local story="$1"
8
+
9
+ # Check if browser validation is enabled in config
10
+ local browser_enabled
11
+ browser_enabled=$(get_config '.verification.browserEnabled' "true")
12
+ if [[ "$browser_enabled" == "false" ]]; then
13
+ echo " (browser validation disabled in config, skipping)"
14
+ return 0
15
+ fi
16
+
17
+ # Get base URL from config (required for relative URLs)
18
+ local base_url
19
+ base_url=$(get_config '.testUrlBase' "")
20
+
21
+ # Check if Docker mode - Playwright needs special handling
22
+ local docker_enabled
23
+ docker_enabled=$(get_config '.docker.enabled' "false")
24
+ if [[ "$docker_enabled" == "true" ]]; then
25
+ echo " (Docker mode: using curl check - set verification.browserEnabled=false to hide this)"
26
+ # In Docker, fall back to curl unless they've set up remote browser
27
+ local url
28
+ url=$(jq -r --arg id "$story" '.stories[] | select(.id==$id) | .testUrl // empty' "$RALPH_DIR/prd.json" 2>/dev/null)
29
+ if [[ -n "$url" ]]; then
30
+ # Check for unsubstituted template variables
31
+ if [[ "$url" =~ \{[a-zA-Z_][a-zA-Z0-9_]*\} ]]; then
32
+ print_warning " testUrl has unsubstituted variable: $url"
33
+ return 0
34
+ fi
35
+ # Handle relative URLs
36
+ if [[ "$url" =~ ^/ ]]; then
37
+ if [[ -z "$base_url" ]]; then
38
+ print_error "testUrlBase not set in config.json (needed for relative URL: $url)"
39
+ return 1
40
+ fi
41
+ url="${base_url}${url}"
42
+ fi
43
+ return run_curl_check "$url"
44
+ fi
45
+ return 0
46
+ fi
47
+
48
+ local url
49
+ url=$(jq -r --arg id "$story" '.stories[] | select(.id==$id) | .testUrl // empty' "$RALPH_DIR/prd.json" 2>/dev/null)
50
+
51
+ if [[ -z "$url" ]]; then
52
+ echo " skipped (no testUrl - infrastructure or backend story)"
53
+ return 0
54
+ fi
55
+
56
+ # Check for unsubstituted template variables like {collectionId}
57
+ if [[ "$url" =~ \{[a-zA-Z_][a-zA-Z0-9_]*\} ]]; then
58
+ print_warning " testUrl has unsubstituted variable: $url"
59
+ echo " Add concrete ID to testUrl in PRD, or set in .ralph/config.json testUrlVars"
60
+ echo " skipped"
61
+ return 0
62
+ fi
63
+
64
+ # Handle relative URLs by prepending base URL from config
65
+ if [[ "$url" =~ ^/ ]]; then
66
+ if [[ -z "$base_url" ]]; then
67
+ print_error "testUrlBase not set in config.json (needed for relative URL: $url)"
68
+ return 1
69
+ fi
70
+ url="${base_url}${url}"
71
+ fi
72
+
73
+ if ! validate_url "$url"; then
74
+ print_error "Invalid URL: $url"
75
+ return 1
76
+ fi
77
+
78
+ echo " URL: $url"
79
+
80
+ # Check if npx is available
81
+ if ! command -v npx &>/dev/null; then
82
+ print_warning " npx not found, skipping browser validation"
83
+ return 0
84
+ fi
85
+
86
+ # Check if Playwright package is installed
87
+ local playwright_installed=false
88
+ if npm list playwright &>/dev/null || npm list -g playwright &>/dev/null; then
89
+ playwright_installed=true
90
+ fi
91
+
92
+ # Check if browser binaries are installed (look for chromium in cache)
93
+ # macOS uses ~/Library/Caches, Linux uses ~/.cache
94
+ local browser_installed=false
95
+ local playwright_cache=""
96
+ if [[ -d "$HOME/Library/Caches/ms-playwright" ]]; then
97
+ playwright_cache="$HOME/Library/Caches/ms-playwright"
98
+ elif [[ -d "$HOME/.cache/ms-playwright" ]]; then
99
+ playwright_cache="$HOME/.cache/ms-playwright"
100
+ fi
101
+ if [[ -n "$playwright_cache" ]] && ls "$playwright_cache"/chromium-* &>/dev/null; then
102
+ browser_installed=true
103
+ fi
104
+
105
+ # If either is missing, auto-install (Ralph runs autonomously)
106
+ if [[ "$playwright_installed" == "false" ]] || [[ "$browser_installed" == "false" ]]; then
107
+ echo ""
108
+ print_info " Installing Playwright for browser verification (~150MB)..."
109
+ if npm install playwright &>/dev/null && npx playwright install chromium &>/dev/null; then
110
+ print_success " Playwright installed!"
111
+ else
112
+ print_warning " Installation failed, falling back to curl check"
113
+ return run_curl_check "$url"
114
+ fi
115
+ fi
116
+
117
+ # Get selectors to check from story (if defined)
118
+ local selectors
119
+ selectors=$(jq -r --arg id "$story" '.stories[] | select(.id==$id) | .selectors // [] | @json' "$RALPH_DIR/prd.json" 2>/dev/null)
120
+ if [[ "$selectors" == "null" || -z "$selectors" ]]; then
121
+ selectors="[]"
122
+ fi
123
+
124
+ # Screenshot path
125
+ mkdir -p "$RALPH_DIR/screenshots"
126
+ local screenshot_path="$RALPH_DIR/screenshots/${story}.png"
127
+
128
+ # Check mobile too?
129
+ local check_mobile
130
+ check_mobile=$(jq -r --arg id "$story" '.stories[] | select(.id==$id) | .mobile // empty' "$RALPH_DIR/prd.json" 2>/dev/null)
131
+
132
+ # Build command - use the browser-verify skill
133
+ local verify_script="$RALPH_LIB/browser-verify/verify.ts"
134
+
135
+ if [[ ! -f "$verify_script" ]]; then
136
+ print_warning " browser-verify.ts not found, falling back to curl check"
137
+ return run_curl_check "$url"
138
+ fi
139
+
140
+ # Check for auth config (env vars take precedence over config file)
141
+ local auth_login=""
142
+ local login_url
143
+ local test_user
144
+ local test_password
145
+ login_url=$(get_config '.auth.loginUrl' "")
146
+ test_user="${RALPH_TEST_USER:-$(get_config '.auth.testUser' "")}"
147
+ test_password="${RALPH_TEST_PASSWORD:-$(get_config '.auth.testPassword' "")}"
148
+
149
+ if [[ -n "$login_url" && -n "$test_user" && -n "$test_password" ]]; then
150
+ # Build auth login JSON
151
+ local username_selector
152
+ local password_selector
153
+ local submit_selector
154
+ local success_indicator
155
+ username_selector=$(get_config '.auth.usernameSelector' "input[name='email'], input[name='username'], input[type='email']")
156
+ password_selector=$(get_config '.auth.passwordSelector' "input[name='password'], input[type='password']")
157
+ submit_selector=$(get_config '.auth.submitSelector' "button[type='submit'], input[type='submit']")
158
+ success_indicator=$(get_config '.auth.successIndicator' "")
159
+
160
+ auth_login=$(jq -n \
161
+ --arg loginUrl "$login_url" \
162
+ --arg usernameSelector "$username_selector" \
163
+ --arg passwordSelector "$password_selector" \
164
+ --arg submitSelector "$submit_selector" \
165
+ --arg username "$test_user" \
166
+ --arg password "$test_password" \
167
+ --arg successIndicator "$success_indicator" \
168
+ '{
169
+ loginUrl: $loginUrl,
170
+ usernameSelector: $usernameSelector,
171
+ passwordSelector: $passwordSelector,
172
+ submitSelector: $submitSelector,
173
+ username: $username,
174
+ password: $password,
175
+ successIndicator: (if $successIndicator == "" then null else $successIndicator end)
176
+ }')
177
+ fi
178
+
179
+ echo " Running Playwright verification..."
180
+
181
+ local result
182
+ local exit_code=0
183
+
184
+ # Build the command with optional auth
185
+ local cmd_args=(
186
+ npx tsx "$verify_script" "$url"
187
+ --selectors "$selectors"
188
+ --screenshot "$screenshot_path"
189
+ --timeout "$BROWSER_PAGE_TIMEOUT_MS"
190
+ )
191
+
192
+ if [[ -n "$auth_login" ]]; then
193
+ cmd_args+=(--auth-login "$auth_login")
194
+ fi
195
+
196
+ # Run browser verification with wrapper timeout (in case Playwright hangs)
197
+ result=$(run_with_timeout "$BROWSER_TIMEOUT_SECONDS" "${cmd_args[@]}" 2>&1) || exit_code=$?
198
+
199
+ # Check for timeout
200
+ if [[ $exit_code -eq 124 ]]; then
201
+ print_error "failed (timed out after ${BROWSER_TIMEOUT_SECONDS}s)"
202
+ echo " The page may be stuck loading or the dev server isn't responding."
203
+ echo " Check: curl -I $url"
204
+ return run_curl_check "$url"
205
+ fi
206
+
207
+ # Check if we got any output
208
+ if [[ -z "$result" ]]; then
209
+ print_error "failed (no output from Playwright)"
210
+ echo " Exit code: $exit_code"
211
+ echo " This usually means Playwright crashed or isn't installed correctly."
212
+ echo " Try: npm install playwright && npx playwright install chromium"
213
+ return run_curl_check "$url"
214
+ fi
215
+
216
+ # Check if result is valid JSON
217
+ if ! echo "$result" | jq -e . >/dev/null 2>&1; then
218
+ print_error "failed (invalid response)"
219
+ echo " Raw output:"
220
+ echo "$result" | head -20 | sed 's/^/ /'
221
+ return run_curl_check "$url"
222
+ fi
223
+
224
+ # Parse result
225
+ local passed
226
+ passed=$(echo "$result" | jq -r '.pass // false' 2>/dev/null)
227
+
228
+ if [[ "$passed" == "true" ]]; then
229
+ local load_time
230
+ load_time=$(echo "$result" | jq -r '.loadTime // 0' 2>/dev/null)
231
+ print_success "passed (${load_time}ms)"
232
+
233
+ # Show any warnings
234
+ local warnings
235
+ warnings=$(echo "$result" | jq -r '.warnings[]?' 2>/dev/null)
236
+ if [[ -n "$warnings" ]]; then
237
+ echo "$warnings" | sed 's/^/ Warning: /'
238
+ fi
239
+
240
+ # Run mobile check if required
241
+ if [[ -n "$check_mobile" ]]; then
242
+ echo -n " Mobile viewport... "
243
+ local mobile_result
244
+ mobile_result=$(run_with_timeout "$BROWSER_TIMEOUT_SECONDS" npx tsx "$verify_script" "$url" \
245
+ --selectors "$selectors" \
246
+ --screenshot "$RALPH_DIR/screenshots/${story}-mobile.png" \
247
+ --mobile \
248
+ 2>&1) || true
249
+
250
+ local mobile_passed
251
+ mobile_passed=$(echo "$mobile_result" | jq -r '.pass // false' 2>/dev/null)
252
+
253
+ if [[ "$mobile_passed" == "true" ]]; then
254
+ print_success "passed"
255
+ else
256
+ print_warning "issues found"
257
+ echo "$mobile_result" | jq -r '.errors[]?' 2>/dev/null | head -3 | sed 's/^/ /'
258
+ fi
259
+ fi
260
+
261
+ return 0
262
+ else
263
+ print_error "failed"
264
+ echo ""
265
+
266
+ # Show errors
267
+ echo " Errors:"
268
+ echo "$result" | jq -r '.errors[]?' 2>/dev/null | sed 's/^/ /'
269
+
270
+ # Show console errors if any
271
+ local console_errors
272
+ console_errors=$(echo "$result" | jq -r '.consoleErrors[]?' 2>/dev/null)
273
+ if [[ -n "$console_errors" ]]; then
274
+ echo ""
275
+ echo " Console errors:"
276
+ echo "$console_errors" | head -5 | sed 's/^/ /'
277
+ fi
278
+
279
+ # Show missing elements if any
280
+ local missing
281
+ missing=$(echo "$result" | jq -r '.elementsMissing[]?' 2>/dev/null)
282
+ if [[ -n "$missing" ]]; then
283
+ echo ""
284
+ echo " Missing elements:"
285
+ echo "$missing" | sed 's/^/ /'
286
+ fi
287
+
288
+ # Save for failure context
289
+ echo "$result" > "$RALPH_DIR/last_browser_failure.json"
290
+
291
+ return 1
292
+ fi
293
+ }
294
+
295
+ # Fallback curl check when Playwright isn't available
296
+ run_curl_check() {
297
+ local url="$1"
298
+
299
+ local http_response
300
+ http_response=$(curl -s -o /dev/null -w "%{http_code}" --max-time "$CURL_TIMEOUT_SECONDS" "$url" 2>/dev/null) || http_response="000"
301
+
302
+ if [[ "$http_response" == "000" ]]; then
303
+ print_error "Cannot reach $url - server not responding"
304
+ return 1
305
+ elif [[ "$http_response" -ge 500 ]]; then
306
+ print_error "Server error $http_response at $url"
307
+ return 1
308
+ elif [[ "$http_response" -ge 400 ]]; then
309
+ print_warning "HTTP $http_response (may be expected for auth pages)"
310
+ return 0
311
+ fi
312
+
313
+ # Check for error messages in response
314
+ local page_content
315
+ page_content=$(curl -s --max-time "$CURL_TIMEOUT_SECONDS" "$url" 2>/dev/null)
316
+
317
+ if echo "$page_content" | grep -qi "something went wrong\|error.*occurred\|500 internal\|503 service\|oops\!" 2>/dev/null; then
318
+ print_error "Page contains error message"
319
+ return 1
320
+ fi
321
+
322
+ print_success "HTTP $http_response"
323
+ return 0
324
+ }
@@ -0,0 +1,363 @@
1
+ #!/usr/bin/env bash
2
+ # shellcheck shell=bash
3
+ # lint.sh - Lint and auto-fix verification module for ralph
4
+
5
+ # Auto-fix lint issues before running checks
6
+ run_auto_fix() {
7
+ echo " Auto-fixing lint issues..."
8
+
9
+ # Python: ruff fix (auto-fix what we can)
10
+ if command -v ruff &>/dev/null; then
11
+ ruff check --fix . 2>/dev/null || true
12
+ fi
13
+
14
+ # JavaScript/TypeScript: eslint fix (root)
15
+ if [[ -f "package.json" ]] && command -v npx &>/dev/null; then
16
+ if grep -q '"eslint"' package.json 2>/dev/null || [[ -f ".eslintrc.js" ]] || [[ -f "eslint.config.js" ]]; then
17
+ npx eslint --fix . 2>/dev/null || true
18
+ fi
19
+ fi
20
+
21
+ # Check frontend directories (monorepo support)
22
+ local fe_dirs
23
+ fe_dirs=$(get_frontend_dirs)
24
+
25
+ while IFS= read -r fe_dir; do
26
+ [[ -z "$fe_dir" ]] && continue
27
+ [[ ! -f "$fe_dir/package.json" ]] && continue
28
+ # ESLint fix
29
+ if grep -q '"eslint"' "$fe_dir/package.json" 2>/dev/null; then
30
+ (cd "$fe_dir" && npx eslint --fix . 2>/dev/null) || true
31
+ fi
32
+
33
+ # Next.js lint fix
34
+ if grep -q '"next"' "$fe_dir/package.json" 2>/dev/null; then
35
+ (cd "$fe_dir" && npx next lint --fix 2>/dev/null) || true
36
+ fi
37
+
38
+ # Prettier fix (common in frontend)
39
+ if grep -q '"prettier"' "$fe_dir/package.json" 2>/dev/null; then
40
+ (cd "$fe_dir" && npx prettier --write . 2>/dev/null) || true
41
+ fi
42
+ done <<< "$fe_dirs"
43
+
44
+ # Prettier fix (root - for non-monorepo)
45
+ if [[ -f "package.json" ]] && grep -q '"prettier"' package.json 2>/dev/null; then
46
+ npx prettier --write . 2>/dev/null || true
47
+ fi
48
+ }
49
+
50
+ # Verify lint passes after auto-fix (catch unfixable errors)
51
+ verify_lint() {
52
+ local failed=0
53
+
54
+ # Python: ruff lint check
55
+ if command -v ruff &>/dev/null && [[ -f "pyproject.toml" || -f "ruff.toml" ]]; then
56
+ echo -n " Ruff lint check... "
57
+ if ruff check . --quiet 2>/dev/null; then
58
+ print_success "passed"
59
+ else
60
+ print_error "failed"
61
+ echo ""
62
+ echo " Unfixable lint errors:"
63
+ ruff check . 2>/dev/null | head -"$MAX_LINT_ERROR_LINES" | sed 's/^/ /'
64
+ failed=1
65
+ fi
66
+ fi
67
+
68
+ # Check for monorepo backend directories
69
+ local api_dirs
70
+ api_dirs=$(get_backend_dirs)
71
+
72
+ while IFS= read -r api_dir; do
73
+ [[ -z "$api_dir" ]] && continue
74
+ if [[ -f "$api_dir/pyproject.toml" || -f "$api_dir/ruff.toml" ]]; then
75
+ echo -n " Ruff lint check ($api_dir)... "
76
+ if (cd "$api_dir" && ruff check . --quiet 2>/dev/null); then
77
+ print_success "passed"
78
+ else
79
+ print_error "failed"
80
+ echo ""
81
+ echo " Unfixable lint errors in $api_dir:"
82
+ (cd "$api_dir" && ruff check . 2>/dev/null) | head -"$MAX_LINT_ERROR_LINES" | sed 's/^/ /'
83
+ failed=1
84
+ fi
85
+ fi
86
+ done <<< "$api_dirs"
87
+
88
+ return $failed
89
+ }
90
+
91
+ # Check FastAPI endpoints have Pydantic response models (for Swagger docs)
92
+ run_fastapi_response_check() {
93
+ # Use RALPH_LIB which points to the ralph/ directory
94
+ local check_script="${RALPH_LIB:-$(dirname "${BASH_SOURCE[0]}")/..}/checks/check-fastapi-responses.py"
95
+
96
+ # Skip if check script doesn't exist
97
+ [[ ! -f "$check_script" ]] && return 0
98
+
99
+ # Find FastAPI directories (check root + backend dirs)
100
+ local fastapi_dirs=()
101
+ local all_dirs=(".")
102
+ while IFS= read -r dir; do
103
+ [[ -n "$dir" ]] && all_dirs+=("$dir")
104
+ done < <(get_backend_dirs)
105
+
106
+ for dir in "${all_dirs[@]}"; do
107
+ [[ ! -d "$dir" ]] && continue
108
+ # Check for FastAPI imports
109
+ if grep -rq "from fastapi" "$dir"/*.py 2>/dev/null || \
110
+ grep -rq "from fastapi" "$dir"/**/*.py 2>/dev/null; then
111
+ fastapi_dirs+=("$dir")
112
+ fi
113
+ done
114
+
115
+ [[ ${#fastapi_dirs[@]} -eq 0 ]] && return 0
116
+
117
+ local failed=0
118
+ for dir in "${fastapi_dirs[@]}"; do
119
+ echo -n " FastAPI response models ($dir)... "
120
+
121
+ local output
122
+ local log_file="$RALPH_DIR/last_fastapi_response_check.log"
123
+
124
+ if output=$(python3 "$check_script" "$dir" 2>&1); then
125
+ print_success "passed"
126
+ rm -f "$log_file"
127
+ else
128
+ print_error "failed"
129
+ echo ""
130
+ echo "$output" | head -"$MAX_ERROR_PREVIEW_LINES" | sed 's/^/ /'
131
+ # Save for failure context so Claude can fix
132
+ echo "$output" > "$log_file"
133
+ failed=1
134
+ fi
135
+ done
136
+
137
+ return $failed
138
+ }
139
+
140
+ # Run all checks defined in config.json
141
+ run_configured_checks() {
142
+ local config="$RALPH_DIR/config.json"
143
+
144
+ # ALWAYS run auto-fix and lint verification, even without config.json
145
+ run_auto_fix
146
+
147
+ # Verify lint passes after auto-fix (catch unfixable errors)
148
+ if ! verify_lint; then
149
+ return 1
150
+ fi
151
+
152
+ # Auto-detect and run FastAPI response model check
153
+ run_fastapi_response_check
154
+
155
+ # Run pre-commit hooks if available (catches errors before commit attempt)
156
+ if command -v pre-commit &>/dev/null && [[ -f ".pre-commit-config.yaml" ]]; then
157
+ echo -n " pre-commit hooks... "
158
+ local precommit_log="$RALPH_DIR/last_precommit_failure.log"
159
+
160
+ # Helper function: check if pre-commit output has REAL errors (not just file modifications or warnings)
161
+ has_real_errors() {
162
+ local log_file="$1"
163
+
164
+ # If all "Failed" hooks only have "files were modified" - not real errors
165
+ # Real errors have patterns like: "error:", "Error:", numbered errors "✖ N problems (N errors"
166
+ # But ESLint "0 errors, N warnings" is NOT a real error
167
+
168
+ # Check for actual error indicators (not warnings-only)
169
+ if grep -qE "^error:|: error:|Error:|SyntaxError|TypeError|NameError" "$log_file" 2>/dev/null; then
170
+ return 0 # Has real errors
171
+ fi
172
+
173
+ # Check ESLint output - fail only if errors > 0
174
+ if grep -qE "✖ [0-9]+ problems? \([1-9][0-9]* errors?" "$log_file" 2>/dev/null; then
175
+ return 0 # Has real ESLint errors
176
+ fi
177
+
178
+ # Check ruff output - actual errors have file:line:col: error pattern
179
+ if grep -qE "^[^:]+:[0-9]+:[0-9]+: [EF][0-9]+" "$log_file" 2>/dev/null; then
180
+ return 0 # Has real ruff errors
181
+ fi
182
+
183
+ # Check for hooks that failed for reasons OTHER than file modification
184
+ # Get all "Failed" hooks and check if any DON'T have "files were modified"
185
+ local failed_hooks
186
+ failed_hooks=$(grep -B 5 "^- hook id:" "$log_file" | grep -B 1 "Failed" | grep "hook id:" | sed 's/.*hook id: //' 2>/dev/null)
187
+
188
+ while IFS= read -r hook_id; do
189
+ [[ -z "$hook_id" ]] && continue
190
+ # Check if this hook's failure section contains "files were modified"
191
+ if ! grep -A 3 "hook id: $hook_id" "$log_file" | grep -q "files were modified"; then
192
+ # This hook failed for a real reason
193
+ return 0
194
+ fi
195
+ done <<< "$failed_hooks"
196
+
197
+ return 1 # No real errors found
198
+ }
199
+
200
+ # Run pre-commit up to 3 times to handle auto-fix chains
201
+ local max_attempts=3
202
+ local attempt=1
203
+ local passed=false
204
+
205
+ while [[ $attempt -le $max_attempts ]]; do
206
+ if pre-commit run --all-files > "$precommit_log" 2>&1; then
207
+ passed=true
208
+ break
209
+ fi
210
+
211
+ # Check if failure is due to file modifications (auto-fix)
212
+ if grep -q "files were modified by this hook" "$precommit_log"; then
213
+ # Check if there are also REAL errors (not just file mods)
214
+ if has_real_errors "$precommit_log"; then
215
+ # Real errors exist - fail
216
+ break
217
+ fi
218
+
219
+ # Only file modifications - stage and retry
220
+ if [[ $attempt -lt $max_attempts ]]; then
221
+ echo -n "auto-fixing (attempt $attempt)... "
222
+ git add -A 2>/dev/null || true
223
+ ((attempt++))
224
+ continue
225
+ else
226
+ # Max attempts reached, but only file mods - consider it passed
227
+ # Some hooks (like backup-database) always modify files
228
+ echo -n "auto-fix complete... "
229
+ git add -A 2>/dev/null || true
230
+ passed=true
231
+ break
232
+ fi
233
+ else
234
+ # Failed without "files were modified" - check for real errors
235
+ if has_real_errors "$precommit_log"; then
236
+ break # Real errors
237
+ else
238
+ # No real errors detected (warnings only, etc.)
239
+ passed=true
240
+ break
241
+ fi
242
+ fi
243
+
244
+ ((attempt++))
245
+ done
246
+
247
+ if [[ "$passed" == "true" ]]; then
248
+ if [[ $attempt -gt 1 ]]; then
249
+ print_success "passed (after auto-fix)"
250
+ else
251
+ print_success "passed"
252
+ fi
253
+ rm -f "$precommit_log"
254
+ else
255
+ print_error "failed"
256
+ echo ""
257
+ echo " Pre-commit hook errors:"
258
+ # Show actual errors, not just "Failed" status lines
259
+ grep -E "^error:|: error:|Error:|SyntaxError|✖ [0-9]+ problems|^[^:]+:[0-9]+:[0-9]+:" "$precommit_log" | head -"$MAX_ERROR_PREVIEW_LINES" | sed 's/^/ /'
260
+ # If no errors shown, show more context
261
+ if ! grep -qE "^error:|: error:|Error:|SyntaxError|✖ [0-9]+ problems" "$precommit_log"; then
262
+ echo " Full output:"
263
+ tail -30 "$precommit_log" | sed 's/^/ /'
264
+ fi
265
+ return 1
266
+ fi
267
+ fi
268
+
269
+ # Config-based checks are optional
270
+ if [[ ! -f "$config" ]]; then
271
+ echo " (no config.json for additional checks)"
272
+ return 0
273
+ fi
274
+
275
+ # Get list of check names (excluding 'test' which we run separately)
276
+ local check_names
277
+ check_names=$(jq -r '.checks | keys[] | select(. != "test")' "$config" 2>/dev/null)
278
+
279
+ if [[ -z "$check_names" ]]; then
280
+ echo " (no additional checks configured)"
281
+ return 0
282
+ fi
283
+
284
+ local all_passed=0
285
+
286
+ while IFS= read -r check_name; do
287
+ [[ -z "$check_name" ]] && continue
288
+
289
+ local cmd
290
+ cmd=$(jq -r ".checks[\"$check_name\"] // empty" "$config")
291
+
292
+ if [[ -z "$cmd" || "$cmd" == "null" ]]; then
293
+ continue
294
+ fi
295
+
296
+ # Check if command exists
297
+ local first_word
298
+ first_word=$(echo "$cmd" | awk '{print $1}')
299
+
300
+ if [[ "$first_word" == "cd" ]]; then
301
+ local actual_cmd
302
+ actual_cmd=$(echo "$cmd" | sed 's/.*&& *//' | awk '{print $1}')
303
+ if [[ -n "$actual_cmd" ]] && ! command -v "$actual_cmd" &>/dev/null; then
304
+ echo " Skipping $check_name ($actual_cmd not found)"
305
+ continue
306
+ fi
307
+ elif ! command -v "$first_word" &>/dev/null; then
308
+ echo " Skipping $check_name ($first_word not found)"
309
+ continue
310
+ fi
311
+
312
+ if ! run_check "$check_name" "$cmd"; then
313
+ all_passed=1
314
+ fi
315
+ done <<< "$check_names"
316
+
317
+ return $all_passed
318
+ }
319
+
320
+ # Run a single check
321
+ run_check() {
322
+ local name="$1"
323
+ local cmd="$2"
324
+ local log_file
325
+ log_file=$(create_temp_file ".log") || return 1
326
+
327
+ echo -n " $name... "
328
+
329
+ if safe_exec "$cmd" "$log_file"; then
330
+ print_success "passed"
331
+ rm -f "$log_file"
332
+ return 0
333
+ else
334
+ print_error "failed"
335
+ echo ""
336
+ echo " Output (last $MAX_LOG_LINES lines):"
337
+ tail -"$MAX_LOG_LINES" "$log_file" | sed 's/^/ /'
338
+ rm -f "$log_file"
339
+ return 1
340
+ fi
341
+ }
342
+
343
+ # Quick check command (subset of verification)
344
+ ralph_check() {
345
+ if [[ ! -f "$RALPH_DIR/config.json" ]]; then
346
+ print_error "Ralph not initialized. Run 'ralph init' first."
347
+ exit 1
348
+ fi
349
+
350
+ echo ""
351
+ print_info "=== Running Checks ==="
352
+ echo ""
353
+
354
+ if run_configured_checks; then
355
+ echo ""
356
+ print_success "All checks passed!"
357
+ return 0
358
+ else
359
+ echo ""
360
+ print_error "Some checks failed"
361
+ return 1
362
+ fi
363
+ }