shipwright-cli 1.7.1 → 1.9.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 (105) hide show
  1. package/.claude/agents/code-reviewer.md +90 -0
  2. package/.claude/agents/devops-engineer.md +142 -0
  3. package/.claude/agents/pipeline-agent.md +80 -0
  4. package/.claude/agents/shell-script-specialist.md +150 -0
  5. package/.claude/agents/test-specialist.md +196 -0
  6. package/.claude/hooks/post-tool-use.sh +38 -0
  7. package/.claude/hooks/pre-tool-use.sh +25 -0
  8. package/.claude/hooks/session-started.sh +37 -0
  9. package/README.md +212 -814
  10. package/claude-code/CLAUDE.md.shipwright +54 -0
  11. package/claude-code/hooks/notify-idle.sh +2 -2
  12. package/claude-code/hooks/session-start.sh +24 -0
  13. package/claude-code/hooks/task-completed.sh +6 -2
  14. package/claude-code/settings.json.template +12 -0
  15. package/dashboard/public/app.js +4422 -0
  16. package/dashboard/public/index.html +816 -0
  17. package/dashboard/public/styles.css +4755 -0
  18. package/dashboard/server.ts +4315 -0
  19. package/docs/KNOWN-ISSUES.md +18 -10
  20. package/docs/TIPS.md +38 -26
  21. package/docs/patterns/README.md +33 -23
  22. package/package.json +9 -5
  23. package/scripts/adapters/iterm2-adapter.sh +1 -1
  24. package/scripts/adapters/tmux-adapter.sh +52 -23
  25. package/scripts/adapters/wezterm-adapter.sh +26 -14
  26. package/scripts/lib/compat.sh +200 -0
  27. package/scripts/lib/helpers.sh +72 -0
  28. package/scripts/postinstall.mjs +72 -13
  29. package/scripts/{cct → sw} +109 -21
  30. package/scripts/sw-adversarial.sh +274 -0
  31. package/scripts/sw-architecture-enforcer.sh +330 -0
  32. package/scripts/sw-checkpoint.sh +390 -0
  33. package/scripts/{cct-cleanup.sh → sw-cleanup.sh} +3 -1
  34. package/scripts/sw-connect.sh +619 -0
  35. package/scripts/{cct-cost.sh → sw-cost.sh} +368 -34
  36. package/scripts/{cct-daemon.sh → sw-daemon.sh} +2217 -204
  37. package/scripts/sw-dashboard.sh +477 -0
  38. package/scripts/sw-developer-simulation.sh +252 -0
  39. package/scripts/sw-docs.sh +635 -0
  40. package/scripts/sw-doctor.sh +907 -0
  41. package/scripts/{cct-fix.sh → sw-fix.sh} +10 -6
  42. package/scripts/{cct-fleet.sh → sw-fleet.sh} +498 -22
  43. package/scripts/sw-github-checks.sh +521 -0
  44. package/scripts/sw-github-deploy.sh +533 -0
  45. package/scripts/sw-github-graphql.sh +972 -0
  46. package/scripts/sw-heartbeat.sh +293 -0
  47. package/scripts/{cct-init.sh → sw-init.sh} +144 -11
  48. package/scripts/sw-intelligence.sh +1196 -0
  49. package/scripts/sw-jira.sh +643 -0
  50. package/scripts/sw-launchd.sh +364 -0
  51. package/scripts/sw-linear.sh +648 -0
  52. package/scripts/{cct-logs.sh → sw-logs.sh} +72 -2
  53. package/scripts/{cct-loop.sh → sw-loop.sh} +534 -44
  54. package/scripts/{cct-memory.sh → sw-memory.sh} +321 -38
  55. package/scripts/sw-patrol-meta.sh +417 -0
  56. package/scripts/sw-pipeline-composer.sh +455 -0
  57. package/scripts/{cct-pipeline.sh → sw-pipeline.sh} +2319 -178
  58. package/scripts/sw-predictive.sh +820 -0
  59. package/scripts/{cct-prep.sh → sw-prep.sh} +339 -49
  60. package/scripts/{cct-ps.sh → sw-ps.sh} +6 -4
  61. package/scripts/{cct-reaper.sh → sw-reaper.sh} +6 -4
  62. package/scripts/sw-remote.sh +687 -0
  63. package/scripts/sw-self-optimize.sh +947 -0
  64. package/scripts/sw-session.sh +519 -0
  65. package/scripts/sw-setup.sh +234 -0
  66. package/scripts/sw-status.sh +605 -0
  67. package/scripts/{cct-templates.sh → sw-templates.sh} +9 -4
  68. package/scripts/sw-tmux.sh +591 -0
  69. package/scripts/sw-tracker-jira.sh +277 -0
  70. package/scripts/sw-tracker-linear.sh +292 -0
  71. package/scripts/sw-tracker.sh +409 -0
  72. package/scripts/{cct-upgrade.sh → sw-upgrade.sh} +103 -46
  73. package/scripts/{cct-worktree.sh → sw-worktree.sh} +3 -0
  74. package/templates/pipelines/autonomous.json +27 -5
  75. package/templates/pipelines/full.json +12 -0
  76. package/templates/pipelines/standard.json +12 -0
  77. package/tmux/{claude-teams-overlay.conf → shipwright-overlay.conf} +27 -9
  78. package/tmux/templates/accessibility.json +34 -0
  79. package/tmux/templates/api-design.json +35 -0
  80. package/tmux/templates/architecture.json +1 -0
  81. package/tmux/templates/bug-fix.json +9 -0
  82. package/tmux/templates/code-review.json +1 -0
  83. package/tmux/templates/compliance.json +36 -0
  84. package/tmux/templates/data-pipeline.json +36 -0
  85. package/tmux/templates/debt-paydown.json +34 -0
  86. package/tmux/templates/devops.json +1 -0
  87. package/tmux/templates/documentation.json +1 -0
  88. package/tmux/templates/exploration.json +1 -0
  89. package/tmux/templates/feature-dev.json +1 -0
  90. package/tmux/templates/full-stack.json +8 -0
  91. package/tmux/templates/i18n.json +34 -0
  92. package/tmux/templates/incident-response.json +36 -0
  93. package/tmux/templates/migration.json +1 -0
  94. package/tmux/templates/observability.json +35 -0
  95. package/tmux/templates/onboarding.json +33 -0
  96. package/tmux/templates/performance.json +35 -0
  97. package/tmux/templates/refactor.json +1 -0
  98. package/tmux/templates/release.json +35 -0
  99. package/tmux/templates/security-audit.json +8 -0
  100. package/tmux/templates/spike.json +34 -0
  101. package/tmux/templates/testing.json +1 -0
  102. package/tmux/tmux.conf +98 -9
  103. package/scripts/cct-doctor.sh +0 -414
  104. package/scripts/cct-session.sh +0 -284
  105. package/scripts/cct-status.sh +0 -169
