sequant 1.12.0 → 1.13.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 (48) hide show
  1. package/README.md +10 -8
  2. package/dist/bin/cli.js +12 -9
  3. package/dist/src/commands/doctor.js +25 -20
  4. package/dist/src/commands/init.js +152 -65
  5. package/dist/src/commands/logs.js +7 -6
  6. package/dist/src/commands/run.d.ts +13 -1
  7. package/dist/src/commands/run.js +75 -12
  8. package/dist/src/commands/stats.js +67 -48
  9. package/dist/src/commands/status.js +30 -12
  10. package/dist/src/index.d.ts +6 -0
  11. package/dist/src/index.js +4 -0
  12. package/dist/src/lib/cli-ui.d.ts +196 -0
  13. package/dist/src/lib/cli-ui.js +544 -0
  14. package/dist/src/lib/content-analyzer.d.ts +89 -0
  15. package/dist/src/lib/content-analyzer.js +437 -0
  16. package/dist/src/lib/phase-signal.d.ts +94 -0
  17. package/dist/src/lib/phase-signal.js +171 -0
  18. package/dist/src/lib/solve-comment-parser.d.ts +84 -0
  19. package/dist/src/lib/solve-comment-parser.js +200 -0
  20. package/dist/src/lib/stack-config.d.ts +51 -0
  21. package/dist/src/lib/stack-config.js +77 -0
  22. package/dist/src/lib/stacks.d.ts +52 -0
  23. package/dist/src/lib/stacks.js +173 -0
  24. package/dist/src/lib/templates.d.ts +2 -0
  25. package/dist/src/lib/templates.js +5 -2
  26. package/dist/src/lib/upstream/assessment.d.ts +70 -0
  27. package/dist/src/lib/upstream/assessment.js +385 -0
  28. package/dist/src/lib/upstream/index.d.ts +11 -0
  29. package/dist/src/lib/upstream/index.js +14 -0
  30. package/dist/src/lib/upstream/issues.d.ts +38 -0
  31. package/dist/src/lib/upstream/issues.js +267 -0
  32. package/dist/src/lib/upstream/relevance.d.ts +50 -0
  33. package/dist/src/lib/upstream/relevance.js +209 -0
  34. package/dist/src/lib/upstream/report.d.ts +29 -0
  35. package/dist/src/lib/upstream/report.js +391 -0
  36. package/dist/src/lib/upstream/types.d.ts +207 -0
  37. package/dist/src/lib/upstream/types.js +5 -0
  38. package/dist/src/lib/workflow/log-writer.d.ts +1 -1
  39. package/dist/src/lib/workflow/metrics-schema.d.ts +3 -3
  40. package/dist/src/lib/workflow/qa-cache.d.ts +199 -0
  41. package/dist/src/lib/workflow/qa-cache.js +440 -0
  42. package/dist/src/lib/workflow/run-log-schema.d.ts +34 -6
  43. package/dist/src/lib/workflow/run-log-schema.js +12 -1
  44. package/dist/src/lib/workflow/state-schema.d.ts +4 -4
  45. package/dist/src/lib/workflow/types.d.ts +4 -0
  46. package/package.json +6 -1
  47. package/templates/skills/qa/scripts/quality-checks.sh +509 -53
  48. package/templates/skills/spec/SKILL.md +107 -5
@@ -1,33 +1,196 @@
1
1
  #!/bin/bash
2
2
  # Quality checks script for /qa command
3
3
  # Run these checks before detailed review
4
+ #
5
+ # Supports caching to skip unchanged checks on re-run:
6
+ # --use-cache Enable caching (default when cache exists)
7
+ # --no-cache Force fresh run, ignore cache (AC-3)
8
+ # --cache-dir Custom cache directory (default: .sequant/.cache/qa)
9
+ # --cache-key Custom cache key prefix
4
10
 
5
11
  set -e
6
12
 
