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.
- package/.claude/agents/code-reviewer.md +90 -0
- package/.claude/agents/devops-engineer.md +142 -0
- package/.claude/agents/pipeline-agent.md +80 -0
- package/.claude/agents/shell-script-specialist.md +150 -0
- package/.claude/agents/test-specialist.md +196 -0
- package/.claude/hooks/post-tool-use.sh +38 -0
- package/.claude/hooks/pre-tool-use.sh +25 -0
- package/.claude/hooks/session-started.sh +37 -0
- package/README.md +212 -814
- package/claude-code/CLAUDE.md.shipwright +54 -0
- package/claude-code/hooks/notify-idle.sh +2 -2
- package/claude-code/hooks/session-start.sh +24 -0
- package/claude-code/hooks/task-completed.sh +6 -2
- package/claude-code/settings.json.template +12 -0
- package/dashboard/public/app.js +4422 -0
- package/dashboard/public/index.html +816 -0
- package/dashboard/public/styles.css +4755 -0
- package/dashboard/server.ts +4315 -0
- package/docs/KNOWN-ISSUES.md +18 -10
- package/docs/TIPS.md +38 -26
- package/docs/patterns/README.md +33 -23
- package/package.json +9 -5
- package/scripts/adapters/iterm2-adapter.sh +1 -1
- package/scripts/adapters/tmux-adapter.sh +52 -23
- package/scripts/adapters/wezterm-adapter.sh +26 -14
- package/scripts/lib/compat.sh +200 -0
- package/scripts/lib/helpers.sh +72 -0
- package/scripts/postinstall.mjs +72 -13
- package/scripts/{cct → sw} +109 -21
- package/scripts/sw-adversarial.sh +274 -0
- package/scripts/sw-architecture-enforcer.sh +330 -0
- package/scripts/sw-checkpoint.sh +390 -0
- package/scripts/{cct-cleanup.sh → sw-cleanup.sh} +3 -1
- package/scripts/sw-connect.sh +619 -0
- package/scripts/{cct-cost.sh → sw-cost.sh} +368 -34
- package/scripts/{cct-daemon.sh → sw-daemon.sh} +2217 -204
- package/scripts/sw-dashboard.sh +477 -0
- package/scripts/sw-developer-simulation.sh +252 -0
- package/scripts/sw-docs.sh +635 -0
- package/scripts/sw-doctor.sh +907 -0
- package/scripts/{cct-fix.sh → sw-fix.sh} +10 -6
- package/scripts/{cct-fleet.sh → sw-fleet.sh} +498 -22
- package/scripts/sw-github-checks.sh +521 -0
- package/scripts/sw-github-deploy.sh +533 -0
- package/scripts/sw-github-graphql.sh +972 -0
- package/scripts/sw-heartbeat.sh +293 -0
- package/scripts/{cct-init.sh → sw-init.sh} +144 -11
- package/scripts/sw-intelligence.sh +1196 -0
- package/scripts/sw-jira.sh +643 -0
- package/scripts/sw-launchd.sh +364 -0
- package/scripts/sw-linear.sh +648 -0
- package/scripts/{cct-logs.sh → sw-logs.sh} +72 -2
- package/scripts/{cct-loop.sh → sw-loop.sh} +534 -44
- package/scripts/{cct-memory.sh → sw-memory.sh} +321 -38
- package/scripts/sw-patrol-meta.sh +417 -0
- package/scripts/sw-pipeline-composer.sh +455 -0
- package/scripts/{cct-pipeline.sh → sw-pipeline.sh} +2319 -178
- package/scripts/sw-predictive.sh +820 -0
- package/scripts/{cct-prep.sh → sw-prep.sh} +339 -49
- package/scripts/{cct-ps.sh → sw-ps.sh} +6 -4
- package/scripts/{cct-reaper.sh → sw-reaper.sh} +6 -4
- package/scripts/sw-remote.sh +687 -0
- package/scripts/sw-self-optimize.sh +947 -0
- package/scripts/sw-session.sh +519 -0
- package/scripts/sw-setup.sh +234 -0
- package/scripts/sw-status.sh +605 -0
- package/scripts/{cct-templates.sh → sw-templates.sh} +9 -4
- package/scripts/sw-tmux.sh +591 -0
- package/scripts/sw-tracker-jira.sh +277 -0
- package/scripts/sw-tracker-linear.sh +292 -0
- package/scripts/sw-tracker.sh +409 -0
- package/scripts/{cct-upgrade.sh → sw-upgrade.sh} +103 -46
- package/scripts/{cct-worktree.sh → sw-worktree.sh} +3 -0
- package/templates/pipelines/autonomous.json +27 -5
- package/templates/pipelines/full.json +12 -0
- package/templates/pipelines/standard.json +12 -0
- package/tmux/{claude-teams-overlay.conf → shipwright-overlay.conf} +27 -9
- package/tmux/templates/accessibility.json +34 -0
- package/tmux/templates/api-design.json +35 -0
- package/tmux/templates/architecture.json +1 -0
- package/tmux/templates/bug-fix.json +9 -0
- package/tmux/templates/code-review.json +1 -0
- package/tmux/templates/compliance.json +36 -0
- package/tmux/templates/data-pipeline.json +36 -0
- package/tmux/templates/debt-paydown.json +34 -0
- package/tmux/templates/devops.json +1 -0
- package/tmux/templates/documentation.json +1 -0
- package/tmux/templates/exploration.json +1 -0
- package/tmux/templates/feature-dev.json +1 -0
- package/tmux/templates/full-stack.json +8 -0
- package/tmux/templates/i18n.json +34 -0
- package/tmux/templates/incident-response.json +36 -0
- package/tmux/templates/migration.json +1 -0
- package/tmux/templates/observability.json +35 -0
- package/tmux/templates/onboarding.json +33 -0
- package/tmux/templates/performance.json +35 -0
- package/tmux/templates/refactor.json +1 -0
- package/tmux/templates/release.json +35 -0
- package/tmux/templates/security-audit.json +8 -0
- package/tmux/templates/spike.json +34 -0
- package/tmux/templates/testing.json +1 -0
- package/tmux/tmux.conf +98 -9
- package/scripts/cct-doctor.sh +0 -414
- package/scripts/cct-session.sh +0 -284
- 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
|
|
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.
|
|
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
|
|
88
|
-
echo -e " ${DIM}•
|
|
89
|
-
echo -e " ${DIM}•
|
|
90
|
-
echo -e "
|
|
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
|
|
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
|
|
416
|
-
error "Circuit breaker tripped:
|
|
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" -
|
|
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
|
-
|
|
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
|
|
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
|
-
#
|
|
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" "
|
|
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" "
|
|
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
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
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="
|
|
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}
|
|
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
|
|