@@ -8,10 +8,11 @@
8
8
  # ║ Inspired by Anthropic's autonomous 16-agent C compiler build. ║
9
9
  # ╚═══════════════════════════════════════════════════════════════════════════╝
10
10
  set -euo pipefail
11
+ trap 'echo "ERROR: $BASH_SOURCE:$LINENO exited with status $?" >&2' ERR
11
12
 
12
13
  SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
13
14
 
14
- # ─── Colors (matches cct theme) ──────────────────────────────────────────────
15
+ # ─── Colors (matches shipwright theme) ──────────────────────────────────────────────
15
16
  CYAN='\033[38;2;0;212;255m'
16
17
  PURPLE='\033[38;2;124;58;237m'
17
18
  BLUE='\033[38;2;0;102;255m'
@@ -22,6 +23,10 @@ DIM='\033[2m'
22
23
  BOLD='\033[1m'
23
24
  RESET='\033[0m'
24
25
 
26
+ # ─── Cross-platform compatibility ──────────────────────────────────────────
27
+ # shellcheck source=lib/compat.sh
28
+ [[ -f "$SCRIPT_DIR/lib/compat.sh" ]] && source "$SCRIPT_DIR/lib/compat.sh"
29
+
25
30
  info() { echo -e "${CYAN}${BOLD}▸${RESET} $*"; }
26
31
  success() { echo -e "${GREEN}${BOLD}✓${RESET} $*"; }
27
32
  warn() { echo -e "${YELLOW}${BOLD}⚠${RESET} $*"; }
@@ -29,9 +34,9 @@ error() { echo -e "${RED}${BOLD}✗${RESET} $*" >&2; }
29
34
 
30
35
  # ─── Defaults ─────────────────────────────────────────────────────────────────
31
36
  GOAL=""
32
- MAX_ITERATIONS=20
37
+ MAX_ITERATIONS="${SW_MAX_ITERATIONS:-20}"
33
38
  TEST_CMD=""
34
- MODEL="opus"
39
+ MODEL="${SW_MODEL:-opus}"
35
40
  AGENTS=1
36
41
  USE_WORKTREE=false
37
42
  SKIP_PERMISSIONS=false
@@ -39,7 +44,17 @@ MAX_TURNS=""
39
44
  RESUME=false
40
45
  VERBOSE=false
41
46
  MAX_ITERATIONS_EXPLICIT=false
42
- VERSION="1.7.1"
47
+ VERSION="1.9.0"
48
+
49
+ # ─── Flexible Iteration Defaults ────────────────────────────────────────────
50
+ AUTO_EXTEND=true # Auto-extend iterations when work is incomplete
51
+ EXTENSION_SIZE=5 # Additional iterations per extension
52
+ MAX_EXTENSIONS=3 # Max number of extensions (hard cap safety net)
53
+ EXTENSION_COUNT=0 # Current number of extensions applied
54
+
55
+ # ─── Circuit Breaker Defaults ──────────────────────────────────────────────
56
+ CIRCUIT_BREAKER_THRESHOLD=3 # Consecutive low-progress iterations before stopping
57
+ MIN_PROGRESS_LINES=5 # Minimum insertions to count as progress
43
58
 
44
59
  # ─── Audit & Quality Gate Defaults ───────────────────────────────────────────
45
60
  AUDIT_ENABLED=false
@@ -74,6 +89,9 @@ show_help() {
74
89
  echo -e " ${CYAN}--audit-agent${RESET} Run separate auditor agent (haiku) after each iteration"
75
90
  echo -e " ${CYAN}--quality-gates${RESET} Enable automated quality gates before accepting completion"
76
91
  echo -e " ${CYAN}--definition-of-done${RESET} FILE DoD checklist file — evaluated by AI against git diff"
92
+ echo -e " ${CYAN}--no-auto-extend${RESET} Disable auto-extension when max iterations reached"
93
+ echo -e " ${CYAN}--extension-size${RESET} N Additional iterations per extension (default: 5)"
94
+ echo -e " ${CYAN}--max-extensions${RESET} N Max number of auto-extensions (default: 3)"
77
95
  echo ""
78
96
  echo -e "${BOLD}EXAMPLES${RESET}"
79
97
  echo -e " ${DIM}shipwright loop \"Build user auth with JWT\"${RESET}"
@@ -83,11 +101,13 @@ show_help() {
83
101
  echo -e " ${DIM}shipwright loop \"Add auth\" --audit --audit-agent --quality-gates${RESET}"
84
102
  echo -e " ${DIM}shipwright loop \"Ship feature\" --quality-gates --definition-of-done dod.md${RESET}"
85
103
  echo ""
86
- echo -e "${BOLD}CIRCUIT BREAKER${RESET}"
87
- echo -e " The loop automatically stops if:"
88
- echo -e " ${DIM}• 3 consecutive iterations with < 5 lines changed${RESET}"
89
- echo -e " ${DIM}• Claude outputs LOOP_COMPLETE (validated by quality gates if enabled)${RESET}"
90
- echo -e " ${DIM}• Max iterations reached${RESET}"
104
+ echo -e "${BOLD}COMPLETION & CIRCUIT BREAKER${RESET}"
105
+ echo -e " The loop completes when:"
106
+ echo -e " ${DIM}• Claude outputs LOOP_COMPLETE and all quality gates pass${RESET}"
107
+ echo -e " ${DIM}• Max iterations reached (auto-extends if work is incomplete)${RESET}"
108
+ echo -e " The loop stops (circuit breaker) if:"
109
+ echo -e " ${DIM}• ${CIRCUIT_BREAKER_THRESHOLD} consecutive iterations with < ${MIN_PROGRESS_LINES} lines changed${RESET}"
110
+ echo -e " ${DIM}• Hard cap reached (max_iterations + max_extensions * extension_size)${RESET}"
91
111
  echo -e " ${DIM}• Ctrl-C (graceful shutdown with summary)${RESET}"
92
112
  echo ""
93
113
  echo -e "${BOLD}STATE & LOGS${RESET}"
@@ -142,6 +162,19 @@ while [[ $# -gt 0 ]]; do
142
162
  ;;
143
163
  --definition-of-done=*) DOD_FILE="${1#--definition-of-done=}"; shift ;;
144
164
  --quality-gates) QUALITY_GATES_ENABLED=true; shift ;;
165
+ --no-auto-extend) AUTO_EXTEND=false; shift ;;
166
+ --extension-size)
167
+ EXTENSION_SIZE="${2:-}"
168
+ [[ -z "$EXTENSION_SIZE" ]] && { error "Missing value for --extension-size"; exit 1; }
169
+ shift 2
170
+ ;;
171
+ --extension-size=*) EXTENSION_SIZE="${1#--extension-size=}"; shift ;;
172
+ --max-extensions)
173
+ MAX_EXTENSIONS="${2:-}"
174
+ [[ -z "$MAX_EXTENSIONS" ]] && { error "Missing value for --max-extensions"; exit 1; }
175
+ shift 2
176
+ ;;
177
+ --max-extensions=*) MAX_EXTENSIONS="${1#--max-extensions=}"; shift ;;
145
178
  --help|-h)