13
+ # =============================================================================
14
+ # Configuration
15
+ # =============================================================================
16
+
17
+ USE_CACHE=true
18
+ CACHE_DIR=".sequant/.cache/qa"
19
+ CACHE_KEY=""
20
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
21
+
22
+ # Parse command line arguments
23
+ while [[ $# -gt 0 ]]; do
24
+ case $1 in
25
+ --use-cache)
26
+ USE_CACHE=true
27
+ shift
28
+ ;;
29
+ --no-cache)
30
+ USE_CACHE=false
31
+ shift
32
+ ;;
33
+ --cache-dir)
34
+ CACHE_DIR="$2"
35
+ shift 2
36
+ ;;
37
+ --cache-key)
38
+ CACHE_KEY="$2"
39
+ shift 2
40
+ ;;
41
+ *)
42
+ shift
43
+ ;;
44
+ esac
45
+ done
46
+
47
+ # Resolve script directory for cache CLI
48
+ # Try multiple paths since we might be in a worktree or main repo
49
+ CACHE_CLI=""
50
+ if [[ -f "$SCRIPT_DIR/../../../scripts/qa/qa-cache-cli.ts" ]]; then
51
+ CACHE_CLI="$SCRIPT_DIR/../../../scripts/qa/qa-cache-cli.ts"
52
+ elif [[ -f "scripts/qa/qa-cache-cli.ts" ]]; then
53
+ CACHE_CLI="scripts/qa/qa-cache-cli.ts"
54
+ fi
55
+
56
+ # =============================================================================
57
+ # Cache Helper Functions
58
+ # =============================================================================
59
+
60
+ # Check if a check has valid cached result
61
+ # Returns: 0 = cache hit, 1 = cache miss
62
+ cache_check() {
63
+ local check_type=$1
64
+
65
+ if [[ "$USE_CACHE" != "true" ]] || [[ -z "$CACHE_CLI" ]]; then
66
+ return 1
67
+ fi
68
+
69
+ if npx tsx "$CACHE_CLI" check "$check_type" 2>/dev/null | grep -q "^HIT"; then
70
+ return 0
71
+ else
72
+ return 1
73
+ fi
74
+ }
75
+
76
+ # Get cached result for a check
77
+ cache_get() {
78
+ local check_type=$1
79
+
80
+ if [[ "$USE_CACHE" != "true" ]] || [[ -z "$CACHE_CLI" ]]; then
81
+ return 1
82
+ fi
83
+
84
+ npx tsx "$CACHE_CLI" get "$check_type" 2>/dev/null
85
+ }
86
+
87
+ # Cache a check result
88
+ cache_set() {
89
+ local check_type=$1
90
+ local passed=$2
91
+ local message=$3
92
+ local details=${4:-"{}"}
93
+
94
+ if [[ "$USE_CACHE" != "true" ]] || [[ -z "$CACHE_CLI" ]]; then
95
+ return 0
96
+ fi
97
+
98
+ echo "{\"passed\":$passed,\"message\":\"$message\",\"details\":$details}" | \
99
+ npx tsx "$CACHE_CLI" set "$check_type" 2>/dev/null || true
100
+ }
101
+
102
+ # Print cache status report (AC-4)
103
+ print_cache_status() {
104
+ if [[ "$USE_CACHE" != "true" ]] || [[ -z "$CACHE_CLI" ]]; then
105
+ echo "Cache: Disabled"
106
+ return
107
+ fi
108
+
109
+ echo ""
110
+ echo "### Cache Status"
111
+ echo ""
112
+ npx tsx "$CACHE_CLI" status 2>/dev/null || echo "Cache: Error reading status"
113
+ }
114
+
115
+ # =============================================================================
116
+ # Main Script
117
+ # =============================================================================
118
+
7
119
  echo "🔍 Running automated quality checks..."
120
+ if [[ "$USE_CACHE" == "true" && -n "$CACHE_CLI" ]]; then
121
+ echo " Cache: Enabled (use --no-cache to force fresh run)"
122
+ else
123
+ echo " Cache: Disabled"
124
+ fi
8
125
  echo ""
9
126
 
127
+ # Track cache hits/misses for final report
128
+ declare -A CACHE_STATUS
129
+
130
+ # =============================================================================
10
131
  # 1. Type safety check - detect 'any' type usage
