thrivekit 2.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.
- package/.claude/commands/explain.md +114 -0
- package/.claude/commands/idea.md +370 -0
- package/.claude/commands/my-dna.md +122 -0
- package/.claude/commands/prd.md +286 -0
- package/.claude/commands/review.md +167 -0
- package/.claude/commands/sign.md +32 -0
- package/.claude/commands/styleguide.md +450 -0
- package/.claude/commands/tour.md +301 -0
- package/.claude/commands/vibe-check.md +116 -0
- package/.claude/commands/vibe-help.md +47 -0
- package/.claude/commands/vibe-list.md +203 -0
- package/.claude/settings.json +75 -0
- package/.claude/settings.local.json +12 -0
- package/.pre-commit-hooks.yaml +102 -0
- package/LICENSE +21 -0
- package/README.md +214 -0
- package/bin/postinstall.sh +29 -0
- package/bin/ralph.sh +171 -0
- package/bin/thrivekit.sh +24 -0
- package/bin/vibe-check.js +19 -0
- package/dist/checks/check-any-types.d.ts +6 -0
- package/dist/checks/check-any-types.d.ts.map +1 -0
- package/dist/checks/check-any-types.js +73 -0
- package/dist/checks/check-any-types.js.map +1 -0
- package/dist/checks/check-commented-code.d.ts +6 -0
- package/dist/checks/check-commented-code.d.ts.map +1 -0
- package/dist/checks/check-commented-code.js +81 -0
- package/dist/checks/check-commented-code.js.map +1 -0
- package/dist/checks/check-console-error.d.ts +6 -0
- package/dist/checks/check-console-error.d.ts.map +1 -0
- package/dist/checks/check-console-error.js +41 -0
- package/dist/checks/check-console-error.js.map +1 -0
- package/dist/checks/check-debug-statements.d.ts +6 -0
- package/dist/checks/check-debug-statements.d.ts.map +1 -0
- package/dist/checks/check-debug-statements.js +120 -0
- package/dist/checks/check-debug-statements.js.map +1 -0
- package/dist/checks/check-deep-nesting.d.ts +6 -0
- package/dist/checks/check-deep-nesting.d.ts.map +1 -0
- package/dist/checks/check-deep-nesting.js +116 -0
- package/dist/checks/check-deep-nesting.js.map +1 -0
- package/dist/checks/check-docker-platform.d.ts +6 -0
- package/dist/checks/check-docker-platform.d.ts.map +1 -0
- package/dist/checks/check-docker-platform.js +42 -0
- package/dist/checks/check-docker-platform.js.map +1 -0
- package/dist/checks/check-dry-violations.d.ts +6 -0
- package/dist/checks/check-dry-violations.d.ts.map +1 -0
- package/dist/checks/check-dry-violations.js +124 -0
- package/dist/checks/check-dry-violations.js.map +1 -0
- package/dist/checks/check-empty-catch.d.ts +6 -0
- package/dist/checks/check-empty-catch.d.ts.map +1 -0
- package/dist/checks/check-empty-catch.js +111 -0
- package/dist/checks/check-empty-catch.js.map +1 -0
- package/dist/checks/check-function-length.d.ts +6 -0
- package/dist/checks/check-function-length.d.ts.map +1 -0
- package/dist/checks/check-function-length.js +152 -0
- package/dist/checks/check-function-length.js.map +1 -0
- package/dist/checks/check-hardcoded-ai-models.d.ts +10 -0
- package/dist/checks/check-hardcoded-ai-models.d.ts.map +1 -0
- package/dist/checks/check-hardcoded-ai-models.js +102 -0
- package/dist/checks/check-hardcoded-ai-models.js.map +1 -0
- package/dist/checks/check-hardcoded-urls.d.ts +6 -0
- package/dist/checks/check-hardcoded-urls.d.ts.map +1 -0
- package/dist/checks/check-hardcoded-urls.js +124 -0
- package/dist/checks/check-hardcoded-urls.js.map +1 -0
- package/dist/checks/check-magic-numbers.d.ts +6 -0
- package/dist/checks/check-magic-numbers.d.ts.map +1 -0
- package/dist/checks/check-magic-numbers.js +116 -0
- package/dist/checks/check-magic-numbers.js.map +1 -0
- package/dist/checks/check-secrets.d.ts +6 -0
- package/dist/checks/check-secrets.d.ts.map +1 -0
- package/dist/checks/check-secrets.js +138 -0
- package/dist/checks/check-secrets.js.map +1 -0
- package/dist/checks/check-snake-case-ts.d.ts +6 -0
- package/dist/checks/check-snake-case-ts.d.ts.map +1 -0
- package/dist/checks/check-snake-case-ts.js +78 -0
- package/dist/checks/check-snake-case-ts.js.map +1 -0
- package/dist/checks/check-todo-fixme.d.ts +6 -0
- package/dist/checks/check-todo-fixme.d.ts.map +1 -0
- package/dist/checks/check-todo-fixme.js +41 -0
- package/dist/checks/check-todo-fixme.js.map +1 -0
- package/dist/checks/check-unsafe-html.d.ts +6 -0
- package/dist/checks/check-unsafe-html.d.ts.map +1 -0
- package/dist/checks/check-unsafe-html.js +101 -0
- package/dist/checks/check-unsafe-html.js.map +1 -0
- package/dist/checks/index.d.ts +30 -0
- package/dist/checks/index.d.ts.map +1 -0
- package/dist/checks/index.js +57 -0
- package/dist/checks/index.js.map +1 -0
- package/dist/cli.d.ts +13 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +206 -0
- package/dist/cli.js.map +1 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +10 -0
- package/dist/index.js.map +1 -0
- package/dist/utils/file-reader.d.ts +24 -0
- package/dist/utils/file-reader.d.ts.map +1 -0
- package/dist/utils/file-reader.js +140 -0
- package/dist/utils/file-reader.js.map +1 -0
- package/dist/utils/patterns.d.ts +27 -0
- package/dist/utils/patterns.d.ts.map +1 -0
- package/dist/utils/patterns.js +84 -0
- package/dist/utils/patterns.js.map +1 -0
- package/dist/utils/reporters.d.ts +21 -0
- package/dist/utils/reporters.d.ts.map +1 -0
- package/dist/utils/reporters.js +115 -0
- package/dist/utils/reporters.js.map +1 -0
- package/dist/utils/types.d.ts +71 -0
- package/dist/utils/types.d.ts.map +1 -0
- package/dist/utils/types.js +5 -0
- package/dist/utils/types.js.map +1 -0
- package/package.json +82 -0
- package/ralph/api.sh +210 -0
- package/ralph/backup.sh +838 -0
- package/ralph/browser-verify/README.md +135 -0
- package/ralph/browser-verify/verify.ts +450 -0
- package/ralph/checks/check-fastapi-responses.py +155 -0
- package/ralph/hooks/hooks-config.json +72 -0
- package/ralph/hooks/inject-context.sh +44 -0
- package/ralph/hooks/install.sh +207 -0
- package/ralph/hooks/log-tools.sh +45 -0
- package/ralph/hooks/protect-prd.sh +27 -0
- package/ralph/hooks/save-learnings.sh +36 -0
- package/ralph/hooks/warn-debug.sh +54 -0
- package/ralph/hooks/warn-empty-catch.sh +63 -0
- package/ralph/hooks/warn-secrets.sh +89 -0
- package/ralph/hooks/warn-urls.sh +77 -0
- package/ralph/init.sh +388 -0
- package/ralph/loop.sh +570 -0
- package/ralph/playwright.sh +238 -0
- package/ralph/prd.sh +295 -0
- package/ralph/setup/feature-tour.sh +155 -0
- package/ralph/setup/quick-setup.sh +239 -0
- package/ralph/setup/tutorial.sh +159 -0
- package/ralph/setup/ui.sh +136 -0
- package/ralph/setup.sh +353 -0
- package/ralph/signs.sh +150 -0
- package/ralph/utils.sh +682 -0
- package/ralph/verify/browser.sh +324 -0
- package/ralph/verify/lint.sh +363 -0
- package/ralph/verify/review.sh +164 -0
- package/ralph/verify/tests.sh +81 -0
- package/ralph/verify.sh +224 -0
- package/templates/PROMPT.md +235 -0
- package/templates/config/fullstack.json +86 -0
- package/templates/config/go.json +81 -0
- package/templates/config/minimal.json +76 -0
- package/templates/config/node.json +81 -0
- package/templates/config/python.json +81 -0
- package/templates/config/rust.json +81 -0
- package/templates/examples/CLAUDE-django.md +174 -0
- package/templates/examples/CLAUDE-fastapi.md +270 -0
- package/templates/examples/CLAUDE-fastmcp.md +352 -0
- package/templates/examples/CLAUDE-fullstack.md +256 -0
- package/templates/examples/CLAUDE-node.md +246 -0
- package/templates/examples/CLAUDE-react.md +138 -0
- package/templates/optional/cursorrules.template +147 -0
- package/templates/optional/eslint.config.js +34 -0
- package/templates/optional/lint-staged.config.js +34 -0
- package/templates/optional/ruff.toml +125 -0
- package/templates/optional/vibe-check.yml +116 -0
- package/templates/optional/vscode-settings.json +127 -0
- 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
|
+
}
|