agentic-loop 3.0.1 → 3.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +39 -187
- package/dist/checks/check-debug-statements.d.ts.map +1 -1
- package/dist/checks/check-debug-statements.js +5 -3
- package/dist/checks/check-debug-statements.js.map +1 -1
- package/dist/checks/check-hardcoded-urls.d.ts.map +1 -1
- package/dist/checks/check-hardcoded-urls.js +5 -0
- package/dist/checks/check-hardcoded-urls.js.map +1 -1
- package/dist/checks/check-secrets.d.ts.map +1 -1
- package/dist/checks/check-secrets.js +5 -0
- package/dist/checks/check-secrets.js.map +1 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +18 -4
- package/dist/cli.js.map +1 -1
- package/dist/utils/file-reader.d.ts +1 -1
- package/dist/utils/file-reader.d.ts.map +1 -1
- package/dist/utils/file-reader.js +29 -2
- package/dist/utils/file-reader.js.map +1 -1
- package/dist/utils/types.d.ts +7 -0
- package/dist/utils/types.d.ts.map +1 -1
- package/dist/utils/types.js +20 -1
- package/dist/utils/types.js.map +1 -1
- package/package.json +1 -1
- package/ralph/loop.sh +5 -0
- package/ralph/verify/lint.sh +402 -173
- package/ralph/verify.sh +66 -0
- package/templates/config/fullstack.json +5 -5
- package/templates/config/go.json +5 -3
- package/templates/config/minimal.json +7 -1
- package/templates/config/node.json +5 -3
- package/templates/config/python.json +5 -3
- package/templates/config/rust.json +5 -3
package/ralph/verify/lint.sh
CHANGED
|
@@ -47,9 +47,13 @@ run_auto_fix() {
|
|
|
47
47
|
fi
|
|
48
48
|
}
|
|
49
49
|
|
|
50
|
-
# Verify lint passes after auto-fix (catch
|
|
50
|
+
# Verify lint passes after auto-fix (catch remaining errors that need manual fix)
|
|
51
51
|
verify_lint() {
|
|
52
52
|
local failed=0
|
|
53
|
+
local lint_log="$RALPH_DIR/last_lint_failure.log"
|
|
54
|
+
|
|
55
|
+
# Clear previous lint failure log
|
|
56
|
+
rm -f "$lint_log"
|
|
53
57
|
|
|
54
58
|
# Python: ruff lint check
|
|
55
59
|
if command -v ruff &>/dev/null && [[ -f "pyproject.toml" || -f "ruff.toml" ]]; then
|
|
@@ -59,8 +63,14 @@ verify_lint() {
|
|
|
59
63
|
else
|
|
60
64
|
print_error "failed"
|
|
61
65
|
echo ""
|
|
62
|
-
echo "
|
|
63
|
-
|
|
66
|
+
echo " Lint errors (auto-fix couldn't resolve - Claude should fix these):"
|
|
67
|
+
local lint_output
|
|
68
|
+
lint_output=$(ruff check . 2>/dev/null | head -"$MAX_LINT_ERROR_LINES")
|
|
69
|
+
echo "$lint_output" | sed 's/^/ /'
|
|
70
|
+
{
|
|
71
|
+
echo "Lint errors in root directory:"
|
|
72
|
+
echo "$lint_output"
|
|
73
|
+
} >> "$lint_log"
|
|
64
74
|
failed=1
|
|
65
75
|
fi
|
|
66
76
|
fi
|
|
@@ -78,16 +88,288 @@ verify_lint() {
|
|
|
78
88
|
else
|
|
79
89
|
print_error "failed"
|
|
80
90
|
echo ""
|
|
81
|
-
echo "
|
|
82
|
-
|
|
91
|
+
echo " Lint errors in $api_dir (auto-fix couldn't resolve - Claude should fix these):"
|
|
92
|
+
local lint_output
|
|
93
|
+
lint_output=$(cd "$api_dir" && ruff check . 2>/dev/null | head -"$MAX_LINT_ERROR_LINES")
|
|
94
|
+
echo "$lint_output" | sed 's/^/ /'
|
|
95
|
+
{
|
|
96
|
+
echo ""
|
|
97
|
+
echo "Lint errors in $api_dir:"
|
|
98
|
+
echo "$lint_output"
|
|
99
|
+
} >> "$lint_log"
|
|
83
100
|
failed=1
|
|
84
101
|
fi
|
|
85
102
|
fi
|
|
86
103
|
done <<< "$api_dirs"
|
|
87
104
|
|
|
105
|
+
# JavaScript/TypeScript: ESLint check (root)
|
|
106
|
+
if [[ -f "package.json" ]] && command -v npx &>/dev/null; then
|
|
107
|
+
if grep -q '"eslint"' package.json 2>/dev/null || [[ -f ".eslintrc.js" ]] || [[ -f "eslint.config.js" ]]; then
|
|
108
|
+
echo -n " ESLint check... "
|
|
109
|
+
local eslint_output
|
|
110
|
+
if eslint_output=$(npx eslint . --max-warnings 0 2>&1); then
|
|
111
|
+
print_success "passed"
|
|
112
|
+
else
|
|
113
|
+
# Check if it's real errors or just warnings
|
|
114
|
+
if echo "$eslint_output" | grep -qE "✖ [0-9]+ problems? \([1-9]"; then
|
|
115
|
+
print_error "failed"
|
|
116
|
+
echo ""
|
|
117
|
+
echo " ESLint errors:"
|
|
118
|
+
echo "$eslint_output" | tail -"$MAX_LINT_ERROR_LINES" | sed 's/^/ /'
|
|
119
|
+
{
|
|
120
|
+
echo ""
|
|
121
|
+
echo "ESLint errors in root:"
|
|
122
|
+
echo "$eslint_output"
|
|
123
|
+
} >> "$lint_log"
|
|
124
|
+
failed=1
|
|
125
|
+
else
|
|
126
|
+
print_success "passed (warnings only)"
|
|
127
|
+
fi
|
|
128
|
+
fi
|
|
129
|
+
fi
|
|
130
|
+
fi
|
|
131
|
+
|
|
132
|
+
# Check frontend directories (monorepo support)
|
|
133
|
+
local fe_dirs
|
|
134
|
+
fe_dirs=$(get_frontend_dirs)
|
|
135
|
+
|
|
136
|
+
while IFS= read -r fe_dir; do
|
|
137
|
+
[[ -z "$fe_dir" ]] && continue
|
|
138
|
+
[[ ! -f "$fe_dir/package.json" ]] && continue
|
|
139
|
+
grep -q '"eslint"' "$fe_dir/package.json" 2>/dev/null || continue
|
|
140
|
+
|
|
141
|
+
echo -n " ESLint check ($fe_dir)... "
|
|
142
|
+
local eslint_output
|
|
143
|
+
if eslint_output=$(cd "$fe_dir" && npx eslint . --max-warnings 0 2>&1); then
|
|
144
|
+
print_success "passed"
|
|
145
|
+
else
|
|
146
|
+
if echo "$eslint_output" | grep -qE "✖ [0-9]+ problems? \([1-9]"; then
|
|
147
|
+
print_error "failed"
|
|
148
|
+
echo ""
|
|
149
|
+
echo " ESLint errors in $fe_dir:"
|
|
150
|
+
echo "$eslint_output" | tail -"$MAX_LINT_ERROR_LINES" | sed 's/^/ /'
|
|
151
|
+
{
|
|
152
|
+
echo ""
|
|
153
|
+
echo "ESLint errors in $fe_dir:"
|
|
154
|
+
echo "$eslint_output"
|
|
155
|
+
} >> "$lint_log"
|
|
156
|
+
failed=1
|
|
157
|
+
else
|
|
158
|
+
print_success "passed (warnings only)"
|
|
159
|
+
fi
|
|
160
|
+
fi
|
|
161
|
+
done <<< "$fe_dirs"
|
|
162
|
+
|
|
88
163
|
return $failed
|
|
89
164
|
}
|
|
90
165
|
|
|
166
|
+
# Verify TypeScript types compile
|
|
167
|
+
verify_typescript() {
|
|
168
|
+
local failed=0
|
|
169
|
+
local ts_log="$RALPH_DIR/last_typescript_failure.log"
|
|
170
|
+
|
|
171
|
+
# Clear previous failure log
|
|
172
|
+
rm -f "$ts_log"
|
|
173
|
+
|
|
174
|
+
# Check root tsconfig
|
|
175
|
+
if [[ -f "tsconfig.json" ]] && command -v npx &>/dev/null; then
|
|
176
|
+
echo -n " TypeScript typecheck... "
|
|
177
|
+
local ts_output
|
|
178
|
+
if ts_output=$(npx tsc --noEmit 2>&1); then
|
|
179
|
+
print_success "passed"
|
|
180
|
+
else
|
|
181
|
+
print_error "failed"
|
|
182
|
+
echo ""
|
|
183
|
+
echo " TypeScript errors:"
|
|
184
|
+
echo "$ts_output" | head -"$MAX_LINT_ERROR_LINES" | sed 's/^/ /'
|
|
185
|
+
# Save for retry context
|
|
186
|
+
{
|
|
187
|
+
echo "TypeScript errors in root:"
|
|
188
|
+
echo "$ts_output"
|
|
189
|
+
} >> "$ts_log"
|
|
190
|
+
failed=1
|
|
191
|
+
fi
|
|
192
|
+
fi
|
|
193
|
+
|
|
194
|
+
# Check frontend directories (monorepo support)
|
|
195
|
+
local fe_dirs
|
|
196
|
+
fe_dirs=$(get_frontend_dirs)
|
|
197
|
+
|
|
198
|
+
while IFS= read -r fe_dir; do
|
|
199
|
+
[[ -z "$fe_dir" ]] && continue
|
|
200
|
+
[[ ! -f "$fe_dir/tsconfig.json" ]] && continue
|
|
201
|
+
|
|
202
|
+
echo -n " TypeScript typecheck ($fe_dir)... "
|
|
203
|
+
local ts_output
|
|
204
|
+
if ts_output=$(cd "$fe_dir" && npx tsc --noEmit 2>&1); then
|
|
205
|
+
print_success "passed"
|
|
206
|
+
else
|
|
207
|
+
print_error "failed"
|
|
208
|
+
echo ""
|
|
209
|
+
echo " TypeScript errors in $fe_dir:"
|
|
210
|
+
echo "$ts_output" | head -"$MAX_LINT_ERROR_LINES" | sed 's/^/ /'
|
|
211
|
+
# Save for retry context
|
|
212
|
+
{
|
|
213
|
+
echo ""
|
|
214
|
+
echo "TypeScript errors in $fe_dir:"
|
|
215
|
+
echo "$ts_output"
|
|
216
|
+
} >> "$ts_log"
|
|
217
|
+
failed=1
|
|
218
|
+
fi
|
|
219
|
+
done <<< "$fe_dirs"
|
|
220
|
+
|
|
221
|
+
return $failed
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
# Verify npm build succeeds (catches bundling/SSR issues)
|
|
225
|
+
verify_build() {
|
|
226
|
+
local failed=0
|
|
227
|
+
local build_log="$RALPH_DIR/last_build_failure.log"
|
|
228
|
+
|
|
229
|
+
# Clear previous failure log
|
|
230
|
+
rm -f "$build_log"
|
|
231
|
+
|
|
232
|
+
# Check root package.json for build script
|
|
233
|
+
if [[ -f "package.json" ]] && grep -q '"build"' package.json 2>/dev/null; then
|
|
234
|
+
echo -n " npm build... "
|
|
235
|
+
local build_output
|
|
236
|
+
if build_output=$(npm run build 2>&1); then
|
|
237
|
+
print_success "passed"
|
|
238
|
+
else
|
|
239
|
+
print_error "failed"
|
|
240
|
+
echo ""
|
|
241
|
+
echo " Build errors:"
|
|
242
|
+
echo "$build_output" | tail -"$MAX_LINT_ERROR_LINES" | sed 's/^/ /'
|
|
243
|
+
# Save for retry context
|
|
244
|
+
{
|
|
245
|
+
echo "Build errors in root:"
|
|
246
|
+
echo "$build_output"
|
|
247
|
+
} >> "$build_log"
|
|
248
|
+
failed=1
|
|
249
|
+
fi
|
|
250
|
+
fi
|
|
251
|
+
|
|
252
|
+
# Check frontend directories (monorepo support)
|
|
253
|
+
local fe_dirs
|
|
254
|
+
fe_dirs=$(get_frontend_dirs)
|
|
255
|
+
|
|
256
|
+
while IFS= read -r fe_dir; do
|
|
257
|
+
[[ -z "$fe_dir" ]] && continue
|
|
258
|
+
[[ ! -f "$fe_dir/package.json" ]] && continue
|
|
259
|
+
# Skip if no build script
|
|
260
|
+
grep -q '"build"' "$fe_dir/package.json" 2>/dev/null || continue
|
|
261
|
+
|
|
262
|
+
echo -n " npm build ($fe_dir)... "
|
|
263
|
+
local build_output
|
|
264
|
+
if build_output=$(cd "$fe_dir" && npm run build 2>&1); then
|
|
265
|
+
print_success "passed"
|
|
266
|
+
else
|
|
267
|
+
print_error "failed"
|
|
268
|
+
echo ""
|
|
269
|
+
echo " Build errors in $fe_dir:"
|
|
270
|
+
echo "$build_output" | tail -"$MAX_LINT_ERROR_LINES" | sed 's/^/ /'
|
|
271
|
+
# Save for retry context
|
|
272
|
+
{
|
|
273
|
+
echo ""
|
|
274
|
+
echo "Build errors in $fe_dir:"
|
|
275
|
+
echo "$build_output"
|
|
276
|
+
} >> "$build_log"
|
|
277
|
+
failed=1
|
|
278
|
+
fi
|
|
279
|
+
done <<< "$fe_dirs"
|
|
280
|
+
|
|
281
|
+
return $failed
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
# Verify Go code compiles and passes vet
|
|
285
|
+
verify_go() {
|
|
286
|
+
local go_log="$RALPH_DIR/last_go_failure.log"
|
|
287
|
+
|
|
288
|
+
# Skip if not a Go project
|
|
289
|
+
[[ ! -f "go.mod" ]] && return 0
|
|
290
|
+
command -v go &>/dev/null || return 0
|
|
291
|
+
|
|
292
|
+
# Clear previous failure log
|
|
293
|
+
rm -f "$go_log"
|
|
294
|
+
|
|
295
|
+
# Go vet (catches common mistakes)
|
|
296
|
+
echo -n " Go vet... "
|
|
297
|
+
local vet_output
|
|
298
|
+
if vet_output=$(go vet ./... 2>&1); then
|
|
299
|
+
print_success "passed"
|
|
300
|
+
else
|
|
301
|
+
print_error "failed"
|
|
302
|
+
echo ""
|
|
303
|
+
echo " Go vet errors:"
|
|
304
|
+
echo "$vet_output" | head -"$MAX_LINT_ERROR_LINES" | sed 's/^/ /'
|
|
305
|
+
{
|
|
306
|
+
echo "Go vet errors:"
|
|
307
|
+
echo "$vet_output"
|
|
308
|
+
} >> "$go_log"
|
|
309
|
+
return 1
|
|
310
|
+
fi
|
|
311
|
+
|
|
312
|
+
# Go build (catches compile errors)
|
|
313
|
+
echo -n " Go build... "
|
|
314
|
+
local build_output
|
|
315
|
+
if build_output=$(go build ./... 2>&1); then
|
|
316
|
+
print_success "passed"
|
|
317
|
+
else
|
|
318
|
+
print_error "failed"
|
|
319
|
+
echo ""
|
|
320
|
+
echo " Go build errors:"
|
|
321
|
+
echo "$build_output" | head -"$MAX_LINT_ERROR_LINES" | sed 's/^/ /'
|
|
322
|
+
{
|
|
323
|
+
echo ""
|
|
324
|
+
echo "Go build errors:"
|
|
325
|
+
echo "$build_output"
|
|
326
|
+
} >> "$go_log"
|
|
327
|
+
return 1
|
|
328
|
+
fi
|
|
329
|
+
|
|
330
|
+
return 0
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
# Verify Rust code with clippy
|
|
334
|
+
verify_rust() {
|
|
335
|
+
local rust_log="$RALPH_DIR/last_rust_failure.log"
|
|
336
|
+
|
|
337
|
+
# Skip if not a Rust project
|
|
338
|
+
[[ ! -f "Cargo.toml" ]] && return 0
|
|
339
|
+
command -v cargo &>/dev/null || return 0
|
|
340
|
+
|
|
341
|
+
# Clear previous failure log
|
|
342
|
+
rm -f "$rust_log"
|
|
343
|
+
|
|
344
|
+
# Cargo clippy (Rust's official linter - catches more than cargo check)
|
|
345
|
+
echo -n " Cargo clippy... "
|
|
346
|
+
local clippy_output
|
|
347
|
+
if clippy_output=$(cargo clippy --all-targets --all-features -- -D warnings 2>&1); then
|
|
348
|
+
print_success "passed"
|
|
349
|
+
return 0
|
|
350
|
+
fi
|
|
351
|
+
|
|
352
|
+
# Check if clippy is installed
|
|
353
|
+
if echo "$clippy_output" | grep -q "can't find.*clippy"; then
|
|
354
|
+
echo -n "not installed, trying cargo check... "
|
|
355
|
+
if clippy_output=$(cargo check 2>&1); then
|
|
356
|
+
print_success "passed"
|
|
357
|
+
return 0
|
|
358
|
+
fi
|
|
359
|
+
fi
|
|
360
|
+
|
|
361
|
+
# Failed
|
|
362
|
+
print_error "failed"
|
|
363
|
+
echo ""
|
|
364
|
+
echo " Rust errors:"
|
|
365
|
+
echo "$clippy_output" | head -"$MAX_LINT_ERROR_LINES" | sed 's/^/ /'
|
|
366
|
+
{
|
|
367
|
+
echo "Rust errors:"
|
|
368
|
+
echo "$clippy_output"
|
|
369
|
+
} >> "$rust_log"
|
|
370
|
+
return 1
|
|
371
|
+
}
|
|
372
|
+
|
|
91
373
|
# Check FastAPI endpoints have Pydantic response models (for Swagger docs)
|
|
92
374
|
run_fastapi_response_check() {
|
|
93
375
|
# Use RALPH_LIB which points to the ralph/ directory
|
|
@@ -137,205 +419,152 @@ run_fastapi_response_check() {
|
|
|
137
419
|
return $failed
|
|
138
420
|
}
|
|
139
421
|
|
|
140
|
-
#
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
422
|
+
# Check if a verification step is enabled in config
|
|
423
|
+
# Values: true, false, "final" (only on last story)
|
|
424
|
+
check_enabled() {
|
|
425
|
+
local check_name="$1"
|
|
426
|
+
local default="${2:-true}"
|
|
427
|
+
local value
|
|
428
|
+
value=$(get_config ".checks.$check_name" "$default")
|
|
429
|
+
|
|
430
|
+
# Handle "final" - only run on last story
|
|
431
|
+
if [[ "$value" == "final" ]]; then
|
|
432
|
+
local remaining
|
|
433
|
+
remaining=$(jq '[.stories[] | select(.passes==false)] | length' "$RALPH_DIR/prd.json" 2>/dev/null || echo "1")
|
|
434
|
+
[[ "$remaining" -eq 1 ]]
|
|
435
|
+
return
|
|
150
436
|
fi
|
|
151
437
|
|
|
152
|
-
|
|
153
|
-
|
|
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
|
|
438
|
+
[[ "$value" == "true" ]]
|
|
439
|
+
}
|
|
210
440
|
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
# Real errors exist - fail
|
|
216
|
-
break
|
|
217
|
-
fi
|
|
441
|
+
# Run all checks based on config.json flags
|
|
442
|
+
run_configured_checks() {
|
|
443
|
+
# ALWAYS run auto-fix (harmless, just formats code)
|
|
444
|
+
run_auto_fix
|
|
218
445
|
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
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
|
|
446
|
+
# Lint check (ruff for Python, eslint for JS/TS)
|
|
447
|
+
if check_enabled "lint"; then
|
|
448
|
+
if ! verify_lint; then
|
|
449
|
+
return 1
|
|
450
|
+
fi
|
|
451
|
+
fi
|
|
243
452
|
|
|
244
|
-
|
|
245
|
-
|
|
453
|
+
# TypeScript type checking
|
|
454
|
+
if check_enabled "typecheck"; then
|
|
455
|
+
if ! verify_typescript; then
|
|
456
|
+
return 1
|
|
457
|
+
fi
|
|
458
|
+
fi
|
|
246
459
|
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
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
|
|
460
|
+
# Build verification (npm build, go build, cargo build)
|
|
461
|
+
if check_enabled "build"; then
|
|
462
|
+
if ! verify_build; then
|
|
463
|
+
return 1
|
|
464
|
+
fi
|
|
465
|
+
if ! verify_go; then
|
|
466
|
+
return 1
|
|
467
|
+
fi
|
|
468
|
+
if ! verify_rust; then
|
|
265
469
|
return 1
|
|
266
470
|
fi
|
|
267
471
|
fi
|
|
268
472
|
|
|
269
|
-
#
|
|
270
|
-
if
|
|
271
|
-
|
|
272
|
-
return 0
|
|
473
|
+
# FastAPI response model check
|
|
474
|
+
if check_enabled "fastapi" "false"; then
|
|
475
|
+
run_fastapi_response_check
|
|
273
476
|
fi
|
|
274
477
|
|
|
275
|
-
#
|
|
276
|
-
|
|
277
|
-
check_names=$(jq -r '.checks | keys[] | select(. != "test")' "$config" 2>/dev/null)
|
|
478
|
+
# Run pre-commit hooks if available (catches errors before commit attempt)
|
|
479
|
+
run_precommit_hooks
|
|
278
480
|
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
return 0
|
|
282
|
-
fi
|
|
481
|
+
return 0
|
|
482
|
+
}
|
|
283
483
|
|
|
284
|
-
|
|
484
|
+
# Run pre-commit hooks with auto-fix retry logic
|
|
485
|
+
run_precommit_hooks() {
|
|
486
|
+
# Skip if pre-commit not available
|
|
487
|
+
command -v pre-commit &>/dev/null || return 0
|
|
488
|
+
[[ -f ".pre-commit-config.yaml" ]] || return 0
|
|
285
489
|
|
|
286
|
-
|
|
287
|
-
|
|
490
|
+
echo -n " pre-commit hooks... "
|
|
491
|
+
local precommit_log="$RALPH_DIR/last_precommit_failure.log"
|
|
288
492
|
|
|
289
|
-
|
|
290
|
-
|
|
493
|
+
# Helper function: check if pre-commit output has REAL errors
|
|
494
|
+
has_real_errors() {
|
|
495
|
+
local log_file="$1"
|
|
291
496
|
|
|
292
|
-
|
|
293
|
-
|
|
497
|
+
# Check for actual error indicators (not warnings-only)
|
|
498
|
+
if grep -qE "^error:|: error:|Error:|SyntaxError|TypeError|NameError" "$log_file" 2>/dev/null; then
|
|
499
|
+
return 0
|
|
294
500
|
fi
|
|
295
501
|
|
|
296
|
-
# Check if
|
|
297
|
-
|
|
298
|
-
|
|
502
|
+
# Check ESLint output - fail only if errors > 0
|
|
503
|
+
if grep -qE "✖ [0-9]+ problems? \([1-9][0-9]* errors?" "$log_file" 2>/dev/null; then
|
|
504
|
+
return 0
|
|
505
|
+
fi
|
|
299
506
|
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
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
|
|
507
|
+
# Check ruff output - actual errors have file:line:col: error pattern
|
|
508
|
+
if grep -qE "^[^:]+:[0-9]+:[0-9]+: [EF][0-9]+" "$log_file" 2>/dev/null; then
|
|
509
|
+
return 0
|
|
310
510
|
fi
|
|
311
511
|
|
|
312
|
-
|
|
313
|
-
|
|
512
|
+
return 1
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
# Run pre-commit up to 3 times to handle auto-fix chains
|
|
516
|
+
local max_attempts=3
|
|
517
|
+
local attempt=1
|
|
518
|
+
local passed=false
|
|
519
|
+
|
|
520
|
+
while [[ $attempt -le $max_attempts ]]; do
|
|
521
|
+
if pre-commit run --all-files > "$precommit_log" 2>&1; then
|
|
522
|
+
passed=true
|
|
523
|
+
break
|
|
314
524
|
fi
|
|
315
|
-
done <<< "$check_names"
|
|
316
525
|
|
|
317
|
-
|
|
318
|
-
|
|
526
|
+
# Check if failure is due to file modifications (auto-fix)
|
|
527
|
+
if grep -q "files were modified by this hook" "$precommit_log"; then
|
|
528
|
+
if has_real_errors "$precommit_log"; then
|
|
529
|
+
break # Real errors exist
|
|
530
|
+
fi
|
|
319
531
|
|
|
320
|
-
#
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
532
|
+
# Only file modifications - stage and retry
|
|
533
|
+
if [[ $attempt -lt $max_attempts ]]; then
|
|
534
|
+
echo -n "auto-fixing (attempt $attempt)... "
|
|
535
|
+
git add -A 2>/dev/null || true
|
|
536
|
+
((attempt++))
|
|
537
|
+
continue
|
|
538
|
+
else
|
|
539
|
+
git add -A 2>/dev/null || true
|
|
540
|
+
passed=true
|
|
541
|
+
break
|
|
542
|
+
fi
|
|
543
|
+
else
|
|
544
|
+
if has_real_errors "$precommit_log"; then
|
|
545
|
+
break
|
|
546
|
+
else
|
|
547
|
+
passed=true
|
|
548
|
+
break
|
|
549
|
+
fi
|
|
550
|
+
fi
|
|
326
551
|
|
|
327
|
-
|
|
552
|
+
((attempt++))
|
|
553
|
+
done
|
|
328
554
|
|
|
329
|
-
if
|
|
330
|
-
print_success "passed"
|
|
331
|
-
rm -f "$
|
|
555
|
+
if [[ "$passed" == "true" ]]; then
|
|
556
|
+
[[ $attempt -gt 1 ]] && print_success "passed (after auto-fix)" || print_success "passed"
|
|
557
|
+
rm -f "$precommit_log"
|
|
332
558
|
return 0
|
|
333
559
|
else
|
|
334
560
|
print_error "failed"
|
|
335
561
|
echo ""
|
|
336
|
-
echo "
|
|
337
|
-
|
|
338
|
-
|
|
562
|
+
echo " Pre-commit hook errors:"
|
|
563
|
+
grep -E "^error:|: error:|Error:|SyntaxError|✖ [0-9]+ problems|^[^:]+:[0-9]+:[0-9]+:" "$precommit_log" 2>/dev/null | head -"$MAX_ERROR_PREVIEW_LINES" | sed 's/^/ /'
|
|
564
|
+
if ! grep -qE "^error:|: error:|Error:|SyntaxError|✖ [0-9]+ problems" "$precommit_log" 2>/dev/null; then
|
|
565
|
+
echo " Full output:"
|
|
566
|
+
tail -30 "$precommit_log" | sed 's/^/ /'
|
|
567
|
+
fi
|
|
339
568
|
return 1
|
|
340
569
|
fi
|
|
341
570
|
}
|