11
- type_issues=$(git diff main...HEAD | grep -E ":\s*any[,)]|as any" | wc -l | xargs)
12
- if [[ $type_issues -gt 0 ]]; then
13
- echo "⚠️ WARNING: $type_issues potential 'any' type usages"
132
+ # =============================================================================
133
+ if cache_check "type-safety"; then
134
+ CACHE_STATUS["type-safety"]="HIT"
135
+ cached_result=$(cache_get "type-safety")
136
+ type_passed=$(echo "$cached_result" | grep -o '"passed":\s*[^,}]*' | cut -d: -f2 | tr -d ' ')
137
+ type_message=$(echo "$cached_result" | grep -o '"message":\s*"[^"]*"' | cut -d'"' -f4)
138
+
139
+ if [[ "$type_passed" == "true" ]]; then
140
+ echo "✅ Type safety: $type_message (cached)"
141
+ else
142
+ type_issues=$(echo "$cached_result" | grep -o '"count":\s*[0-9]*' | cut -d: -f2 | tr -d ' ')
143
+ echo "⚠️ WARNING: $type_issues potential 'any' type usages (cached)"
144
+ fi
14
145
  else
15
- echo "✅ Type safety: No 'any' type additions"
146
+ CACHE_STATUS["type-safety"]="MISS"
147
+ type_issues=$(git diff main...HEAD | grep -E ":\s*any[,)]|as any" | wc -l | xargs)
148
+ if [[ $type_issues -gt 0 ]]; then
149
+ echo "⚠️ WARNING: $type_issues potential 'any' type usages"
150
+ cache_set "type-safety" false "Found $type_issues potential 'any' type usages" "{\"count\":$type_issues}"
151
+ else
152
+ echo "✅ Type safety: No 'any' type additions"
153
+ cache_set "type-safety" true "No 'any' type additions" "{\"count\":0}"
154
+ fi
16
155
  fi
17
156
 
157
+ # =============================================================================
18
158
  # 2. Deleted tests check
19
- deleted_tests=$(git diff main...HEAD --diff-filter=D --name-only | grep -E "\\.test\\.|\\spec\\." | wc -l | xargs)
20
- if [[ $deleted_tests -gt 0 ]]; then
21
- echo "❌ BLOCKER: $deleted_tests test files deleted"
159
+ # =============================================================================
160
+ if cache_check "deleted-tests"; then
161
+ CACHE_STATUS["deleted-tests"]="HIT"
162
+ cached_result=$(cache_get "deleted-tests")
163
+ tests_passed=$(echo "$cached_result" | grep -o '"passed":\s*[^,}]*' | cut -d: -f2 | tr -d ' ')
164
+
165
+ if [[ "$tests_passed" == "true" ]]; then
166
+ echo "✅ Test coverage: No test files deleted (cached)"
167
+ else
168
+ deleted_count=$(echo "$cached_result" | grep -o '"count":\s*[0-9]*' | cut -d: -f2 | tr -d ' ')
169
+ echo "❌ BLOCKER: $deleted_count test files deleted (cached)"
170
+ fi
22
171
  else
23
- echo "✅ Test coverage: No test files deleted"
172
+ CACHE_STATUS["deleted-tests"]="MISS"
173
+ deleted_tests=$(git diff main...HEAD --diff-filter=D --name-only | grep -E "\\.test\\.|\\spec\\." | wc -l | xargs)
174
+ if [[ $deleted_tests -gt 0 ]]; then
175
+ echo "❌ BLOCKER: $deleted_tests test files deleted"
176
+ cache_set "deleted-tests" false "Found $deleted_tests deleted test files" "{\"count\":$deleted_tests}"
177
+ else
178
+ echo "✅ Test coverage: No test files deleted"
179
+ cache_set "deleted-tests" true "No test files deleted" "{\"count\":0}"
180
+ fi
24
181
  fi
25
182
 
26
- # 3. Scope check - files changed
183
+ # =============================================================================
184
+ # 3. Scope check - files changed (always fresh - cheap operation)
185
+ # =============================================================================
186
+ CACHE_STATUS["scope"]="SKIP"
27
187
  files_changed=$(git diff main...HEAD --name-only | wc -l | xargs)
28
188
  echo "📊 Files changed: $files_changed"
29
189
 
