sequant 1.17.0 → 1.18.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 (63) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.claude-plugin/plugin.json +14 -2
  3. package/dist/marketplace/external_plugins/sequant/.claude-plugin/plugin.json +21 -0
  4. package/dist/marketplace/external_plugins/sequant/README.md +38 -0
  5. package/dist/marketplace/external_plugins/sequant/hooks/post-tool.sh +292 -0
  6. package/dist/marketplace/external_plugins/sequant/hooks/pre-tool.sh +463 -0
  7. package/dist/marketplace/external_plugins/sequant/skills/_shared/references/prompt-templates.md +350 -0
  8. package/dist/marketplace/external_plugins/sequant/skills/_shared/references/subagent-types.md +131 -0
  9. package/dist/marketplace/external_plugins/sequant/skills/assess/SKILL.md +474 -0
  10. package/dist/marketplace/external_plugins/sequant/skills/clean/SKILL.md +211 -0
  11. package/dist/marketplace/external_plugins/sequant/skills/docs/SKILL.md +337 -0
  12. package/dist/marketplace/external_plugins/sequant/skills/exec/SKILL.md +807 -0
  13. package/dist/marketplace/external_plugins/sequant/skills/fullsolve/SKILL.md +678 -0
  14. package/dist/marketplace/external_plugins/sequant/skills/improve/SKILL.md +668 -0
  15. package/dist/marketplace/external_plugins/sequant/skills/loop/SKILL.md +374 -0
  16. package/dist/marketplace/external_plugins/sequant/skills/qa/SKILL.md +570 -0
  17. package/dist/marketplace/external_plugins/sequant/skills/qa/references/code-quality-exemplars.md +107 -0
  18. package/dist/marketplace/external_plugins/sequant/skills/qa/references/code-review-checklist.md +65 -0
  19. package/dist/marketplace/external_plugins/sequant/skills/qa/references/quality-gates.md +179 -0
  20. package/dist/marketplace/external_plugins/sequant/skills/qa/references/semgrep-rules.md +207 -0
  21. package/dist/marketplace/external_plugins/sequant/skills/qa/references/testing-requirements.md +109 -0
  22. package/dist/marketplace/external_plugins/sequant/skills/qa/scripts/quality-checks.sh +622 -0
  23. package/dist/marketplace/external_plugins/sequant/skills/reflect/SKILL.md +175 -0
  24. package/dist/marketplace/external_plugins/sequant/skills/reflect/references/documentation-tiers.md +70 -0
  25. package/dist/marketplace/external_plugins/sequant/skills/reflect/references/phase-reflection.md +95 -0
  26. package/dist/marketplace/external_plugins/sequant/skills/security-review/SKILL.md +358 -0
  27. package/dist/marketplace/external_plugins/sequant/skills/security-review/references/security-checklists.md +432 -0
  28. package/dist/marketplace/external_plugins/sequant/skills/solve/SKILL.md +697 -0
  29. package/dist/marketplace/external_plugins/sequant/skills/spec/SKILL.md +754 -0
  30. package/dist/marketplace/external_plugins/sequant/skills/spec/references/parallel-groups.md +72 -0
  31. package/dist/marketplace/external_plugins/sequant/skills/spec/references/recommended-workflow.md +92 -0
  32. package/dist/marketplace/external_plugins/sequant/skills/spec/references/verification-criteria.md +104 -0
  33. package/dist/marketplace/external_plugins/sequant/skills/test/SKILL.md +600 -0
  34. package/dist/marketplace/external_plugins/sequant/skills/testgen/SKILL.md +576 -0
  35. package/dist/marketplace/external_plugins/sequant/skills/verify/SKILL.md +281 -0
  36. package/dist/src/commands/run.d.ts +13 -280
  37. package/dist/src/commands/run.js +23 -1956
  38. package/dist/src/commands/sync.js +3 -0
  39. package/dist/src/commands/update.js +3 -0
  40. package/dist/src/lib/plugin-version-sync.d.ts +2 -1
  41. package/dist/src/lib/plugin-version-sync.js +28 -7
  42. package/dist/src/lib/solve-comment-parser.d.ts +26 -0
  43. package/dist/src/lib/solve-comment-parser.js +63 -7
  44. package/dist/src/lib/workflow/batch-executor.d.ts +117 -0
  45. package/dist/src/lib/workflow/batch-executor.js +574 -0
  46. package/dist/src/lib/workflow/phase-executor.d.ts +40 -0
  47. package/dist/src/lib/workflow/phase-executor.js +381 -0
  48. package/dist/src/lib/workflow/phase-mapper.d.ts +65 -0
  49. package/dist/src/lib/workflow/phase-mapper.js +147 -0
  50. package/dist/src/lib/workflow/pr-operations.d.ts +86 -0
  51. package/dist/src/lib/workflow/pr-operations.js +326 -0
  52. package/dist/src/lib/workflow/pr-status.d.ts +9 -7
  53. package/dist/src/lib/workflow/pr-status.js +13 -11
  54. package/dist/src/lib/workflow/run-summary.d.ts +36 -0
  55. package/dist/src/lib/workflow/run-summary.js +142 -0
  56. package/dist/src/lib/workflow/worktree-manager.d.ts +205 -0
  57. package/dist/src/lib/workflow/worktree-manager.js +918 -0
  58. package/package.json +3 -1
  59. package/templates/skills/fullsolve/SKILL.md +11 -1
  60. package/templates/skills/qa/SKILL.md +41 -1
  61. package/templates/skills/solve/SKILL.md +86 -0
  62. package/templates/skills/spec/SKILL.md +53 -0
  63. package/templates/skills/test/SKILL.md +10 -0
