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.
@@ -47,9 +47,13 @@ run_auto_fix() {
47
47
  fi
48
48
  }
49
49
 
50
- # Verify lint passes after auto-fix (catch unfixable errors)
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 " Unfixable lint errors:"
63
- ruff check . 2>/dev/null | head -"$MAX_LINT_ERROR_LINES" | sed 's/^/ /'
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 " Unfixable lint errors in $api_dir:"
82
- (cd "$api_dir" && ruff check . 2>/dev/null) | head -"$MAX_LINT_ERROR_LINES" | sed 's/^/ /'
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
- # 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
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
- # 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
438
+ [[ "$value" == "true" ]]
439
+ }
210
440
 
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
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
- # 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
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
- ((attempt++))
245
- done
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
- 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
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
- # Config-based checks are optional
270
- if [[ ! -f "$config" ]]; then
271
- echo " (no config.json for additional checks)"
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
- # 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)
478
+ # Run pre-commit hooks if available (catches errors before commit attempt)
479
+ run_precommit_hooks
278
480
 
279
- if [[ -z "$check_names" ]]; then
280
- echo " (no additional checks configured)"
281
- return 0
282
- fi
481
+ return 0
482
+ }
283
483
 
284
- local all_passed=0
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
- while IFS= read -r check_name; do
287
- [[ -z "$check_name" ]] && continue
490
+ echo -n " pre-commit hooks... "
491
+ local precommit_log="$RALPH_DIR/last_precommit_failure.log"
288
492
 
289
- local cmd
290
- cmd=$(jq -r ".checks[\"$check_name\"] // empty" "$config")
493
+ # Helper function: check if pre-commit output has REAL errors
494
+ has_real_errors() {
495
+ local log_file="$1"
291
496
 
292
- if [[ -z "$cmd" || "$cmd" == "null" ]]; then
293
- continue
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 command exists
297
- local first_word
298
- first_word=$(echo "$cmd" | awk '{print $1}')
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
- 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
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
- if ! run_check "$check_name" "$cmd"; then
313
- all_passed=1
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
- return $all_passed
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
- # 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
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
- echo -n " $name... "
552
+ ((attempt++))
553
+ done
328
554
 
329
- if safe_exec "$cmd" "$log_file"; then
330
- print_success "passed"
331
- rm -f "$log_file"
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 " Output (last $MAX_LOG_LINES lines):"
337
- tail -"$MAX_LOG_LINES" "$log_file" | sed 's/^/ /'
338
- rm -f "$log_file"
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
  }