30
- # 4. Size check - LOC added/removed
190
+ # =============================================================================
191
+ # 4. Size check - LOC added/removed (always fresh - cheap operation)
192
+ # =============================================================================
193
+ CACHE_STATUS["size"]="SKIP"
31
194
  additions=$(git diff main...HEAD --numstat | awk '{sum+=$1} END {print sum+0}')
32
195
  deletions=$(git diff main...HEAD --numstat | awk '{sum+=$2} END {print sum+0}')
33
196
  net_change=$((additions - deletions))
@@ -45,7 +208,9 @@ else
45
208
  echo "❌ Size: Very large (>500 net LOC) - may indicate scope creep"
46
209
  fi
47
210
 
48
- # 6. Database access check (admin pages should use proper access controls)
211
+ # =============================================================================
212
+ # 6. Database access check (always fresh - cheap operation)
213
+ # =============================================================================
49
214
  echo ""
50
215
  echo "🔒 Checking database access patterns..."
51
216
  admin_files=$(git diff main...HEAD --name-only | grep -E "^app/admin/" || true)
@@ -56,7 +221,9 @@ else
56
221
  echo " No admin files modified"
57
222
  fi
58
223
 
224
+ # =============================================================================
59
225
  # 7. Integration check - verify new exports are imported somewhere
226
+ # =============================================================================
60
227
  echo ""
61
228
  echo "🔌 Checking integration of new exports..."
62
229
  new_files=$(git diff main...HEAD --name-only --diff-filter=A | grep -E "\.(ts|tsx)$" || true)
@@ -83,68 +250,357 @@ else
83
250
  echo " No new TypeScript files added"
84
251
  fi
85
252
 
86
- # 8. Security scan - OWASP vulnerability checks
253
+ # =============================================================================
254
+ # 8. Security scan - OWASP vulnerability checks (cacheable)
255
+ # =============================================================================
87
256
  echo ""
88
257
  echo "🔒 Running security scan..."
89
- if command -v npx &> /dev/null; then
90
- npx tsx scripts/lib/__tests__/run-security-scan.ts 2>/dev/null || echo " Security scanner not available, skipping..."
258
+ if cache_check "security"; then
259
+ CACHE_STATUS["security"]="HIT"
260
+ cached_result=$(cache_get "security")
261
+ security_passed=$(echo "$cached_result" | grep -o '"passed":\s*[^,}]*' | cut -d: -f2 | tr -d ' ')
262
+ security_message=$(echo "$cached_result" | grep -o '"message":\s*"[^"]*"' | cut -d'"' -f4)
263
+
264
+ if [[ "$security_passed" == "true" ]]; then
265
+ echo " ✅ Security scan: $security_message (cached)"
266
+ else
267
+ echo " ⚠️ Security scan: $security_message (cached)"
268
+ fi
91
269
  else
92
- echo " npx not available, skipping security scan"
270
+ CACHE_STATUS["security"]="MISS"
271
+ if command -v npx &> /dev/null; then
272
+ security_output=$(npx tsx scripts/lib/__tests__/run-security-scan.ts 2>&1) || security_exit=$?
273
+ if [[ -z "$security_exit" || "$security_exit" -eq 0 ]]; then
274
+ echo " ✅ Security scan: Passed"
275
+ cache_set "security" true "Passed"
276
+ else
277
+ echo " ⚠️ Security scan: Issues found"
278
+ echo "$security_output" | head -5
279
+ cache_set "security" false "Issues found"
280
+ fi
281
+ else
282
+ echo " Security scanner not available, skipping..."
283
+ CACHE_STATUS["security"]="SKIP"
284
+ fi
93
285
  fi
94
286
 
95
- # 9. Semgrep static analysis (optional - graceful skip if not installed)
287
+ # =============================================================================
288
+ # 9. Semgrep static analysis (cacheable - expensive operation)
289
+ # =============================================================================
96
290
  echo ""
97
291
  echo "🔍 Running Semgrep static analysis..."
98
292
 