146
179
  show_help
147
180
  exit 0
@@ -191,6 +224,15 @@ if ! git rev-parse --is-inside-work-tree &>/dev/null 2>&1; then
191
224
  exit 1
192
225
  fi
193
226
 
227
+ # ─── Timeout Detection ────────────────────────────────────────────────────────
228
+ TIMEOUT_CMD=""
229
+ if command -v timeout &>/dev/null; then
230
+ TIMEOUT_CMD="timeout"
231
+ elif command -v gtimeout &>/dev/null; then
232
+ TIMEOUT_CMD="gtimeout"
233
+ fi
234
+ CLAUDE_TIMEOUT="${CLAUDE_TIMEOUT:-1800}" # 30 min default
235
+
194
236
  if [[ "$AGENTS" -gt 1 ]]; then
195
237
  if ! command -v tmux &>/dev/null; then
196
238
  error "tmux is required for multi-agent mode."
@@ -214,6 +256,114 @@ WORKTREE_DIR="$PROJECT_ROOT/.worktrees"
214
256
 
215
257
  mkdir -p "$STATE_DIR" "$LOG_DIR"
216
258
 
259
+ # ─── Adaptive Model Selection ────────────────────────────────────────────────
260
+ # Uses intelligence engine when available, falls back to defaults.
261
+ select_adaptive_model() {
262
+ local role="${1:-build}"
263
+ local default_model="${2:-opus}"
264
+ # If user explicitly set --model, respect it
265
+ if [[ "$default_model" != "${SW_MODEL:-opus}" ]]; then
266
+ echo "$default_model"
267
+ return 0
268
+ fi
269
+ # Try intelligence-based recommendation
270
+ if type intelligence_recommend_model &>/dev/null 2>&1; then
271
+ local rec
272
+ rec=$(intelligence_recommend_model "$role" "${COMPLEXITY:-5}" "${BUDGET:-0}" 2>/dev/null || echo "")
273
+ if [[ -n "$rec" ]]; then
274
+ local recommended
275
+ recommended=$(echo "$rec" | jq -r '.model // ""' 2>/dev/null || echo "")
276
+ if [[ -n "$recommended" && "$recommended" != "null" ]]; then
277
+ echo "$recommended"
278
+ return 0
279
+ fi
280
+ fi
281
+ fi
282
+ echo "$default_model"
283
+ }
284
+
285
+ # Select audit/DoD model — uses haiku if success rate is high enough, else sonnet
286
+ select_audit_model() {
287
+ local default_model="haiku"
288
+ local opt_file="$HOME/.shipwright/optimization/audit-tuning.json"
289
+ if [[ -f "$opt_file" ]] && command -v jq &>/dev/null; then
290
+ local success_rate
291
+ success_rate=$(jq -r '.haiku_success_rate // 100' "$opt_file" 2>/dev/null || echo "100")
292
+ if [[ "${success_rate%%.*}" -lt 90 ]]; then
293
+ echo "sonnet"
294
+ return 0
295
+ fi
296
+ fi
297
+ echo "$default_model"
298
+ }
299
+
300
+ # ─── Adaptive Iteration Budget ──────────────────────────────────────────────
301
+ # Reads tuning config for smarter iteration/circuit-breaker thresholds.
302
+ apply_adaptive_budget() {
303
+ local tuning_file="$HOME/.shipwright/optimization/loop-tuning.json"
304
+ if [[ -f "$tuning_file" ]] && command -v jq &>/dev/null; then
305
+ local tuned_max tuned_ext tuned_ext_count tuned_cb
306
+ tuned_max=$(jq -r '.max_iterations // ""' "$tuning_file" 2>/dev/null || echo "")
307
+ tuned_ext=$(jq -r '.extension_size // ""' "$tuning_file" 2>/dev/null || echo "")
308
+ tuned_ext_count=$(jq -r '.max_extensions // ""' "$tuning_file" 2>/dev/null || echo "")
309
+ tuned_cb=$(jq -r '.circuit_breaker_threshold // ""' "$tuning_file" 2>/dev/null || echo "")
310
+
311
+ # Only apply tuned values if user didn't explicitly set them
312
+ if ! $MAX_ITERATIONS_EXPLICIT && [[ -n "$tuned_max" && "$tuned_max" != "null" ]]; then
313
+ MAX_ITERATIONS="$tuned_max"
314
+ fi
315
+ [[ -n "$tuned_ext" && "$tuned_ext" != "null" ]] && EXTENSION_SIZE="$tuned_ext"
316
+ [[ -n "$tuned_ext_count" && "$tuned_ext_count" != "null" ]] && MAX_EXTENSIONS="$tuned_ext_count"
317
+ [[ -n "$tuned_cb" && "$tuned_cb" != "null" ]] && CIRCUIT_BREAKER_THRESHOLD="$tuned_cb"
318
+ fi
319
+
320
+ # Try intelligence-based iteration estimate
321
+ if type intelligence_estimate_iterations &>/dev/null 2>&1 && ! $MAX_ITERATIONS_EXPLICIT; then
322
+ local est
323
+ est=$(intelligence_estimate_iterations "${GOAL:-}" "${COMPLEXITY:-5}" 2>/dev/null || echo "")
324
+ if [[ -n "$est" && "$est" =~ ^[0-9]+$ ]]; then
325
+ MAX_ITERATIONS="$est"
326
+ fi
327
+ fi
328
+ }
329
+
330
+ # ─── Progress Velocity Tracking ─────────────────────────────────────────────
331
+ ITERATION_LINES_CHANGED=""
332
+ VELOCITY_HISTORY=""
333
+
334
+ track_iteration_velocity() {
335
+ local changes
336
+ changes="$(git -C "$PROJECT_ROOT" diff --stat HEAD~1 2>/dev/null | tail -1 || echo "")"
337
+ local insertions
338
+ insertions="$(echo "$changes" | grep -oE '[0-9]+ insertion' | grep -oE '[0-9]+' || echo 0)"
339
+ ITERATION_LINES_CHANGED="${insertions:-0}"
340
+ if [[ -n "$VELOCITY_HISTORY" ]]; then
341
+ VELOCITY_HISTORY="${VELOCITY_HISTORY},${ITERATION_LINES_CHANGED}"
342
+ else
343
+ VELOCITY_HISTORY="${ITERATION_LINES_CHANGED}"
344
+ fi
345
+ }
346
+
347
+ # Compute average lines/iteration from recent history
348
+ compute_velocity_avg() {
349
+ if [[ -z "$VELOCITY_HISTORY" ]]; then
350
+ echo "0"
351
+ return 0
352
+ fi
353
+ local total=0 count=0
354
+ local IFS=','
355
+ local val
356
+ for val in $VELOCITY_HISTORY; do
357
+ total=$((total + val))
358
+ count=$((count + 1))
359
+ done
360
+ if [[ "$count" -gt 0 ]]; then
361
+ echo $((total / count))
362
+ else
363
+ echo "0"
364
+ fi
365
+ }
366
+
217
367
  # ─── Timing Helpers ───────────────────────────────────────────────────────────