@@ -0,0 +1,622 @@
1
+ #!/bin/bash
2
+ # Quality checks script for /qa command
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
10
+
11
+ set -e
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
+
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
125
+ echo ""
126
+
127
+ # Track cache hits/misses for final report
128
+ declare -A CACHE_STATUS
129
+
130
+ # =============================================================================
131
+ # 1. Type safety check - detect 'any' type usage
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
145
+ else
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
155
+ fi
156
+
157
+ # =============================================================================
158
+ # 2. Deleted tests check
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
171
+ else
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
181
+ fi
182
+
183
+ # =============================================================================
184
+ # 3. Scope check - files changed (always fresh - cheap operation)
185
+ # =============================================================================
186
+ CACHE_STATUS["scope"]="SKIP"
187
+ files_changed=$(git diff main...HEAD --name-only | wc -l | xargs)
188
+ echo "📊 Files changed: $files_changed"
189
+
190
+ # =============================================================================
191
+ # 4. Size check - LOC added/removed (always fresh - cheap operation)
192
+ # =============================================================================
193
+ CACHE_STATUS["size"]="SKIP"
194
+ additions=$(git diff main...HEAD --numstat | awk '{sum+=$1} END {print sum+0}')
195
+ deletions=$(git diff main...HEAD --numstat | awk '{sum+=$2} END {print sum+0}')
196
+ net_change=$((additions - deletions))
197
+ echo "📊 Diff size: +$additions -$deletions (net: $net_change lines)"
198
+
199
+ # 5. AC proportionality assessment
200
+ echo ""
201
+ if [[ $net_change -lt 100 ]]; then
202
+ echo "✅ Size: Small change (<100 net LOC)"
203
+ elif [[ $net_change -lt 300 ]]; then
204
+ echo "✅ Size: Medium change (100-300 net LOC)"
205
+ elif [[ $net_change -lt 500 ]]; then
206
+ echo "⚠️ Size: Large change (300-500 net LOC) - verify proportional to AC"
207
+ else
208
+ echo "❌ Size: Very large (>500 net LOC) - may indicate scope creep"
209
+ fi
210
+
211
+ # =============================================================================
212
+ # 6. Database access check (always fresh - cheap operation)
213
+ # =============================================================================
214
+ echo ""
215
+ echo "🔒 Checking database access patterns..."
216
+ admin_files=$(git diff main...HEAD --name-only | grep -E "^app/admin/" || true)
217
+ if [[ -n "$admin_files" ]]; then
218
+ echo " Admin files modified - manually verify proper database access controls"
219
+ echo " (admin pages should use service/admin clients, not anonymous clients)"
220
+ else
221
+ echo " No admin files modified"
222
+ fi
223
+
224
+ # =============================================================================
225
+ # 7. Integration check - verify new exports are imported somewhere
226
+ # =============================================================================
227
+ echo ""
228
+ echo "🔌 Checking integration of new exports..."
229
+ new_files=$(git diff main...HEAD --name-only --diff-filter=A | grep -E "\.(ts|tsx)$" || true)
230
+ if [[ -n "$new_files" ]]; then
231
+ unintegrated=0
232
+ for file in $new_files; do
233
+ if [[ -f "$file" ]]; then
234
+ exports=$(grep -oE "export (const|function|class|type|interface) ([A-Za-z_][A-Za-z0-9_]*)" "$file" 2>/dev/null | awk '{print $3}' || true)
235
+ for exp in $exports; do
236
+ if [[ -n "$exp" ]]; then
237
+ import_count=$(grep -r "import.*$exp" --include="*.ts" --include="*.tsx" . 2>/dev/null | grep -v "$file" | wc -l | xargs)
238
+ if [[ $import_count -eq 0 ]]; then
239
+ echo "⚠️ WARNING: '$exp' exported from $file but never imported"
240
+ unintegrated=$((unintegrated + 1))
241
+ fi
242
+ fi
243
+ done
244
+ fi
245
+ done
246
+ if [[ $unintegrated -eq 0 ]]; then
247
+ echo "✅ Integration: All exports are imported"
248
+ fi
249
+ else
250
+ echo " No new TypeScript files added"
251
+ fi
252
+
253
+ # =============================================================================
254
+ # 8. Security scan - OWASP vulnerability checks (cacheable)
255
+ # =============================================================================
256
+ echo ""
257
+ echo "🔒 Running security scan..."
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
269
+ else
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
285
+ fi
286
+
287
+ # =============================================================================
288
+ # 9. Semgrep static analysis (cacheable - expensive operation)
289
+ # =============================================================================
290
+ echo ""
291
+ echo "🔍 Running Semgrep static analysis..."
292
+
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"
302
+
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
312
+
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)
316
+
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)..."
320
+
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=$?
326
+
327
+ if [[ -z "$semgrep_output" ]] || ! echo "$semgrep_output" | grep -q '"results"'; then
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"
380
+ fi
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
410
+ else
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
533
+ fi
534
+ else
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
558
+ fi
559
+
560
+ # =============================================================================
561
+ # Cache Status Report (AC-4)
562
+ # =============================================================================
563
+ echo ""
564
+ echo "=========================================="
565
+ echo "### Cache Status Report"
566
+ echo "=========================================="
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
+
603
+ # Write structured cache metrics JSON for sequant observability (AC-7)
604
+ # This file is read by run.ts to populate PhaseLog.cacheMetrics
605
+ CACHE_METRICS_DIR=".sequant/.cache/qa"
606
+ mkdir -p "$CACHE_METRICS_DIR"
607
+
608
+ # Build JSON with per-check status
609
+ CACHE_JSON="{\"hits\":$hit_count,\"misses\":$miss_count,\"skipped\":$skip_count,\"checks\":{"
610
+ first=true
611
+ for check in "type-safety" "deleted-tests" "scope" "size" "security" "semgrep" "build"; do
612
+ $first || CACHE_JSON+=","
613
+ CACHE_JSON+="\"$check\":\"${CACHE_STATUS[$check]:-MISS}\""
614
+ first=false
615
+ done
616
+ CACHE_JSON+="}}"
617
+ echo "$CACHE_JSON" > "$CACHE_METRICS_DIR/cache-metrics.json"
618
+
619
+ echo "✅ Quality checks complete"
620
+
621
+ # Exit with build verification result if it indicates a problem
622
+ exit $build_verification_result