99
- # Check if Semgrep is available
100
- semgrep_available=false
101
- if command -v semgrep &> /dev/null; then
102
- semgrep_available=true
103
- semgrep_cmd="semgrep"
104
- elif command -v npx &> /dev/null && npx semgrep --version &> /dev/null 2>&1; then
105
- semgrep_available=true
106
- semgrep_cmd="npx semgrep"
107
- fi
293
+ if cache_check "semgrep"; then
294
+ CACHE_STATUS["semgrep"]="HIT"
295
+ cached_result=$(cache_get "semgrep")
296
+ semgrep_passed=$(echo "$cached_result" | grep -o '"passed":\s*[^,}]*' | cut -d: -f2 | tr -d ' ')
297
+ semgrep_message=$(echo "$cached_result" | grep -o '"message":\s*"[^"]*"' | cut -d'"' -f4)
298
+
299
+ echo " $semgrep_message (cached)"
300
+ else
301
+ CACHE_STATUS["semgrep"]="MISS"
108
302
 
109
- if [[ "$semgrep_available" == "true" ]]; then
110
- # Get changed files for targeted scan
111
- changed_files=$(git diff main...HEAD --name-only | grep -E '\.(ts|tsx|js|jsx|py|go|rs)$' || true)
303
+ # Check if Semgrep is available
304
+ semgrep_available=false
305
+ if command -v semgrep &> /dev/null; then
306
+ semgrep_available=true
307
+ semgrep_cmd="semgrep"
308
+ elif command -v npx &> /dev/null && npx semgrep --version &> /dev/null 2>&1; then
309
+ semgrep_available=true
310
+ semgrep_cmd="npx semgrep"
311
+ fi
112
312
 
113
- if [[ -n "$changed_files" ]]; then
114
- # Run Semgrep with security rules on changed files
115
- echo " Scanning $(echo "$changed_files" | wc -l | xargs) changed file(s)..."
313
+ if [[ "$semgrep_available" == "true" ]]; then
314
+ # Get changed files for targeted scan
315
+ changed_files=$(git diff main...HEAD --name-only | grep -E '\.(ts|tsx|js|jsx|py|go|rs)$' || true)
116
316
 
117
- # Run with basic security rules, JSON output for reliable parsing
118
- # Use --quiet to suppress progress
119
- semgrep_output=$($semgrep_cmd --config p/security-audit --config p/secrets \
120
- --quiet --no-git-ignore --json \
121
- $changed_files 2>&1) || semgrep_exit=$?
317
+ if [[ -n "$changed_files" ]]; then
318
+ # Run Semgrep with security rules on changed files
319
+ echo " Scanning $(echo "$changed_files" | wc -l | xargs) changed file(s)..."
122
320
 
123
- if [[ -z "$semgrep_output" ]] || ! echo "$semgrep_output" | grep -q '"results"'; then
124
- echo " ✅ Semgrep: No security issues found"
125
- else
126
- # Count findings by severity from JSON output
127
- # Semgrep JSON uses "severity":"ERROR" (uppercase) for critical, "WARNING" for warnings
128
- critical_count=$(echo "$semgrep_output" | grep -o '"severity":"ERROR"' | wc -l | xargs)
129
- warning_count=$(echo "$semgrep_output" | grep -o '"severity":"WARNING"' | wc -l | xargs)
321
+ # Run with basic security rules, JSON output for reliable parsing
322
+ # Use --quiet to suppress progress
323
+ semgrep_output=$($semgrep_cmd --config p/security-audit --config p/secrets \
324
+ --quiet --no-git-ignore --json \
325
+ $changed_files 2>&1) || semgrep_exit=$?
130
326
 
131
- if [[ "$critical_count" -gt 0 ]]; then
132
- echo " ❌ Semgrep: $critical_count critical finding(s) - REVIEW REQUIRED"
133
- fi
134
- if [[ "$warning_count" -gt 0 ]]; then
135
- echo " ⚠️ Semgrep: $warning_count warning(s)"
136
- fi
137
- if [[ "$critical_count" -eq 0 && "$warning_count" -eq 0 ]]; then
327
+ if [[ -z "$semgrep_output" ]] || ! echo "$semgrep_output" | grep -q '"results"'; then
138
328
  echo " ✅ Semgrep: No security issues found"