218
368
 
219
369
  now_iso() { date -u +%Y-%m-%dT%H:%M:%SZ; }
@@ -290,6 +440,9 @@ resume_state() {
290
440
  audit_agent_enabled:*) AUDIT_AGENT_ENABLED="$(echo "${line#audit_agent_enabled:}" | tr -d ' ')" ;;
291
441
  quality_gates_enabled:*) QUALITY_GATES_ENABLED="$(echo "${line#quality_gates_enabled:}" | tr -d ' ')" ;;
292
442
  dod_file:*) DOD_FILE="$(echo "${line#dod_file:}" | sed 's/^ *"//;s/" *$//')" ;;
443
+ auto_extend:*) AUTO_EXTEND="$(echo "${line#auto_extend:}" | tr -d ' ')" ;;
444
+ extension_count:*) EXTENSION_COUNT="$(echo "${line#extension_count:}" | tr -d ' ')" ;;
445
+ max_extensions:*) MAX_EXTENSIONS="$(echo "${line#max_extensions:}" | tr -d ' ')" ;;
293
446
  esac
294
447
  fi
295
448
  done < "$STATE_FILE"
@@ -345,6 +498,9 @@ audit_enabled: $AUDIT_ENABLED
345
498
  audit_agent_enabled: $AUDIT_AGENT_ENABLED
346
499
  quality_gates_enabled: $QUALITY_GATES_ENABLED
347
500
  dod_file: "$DOD_FILE"
501
+ auto_extend: $AUTO_EXTEND
502
+ extension_count: $EXTENSION_COUNT
503
+ max_extensions: $MAX_EXTENSIONS
348
504
  ---
349
505
 
350
506
  ## Log
@@ -400,7 +556,7 @@ check_progress() {
400
556
  changes="$(git -C "$PROJECT_ROOT" diff --stat HEAD~1 2>/dev/null | tail -1 || echo "")"
401
557
  local insertions
402
558
  insertions="$(echo "$changes" | grep -oE '[0-9]+ insertion' | grep -oE '[0-9]+' || echo 0)"
403
- if [[ "${insertions:-0}" -lt 5 ]]; then
559
+ if [[ "${insertions:-0}" -lt "$MIN_PROGRESS_LINES" ]]; then
404
560
  return 1 # No meaningful progress
405
561
  fi
406
562
  return 0
@@ -412,8 +568,8 @@ check_completion() {
412
568
  }
413
569
 
414
570
  check_circuit_breaker() {
415
- if [[ "$CONSECUTIVE_FAILURES" -ge 3 ]]; then
416
- error "Circuit breaker tripped: 3 consecutive iterations with no meaningful progress."
571
+ if [[ "$CONSECUTIVE_FAILURES" -ge "$CIRCUIT_BREAKER_THRESHOLD" ]]; then
572
+ error "Circuit breaker tripped: ${CIRCUIT_BREAKER_THRESHOLD} consecutive iterations with no meaningful progress."
417
573
  STATUS="circuit_breaker"
418
574
  return 1
419
575
  fi
@@ -421,12 +577,64 @@ check_circuit_breaker() {
421
577
  }
422
578
 
423
579
  check_max_iterations() {
424
- if [[ "$ITERATION" -gt "$MAX_ITERATIONS" ]]; then
580
+ if [[ "$ITERATION" -le "$MAX_ITERATIONS" ]]; then
581
+ return 0
582
+ fi
583
+
584
+ # Hit the cap — check if we should auto-extend
585
+ if ! $AUTO_EXTEND || [[ "$EXTENSION_COUNT" -ge "$MAX_EXTENSIONS" ]]; then
586
+ if [[ "$EXTENSION_COUNT" -ge "$MAX_EXTENSIONS" ]]; then
587
+ warn "Hard cap reached: ${EXTENSION_COUNT} extensions applied (max ${MAX_EXTENSIONS})."
588
+ fi
425
589
  warn "Max iterations ($MAX_ITERATIONS) reached."
426
590
  STATUS="max_iterations"
427
591
  return 1
428
592
  fi
429
- return 0
593
+
594
+ # Checkpoint audit: is there meaningful progress worth extending for?
595
+ echo -e "\n ${CYAN}${BOLD}▸ Checkpoint${RESET} — max iterations ($MAX_ITERATIONS) reached, evaluating progress..."
596
+
597
+ local should_extend=false
598
+ local extension_reason=""
599
+
600
+ # Check 1: recent meaningful progress (not stuck)
601
+ if [[ "${CONSECUTIVE_FAILURES:-0}" -lt 2 ]]; then
602
+ # Check 2: agent hasn't signaled completion (if it did, guard_completion handles it)
603
+ local last_log="$LOG_DIR/iteration-$(( ITERATION - 1 )).log"
604
+ if [[ -f "$last_log" ]] && ! grep -q "LOOP_COMPLETE" "$last_log" 2>/dev/null; then
605
+ should_extend=true
606
+ extension_reason="work in progress with recent progress"
607
+ fi
608
+ fi
609
+
610
+ # Check 3: if quality gates or tests are failing, extend to let agent fix them
611
+ if [[ "$TEST_PASSED" == "false" ]] || ! $QUALITY_GATE_PASSED; then
612
+ should_extend=true
613
+ extension_reason="quality gates or tests not yet passing"
614
+ fi
615
+
616
+ if $should_extend; then
617
+ # Scale extension size by velocity — good progress earns more iterations
618
+ local velocity_avg
619
+ velocity_avg="$(compute_velocity_avg)"
620
+ local effective_extension="$EXTENSION_SIZE"
621
+ if [[ "$velocity_avg" -gt 20 ]]; then
622
+ # High velocity: grant more iterations
623
+ effective_extension=$(( EXTENSION_SIZE + 3 ))
624
+ elif [[ "$velocity_avg" -lt 5 ]]; then
625
+ # Low velocity: grant fewer iterations
626
+ effective_extension=$(( EXTENSION_SIZE > 2 ? EXTENSION_SIZE - 2 : 1 ))
627
+ fi
628
+ EXTENSION_COUNT=$(( EXTENSION_COUNT + 1 ))
629
+ MAX_ITERATIONS=$(( MAX_ITERATIONS + effective_extension ))
630
+ echo -e " ${GREEN}✓${RESET} Auto-extending: +${effective_extension} iterations (now ${MAX_ITERATIONS} max, extension ${EXTENSION_COUNT}/${MAX_EXTENSIONS})"
631
+ echo -e " ${DIM}Reason: ${extension_reason} | velocity: ~${velocity_avg} lines/iter${RESET}"
632
+ return 0
633
+ fi
634
+
635
+ warn "Max iterations reached — no recent progress detected."
636
+ STATUS="max_iterations"
637
+ return 1
430
638
  }
431
639
 
432
640
  # ─── Test Gate ────────────────────────────────────────────────────────────────
@@ -439,7 +647,7 @@ run_test_gate() {
439
647
  fi
440
648
 
441
649
  local test_log="$LOG_DIR/tests-iter-${ITERATION}.log"
442
- if eval "$TEST_CMD" > "$test_log" 2>&1; then
650
+ if bash -c "$TEST_CMD" > "$test_log" 2>&1; then
443
651
  TEST_PASSED=true
444
652
  TEST_OUTPUT="All tests passed."
445
653
  else
@@ -491,9 +699,11 @@ AUDIT_PROMPT
491
699
 
492
700
  echo -e " ${PURPLE}▸${RESET} Running audit agent..."
493
701
 
494
- # Build flags with haiku model override for fast/cheap audit
702
+ # Select audit model adaptively (haiku if success rate high, else sonnet)
703
+ local audit_model
704
+ audit_model="$(select_audit_model)"
495
705
  local audit_flags=()
496
- audit_flags+=("--model" "haiku")
706
+ audit_flags+=("--model" "$audit_model")
497
707
  if $SKIP_PERMISSIONS; then
498
708
  audit_flags+=("--dangerously-skip-permissions")
499
709
  fi
@@ -588,8 +798,10 @@ Otherwise, list which items are NOT satisfied and why.
588
798
  DOD_PROMPT
589
799
 
590
800
  local dod_log="$LOG_DIR/dod-iter-${ITERATION}.log"
801
+ local dod_model
802
+ dod_model="$(select_audit_model)"
591
803
  local dod_flags=()
592
- dod_flags+=("--model" "haiku")
804
+ dod_flags+=("--model" "$dod_model")
593
805
  if $SKIP_PERMISSIONS; then
594
806
  dod_flags+=("--dangerously-skip-permissions")
595
807
  fi
@@ -680,6 +892,120 @@ $TEST_OUTPUT"
680
892
  local rejection_notice_section
681
893
  rejection_notice_section="$(compose_rejection_notice_section)"
682
894
 
895
+ # Memory context injection (failure patterns + past learnings)
896
+ local memory_section=""
897
+ if type memory_inject_context &>/dev/null 2>&1; then
898
+ memory_section="$(memory_inject_context "build" 2>/dev/null || true)"
899
+ elif [[ -f "$SCRIPT_DIR/sw-memory.sh" ]]; then
900
+ memory_section="$("$SCRIPT_DIR/sw-memory.sh" inject build 2>/dev/null || true)"
901
+ fi
902
+
903
+ # DORA baselines for context
904
+ local dora_section=""
905
+ if type memory_get_dora_baseline &>/dev/null 2>&1; then
906
+ local dora_json
907
+ dora_json="$(memory_get_dora_baseline 7 2>/dev/null || echo "{}")"
908
+ local dora_total
909
+ dora_total=$(echo "$dora_json" | jq -r '.total // 0' 2>/dev/null || echo "0")
910
+ if [[ "$dora_total" -gt 0 ]]; then
911
+ local dora_df dora_cfr
912
+ dora_df=$(echo "$dora_json" | jq -r '.deploy_freq // 0' 2>/dev/null || echo "0")
913
+ dora_cfr=$(echo "$dora_json" | jq -r '.cfr // 0' 2>/dev/null || echo "0")
914
+ dora_section="## Performance Baselines (Last 7 Days)
915
+ - Deploy frequency: ${dora_df}/week
916
+ - Change failure rate: ${dora_cfr}%
917
+ - Total pipeline runs: ${dora_total}"
918
+ fi
919
+ fi
920
+
921
+ # Append mid-loop memory refresh if available
922
+ local memory_refresh_file="$LOG_DIR/memory-refresh-$(( ITERATION - 1 )).txt"
923
+ if [[ -f "$memory_refresh_file" ]]; then
924
+ memory_section="${memory_section}
925
+
926
+ ## Fresh Context (from iteration $(( ITERATION - 1 )) analysis)
927
+ $(cat "$memory_refresh_file")"
928
+ fi
929
+
930
+ # GitHub intelligence context (gated by availability)
931
+ local intelligence_section=""
932
+ if [[ "${NO_GITHUB:-}" != "true" ]]; then
933
+ # File hotspots — top 5 most-changed files
934
+ if type gh_file_change_frequency &>/dev/null 2>&1; then
935
+ local hotspots
936
+ hotspots=$(gh_file_change_frequency 2>/dev/null | head -5 || true)
937
+ if [[ -n "$hotspots" ]]; then
938
+ intelligence_section="${intelligence_section}
939
+ ## File Hotspots (most frequently changed)
940
+ ${hotspots}"
941
+ fi
942
+ fi
943
+
944
+ # CODEOWNERS context
945
+ if type gh_codeowners &>/dev/null 2>&1; then
946
+ local owners
947
+ owners=$(gh_codeowners 2>/dev/null | head -10 || true)
948
+ if [[ -n "$owners" ]]; then
949
+ intelligence_section="${intelligence_section}
950
+ ## Code Owners
951
+ ${owners}"
952
+ fi
953
+ fi
954
+
955
+ # Active security alerts
956
+ if type gh_security_alerts &>/dev/null 2>&1; then
957
+ local alerts
958
+ alerts=$(gh_security_alerts 2>/dev/null | head -5 || true)
959
+ if [[ -n "$alerts" ]]; then
960
+ intelligence_section="${intelligence_section}
961
+ ## Active Security Alerts
962
+ ${alerts}"
963
+ fi
964
+ fi
965
+ fi
966
+
967
+ # Architecture rules (from intelligence layer)
968
+ local repo_hash
969
+ repo_hash=$(echo -n "$(pwd)" | shasum -a 256 2>/dev/null | cut -c1-12 || echo "unknown")
970
+ local arch_file="${HOME}/.shipwright/memory/${repo_hash}/architecture.json"
971
+ if [[ -f "$arch_file" ]]; then
972
+ local arch_rules
973
+ arch_rules=$(jq -r '.rules[]? // empty' "$arch_file" 2>/dev/null | head -10 || true)
974
+ if [[ -n "$arch_rules" ]]; then
975
+ intelligence_section="${intelligence_section}
976
+ ## Architecture Rules
977
+ ${arch_rules}"
978
+ fi
979
+ fi
980
+
981
+ # Coverage baseline
982
+ local coverage_file="${HOME}/.shipwright/baselines/${repo_hash}/coverage.json"
983
+ if [[ -f "$coverage_file" ]]; then
984
+ local coverage_pct
985
+ coverage_pct=$(jq -r '.coverage_percent // empty' "$coverage_file" 2>/dev/null || true)
986
+ if [[ -n "$coverage_pct" ]]; then
987
+ intelligence_section="${intelligence_section}
988
+ ## Coverage Baseline
989
+ Current coverage: ${coverage_pct}% — do not decrease this."
990
+ fi
991
+ fi
992
+
993
+ # Error classification from last failure
994
+ local error_log=".claude/pipeline-artifacts/error-log.jsonl"
995
+ if [[ -f "$error_log" ]]; then
996
+ local last_error
997
+ last_error=$(tail -1 "$error_log" 2>/dev/null | jq -r '"Type: \(.type), Exit: \(.exit_code), Error: \(.error | split("\n") | first)"' 2>/dev/null || true)
998
+ if [[ -n "$last_error" ]]; then
999
+ intelligence_section="${intelligence_section}
1000
+ ## Last Error Context
1001
+ ${last_error}"
1002
+ fi
1003
+ fi
1004
+
1005
+ # Stuckness detection — compare last 3 iteration outputs
1006
+ local stuckness_section=""
1007
+ stuckness_section="$(detect_stuckness)"
1008
+
683
1009
  cat <<PROMPT
684
1010
  You are an autonomous coding agent on iteration ${ITERATION}/${MAX_ITERATIONS} of a continuous loop.
685
1011
 
@@ -695,6 +1021,13 @@ ${git_log}
695
1021
  ## Test Results (Previous Iteration)
696
1022
  ${test_section}
697
1023
 
1024
+ ${memory_section:+## Memory Context
1025
+ $memory_section
1026
+ }
1027
+ ${dora_section:+$dora_section
1028
+ }
1029
+ ${intelligence_section:+$intelligence_section
1030
+ }
698
1031
  ## Instructions
699
1032
  1. Read the codebase and understand the current state
700
1033
  2. Identify the highest-priority remaining work toward the goal
@@ -709,6 +1042,8 @@ ${audit_feedback_section}
709
1042
 
710
1043
  ${rejection_notice_section}
711
1044
 
1045
+ ${stuckness_section}
1046
+
712
1047
  ## Rules
713
1048
  - Focus on ONE task per iteration — do it well
714
1049
  - Always commit with descriptive messages
@@ -718,22 +1053,107 @@ ${rejection_notice_section}
718
1053
  PROMPT
719
1054
  }
720
1055
 
1056
+ # ─── Stuckness Detection ─────────────────────────────────────────────────────
1057
+ # Compares last 3 iteration log outputs for high overlap (>90% similar lines).
1058
+ detect_stuckness() {
1059
+ if [[ "$ITERATION" -lt 3 ]]; then
1060
+ return 0
1061
+ fi
1062
+
1063
+ local log1="$LOG_DIR/iteration-$(( ITERATION - 1 )).log"
1064
+ local log2="$LOG_DIR/iteration-$(( ITERATION - 2 )).log"
1065
+ local log3="$LOG_DIR/iteration-$(( ITERATION - 3 )).log"
1066
+
1067
+ # Need at least 2 previous logs
1068
+ if [[ ! -f "$log1" || ! -f "$log2" ]]; then
1069
+ return 0
1070
+ fi
1071
+
1072
+ # Compare last 50 lines of each (ignoring timestamps and blank lines)
1073
+ local lines1 lines2 common total overlap_pct
1074
+ lines1=$(tail -50 "$log1" 2>/dev/null | grep -v '^$' | sort || true)
1075
+ lines2=$(tail -50 "$log2" 2>/dev/null | grep -v '^$' | sort || true)
1076
+
1077
+ if [[ -z "$lines1" || -z "$lines2" ]]; then
1078
+ return 0
1079
+ fi
1080
+
1081
+ total=$(echo "$lines1" | wc -l | tr -d ' ')
1082
+ common=$(comm -12 <(echo "$lines1") <(echo "$lines2") 2>/dev/null | wc -l | tr -d ' ' || echo "0")
1083
+
1084
+ if [[ "$total" -gt 0 ]]; then
1085
+ overlap_pct=$(( common * 100 / total ))
1086
+ else
1087
+ overlap_pct=0
1088
+ fi
1089
+
1090
+ if [[ "$overlap_pct" -ge 90 ]]; then
1091
+ local diff_summary=""
1092
+ if [[ -f "$log3" ]]; then
1093
+ diff_summary=$(diff <(tail -30 "$log3" 2>/dev/null) <(tail -30 "$log1" 2>/dev/null) 2>/dev/null | head -10 || true)
1094
+ fi
1095
+
1096
+ # Gather memory-based alternative approaches
1097
+ local alternatives=""
1098
+ if type memory_inject_context &>/dev/null 2>&1; then
1099
+ alternatives=$(memory_inject_context "build" 2>/dev/null | grep -i "fix:" | head -3 || true)
1100
+ fi
1101
+
1102
+ cat <<STUCK_SECTION
1103
+ ## Stuckness Detected
1104
+ Your last ${CONSECUTIVE_FAILURES:-2}+ iterations produced very similar output (${overlap_pct}% overlap).
1105
+ You appear to be stuck on the same approach.
1106
+
1107
+ ${diff_summary:+Changes between recent iterations:
1108
+ $diff_summary
1109
+ }
1110
+ ${alternatives:+Consider these alternative approaches from past fixes:
1111
+ $alternatives
1112
+ }
1113
+ Try a fundamentally different approach:
1114
+ - Break the problem into smaller steps
1115
+ - Look for an entirely different implementation strategy
1116
+ - Check if there's a dependency or configuration issue blocking progress
1117
+ - Read error messages more carefully — the root cause may differ from your assumption
1118
+ STUCK_SECTION
1119
+ fi
1120
+ }
1121
+
721
1122
  compose_audit_section() {
722
1123
  if ! $AUDIT_ENABLED; then
723
1124
  return
724
1125
  fi
725
- cat <<'AUDIT_SECTION'
726
- ## Self-Audit Checklist
727
- Before declaring LOOP_COMPLETE, critically evaluate your own work:
728
- 1. Does the implementation FULLY satisfy the goal, not just partially?
729
- 2. Are there any edge cases you haven't handled?
730
- 3. Did you leave any TODO, FIXME, HACK, or XXX comments in new code?
731
- 4. Are all new functions/modules tested (if a test command exists)?
732
- 5. Would a code reviewer approve this, or would they request changes?
733
- 6. Is the code clean, well-structured, and following project conventions?
734
-
735
- If ANY answer is "no", do NOT output LOOP_COMPLETE. Instead, fix the issues first.
736
- AUDIT_SECTION
1126
+
1127
+ # Try to inject audit items from past review feedback in memory
1128
+ local memory_audit_items=""
1129
+ if [[ -f "$SCRIPT_DIR/sw-memory.sh" ]]; then
1130
+ local mem_dir_path
1131
+ mem_dir_path="$HOME/.shipwright/memory"
1132
+ # Look for review feedback in any repo memory
1133
+ local repo_hash_val
1134
+ repo_hash_val=$(git config --get remote.origin.url 2>/dev/null | shasum -a 256 2>/dev/null | cut -c1-12 || echo "")
1135
+ if [[ -n "$repo_hash_val" && -f "$mem_dir_path/$repo_hash_val/failures.json" ]]; then
1136
+ memory_audit_items=$(jq -r '.failures[] | select(.stage == "review" and .pattern != "") |
1137
+ "- Check for: \(.pattern[:100])"' \
1138
+ "$mem_dir_path/$repo_hash_val/failures.json" 2>/dev/null | head -5 || true)
1139
+ fi
1140
+ fi
1141
+
1142
+ echo "## Self-Audit Checklist"
1143
+ echo "Before declaring LOOP_COMPLETE, critically evaluate your own work:"
1144
+ echo "1. Does the implementation FULLY satisfy the goal, not just partially?"
1145
+ echo "2. Are there any edge cases you haven't handled?"
1146
+ echo "3. Did you leave any TODO, FIXME, HACK, or XXX comments in new code?"
1147
+ echo "4. Are all new functions/modules tested (if a test command exists)?"
1148
+ echo "5. Would a code reviewer approve this, or would they request changes?"
1149
+ echo "6. Is the code clean, well-structured, and following project conventions?"
1150
+ if [[ -n "$memory_audit_items" ]]; then
1151
+ echo ""
1152
+ echo "Common review findings from this repo's history:"
1153
+ echo "$memory_audit_items"
1154
+ fi
1155
+ echo ""
1156
+ echo "If ANY answer is \"no\", do NOT output LOOP_COMPLETE. Instead, fix the issues first."
737
1157
  }
738
1158
 
739
1159
  compose_audit_feedback_section() {
@@ -809,10 +1229,20 @@ run_claude_iteration() {
809
1229
 
810
1230
  echo -e "\n${CYAN}${BOLD}▸${RESET} ${BOLD}Iteration ${ITERATION}/${MAX_ITERATIONS}${RESET} — Starting..."
811
1231
 
812
- # Run Claude headless
1232
+ # Run Claude headless (with timeout + PID capture for signal handling)
813
1233
  local exit_code=0
814
1234
  # shellcheck disable=SC2086
815
- claude -p "$prompt" $flags > "$log_file" 2>&1 || exit_code=$?
1235
+ if [[ -n "$TIMEOUT_CMD" ]]; then
1236
+ $TIMEOUT_CMD "$CLAUDE_TIMEOUT" claude -p "$prompt" $flags > "$log_file" 2>&1 &
1237
+ else
1238
+ claude -p "$prompt" $flags > "$log_file" 2>&1 &
1239
+ fi
1240
+ CHILD_PID=$!
1241
+ wait "$CHILD_PID" 2>/dev/null || exit_code=$?
1242
+ CHILD_PID=""
1243
+ if [[ "$exit_code" -eq 124 ]]; then
1244
+ warn "Claude CLI timed out after ${CLAUDE_TIMEOUT}s"
1245
+ fi
816
1246
 
817
1247
  local iter_end
818
1248
  iter_end="$(now_epoch)"
@@ -849,7 +1279,11 @@ show_banner() {
849
1279
  echo -e "${CYAN}═══════════════════════════════════════════════${RESET}"
850
1280
  echo ""
851
1281
  echo -e " ${BOLD}Goal:${RESET} $GOAL"
852
- echo -e " ${BOLD}Model:${RESET} $MODEL ${DIM}|${RESET} ${BOLD}Max:${RESET} $MAX_ITERATIONS iterations ${DIM}|${RESET} ${BOLD}Test:${RESET} ${TEST_CMD:-"(none)"}"
1282
+ local extend_info=""
1283
+ if $AUTO_EXTEND; then
1284
+ extend_info=" ${DIM}(auto-extend: +${EXTENSION_SIZE} x${MAX_EXTENSIONS})${RESET}"
1285
+ fi
1286
+ echo -e " ${BOLD}Model:${RESET} $MODEL ${DIM}|${RESET} ${BOLD}Max:${RESET} $MAX_ITERATIONS iterations${extend_info} ${DIM}|${RESET} ${BOLD}Test:${RESET} ${TEST_CMD:-"(none)"}"
853
1287
  if [[ "$AGENTS" -gt 1 ]]; then
854
1288
  echo -e " ${BOLD}Agents:${RESET} $AGENTS ${DIM}(parallel worktree mode)${RESET}"
855
1289
  fi
@@ -898,7 +1332,9 @@ show_summary() {
898
1332
  echo ""
899
1333
  echo -e " ${BOLD}Goal:${RESET} $GOAL"
900
1334
  echo -e " ${BOLD}Status:${RESET} $status_display"
901
- echo -e " ${BOLD}Iterations:${RESET} $ITERATION/$MAX_ITERATIONS"
1335
+ local ext_suffix=""
1336
+ [[ "$EXTENSION_COUNT" -gt 0 ]] && ext_suffix=" ${DIM}(${EXTENSION_COUNT} extensions)${RESET}"
1337
+ echo -e " ${BOLD}Iterations:${RESET} $ITERATION/$MAX_ITERATIONS${ext_suffix}"
902
1338
  echo -e " ${BOLD}Duration:${RESET} $(format_duration "$duration")"
903
1339
  echo -e " ${BOLD}Commits:${RESET} $TOTAL_COMMITS"
904
1340
  echo -e " ${BOLD}Tests:${RESET} $test_display"
@@ -929,6 +1365,16 @@ cleanup() {
929
1365
 
930
1366
  STATUS="interrupted"
931
1367
  write_state
1368
+
1369
+ # Save checkpoint on interruption
1370
+ "$SCRIPT_DIR/sw-checkpoint.sh" save \
1371
+ --stage "build" \
1372
+ --iteration "$ITERATION" \
1373
+ --git-sha "$(git rev-parse HEAD 2>/dev/null || echo unknown)" 2>/dev/null || true
1374
+
1375
+ # Clear heartbeat
1376
+ "$SCRIPT_DIR/sw-heartbeat.sh" clear "${PIPELINE_JOB_ID:-loop-$$}" 2>/dev/null || true
1377
+
932
1378
  show_summary
933
1379
  exit 130
934
1380
  }
@@ -1102,13 +1548,13 @@ echo -e "\n${DIM}Agent ${AGENT_NUM} finished after ${ITERATION} iterations${RESE
1102
1548
  WORKEREOF
1103
1549
 
1104
1550
  # Replace placeholders
1105
- sed -i '' "s|__AGENT_NUM__|${agent_num}|g" "$worker_script"
1106
- sed -i '' "s|__TOTAL_AGENTS__|${total_agents}|g" "$worker_script"
1107
- sed -i '' "s|__WORK_DIR__|${wt_path}|g" "$worker_script"
1108
- sed -i '' "s|__LOG_DIR__|${LOG_DIR}|g" "$worker_script"
1109
- sed -i '' "s|__MAX_ITERATIONS__|${MAX_ITERATIONS}|g" "$worker_script"
1110
- sed -i '' "s|__TEST_CMD__|${TEST_CMD}|g" "$worker_script"
1111
- sed -i '' "s|__CLAUDE_FLAGS__|${claude_flags}|g" "$worker_script"
1551
+ sed_i "s|__AGENT_NUM__|${agent_num}|g" "$worker_script"
1552
+ sed_i "s|__TOTAL_AGENTS__|${total_agents}|g" "$worker_script"
1553
+ sed_i "s|__WORK_DIR__|${wt_path}|g" "$worker_script"
1554
+ sed_i "s|__LOG_DIR__|${LOG_DIR}|g" "$worker_script"
1555
+ sed_i "s|__MAX_ITERATIONS__|${MAX_ITERATIONS}|g" "$worker_script"
1556
+ sed_i "s|__TEST_CMD__|${TEST_CMD}|g" "$worker_script"
1557
+ sed_i "s|__CLAUDE_FLAGS__|${claude_flags}|g" "$worker_script"
1112
1558
  # Goal needs special handling for sed (may contain special chars)
1113
1559
  # Use awk for safe string replacement without python
1114
1560
  awk -v goal="$GOAL" '{gsub(/__GOAL__/, goal); print}' "$worker_script" > "${worker_script}.tmp" \
@@ -1128,7 +1574,7 @@ launch_multi_agent() {
1128
1574
  setup_worktrees || { error "Failed to setup worktrees"; exit 1; }
1129
1575
 
1130
1576
  # Create tmux window for workers
1131
- MULTI_WINDOW_NAME="cct-loop-$(date +%s)"
1577
+ MULTI_WINDOW_NAME="sw-loop-$(date +%s)"
1132
1578
  tmux new-window -n "$MULTI_WINDOW_NAME" -c "$PROJECT_ROOT"
1133
1579
 
1134
1580
  # First pane becomes monitor
@@ -1233,13 +1679,17 @@ run_single_agent_loop() {
1233
1679
  initialize_state
1234
1680
  fi
1235
1681
 
1682
+ # Apply adaptive budget/model before showing banner
1683
+ apply_adaptive_budget
1684
+ MODEL="$(select_adaptive_model "build" "$MODEL")"
1685
+
1236
1686
  show_banner
1237
1687
 
1238
1688
  while true; do
1239
1689
  # Pre-checks (before incrementing — ITERATION tracks completed count)
1240
1690
  check_circuit_breaker || break
1691
+ check_max_iterations || break
1241
1692
  ITERATION=$(( ITERATION + 1 ))
1242
- check_max_iterations || { ITERATION=$(( ITERATION - 1 )); break; }
1243
1693
 
1244
1694
  # Run Claude
1245
1695
  local exit_code=0
@@ -1247,6 +1697,21 @@ run_single_agent_loop() {
1247
1697
 
1248
1698
  local log_file="$LOG_DIR/iteration-${ITERATION}.log"
1249
1699
 
1700
+ # Mid-loop memory refresh — re-query with current error context after iteration 3
1701
+ if [[ "$ITERATION" -ge 3 ]] && type memory_inject_context &>/dev/null 2>&1; then
1702
+ local refresh_ctx
1703
+ refresh_ctx=$(tail -20 "$log_file" 2>/dev/null || true)
1704
+ if [[ -n "$refresh_ctx" ]]; then
1705
+ local refreshed_memory
1706
+ refreshed_memory=$(memory_inject_context "build" "$refresh_ctx" 2>/dev/null | head -5 || true)
1707
+ if [[ -n "$refreshed_memory" ]]; then
1708
+ # Append to next iteration's memory context
1709
+ local memory_refresh_file="$LOG_DIR/memory-refresh-${ITERATION}.txt"
1710
+ echo "$refreshed_memory" > "$memory_refresh_file"
1711
+ fi
1712
+ fi
1713
+ fi
1714
+
1250
1715
  # Auto-commit if Claude didn't
1251
1716
  local commits_before
1252
1717
  commits_before="$(git_commit_count)"
@@ -1263,6 +1728,9 @@ run_single_agent_loop() {
1263
1728
  echo -e " ${GREEN}✓${RESET} Git: $diff_stat"
1264
1729
  fi
1265
1730
 
1731
+ # Track velocity for adaptive extension budget
1732
+ track_iteration_velocity
1733
+
1266
1734
  # Test gate
1267
1735
  run_test_gate
1268
1736
  if [[ -n "$TEST_CMD" ]]; then
@@ -1293,7 +1761,7 @@ run_single_agent_loop() {
1293
1761
  echo -e " ${GREEN}✓${RESET} Progress detected — continuing"
1294
1762
  else
1295
1763
  CONSECUTIVE_FAILURES=$(( CONSECUTIVE_FAILURES + 1 ))
1296
- echo -e " ${YELLOW}⚠${RESET} Low progress (${CONSECUTIVE_FAILURES}/3 before circuit breaker)"
1764
+ echo -e " ${YELLOW}⚠${RESET} Low progress (${CONSECUTIVE_FAILURES}/${CIRCUIT_BREAKER_THRESHOLD} before circuit breaker)"
1297
1765
  fi
1298
1766
 
1299
1767
  # Extract summary and update state
@@ -1304,6 +1772,28 @@ $summary
1304
1772
  "
1305
1773
  write_state
1306
1774
 
1775
+ # Update heartbeat
1776
+ "$SCRIPT_DIR/sw-heartbeat.sh" write "${PIPELINE_JOB_ID:-loop-$$}" \
1777
+ --pid $$ \
1778
+ --stage "build" \
1779
+ --iteration "$ITERATION" \
1780
+ --activity "Loop iteration $ITERATION" 2>/dev/null || true
1781
+
1782
+ # Human intervention: check for human message between iterations
1783
+ local human_msg_file="$STATE_DIR/pipeline-artifacts/human-message.txt"
1784
+ if [[ -f "$human_msg_file" ]]; then
1785
+ local human_msg
1786
+ human_msg="$(cat "$human_msg_file" 2>/dev/null || true)"
1787
+ if [[ -n "$human_msg" ]]; then
1788
+ echo -e " ${PURPLE}${BOLD}💬 Human message:${RESET} $human_msg"
1789
+ # Inject human message as additional context for next iteration
1790
+ GOAL="${GOAL}
1791
+
1792
+ HUMAN FEEDBACK (received after iteration $ITERATION): $human_msg"
1793
+ rm -f "$human_msg_file"
1794
+ fi
1795
+ fi
1796
+
1307
1797
  sleep 2
1308
1798
  done
1309
1799