shipwright-cli 1.9.0 → 1.10.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.
- package/.claude/hooks/post-tool-use.sh +12 -5
- package/package.json +2 -2
- package/scripts/sw +9 -1
- package/scripts/sw-adversarial.sh +1 -1
- package/scripts/sw-architecture-enforcer.sh +1 -1
- package/scripts/sw-checkpoint.sh +79 -1
- package/scripts/sw-cleanup.sh +192 -7
- package/scripts/sw-connect.sh +1 -1
- package/scripts/sw-cost.sh +1 -1
- package/scripts/sw-daemon.sh +409 -37
- package/scripts/sw-dashboard.sh +1 -1
- package/scripts/sw-developer-simulation.sh +1 -1
- package/scripts/sw-docs.sh +1 -1
- package/scripts/sw-doctor.sh +1 -1
- package/scripts/sw-fix.sh +1 -1
- package/scripts/sw-fleet.sh +1 -1
- package/scripts/sw-github-checks.sh +1 -1
- package/scripts/sw-github-deploy.sh +1 -1
- package/scripts/sw-github-graphql.sh +1 -1
- package/scripts/sw-heartbeat.sh +1 -1
- package/scripts/sw-init.sh +1 -1
- package/scripts/sw-intelligence.sh +1 -1
- package/scripts/sw-jira.sh +1 -1
- package/scripts/sw-launchd.sh +4 -4
- package/scripts/sw-linear.sh +1 -1
- package/scripts/sw-logs.sh +1 -1
- package/scripts/sw-loop.sh +444 -49
- package/scripts/sw-memory.sh +198 -3
- package/scripts/sw-pipeline-composer.sh +8 -8
- package/scripts/sw-pipeline-vitals.sh +1096 -0
- package/scripts/sw-pipeline.sh +1692 -84
- package/scripts/sw-predictive.sh +1 -1
- package/scripts/sw-prep.sh +1 -1
- package/scripts/sw-ps.sh +4 -3
- package/scripts/sw-reaper.sh +5 -3
- package/scripts/sw-remote.sh +1 -1
- package/scripts/sw-self-optimize.sh +109 -8
- package/scripts/sw-session.sh +31 -9
- package/scripts/sw-setup.sh +1 -1
- package/scripts/sw-status.sh +192 -1
- package/scripts/sw-templates.sh +1 -1
- package/scripts/sw-tmux.sh +1 -1
- package/scripts/sw-tracker.sh +1 -1
- package/scripts/sw-upgrade.sh +1 -1
- package/scripts/sw-worktree.sh +1 -1
- package/templates/pipelines/autonomous.json +8 -1
- package/templates/pipelines/cost-aware.json +21 -0
- package/templates/pipelines/deployed.json +40 -6
- package/templates/pipelines/enterprise.json +16 -2
- package/templates/pipelines/fast.json +19 -0
- package/templates/pipelines/full.json +16 -2
- package/templates/pipelines/hotfix.json +19 -0
- package/templates/pipelines/standard.json +19 -0
package/scripts/sw-loop.sh
CHANGED
|
@@ -34,17 +34,25 @@ error() { echo -e "${RED}${BOLD}✗${RESET} $*" >&2; }
|
|
|
34
34
|
|
|
35
35
|
# ─── Defaults ─────────────────────────────────────────────────────────────────
|
|
36
36
|
GOAL=""
|
|
37
|
+
ORIGINAL_GOAL="" # Preserved across restarts — GOAL gets appended to
|
|
37
38
|
MAX_ITERATIONS="${SW_MAX_ITERATIONS:-20}"
|
|
38
39
|
TEST_CMD=""
|
|
40
|
+
FAST_TEST_CMD=""
|
|
41
|
+
FAST_TEST_INTERVAL=5
|
|
42
|
+
TEST_LOG_FILE=""
|
|
39
43
|
MODEL="${SW_MODEL:-opus}"
|
|
40
44
|
AGENTS=1
|
|
45
|
+
AGENT_ROLES=""
|
|
41
46
|
USE_WORKTREE=false
|
|
42
47
|
SKIP_PERMISSIONS=false
|
|
43
48
|
MAX_TURNS=""
|
|
44
49
|
RESUME=false
|
|
45
50
|
VERBOSE=false
|
|
46
51
|
MAX_ITERATIONS_EXPLICIT=false
|
|
47
|
-
|
|
52
|
+
MAX_RESTARTS=0
|
|
53
|
+
SESSION_RESTART=false
|
|
54
|
+
RESTART_COUNT=0
|
|
55
|
+
VERSION="1.10.0"
|
|
48
56
|
|
|
49
57
|
# ─── Flexible Iteration Defaults ────────────────────────────────────────────
|
|
50
58
|
AUTO_EXTEND=true # Auto-extend iterations when work is incomplete
|
|
@@ -75,12 +83,16 @@ show_help() {
|
|
|
75
83
|
echo -e "${BOLD}OPTIONS${RESET}"
|
|
76
84
|
echo -e " ${CYAN}--max-iterations${RESET} N Max loop iterations (default: 20)"
|
|
77
85
|
echo -e " ${CYAN}--test-cmd${RESET} \"cmd\" Test command to run between iterations"
|
|
86
|
+
echo -e " ${CYAN}--fast-test-cmd${RESET} \"cmd\" Fast/subset test command (alternates with full)"
|
|
87
|
+
echo -e " ${CYAN}--fast-test-interval${RESET} N Run full tests every N iterations (default: 5)"
|
|
78
88
|
echo -e " ${CYAN}--model${RESET} MODEL Claude model to use (default: opus)"
|
|
79
89
|
echo -e " ${CYAN}--agents${RESET} N Number of parallel agents (default: 1)"
|
|
90
|
+
echo -e " ${CYAN}--roles${RESET} \"r1,r2,...\" Role per agent: builder,reviewer,tester,optimizer,docs,security"
|
|
80
91
|
echo -e " ${CYAN}--worktree${RESET} Use git worktrees for isolation (auto if agents > 1)"
|
|
81
92
|
echo -e " ${CYAN}--skip-permissions${RESET} Pass --dangerously-skip-permissions to Claude"
|
|
82
93
|
echo -e " ${CYAN}--max-turns${RESET} N Max API turns per Claude session"
|
|
83
94
|
echo -e " ${CYAN}--resume${RESET} Resume from existing .claude/loop-state.md"
|
|
95
|
+
echo -e " ${CYAN}--max-restarts${RESET} N Max session restarts on exhaustion (default: 0)"
|
|
84
96
|
echo -e " ${CYAN}--verbose${RESET} Show full Claude output (default: summary)"
|
|
85
97
|
echo -e " ${CYAN}--help${RESET} Show this help"
|
|
86
98
|
echo ""
|
|
@@ -175,6 +187,30 @@ while [[ $# -gt 0 ]]; do
|
|
|
175
187
|
shift 2
|
|
176
188
|
;;
|
|
177
189
|
--max-extensions=*) MAX_EXTENSIONS="${1#--max-extensions=}"; shift ;;
|
|
190
|
+
--fast-test-cmd)
|
|
191
|
+
FAST_TEST_CMD="${2:-}"
|
|
192
|
+
[[ -z "$FAST_TEST_CMD" ]] && { error "Missing value for --fast-test-cmd"; exit 1; }
|
|
193
|
+
shift 2
|
|
194
|
+
;;
|
|
195
|
+
--fast-test-cmd=*) FAST_TEST_CMD="${1#--fast-test-cmd=}"; shift ;;
|
|
196
|
+
--fast-test-interval)
|
|
197
|
+
FAST_TEST_INTERVAL="${2:-}"
|
|
198
|
+
[[ -z "$FAST_TEST_INTERVAL" ]] && { error "Missing value for --fast-test-interval"; exit 1; }
|
|
199
|
+
shift 2
|
|
200
|
+
;;
|
|
201
|
+
--fast-test-interval=*) FAST_TEST_INTERVAL="${1#--fast-test-interval=}"; shift ;;
|
|
202
|
+
--max-restarts)
|
|
203
|
+
MAX_RESTARTS="${2:-}"
|
|
204
|
+
[[ -z "$MAX_RESTARTS" ]] && { error "Missing value for --max-restarts"; exit 1; }
|
|
205
|
+
shift 2
|
|
206
|
+
;;
|
|
207
|
+
--max-restarts=*) MAX_RESTARTS="${1#--max-restarts=}"; shift ;;
|
|
208
|
+
--roles)
|
|
209
|
+
AGENT_ROLES="${2:-}"
|
|
210
|
+
[[ -z "$AGENT_ROLES" ]] && { error "Missing value for --roles"; exit 1; }
|
|
211
|
+
shift 2
|
|
212
|
+
;;
|
|
213
|
+
--roles=*) AGENT_ROLES="${1#--roles=}"; shift ;;
|
|
178
214
|
--help|-h)
|
|
179
215
|
show_help
|
|
180
216
|
exit 0
|
|
@@ -203,6 +239,27 @@ if [[ "$AGENTS" -gt 1 ]]; then
|
|
|
203
239
|
USE_WORKTREE=true
|
|
204
240
|
fi
|
|
205
241
|
|
|
242
|
+
# Warn if --roles without --agents
|
|
243
|
+
if [[ -n "$AGENT_ROLES" ]] && [[ "$AGENTS" -le 1 ]]; then
|
|
244
|
+
warn "--roles requires --agents > 1 (roles are ignored in single-agent mode)"
|
|
245
|
+
fi
|
|
246
|
+
|
|
247
|
+
# Warn if --max-restarts with --agents > 1 (not yet supported)
|
|
248
|
+
if [[ "${MAX_RESTARTS:-0}" -gt 0 ]] && [[ "$AGENTS" -gt 1 ]]; then
|
|
249
|
+
warn "--max-restarts is ignored in multi-agent mode (restart support is single-agent only)"
|
|
250
|
+
MAX_RESTARTS=0
|
|
251
|
+
fi
|
|
252
|
+
|
|
253
|
+
# Validate numeric flags
|
|
254
|
+
if ! [[ "$FAST_TEST_INTERVAL" =~ ^[1-9][0-9]*$ ]]; then
|
|
255
|
+
error "--fast-test-interval must be a positive integer (got: $FAST_TEST_INTERVAL)"
|
|
256
|
+
exit 1
|
|
257
|
+
fi
|
|
258
|
+
if ! [[ "$MAX_RESTARTS" =~ ^[0-9]+$ ]]; then
|
|
259
|
+
error "--max-restarts must be a non-negative integer (got: $MAX_RESTARTS)"
|
|
260
|
+
exit 1
|
|
261
|
+
fi
|
|
262
|
+
|
|
206
263
|
# ─── Validate Inputs ─────────────────────────────────────────────────────────
|
|
207
264
|
|
|
208
265
|
if ! $RESUME && [[ -z "$GOAL" ]]; then
|
|
@@ -224,6 +281,9 @@ if ! git rev-parse --is-inside-work-tree &>/dev/null 2>&1; then
|
|
|
224
281
|
exit 1
|
|
225
282
|
fi
|
|
226
283
|
|
|
284
|
+
# Preserve original goal before any appending (memory fixes, human feedback)
|
|
285
|
+
ORIGINAL_GOAL="$GOAL"
|
|
286
|
+
|
|
227
287
|
# ─── Timeout Detection ────────────────────────────────────────────────────────
|
|
228
288
|
TIMEOUT_CMD=""
|
|
229
289
|
if command -v timeout &>/dev/null; then
|
|
@@ -266,6 +326,17 @@ select_adaptive_model() {
|
|
|
266
326
|
echo "$default_model"
|
|
267
327
|
return 0
|
|
268
328
|
fi
|
|
329
|
+
# Read learned model routing
|
|
330
|
+
local _routing_file="${HOME}/.shipwright/optimization/model-routing.json"
|
|
331
|
+
if [[ -f "$_routing_file" ]] && command -v jq &>/dev/null; then
|
|
332
|
+
local _routed_model
|
|
333
|
+
_routed_model=$(jq -r --arg r "$role" '.routes[$r].model // ""' "$_routing_file" 2>/dev/null) || true
|
|
334
|
+
if [[ -n "${_routed_model:-}" && "${_routed_model:-}" != "null" ]]; then
|
|
335
|
+
echo "${_routed_model}"
|
|
336
|
+
return 0
|
|
337
|
+
fi
|
|
338
|
+
fi
|
|
339
|
+
|
|
269
340
|
# Try intelligence-based recommendation
|
|
270
341
|
if type intelligence_recommend_model &>/dev/null 2>&1; then
|
|
271
342
|
local rec
|
|
@@ -317,6 +388,18 @@ apply_adaptive_budget() {
|
|
|
317
388
|
[[ -n "$tuned_cb" && "$tuned_cb" != "null" ]] && CIRCUIT_BREAKER_THRESHOLD="$tuned_cb"
|
|
318
389
|
fi
|
|
319
390
|
|
|
391
|
+
# Read learned iteration model
|
|
392
|
+
local _iter_model="${HOME}/.shipwright/optimization/iteration-model.json"
|
|
393
|
+
if [[ -f "$_iter_model" ]] && ! $MAX_ITERATIONS_EXPLICIT && command -v jq &>/dev/null; then
|
|
394
|
+
local _complexity="${ISSUE_COMPLEXITY:-${COMPLEXITY:-medium}}"
|
|
395
|
+
local _predicted_max
|
|
396
|
+
_predicted_max=$(jq -r --arg c "$_complexity" '.predictions[$c].max_iterations // ""' "$_iter_model" 2>/dev/null) || true
|
|
397
|
+
if [[ -n "${_predicted_max:-}" && "${_predicted_max:-}" != "null" && "${_predicted_max:-0}" -gt 0 ]]; then
|
|
398
|
+
MAX_ITERATIONS="${_predicted_max}"
|
|
399
|
+
info "Iteration model: ${_complexity} complexity → max ${_predicted_max} iterations"
|
|
400
|
+
fi
|
|
401
|
+
fi
|
|
402
|
+
|
|
320
403
|
# Try intelligence-based iteration estimate
|
|
321
404
|
if type intelligence_estimate_iterations &>/dev/null 2>&1 && ! $MAX_ITERATIONS_EXPLICIT; then
|
|
322
405
|
local est
|
|
@@ -481,31 +564,67 @@ resume_state() {
|
|
|
481
564
|
}
|
|
482
565
|
|
|
483
566
|
write_state() {
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
567
|
+
local tmp_state="${STATE_FILE}.tmp.$$"
|
|
568
|
+
# Use printf instead of heredoc to avoid delimiter injection from GOAL
|
|
569
|
+
{
|
|
570
|
+
printf -- '---\n'
|
|
571
|
+
printf 'goal: "%s"\n' "$GOAL"
|
|
572
|
+
printf 'iteration: %s\n' "$ITERATION"
|
|
573
|
+
printf 'max_iterations: %s\n' "$MAX_ITERATIONS"
|
|
574
|
+
printf 'status: %s\n' "$STATUS"
|
|
575
|
+
printf 'test_cmd: "%s"\n' "$TEST_CMD"
|
|
576
|
+
printf 'model: %s\n' "$MODEL"
|
|
577
|
+
printf 'agents: %s\n' "$AGENTS"
|
|
578
|
+
printf 'started_at: %s\n' "$(now_iso)"
|
|
579
|
+
printf 'last_iteration_at: %s\n' "$(now_iso)"
|
|
580
|
+
printf 'consecutive_failures: %s\n' "$CONSECUTIVE_FAILURES"
|
|
581
|
+
printf 'total_commits: %s\n' "$TOTAL_COMMITS"
|
|
582
|
+
printf 'audit_enabled: %s\n' "$AUDIT_ENABLED"
|
|
583
|
+
printf 'audit_agent_enabled: %s\n' "$AUDIT_AGENT_ENABLED"
|
|
584
|
+
printf 'quality_gates_enabled: %s\n' "$QUALITY_GATES_ENABLED"
|
|
585
|
+
printf 'dod_file: "%s"\n' "$DOD_FILE"
|
|
586
|
+
printf 'auto_extend: %s\n' "$AUTO_EXTEND"
|
|
587
|
+
printf 'extension_count: %s\n' "$EXTENSION_COUNT"
|
|
588
|
+
printf 'max_extensions: %s\n' "$MAX_EXTENSIONS"
|
|
589
|
+
printf -- '---\n\n'
|
|
590
|
+
printf '## Log\n'
|
|
591
|
+
printf '%s\n' "$LOG_ENTRIES"
|
|
592
|
+
} > "$tmp_state"
|
|
593
|
+
if ! mv "$tmp_state" "$STATE_FILE" 2>/dev/null; then
|
|
594
|
+
warn "Failed to write state file: $STATE_FILE"
|
|
595
|
+
fi
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
write_progress() {
|
|
599
|
+
local progress_file="$LOG_DIR/progress.md"
|
|
600
|
+
local recent_commits
|
|
601
|
+
recent_commits=$(git -C "$PROJECT_ROOT" log --oneline -5 2>/dev/null || echo "(no commits)")
|
|
602
|
+
local changed_files
|
|
603
|
+
changed_files=$(git -C "$PROJECT_ROOT" diff --name-only HEAD~3 2>/dev/null | head -20 || echo "(none)")
|
|
604
|
+
local last_error=""
|
|
605
|
+
local prev_test_log="$LOG_DIR/tests-iter-${ITERATION}.log"
|
|
606
|
+
if [[ -f "$prev_test_log" ]] && [[ "${TEST_PASSED:-}" == "false" ]]; then
|
|
607
|
+
last_error=$(tail -10 "$prev_test_log" 2>/dev/null || true)
|
|
608
|
+
fi
|
|
609
|
+
|
|
610
|
+
# Use printf to avoid heredoc delimiter injection from GOAL content
|
|
611
|
+
local tmp_progress="${progress_file}.tmp.$$"
|
|
612
|
+
{
|
|
613
|
+
printf '# Session Progress (Auto-Generated)\n\n'
|
|
614
|
+
printf '## Goal\n%s\n\n' "${GOAL}"
|
|
615
|
+
printf '## Status\n'
|
|
616
|
+
printf -- '- Iteration: %s/%s\n' "${ITERATION}" "${MAX_ITERATIONS}"
|
|
617
|
+
printf -- '- Session restart: %s/%s\n' "${RESTART_COUNT:-0}" "${MAX_RESTARTS:-0}"
|
|
618
|
+
printf -- '- Tests passing: %s\n' "${TEST_PASSED:-unknown}"
|
|
619
|
+
printf -- '- Status: %s\n\n' "${STATUS:-running}"
|
|
620
|
+
printf '## Recent Commits\n%s\n\n' "${recent_commits}"
|
|
621
|
+
printf '## Changed Files\n%s\n\n' "${changed_files}"
|
|
622
|
+
if [[ -n "$last_error" ]]; then
|
|
623
|
+
printf '## Last Error\n%s\n\n' "$last_error"
|
|
624
|
+
fi
|
|
625
|
+
printf '## Timestamp\n%s\n' "$(date -u +"%Y-%m-%dT%H:%M:%SZ")"
|
|
626
|
+
} > "$tmp_progress" 2>/dev/null
|
|
627
|
+
mv "$tmp_progress" "$progress_file" 2>/dev/null || rm -f "$tmp_progress" 2>/dev/null
|
|
509
628
|
}
|
|
510
629
|
|
|
511
630
|
append_log_entry() {
|
|
@@ -568,6 +687,30 @@ check_completion() {
|
|
|
568
687
|
}
|
|
569
688
|
|
|
570
689
|
check_circuit_breaker() {
|
|
690
|
+
# Vitals-driven circuit breaker (preferred over static threshold)
|
|
691
|
+
if type pipeline_compute_vitals &>/dev/null 2>&1 && type pipeline_health_verdict &>/dev/null 2>&1; then
|
|
692
|
+
local _vitals_json _verdict
|
|
693
|
+
local _loop_state="${STATE_FILE:-}"
|
|
694
|
+
local _loop_artifacts="${ARTIFACTS_DIR:-}"
|
|
695
|
+
local _loop_issue="${ISSUE_NUMBER:-}"
|
|
696
|
+
_vitals_json=$(pipeline_compute_vitals "$_loop_state" "$_loop_artifacts" "$_loop_issue" 2>/dev/null) || true
|
|
697
|
+
if [[ -n "$_vitals_json" && "$_vitals_json" != "{}" ]]; then
|
|
698
|
+
_verdict=$(echo "$_vitals_json" | jq -r '.verdict // "continue"' 2>/dev/null || echo "continue")
|
|
699
|
+
if [[ "$_verdict" == "abort" ]]; then
|
|
700
|
+
local _health_score
|
|
701
|
+
_health_score=$(echo "$_vitals_json" | jq -r '.health_score // 0' 2>/dev/null || echo "0")
|
|
702
|
+
error "Vitals circuit breaker: health score ${_health_score}/100 — aborting (${CONSECUTIVE_FAILURES} stagnant iterations)"
|
|
703
|
+
STATUS="circuit_breaker"
|
|
704
|
+
return 1
|
|
705
|
+
fi
|
|
706
|
+
# Vitals say continue/warn/intervene — don't trip circuit breaker yet
|
|
707
|
+
if [[ "$_verdict" == "continue" || "$_verdict" == "warn" ]]; then
|
|
708
|
+
return 0
|
|
709
|
+
fi
|
|
710
|
+
fi
|
|
711
|
+
fi
|
|
712
|
+
|
|
713
|
+
# Fallback: static threshold circuit breaker
|
|
571
714
|
if [[ "$CONSECUTIVE_FAILURES" -ge "$CIRCUIT_BREAKER_THRESHOLD" ]]; then
|
|
572
715
|
error "Circuit breaker tripped: ${CIRCUIT_BREAKER_THRESHOLD} consecutive iterations with no meaningful progress."
|
|
573
716
|
STATUS="circuit_breaker"
|
|
@@ -646,16 +789,88 @@ run_test_gate() {
|
|
|
646
789
|
return
|
|
647
790
|
fi
|
|
648
791
|
|
|
792
|
+
# Determine which test command to use this iteration
|
|
793
|
+
local active_test_cmd="$TEST_CMD"
|
|
794
|
+
local test_mode="full"
|
|
795
|
+
if [[ -n "$FAST_TEST_CMD" ]]; then
|
|
796
|
+
# Use full test every FAST_TEST_INTERVAL iterations, on first iteration, and on final iteration
|
|
797
|
+
if [[ "$ITERATION" -eq 1 ]] || [[ $(( ITERATION % FAST_TEST_INTERVAL )) -eq 0 ]] || [[ "$ITERATION" -ge "$MAX_ITERATIONS" ]]; then
|
|
798
|
+
active_test_cmd="$TEST_CMD"
|
|
799
|
+
test_mode="full"
|
|
800
|
+
else
|
|
801
|
+
active_test_cmd="$FAST_TEST_CMD"
|
|
802
|
+
test_mode="fast"
|
|
803
|
+
fi
|
|
804
|
+
fi
|
|
805
|
+
|
|
649
806
|
local test_log="$LOG_DIR/tests-iter-${ITERATION}.log"
|
|
650
|
-
|
|
807
|
+
TEST_LOG_FILE="$test_log"
|
|
808
|
+
echo -e " ${DIM}Running ${test_mode} tests...${RESET}"
|
|
809
|
+
# Wrap test command with timeout (5 min default) to prevent hanging
|
|
810
|
+
local test_timeout="${SW_TEST_TIMEOUT:-300}"
|
|
811
|
+
local test_wrapper="$active_test_cmd"
|
|
812
|
+
if command -v timeout &>/dev/null; then
|
|
813
|
+
test_wrapper="timeout ${test_timeout} bash -c $(printf '%q' "$active_test_cmd")"
|
|
814
|
+
elif command -v gtimeout &>/dev/null; then
|
|
815
|
+
test_wrapper="gtimeout ${test_timeout} bash -c $(printf '%q' "$active_test_cmd")"
|
|
816
|
+
fi
|
|
817
|
+
if bash -c "$test_wrapper" > "$test_log" 2>&1; then
|
|
651
818
|
TEST_PASSED=true
|
|
652
|
-
TEST_OUTPUT="All tests passed."
|
|
819
|
+
TEST_OUTPUT="All tests passed (${test_mode} mode)."
|
|
653
820
|
else
|
|
654
821
|
TEST_PASSED=false
|
|
655
822
|
TEST_OUTPUT="$(tail -50 "$test_log")"
|
|
656
823
|
fi
|
|
657
824
|
}
|
|
658
825
|
|
|
826
|
+
write_error_summary() {
|
|
827
|
+
local error_json="$LOG_DIR/error-summary.json"
|
|
828
|
+
|
|
829
|
+
# Only write on test failure
|
|
830
|
+
if [[ "${TEST_PASSED:-}" != "false" ]]; then
|
|
831
|
+
# Clear previous error summary on success
|
|
832
|
+
rm -f "$error_json" 2>/dev/null || true
|
|
833
|
+
return
|
|
834
|
+
fi
|
|
835
|
+
|
|
836
|
+
local test_log="${TEST_LOG_FILE:-$LOG_DIR/tests-iter-${ITERATION}.log}"
|
|
837
|
+
[[ ! -f "$test_log" ]] && return
|
|
838
|
+
|
|
839
|
+
# Extract error lines (last 30 lines, grep for error patterns)
|
|
840
|
+
local error_lines_raw
|
|
841
|
+
error_lines_raw=$(tail -30 "$test_log" 2>/dev/null | grep -iE '(error|fail|assert|exception|panic|FAIL|TypeError|ReferenceError|SyntaxError)' | head -10 || true)
|
|
842
|
+
|
|
843
|
+
local error_count=0
|
|
844
|
+
if [[ -n "$error_lines_raw" ]]; then
|
|
845
|
+
error_count=$(echo "$error_lines_raw" | wc -l | tr -d ' ')
|
|
846
|
+
fi
|
|
847
|
+
|
|
848
|
+
local tmp_json="${error_json}.tmp.$$"
|
|
849
|
+
|
|
850
|
+
# Build JSON with jq (preferred) or plain-text fallback
|
|
851
|
+
if command -v jq &>/dev/null; then
|
|
852
|
+
jq -n \
|
|
853
|
+
--argjson iteration "${ITERATION:-0}" \
|
|
854
|
+
--arg timestamp "$(date -u +"%Y-%m-%dT%H:%M:%SZ")" \
|
|
855
|
+
--argjson error_count "${error_count:-0}" \
|
|
856
|
+
--arg error_lines "$error_lines_raw" \
|
|
857
|
+
--arg test_cmd "${TEST_CMD:-}" \
|
|
858
|
+
'{
|
|
859
|
+
iteration: $iteration,
|
|
860
|
+
timestamp: $timestamp,
|
|
861
|
+
error_count: $error_count,
|
|
862
|
+
error_lines: ($error_lines | split("\n") | map(select(length > 0))),
|
|
863
|
+
test_cmd: $test_cmd
|
|
864
|
+
}' > "$tmp_json" 2>/dev/null && mv "$tmp_json" "$error_json" || rm -f "$tmp_json" 2>/dev/null
|
|
865
|
+
else
|
|
866
|
+
# Fallback: write plain-text error summary (still machine-parseable)
|
|
867
|
+
cat > "$tmp_json" <<ERRJSON
|
|
868
|
+
{"iteration":${ITERATION:-0},"error_count":${error_count:-0},"error_lines":[],"test_cmd":"test"}
|
|
869
|
+
ERRJSON
|
|
870
|
+
mv "$tmp_json" "$error_json" 2>/dev/null || rm -f "$tmp_json" 2>/dev/null
|
|
871
|
+
fi
|
|
872
|
+
}
|
|
873
|
+
|
|
659
874
|
# ─── Audit Agent ─────────────────────────────────────────────────────────────
|
|
660
875
|
|
|
661
876
|
run_audit_agent() {
|
|
@@ -884,6 +1099,21 @@ compose_prompt() {
|
|
|
884
1099
|
$TEST_OUTPUT"
|
|
885
1100
|
fi
|
|
886
1101
|
|
|
1102
|
+
# Structured error context (machine-readable)
|
|
1103
|
+
local error_summary_section=""
|
|
1104
|
+
local error_json="$LOG_DIR/error-summary.json"
|
|
1105
|
+
if [[ -f "$error_json" ]]; then
|
|
1106
|
+
local err_count err_lines
|
|
1107
|
+
err_count=$(jq -r '.error_count // 0' "$error_json" 2>/dev/null || echo "0")
|
|
1108
|
+
err_lines=$(jq -r '.error_lines[]? // empty' "$error_json" 2>/dev/null | head -10 || true)
|
|
1109
|
+
if [[ "$err_count" -gt 0 ]] && [[ -n "$err_lines" ]]; then
|
|
1110
|
+
error_summary_section="## Structured Error Summary (${err_count} errors detected)
|
|
1111
|
+
${err_lines}
|
|
1112
|
+
|
|
1113
|
+
Fix these specific errors. Each line above is one distinct error from the test output."
|
|
1114
|
+
fi
|
|
1115
|
+
fi
|
|
1116
|
+
|
|
887
1117
|
# Build audit sections (captured before heredoc to avoid nested heredoc issues)
|
|
888
1118
|
local audit_section
|
|
889
1119
|
audit_section="$(compose_audit_section)"
|
|
@@ -1006,6 +1236,16 @@ ${last_error}"
|
|
|
1006
1236
|
local stuckness_section=""
|
|
1007
1237
|
stuckness_section="$(detect_stuckness)"
|
|
1008
1238
|
|
|
1239
|
+
# Session restart context — inject previous session progress
|
|
1240
|
+
local restart_section=""
|
|
1241
|
+
if [[ "$SESSION_RESTART" == "true" ]] && [[ -f "$LOG_DIR/progress.md" ]]; then
|
|
1242
|
+
restart_section="## Previous Session Progress
|
|
1243
|
+
$(cat "$LOG_DIR/progress.md")
|
|
1244
|
+
|
|
1245
|
+
You are starting a FRESH session after the previous one exhausted its iterations.
|
|
1246
|
+
Read the progress above and continue from where it left off. Do NOT repeat work already done."
|
|
1247
|
+
fi
|
|
1248
|
+
|
|
1009
1249
|
cat <<PROMPT
|
|
1010
1250
|
You are an autonomous coding agent on iteration ${ITERATION}/${MAX_ITERATIONS} of a continuous loop.
|
|
1011
1251
|
|
|
@@ -1021,6 +1261,8 @@ ${git_log}
|
|
|
1021
1261
|
## Test Results (Previous Iteration)
|
|
1022
1262
|
${test_section}
|
|
1023
1263
|
|
|
1264
|
+
${error_summary_section:+$error_summary_section
|
|
1265
|
+
}
|
|
1024
1266
|
${memory_section:+## Memory Context
|
|
1025
1267
|
$memory_section
|
|
1026
1268
|
}
|
|
@@ -1028,6 +1270,8 @@ ${dora_section:+$dora_section
|
|
|
1028
1270
|
}
|
|
1029
1271
|
${intelligence_section:+$intelligence_section
|
|
1030
1272
|
}
|
|
1273
|
+
${restart_section:+$restart_section
|
|
1274
|
+
}
|
|
1031
1275
|
## Instructions
|
|
1032
1276
|
1. Read the codebase and understand the current state
|
|
1033
1277
|
2. Identify the highest-priority remaining work toward the goal
|
|
@@ -1189,6 +1433,37 @@ compose_worker_prompt() {
|
|
|
1189
1433
|
local base_prompt
|
|
1190
1434
|
base_prompt="$(compose_prompt)"
|
|
1191
1435
|
|
|
1436
|
+
# Role-specific instructions
|
|
1437
|
+
local role_section=""
|
|
1438
|
+
if [[ -n "$AGENT_ROLES" ]] && [[ "${agent_num:-0}" -ge 1 ]]; then
|
|
1439
|
+
# Split comma-separated roles and get role for this agent
|
|
1440
|
+
local role=""
|
|
1441
|
+
local IFS_BAK="$IFS"
|
|
1442
|
+
IFS=',' read -ra _roles <<< "$AGENT_ROLES"
|
|
1443
|
+
IFS="$IFS_BAK"
|
|
1444
|
+
if [[ "$agent_num" -le "${#_roles[@]}" ]]; then
|
|
1445
|
+
role="${_roles[$((agent_num - 1))]}"
|
|
1446
|
+
# Trim whitespace and skip empty roles (handles trailing comma)
|
|
1447
|
+
role="$(echo "$role" | tr -d ' ')"
|
|
1448
|
+
fi
|
|
1449
|
+
|
|
1450
|
+
if [[ -n "$role" ]]; then
|
|
1451
|
+
local role_desc=""
|
|
1452
|
+
case "$role" in
|
|
1453
|
+
builder) role_desc="Focus on implementation — writing code, fixing bugs, building features. You are the primary builder." ;;
|
|
1454
|
+
reviewer) role_desc="Focus on code review — look for bugs, security issues, edge cases in recent commits. Make fixes via commits." ;;
|
|
1455
|
+
tester) role_desc="Focus on test coverage — write new tests, fix failing tests, improve assertions and edge case coverage." ;;
|
|
1456
|
+
optimizer) role_desc="Focus on performance — profile hot paths, reduce complexity, optimize algorithms and data structures." ;;
|
|
1457
|
+
docs) role_desc="Focus on documentation — update README, add docstrings, write usage guides for new features." ;;
|
|
1458
|
+
security) role_desc="Focus on security — audit for vulnerabilities, fix injection risks, validate inputs, check auth boundaries." ;;
|
|
1459
|
+
*) role_desc="Focus on: ${role}. Apply your expertise in this area to advance the goal." ;;
|
|
1460
|
+
esac
|
|
1461
|
+
role_section="## Your Role: ${role}
|
|
1462
|
+
${role_desc}
|
|
1463
|
+
Prioritize work in your area of expertise. Coordinate with other agents via git log."
|
|
1464
|
+
fi
|
|
1465
|
+
fi
|
|
1466
|
+
|
|
1192
1467
|
cat <<PROMPT
|
|
1193
1468
|
${base_prompt}
|
|
1194
1469
|
|
|
@@ -1196,6 +1471,8 @@ ${base_prompt}
|
|
|
1196
1471
|
You are Agent ${agent_num} of ${total_agents}. Other agents are working in parallel.
|
|
1197
1472
|
Check git log to see what they've done — avoid duplicating their work.
|
|
1198
1473
|
Focus on areas they haven't touched yet.
|
|
1474
|
+
|
|
1475
|
+
${role_section}
|
|
1199
1476
|
PROMPT
|
|
1200
1477
|
}
|
|
1201
1478
|
|
|
@@ -1547,17 +1824,21 @@ done
|
|
|
1547
1824
|
echo -e "\n${DIM}Agent ${AGENT_NUM} finished after ${ITERATION} iterations${RESET}"
|
|
1548
1825
|
WORKEREOF
|
|
1549
1826
|
|
|
1550
|
-
# Replace placeholders
|
|
1827
|
+
# Replace placeholders — use awk for all values to avoid sed injection
|
|
1828
|
+
# (sed breaks on & | \ in paths and test commands)
|
|
1551
1829
|
sed_i "s|__AGENT_NUM__|${agent_num}|g" "$worker_script"
|
|
1552
1830
|
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
1831
|
sed_i "s|__MAX_ITERATIONS__|${MAX_ITERATIONS}|g" "$worker_script"
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1832
|
+
# Paths and commands may contain sed-special chars — use awk
|
|
1833
|
+
awk -v val="$wt_path" '{gsub(/__WORK_DIR__/, val); print}' "$worker_script" > "${worker_script}.tmp" \
|
|
1834
|
+
&& mv "${worker_script}.tmp" "$worker_script"
|
|
1835
|
+
awk -v val="$LOG_DIR" '{gsub(/__LOG_DIR__/, val); print}' "$worker_script" > "${worker_script}.tmp" \
|
|
1836
|
+
&& mv "${worker_script}.tmp" "$worker_script"
|
|
1837
|
+
awk -v val="$TEST_CMD" '{gsub(/__TEST_CMD__/, val); print}' "$worker_script" > "${worker_script}.tmp" \
|
|
1838
|
+
&& mv "${worker_script}.tmp" "$worker_script"
|
|
1839
|
+
awk -v val="$claude_flags" '{gsub(/__CLAUDE_FLAGS__/, val); print}' "$worker_script" > "${worker_script}.tmp" \
|
|
1840
|
+
&& mv "${worker_script}.tmp" "$worker_script"
|
|
1841
|
+
awk -v val="$GOAL" '{gsub(/__GOAL__/, val); print}' "$worker_script" > "${worker_script}.tmp" \
|
|
1561
1842
|
&& mv "${worker_script}.tmp" "$worker_script"
|
|
1562
1843
|
chmod +x "$worker_script"
|
|
1563
1844
|
echo "$worker_script"
|
|
@@ -1577,10 +1858,14 @@ launch_multi_agent() {
|
|
|
1577
1858
|
MULTI_WINDOW_NAME="sw-loop-$(date +%s)"
|
|
1578
1859
|
tmux new-window -n "$MULTI_WINDOW_NAME" -c "$PROJECT_ROOT"
|
|
1579
1860
|
|
|
1861
|
+
# Capture the first pane's ID (stable regardless of pane-base-index)
|
|
1862
|
+
local monitor_pane_id
|
|
1863
|
+
monitor_pane_id="$(tmux list-panes -t "$MULTI_WINDOW_NAME" -F '#{pane_id}' 2>/dev/null | head -1)"
|
|
1864
|
+
|
|
1580
1865
|
# First pane becomes monitor
|
|
1581
|
-
tmux send-keys -t "$
|
|
1866
|
+
tmux send-keys -t "$monitor_pane_id" "printf '\\033]2;loop-monitor\\033\\\\'" Enter
|
|
1582
1867
|
sleep 0.2
|
|
1583
|
-
tmux send-keys -t "$
|
|
1868
|
+
tmux send-keys -t "$monitor_pane_id" "clear && echo 'Loop Monitor — watching agent logs...'" Enter
|
|
1584
1869
|
|
|
1585
1870
|
# Create worker panes
|
|
1586
1871
|
for i in $(seq 1 "$AGENTS"); do
|
|
@@ -1596,12 +1881,12 @@ launch_multi_agent() {
|
|
|
1596
1881
|
|
|
1597
1882
|
# Layout: monitor pane on top (35%), worker agents tile below
|
|
1598
1883
|
tmux select-layout -t "$MULTI_WINDOW_NAME" main-vertical 2>/dev/null || true
|
|
1599
|
-
tmux resize-pane -t "$
|
|
1884
|
+
tmux resize-pane -t "$monitor_pane_id" -y 35% 2>/dev/null || true
|
|
1600
1885
|
|
|
1601
1886
|
# In the monitor pane, tail all agent logs
|
|
1602
|
-
tmux select-pane -t "$
|
|
1887
|
+
tmux select-pane -t "$monitor_pane_id"
|
|
1603
1888
|
sleep 0.5
|
|
1604
|
-
tmux send-keys -t "$
|
|
1889
|
+
tmux send-keys -t "$monitor_pane_id" "clear && tail -f $LOG_DIR/agent-*-iter-*.log 2>/dev/null || echo 'Waiting for agent logs...'" Enter
|
|
1605
1890
|
|
|
1606
1891
|
success "Launched $AGENTS worker agents in window: $MULTI_WINDOW_NAME"
|
|
1607
1892
|
echo ""
|
|
@@ -1656,12 +1941,13 @@ wait_for_multi_completion() {
|
|
|
1656
1941
|
|
|
1657
1942
|
cleanup_multi_agent() {
|
|
1658
1943
|
if [[ -n "$MULTI_WINDOW_NAME" ]]; then
|
|
1659
|
-
# Send Ctrl-C to all panes
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
|
|
1944
|
+
# Send Ctrl-C to all panes using stable pane IDs (not indices)
|
|
1945
|
+
# Pane IDs (%0, %1, ...) are unaffected by pane-base-index setting
|
|
1946
|
+
local pane_id
|
|
1947
|
+
while IFS= read -r pane_id; do
|
|
1948
|
+
[[ -z "$pane_id" ]] && continue
|
|
1949
|
+
tmux send-keys -t "$pane_id" C-c 2>/dev/null || true
|
|
1950
|
+
done < <(tmux list-panes -t "$MULTI_WINDOW_NAME" -F '#{pane_id}' 2>/dev/null || true)
|
|
1665
1951
|
sleep 1
|
|
1666
1952
|
tmux kill-window -t "$MULTI_WINDOW_NAME" 2>/dev/null || true
|
|
1667
1953
|
fi
|
|
@@ -1673,7 +1959,10 @@ cleanup_multi_agent() {
|
|
|
1673
1959
|
# ─── Main: Single-Agent Loop ─────────────────────────────────────────────────
|
|
1674
1960
|
|
|
1675
1961
|
run_single_agent_loop() {
|
|
1676
|
-
if $
|
|
1962
|
+
if [[ "$SESSION_RESTART" == "true" ]]; then
|
|
1963
|
+
# Restart: state already reset by run_loop_with_restarts, skip init
|
|
1964
|
+
info "Session restart ${RESTART_COUNT}/${MAX_RESTARTS} — fresh context, reading progress"
|
|
1965
|
+
elif $RESUME; then
|
|
1677
1966
|
resume_state
|
|
1678
1967
|
else
|
|
1679
1968
|
initialize_state
|
|
@@ -1683,6 +1972,9 @@ run_single_agent_loop() {
|
|
|
1683
1972
|
apply_adaptive_budget
|
|
1684
1973
|
MODEL="$(select_adaptive_model "build" "$MODEL")"
|
|
1685
1974
|
|
|
1975
|
+
# Track applied memory fix patterns for outcome recording
|
|
1976
|
+
_applied_fix_pattern=""
|
|
1977
|
+
|
|
1686
1978
|
show_banner
|
|
1687
1979
|
|
|
1688
1980
|
while true; do
|
|
@@ -1691,6 +1983,26 @@ run_single_agent_loop() {
|
|
|
1691
1983
|
check_max_iterations || break
|
|
1692
1984
|
ITERATION=$(( ITERATION + 1 ))
|
|
1693
1985
|
|
|
1986
|
+
# Try memory-based fix suggestion on retry after test failure
|
|
1987
|
+
if [[ "${TEST_PASSED:-}" == "false" ]]; then
|
|
1988
|
+
local _last_error=""
|
|
1989
|
+
local _prev_log="$LOG_DIR/iteration-$(( ITERATION - 1 )).log"
|
|
1990
|
+
if [[ -f "$_prev_log" ]]; then
|
|
1991
|
+
_last_error=$(tail -20 "$_prev_log" 2>/dev/null | grep -iE '(error|fail|exception)' | head -1 || true)
|
|
1992
|
+
fi
|
|
1993
|
+
local _fix_suggestion=""
|
|
1994
|
+
if type memory_closed_loop_inject &>/dev/null 2>&1 && [[ -n "${_last_error:-}" ]]; then
|
|
1995
|
+
_fix_suggestion=$(memory_closed_loop_inject "$_last_error" 2>/dev/null) || true
|
|
1996
|
+
fi
|
|
1997
|
+
if [[ -n "${_fix_suggestion:-}" ]]; then
|
|
1998
|
+
_applied_fix_pattern="${_last_error}"
|
|
1999
|
+
GOAL="KNOWN FIX (from past success): ${_fix_suggestion}
|
|
2000
|
+
|
|
2001
|
+
${GOAL}"
|
|
2002
|
+
info "Memory fix injected: ${_fix_suggestion:0:80}"
|
|
2003
|
+
fi
|
|
2004
|
+
fi
|
|
2005
|
+
|
|
1694
2006
|
# Run Claude
|
|
1695
2007
|
local exit_code=0
|
|
1696
2008
|
run_claude_iteration || exit_code=$?
|
|
@@ -1733,6 +2045,7 @@ run_single_agent_loop() {
|
|
|
1733
2045
|
|
|
1734
2046
|
# Test gate
|
|
1735
2047
|
run_test_gate
|
|
2048
|
+
write_error_summary
|
|
1736
2049
|
if [[ -n "$TEST_CMD" ]]; then
|
|
1737
2050
|
if [[ "$TEST_PASSED" == "true" ]]; then
|
|
1738
2051
|
echo -e " ${GREEN}✓${RESET} Tests: passed"
|
|
@@ -1741,6 +2054,18 @@ run_single_agent_loop() {
|
|
|
1741
2054
|
fi
|
|
1742
2055
|
fi
|
|
1743
2056
|
|
|
2057
|
+
# Track fix outcome for memory effectiveness
|
|
2058
|
+
if [[ -n "${_applied_fix_pattern:-}" ]]; then
|
|
2059
|
+
if type memory_record_fix_outcome &>/dev/null 2>&1; then
|
|
2060
|
+
if [[ "${TEST_PASSED:-}" == "true" ]]; then
|
|
2061
|
+
memory_record_fix_outcome "$_applied_fix_pattern" "true" "true" 2>/dev/null || true
|
|
2062
|
+
else
|
|
2063
|
+
memory_record_fix_outcome "$_applied_fix_pattern" "true" "false" 2>/dev/null || true
|
|
2064
|
+
fi
|
|
2065
|
+
fi
|
|
2066
|
+
_applied_fix_pattern=""
|
|
2067
|
+
fi
|
|
2068
|
+
|
|
1744
2069
|
# Audit agent (reviews implementer's work)
|
|
1745
2070
|
run_audit_agent
|
|
1746
2071
|
|
|
@@ -1751,6 +2076,7 @@ run_single_agent_loop() {
|
|
|
1751
2076
|
if guard_completion; then
|
|
1752
2077
|
STATUS="complete"
|
|
1753
2078
|
write_state
|
|
2079
|
+
write_progress
|
|
1754
2080
|
show_summary
|
|
1755
2081
|
return 0
|
|
1756
2082
|
fi
|
|
@@ -1771,6 +2097,7 @@ run_single_agent_loop() {
|
|
|
1771
2097
|
$summary
|
|
1772
2098
|
"
|
|
1773
2099
|
write_state
|
|
2100
|
+
write_progress
|
|
1774
2101
|
|
|
1775
2102
|
# Update heartbeat
|
|
1776
2103
|
"$SCRIPT_DIR/sw-heartbeat.sh" write "${PIPELINE_JOB_ID:-loop-$$}" \
|
|
@@ -1799,9 +2126,77 @@ HUMAN FEEDBACK (received after iteration $ITERATION): $human_msg"
|
|
|
1799
2126
|
|
|
1800
2127
|
# Write final state after loop exits
|
|
1801
2128
|
write_state
|
|
2129
|
+
write_progress
|
|
1802
2130
|
show_summary
|
|
1803
2131
|
}
|
|
1804
2132
|
|
|
2133
|
+
# ─── Session Restart Wrapper ─────────────────────────────────────────────────
|
|
2134
|
+
|
|
2135
|
+
run_loop_with_restarts() {
|
|
2136
|
+
while true; do
|
|
2137
|
+
local loop_exit=0
|
|
2138
|
+
run_single_agent_loop || loop_exit=$?
|
|
2139
|
+
|
|
2140
|
+
# If completed successfully or no restarts configured, exit
|
|
2141
|
+
if [[ "$STATUS" == "complete" ]]; then
|
|
2142
|
+
return 0
|
|
2143
|
+
fi
|
|
2144
|
+
if [[ "$MAX_RESTARTS" -le 0 ]]; then
|
|
2145
|
+
return "$loop_exit"
|
|
2146
|
+
fi
|
|
2147
|
+
if [[ "$RESTART_COUNT" -ge "$MAX_RESTARTS" ]]; then
|
|
2148
|
+
warn "Max restarts ($MAX_RESTARTS) reached — stopping"
|
|
2149
|
+
return "$loop_exit"
|
|
2150
|
+
fi
|
|
2151
|
+
# Hard cap safety net
|
|
2152
|
+
if [[ "$RESTART_COUNT" -ge 5 ]]; then
|
|
2153
|
+
warn "Hard restart cap (5) reached — stopping"
|
|
2154
|
+
return "$loop_exit"
|
|
2155
|
+
fi
|
|
2156
|
+
|
|
2157
|
+
# Check if tests are still failing (worth restarting)
|
|
2158
|
+
if [[ "${TEST_PASSED:-}" == "true" ]]; then
|
|
2159
|
+
info "Tests passing but loop incomplete — restarting session"
|
|
2160
|
+
else
|
|
2161
|
+
info "Tests failing and loop exhausted — restarting with fresh context"
|
|
2162
|
+
fi
|
|
2163
|
+
|
|
2164
|
+
RESTART_COUNT=$(( RESTART_COUNT + 1 ))
|
|
2165
|
+
if type emit_event &>/dev/null 2>&1; then
|
|
2166
|
+
emit_event "loop.restart" "restart=$RESTART_COUNT" "max=$MAX_RESTARTS" "iteration=$ITERATION"
|
|
2167
|
+
fi
|
|
2168
|
+
info "Session restart ${RESTART_COUNT}/${MAX_RESTARTS} — resetting iteration counter"
|
|
2169
|
+
|
|
2170
|
+
# Reset ALL iteration-level state for the new session
|
|
2171
|
+
# SESSION_RESTART tells run_single_agent_loop to skip init/resume
|
|
2172
|
+
SESSION_RESTART=true
|
|
2173
|
+
ITERATION=0
|
|
2174
|
+
CONSECUTIVE_FAILURES=0
|
|
2175
|
+
EXTENSION_COUNT=0
|
|
2176
|
+
STATUS="running"
|
|
2177
|
+
LOG_ENTRIES=""
|
|
2178
|
+
TEST_PASSED=""
|
|
2179
|
+
TEST_OUTPUT=""
|
|
2180
|
+
TEST_LOG_FILE=""
|
|
2181
|
+
# Reset GOAL to original — prevent unbounded growth from memory/human injections
|
|
2182
|
+
GOAL="$ORIGINAL_GOAL"
|
|
2183
|
+
|
|
2184
|
+
# Archive old artifacts so they don't get overwritten or pollute new session
|
|
2185
|
+
local restart_archive="$LOG_DIR/restart-${RESTART_COUNT}"
|
|
2186
|
+
mkdir -p "$restart_archive"
|
|
2187
|
+
for old_log in "$LOG_DIR"/iteration-*.log "$LOG_DIR"/tests-iter-*.log; do
|
|
2188
|
+
[[ -f "$old_log" ]] && mv "$old_log" "$restart_archive/" 2>/dev/null || true
|
|
2189
|
+
done
|
|
2190
|
+
# Archive progress.md and error-summary.json from previous session
|
|
2191
|
+
[[ -f "$LOG_DIR/progress.md" ]] && cp "$LOG_DIR/progress.md" "$restart_archive/progress.md" 2>/dev/null || true
|
|
2192
|
+
[[ -f "$LOG_DIR/error-summary.json" ]] && mv "$LOG_DIR/error-summary.json" "$restart_archive/" 2>/dev/null || true
|
|
2193
|
+
|
|
2194
|
+
write_state
|
|
2195
|
+
|
|
2196
|
+
sleep 2
|
|
2197
|
+
done
|
|
2198
|
+
}
|
|
2199
|
+
|
|
1805
2200
|
# ─── Main: Entry Point ───────────────────────────────────────────────────────
|
|
1806
2201
|
|
|
1807
2202
|
main() {
|
|
@@ -1815,7 +2210,7 @@ main() {
|
|
|
1815
2210
|
launch_multi_agent
|
|
1816
2211
|
show_summary
|
|
1817
2212
|
else
|
|
1818
|
-
|
|
2213
|
+
run_loop_with_restarts
|
|
1819
2214
|
fi
|
|
1820
2215
|
}
|
|
1821
2216
|
|