329
+ cache_set "semgrep" true "✅ Semgrep: No security issues found" "{\"critical\":0,\"warning\":0}"
330
+ else
331
+ # Count findings by severity from JSON output
332
+ critical_count=$(echo "$semgrep_output" | grep -o '"severity":"ERROR"' | wc -l | xargs)
333
+ warning_count=$(echo "$semgrep_output" | grep -o '"severity":"WARNING"' | wc -l | xargs)
334
+
335
+ if [[ "$critical_count" -gt 0 ]]; then
336
+ echo " ❌ Semgrep: $critical_count critical finding(s) - REVIEW REQUIRED"
337
+ cache_set "semgrep" false "❌ Semgrep: $critical_count critical finding(s)" "{\"critical\":$critical_count,\"warning\":$warning_count}"
338
+ elif [[ "$warning_count" -gt 0 ]]; then
339
+ echo " ⚠️ Semgrep: $warning_count warning(s)"
340
+ cache_set "semgrep" true "⚠️ Semgrep: $warning_count warning(s)" "{\"critical\":0,\"warning\":$warning_count}"
341
+ else
342
+ echo " ✅ Semgrep: No security issues found"
343
+ cache_set "semgrep" true "✅ Semgrep: No security issues found" "{\"critical\":0,\"warning\":0}"
344
+ fi
345
+ fi
346
+ else
347
+ echo " No source files changed, skipping Semgrep scan"
348
+ CACHE_STATUS["semgrep"]="SKIP"
349
+ fi
350
+ else
351
+ echo " ⚠️ Semgrep not installed (optional)"
352
+ echo " Install with: pip install semgrep"
353
+ CACHE_STATUS["semgrep"]="SKIP"
354
+ fi
355
+ fi
356
+
357
+ # =============================================================================
358
+ # 10. Shell Script Semantic Checks (unused functions, integration)
359
+ # =============================================================================
360
+ echo ""
361
+ echo "🔍 Checking shell script semantics..."
362
+ shell_scripts=$(git diff main...HEAD --name-only | grep -E '\.sh$' || true)
363
+ if [[ -n "$shell_scripts" ]]; then
364
+ for script in $shell_scripts; do
365
+ if [[ -f "$script" ]]; then
366
+ echo " Analyzing: $script"
367
+ # Extract function definitions and check if they're called
368
+ funcs=$(grep -oE "^[a-zA-Z_][a-zA-Z0-9_]*\(\)" "$script" 2>/dev/null | sed 's/()//' || true)
369
+ unused_count=0
370
+ for func in $funcs; do
371
+ # Count calls (excluding the definition line)
372
+ call_count=$(grep -c "\b${func}\b" "$script" 2>/dev/null || echo "0")
373
+ if [[ $call_count -lt 2 ]]; then # Only definition, no calls
374
+ echo " ⚠️ Function '$func' defined but possibly not called"
375
+ unused_count=$((unused_count + 1))
376
+ fi
377
+ done
378
+ if [[ $unused_count -eq 0 && -n "$funcs" ]]; then
379
+ echo " ✅ All functions are called"
139
380
  fi
140
381
  fi
382
+ done
383
+ else
384
+ echo " No shell scripts changed"
385
+ fi
386
+
387
+ # =============================================================================
388
+ # 11. Build Verification (cacheable - expensive operation)
389
+ # =============================================================================
390
+
391
+ verify_build_against_main() {
392
+ local feature_exit_code=$1
393
+ local feature_error_output=$2
394
+
395
+ echo ""
396
+ echo "🔍 Verifying build failure against main branch..."
397
+
398
+ # Get current directory and branch info
399
+ local current_dir=$(pwd)
400
+ local current_branch=$(git rev-parse --abbrev-ref HEAD)
401
+ local main_repo_dir=""
402
+
403
+ # Find the main repository (parent of worktrees)
404
+ if [[ "$current_dir" == *"/worktrees/"* ]]; then
405
+ main_repo_dir=$(git worktree list | grep -E "\[(main|master)\]" | awk '{print $1}' | head -1)
406
+ if [[ -z "$main_repo_dir" ]]; then
407
+ main_repo_dir=$(git worktree list | head -1 | awk '{print $1}')
408
+ echo " Note: Using fallback worktree detection (no [main] or [master] found)"
409
+ fi
141
410
  else
142
- echo " No source files changed, skipping Semgrep scan"
411
+ main_repo_dir="$current_dir"
412
+ fi
413
+
414
+ if [[ -z "$main_repo_dir" || ! -d "$main_repo_dir" ]]; then
415
+ echo " ⚠️ Could not locate main repository for comparison"
416
+ echo " Skipping build verification against main"
417
+ return 3
418
+ fi
419
+
420
+ echo " Running build on main branch..."
421
+
422
+ local main_exit_code=0
423
+ local main_error_output=""
424
+
425
+ if command -v timeout &> /dev/null; then
426
+ main_error_output=$(cd "$main_repo_dir" && timeout 120 npm run build 2>&1 | head -30) || main_exit_code=$?
427
+ else
428
+ main_error_output=$(cd "$main_repo_dir" && perl -e 'alarm 120; exec @ARGV' npm run build 2>&1 | head -30) || main_exit_code=$?
429
+ fi
430
+
431
+ local feature_first_error=$(echo "$feature_error_output" | grep -E "Error:|error:|ERROR:" | head -1)
432
+ local main_first_error=$(echo "$main_error_output" | grep -E "Error:|error:|ERROR:" | head -1)
433
+
434
+ echo ""
435
+ echo "### Build Verification"
436
+ echo ""
437
+ echo "| Check | Status |"
438
+ echo "|-------|--------|"
439
+
440
+ if [[ $feature_exit_code -ne 0 ]]; then
441
+ echo "| Feature branch build | ❌ Failed |"
442
+ else
443
+ echo "| Feature branch build | ✅ Passed |"
444
+ fi
445
+
446
+ if [[ $main_exit_code -ne 0 ]]; then
447
+ echo "| Main branch build | ❌ Failed |"
448
+
449
+ if [[ "$feature_first_error" == "$main_first_error" ]] || \
450
+ [[ -n "$feature_first_error" && -n "$main_first_error" && \
451
+ "$(echo "$feature_first_error" | cut -c1-50)" == "$(echo "$main_first_error" | cut -c1-50)" ]]; then
452
+ echo "| Error match | ✅ Same error |"
453
+ echo "| Regression | **No** (pre-existing) |"
454
+ echo ""
455
+ echo "**Note:** Build failure is pre-existing on main branch. Not blocking this PR."
456
+ return 0
457
+ else
458
+ echo "| Error match | ❌ Different errors |"
459
+ echo "| Regression | **Unknown** (different failure modes) |"
460
+ echo ""
461
+ echo "**Note:** Build failures differ between branches. Manual review recommended."
462
+ echo ""
463
+ echo "Feature branch error:"
464
+ echo "\`\`\`"
465
+ echo "$feature_first_error"
466
+ echo "\`\`\`"
467
+ echo ""
468
+ echo "Main branch error:"
469
+ echo "\`\`\`"
470
+ echo "$main_first_error"
471
+ echo "\`\`\`"
472
+ return 2
473
+ fi
474
+ else
475
+ echo "| Main branch build | ✅ Passed |"
476
+ echo "| Regression | **Yes** (new failure) |"
477
+ echo ""
478
+ echo "⚠️ **REGRESSION DETECTED:** Build passes on main but fails on feature branch."
479
+ echo "This failure was introduced by changes in this PR."
480
+ echo ""
481
+ echo "Feature branch error:"
482
+ echo "\`\`\`"
483
+ echo "$feature_first_error"
484
+ echo "\`\`\`"
485
+ return 1
486
+ fi
487
+ }
488
+
489
+ run_build_with_verification() {
490
+ echo ""
491
+ echo "🏗️ Running build check..."
492
+
493
+ local build_output=""
494
+ local build_exit_code=0
495
+
496
+ if command -v timeout &> /dev/null; then
497
+ build_output=$(timeout 120 npm run build 2>&1) || build_exit_code=$?
498
+ else
499
+ build_output=$(perl -e 'alarm 120; exec @ARGV' npm run build 2>&1) || build_exit_code=$?
500
+ fi
501
+
502
+ if [[ $build_exit_code -eq 0 ]]; then
503
+ echo "✅ Build: Passed"
504
+ return 0
505
+ else
506
+ echo "❌ Build: Failed (exit code: $build_exit_code)"
507
+ echo ""
508
+ echo "Build error output (first 20 lines):"
509
+ echo "$build_output" | head -20
510
+ echo ""
511
+
512
+ verify_build_against_main "$build_exit_code" "$build_output"
513
+ local verification_result=$?
514
+
515
+ return $verification_result
516
+ fi
517
+ }
518
+
519
+ # Check build cache
520
+ if cache_check "build"; then
521
+ CACHE_STATUS["build"]="HIT"
522
+ cached_result=$(cache_get "build")
523
+ build_passed=$(echo "$cached_result" | grep -o '"passed":\s*[^,}]*' | cut -d: -f2 | tr -d ' ')
524
+ build_message=$(echo "$cached_result" | grep -o '"message":\s*"[^"]*"' | cut -d'"' -f4)
525
+
526
+ echo ""
527
+ echo "🏗️ Build check: $build_message (cached)"
528
+
529
+ if [[ "$build_passed" == "true" ]]; then
530
+ build_verification_result=0
531
+ else
532
+ build_verification_result=1
143
533
  fi
144
534
  else
145
- echo " ⚠️ Semgrep not installed (optional)"
146
- echo " Install with: pip install semgrep"
535
+ CACHE_STATUS["build"]="MISS"
536
+ build_verification_result=0
537
+ run_build_with_verification || build_verification_result=$?
538
+
539
+ # Cache the result
540
+ if [[ $build_verification_result -eq 0 ]]; then
541
+ cache_set "build" true "Passed"
542
+ else
543
+ cache_set "build" false "Failed (exit code: $build_verification_result)"
544
+ fi
545
+ fi
546
+
547
+ # Report build verification status
548
+ if [[ $build_verification_result -eq 1 ]]; then
549
+ echo ""
550
+ echo "⚠️ Build verification: REGRESSION DETECTED (blocking)"
551
+ elif [[ $build_verification_result -eq 2 ]]; then
552
+ echo ""
553
+ echo "⚠️ Build verification: Different errors detected (needs review)"
554
+ elif [[ $build_verification_result -eq 3 ]]; then
555
+ echo ""
556
+ echo "⚠️ Build verification: SKIPPED (could not locate main repo)"
557
+ build_verification_result=0
147
558
  fi
148
559
 
560
+ # =============================================================================
561
+ # Cache Status Report (AC-4)
562
+ # =============================================================================
563
+ echo ""
564
+ echo "=========================================="
565
+ echo "### Cache Status Report"
566
+ echo "=========================================="
149
567
  echo ""
568
+ echo "| Check | Cache Status |"
569
+ echo "|-------|--------------|"
570
+ for check in "type-safety" "deleted-tests" "scope" "size" "security" "semgrep" "build"; do
571
+ status="${CACHE_STATUS[$check]:-MISS}"
572
+ if [[ "$status" == "HIT" ]]; then
573
+ echo "| $check | ✅ HIT |"
574
+ elif [[ "$status" == "SKIP" ]]; then
575
+ echo "| $check | ⏭️ SKIP |"
576
+ else
577
+ echo "| $check | ❌ MISS |"
578
+ fi
579
+ done
580
+ echo ""
581
+
582
+ # Count hits and misses
583
+ hit_count=0
584
+ miss_count=0
585
+ skip_count=0
586
+ for check in "type-safety" "deleted-tests" "scope" "size" "security" "semgrep" "build"; do
587
+ status="${CACHE_STATUS[$check]:-MISS}"
588
+ if [[ "$status" == "HIT" ]]; then
589
+ ((hit_count++))
590
+ elif [[ "$status" == "SKIP" ]]; then
591
+ ((skip_count++))
592
+ else
593
+ ((miss_count++))
594
+ fi
595
+ done
596
+
597
+ echo "**Summary:** $hit_count hits, $miss_count misses, $skip_count skipped"
598
+ if [[ $hit_count -gt 0 ]]; then
599
+ echo "**Performance:** Cached checks saved execution time"
600
+ fi
601
+ echo ""
602
+
150
603
  echo "✅ Quality checks complete"
604
+
605
+ # Exit with build verification result if it indicates a problem
606
+ exit $build_verification_result