prizmkit 1.1.91 → 1.1.93
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/bundled/VERSION.json +3 -3
- package/bundled/dev-pipeline/.env.example +0 -4
- package/bundled/dev-pipeline/README.md +9 -39
- package/bundled/dev-pipeline/SCHEMA_ANALYSIS.md +0 -4
- package/bundled/dev-pipeline/assets/prizm-dev-team-integration.md +0 -2
- package/bundled/dev-pipeline/launch-bugfix-daemon.sh +0 -1
- package/bundled/dev-pipeline/launch-feature-daemon.sh +2 -3
- package/bundled/dev-pipeline/launch-refactor-daemon.sh +0 -1
- package/bundled/dev-pipeline/lib/branch.sh +8 -5
- package/bundled/dev-pipeline/lib/common.sh +122 -0
- package/bundled/dev-pipeline/lib/heartbeat.sh +13 -3
- package/bundled/dev-pipeline/run-bugfix.sh +149 -98
- package/bundled/dev-pipeline/run-feature.sh +90 -103
- package/bundled/dev-pipeline/run-recovery.sh +1 -32
- package/bundled/dev-pipeline/run-refactor.sh +148 -99
- package/bundled/dev-pipeline/scripts/update-bug-status.py +39 -4
- package/bundled/dev-pipeline/scripts/update-feature-status.py +43 -6
- package/bundled/dev-pipeline/scripts/update-refactor-status.py +36 -4
- package/bundled/dev-pipeline/tests/test-deploy-safety.sh +3 -3
- package/bundled/dev-pipeline/tests/test_auto_skip.py +92 -3
- package/bundled/dev-pipeline-windows/.env.example +0 -4
- package/bundled/dev-pipeline-windows/SCHEMA_ANALYSIS.md +0 -4
- package/bundled/dev-pipeline-windows/assets/prizm-dev-team-integration.md +0 -2
- package/bundled/dev-pipeline-windows/lib/branch.ps1 +17 -4
- package/bundled/dev-pipeline-windows/lib/common.ps1 +9 -0
- package/bundled/dev-pipeline-windows/lib/pipeline.ps1 +89 -108
- package/bundled/dev-pipeline-windows/run-recovery.ps1 +4 -19
- package/bundled/dev-pipeline-windows/scripts/update-bug-status.py +39 -4
- package/bundled/dev-pipeline-windows/scripts/update-feature-status.py +43 -6
- package/bundled/dev-pipeline-windows/scripts/update-refactor-status.py +36 -4
- package/bundled/skills/_metadata.json +1 -1
- package/bundled/skills/bugfix-pipeline-launcher/SKILL.md +1 -1
- package/bundled/skills/bugfix-pipeline-launcher/references/configuration.md +5 -15
- package/bundled/skills/feature-pipeline-launcher/SKILL.md +4 -4
- package/bundled/skills/feature-pipeline-launcher/references/configuration.md +3 -13
- package/bundled/skills/refactor-pipeline-launcher/SKILL.md +3 -3
- package/bundled/skills/refactor-pipeline-launcher/references/configuration.md +4 -14
- package/bundled/skills-windows/bugfix-pipeline-launcher/SKILL.md +1 -1
- package/bundled/skills-windows/bugfix-pipeline-launcher/references/configuration.md +4 -14
- package/bundled/skills-windows/feature-pipeline-launcher/SKILL.md +4 -4
- package/bundled/skills-windows/feature-pipeline-launcher/references/configuration.md +2 -12
- package/bundled/skills-windows/refactor-pipeline-launcher/SKILL.md +3 -3
- package/bundled/skills-windows/refactor-pipeline-launcher/references/configuration.md +4 -14
- package/package.json +1 -1
- package/bundled/dev-pipeline/scripts/cleanup-logs.py +0 -192
- package/bundled/dev-pipeline-windows/scripts/cleanup-logs.py +0 -192
|
@@ -9,14 +9,14 @@ set -euo pipefail
|
|
|
9
9
|
# from a feature list.
|
|
10
10
|
#
|
|
11
11
|
# Usage:
|
|
12
|
-
# ./run-feature.sh run [.prizmkit/plans/feature-list.json]
|
|
12
|
+
# ./run-feature.sh run [.prizmkit/plans/feature-list.json] [options] Run all features
|
|
13
13
|
# ./run-feature.sh run <feature-id> [options] Run a single feature
|
|
14
14
|
# ./run-feature.sh status [.prizmkit/plans/feature-list.json] Show pipeline status
|
|
15
15
|
# ./run-feature.sh reset Clear all state
|
|
16
16
|
#
|
|
17
17
|
# Environment Variables:
|
|
18
|
-
# MAX_RETRIES Max retries per feature (default: 3)
|
|
19
|
-
#
|
|
18
|
+
# MAX_RETRIES Max code retries per feature (default: 3)
|
|
19
|
+
# MAX_INFRA_RETRIES Max infrastructure/provider retries per feature (default: 3)
|
|
20
20
|
# AI_CLI AI CLI command name (override; also readable from .prizmkit/config.json)
|
|
21
21
|
# CODEBUDDY_CLI Legacy alias for AI_CLI (deprecated, use AI_CLI instead)
|
|
22
22
|
# PRIZMKIT_PLATFORM Platform hint for custom AI_CLI wrappers: codebuddy, claude, or codex
|
|
@@ -25,9 +25,6 @@ set -euo pipefail
|
|
|
25
25
|
# HEARTBEAT_INTERVAL Heartbeat log interval in seconds (default: 30)
|
|
26
26
|
# STALE_KILL_THRESHOLD Auto-kill session after N seconds of no progress (default: 900)
|
|
27
27
|
# HEARTBEAT_STALE_THRESHOLD Heartbeat stale threshold in seconds (default: 600)
|
|
28
|
-
# LOG_CLEANUP_ENABLED Run periodic log cleanup (default: 1)
|
|
29
|
-
# LOG_RETENTION_DAYS Delete logs older than N days (default: 14)
|
|
30
|
-
# LOG_MAX_TOTAL_MB Keep total logs under N MB via oldest-first cleanup (default: 1024)
|
|
31
28
|
# PIPELINE_MODE Override mode for all features: lite|standard|full (used by daemon)
|
|
32
29
|
# DEV_BRANCH Custom dev branch name (default: auto-generated dev/{feature_id}-YYYYMMDDHHmm)
|
|
33
30
|
# AUTO_PUSH Auto-push to remote after successful feature (default: 0). Set to 1 to enable.
|
|
@@ -45,13 +42,10 @@ SCRIPTS_DIR="$SCRIPT_DIR/scripts"
|
|
|
45
42
|
|
|
46
43
|
# Configuration (override via environment variables)
|
|
47
44
|
MAX_RETRIES=${MAX_RETRIES:-3}
|
|
48
|
-
|
|
45
|
+
MAX_INFRA_RETRIES=${MAX_INFRA_RETRIES:-3}
|
|
49
46
|
HEARTBEAT_STALE_THRESHOLD=${HEARTBEAT_STALE_THRESHOLD:-600}
|
|
50
47
|
HEARTBEAT_INTERVAL=${HEARTBEAT_INTERVAL:-30}
|
|
51
48
|
STALE_KILL_THRESHOLD=${STALE_KILL_THRESHOLD:-900}
|
|
52
|
-
LOG_CLEANUP_ENABLED=${LOG_CLEANUP_ENABLED:-1}
|
|
53
|
-
LOG_RETENTION_DAYS=${LOG_RETENTION_DAYS:-14}
|
|
54
|
-
LOG_MAX_TOTAL_MB=${LOG_MAX_TOTAL_MB:-1024}
|
|
55
49
|
VERBOSE=${VERBOSE:-0}
|
|
56
50
|
MODEL=${MODEL:-""}
|
|
57
51
|
PRIZMKIT_EFFORT=${PRIZMKIT_EFFORT:-""}
|
|
@@ -124,13 +118,6 @@ spawn_and_wait_session() {
|
|
|
124
118
|
start_progress_parser "$session_log" "$progress_json" "$SCRIPTS_DIR"
|
|
125
119
|
local parser_pid="${_PARSER_PID:-}"
|
|
126
120
|
|
|
127
|
-
# Timeout watchdog (only if SESSION_TIMEOUT > 0)
|
|
128
|
-
local watcher_pid=""
|
|
129
|
-
if [[ $SESSION_TIMEOUT -gt 0 ]]; then
|
|
130
|
-
( sleep "$SESSION_TIMEOUT" && kill -TERM "$cbc_pid" 2>/dev/null ) &
|
|
131
|
-
watcher_pid=$!
|
|
132
|
-
fi
|
|
133
|
-
|
|
134
121
|
# Heartbeat monitor (reads progress.json when available, falls back to tail)
|
|
135
122
|
# Also monitors for stale sessions and auto-kills if no progress for STALE_KILL_THRESHOLD seconds
|
|
136
123
|
start_heartbeat "$cbc_pid" "$session_log" "$progress_json" "$HEARTBEAT_INTERVAL" "$STALE_KILL_THRESHOLD"
|
|
@@ -144,27 +131,35 @@ spawn_and_wait_session() {
|
|
|
144
131
|
exit_code=$?
|
|
145
132
|
fi
|
|
146
133
|
|
|
147
|
-
# Clean up
|
|
148
|
-
[[ -n "$watcher_pid" ]] && kill "$watcher_pid" 2>/dev/null || true
|
|
134
|
+
# Clean up heartbeat and parser
|
|
149
135
|
stop_heartbeat "$heartbeat_pid"
|
|
150
136
|
stop_progress_parser "$parser_pid"
|
|
151
|
-
[[ -n "$watcher_pid" ]] && wait "$watcher_pid" 2>/dev/null || true
|
|
152
137
|
|
|
153
138
|
# Recover session log from backup if truncated by subagent stdout redirection
|
|
154
139
|
prizm_recover_session_log "$session_log"
|
|
155
140
|
|
|
156
|
-
# Map SIGTERM (143) to timeout code 124
|
|
157
|
-
if [[ $exit_code -eq 143 ]]; then
|
|
158
|
-
exit_code=124
|
|
159
|
-
fi
|
|
160
141
|
_SPAWN_EXIT_CODE="$exit_code"
|
|
161
142
|
|
|
162
143
|
# Check for stale-kill marker (heartbeat killed the process due to no progress)
|
|
163
144
|
local stale_kill_marker="$session_dir/logs/stale-kill.json"
|
|
164
145
|
local was_stale_killed=false
|
|
146
|
+
local stale_kill_reason=""
|
|
165
147
|
if [[ -f "$stale_kill_marker" ]]; then
|
|
166
148
|
was_stale_killed=true
|
|
167
|
-
|
|
149
|
+
stale_kill_reason=$(python3 - "$stale_kill_marker" <<'PY' 2>/dev/null || true
|
|
150
|
+
import json, sys
|
|
151
|
+
try:
|
|
152
|
+
with open(sys.argv[1], encoding="utf-8") as fh:
|
|
153
|
+
print(json.load(fh).get("reason") or "")
|
|
154
|
+
except Exception:
|
|
155
|
+
pass
|
|
156
|
+
PY
|
|
157
|
+
)
|
|
158
|
+
if [[ "$stale_kill_reason" == "error_loop" ]]; then
|
|
159
|
+
log_warn "Session was killed by heartbeat monitor due to repeated read-offset/wasted-call errors"
|
|
160
|
+
else
|
|
161
|
+
log_warn "Session was stale-killed by heartbeat monitor (no progress for too long)"
|
|
162
|
+
fi
|
|
168
163
|
fi
|
|
169
164
|
|
|
170
165
|
local was_infra_error=false
|
|
@@ -213,16 +208,17 @@ spawn_and_wait_session() {
|
|
|
213
208
|
log_warn "Session failed due to structured AI runtime/context error"
|
|
214
209
|
log_warn "AI runtime errors are retried without consuming code retry budget"
|
|
215
210
|
session_status="infra_error"
|
|
216
|
-
elif [[ $exit_code -eq 124 ]]; then
|
|
217
|
-
log_warn "Session timed out after ${SESSION_TIMEOUT}s"
|
|
218
|
-
session_status="timed_out"
|
|
219
211
|
elif [[ "$was_infra_error" == true ]]; then
|
|
220
212
|
log_warn "Session failed due to AI CLI/provider infrastructure error"
|
|
221
213
|
log_warn "Infrastructure errors are retried without consuming code retry budget"
|
|
222
214
|
session_status="infra_error"
|
|
223
215
|
elif [[ "$was_stale_killed" == true ]]; then
|
|
224
|
-
|
|
225
|
-
|
|
216
|
+
if [[ "$stale_kill_reason" == "error_loop" ]]; then
|
|
217
|
+
log_warn "Session killed due to repeated read-offset/wasted-call error loop"
|
|
218
|
+
else
|
|
219
|
+
log_warn "Session stale-killed (no progress for ${STALE_KILL_THRESHOLD}s)"
|
|
220
|
+
fi
|
|
221
|
+
log_warn "Heartbeat-killed sessions are treated as failed; dev branch is preserved for inspection"
|
|
226
222
|
session_status="crashed"
|
|
227
223
|
elif [[ $exit_code -ne 0 ]]; then
|
|
228
224
|
log_warn "Session exited with code $exit_code"
|
|
@@ -240,10 +236,10 @@ spawn_and_wait_session() {
|
|
|
240
236
|
# No commits found — check if there are uncommitted changes (session
|
|
241
237
|
# did work but didn't commit, e.g. context window exhausted)
|
|
242
238
|
local uncommitted=""
|
|
243
|
-
uncommitted=$(
|
|
239
|
+
uncommitted=$(prizm_git_status_safe "$project_root" | head -1 || true)
|
|
244
240
|
if [[ -n "$uncommitted" ]]; then
|
|
245
241
|
log_warn "Session exited cleanly but produced no commits (uncommitted changes found) — auto-committing..."
|
|
246
|
-
|
|
242
|
+
prizm_git_add_all_safe "$project_root" || true
|
|
247
243
|
if git -C "$project_root" commit --no-verify -m "chore($feature_id): auto-commit session work" 2>/dev/null; then
|
|
248
244
|
log_info "Auto-commit succeeded"
|
|
249
245
|
session_status="success"
|
|
@@ -262,10 +258,10 @@ spawn_and_wait_session() {
|
|
|
262
258
|
if [[ "$session_status" == "success" ]]; then
|
|
263
259
|
if git -C "$project_root" rev-parse --is-inside-work-tree >/dev/null 2>&1; then
|
|
264
260
|
local dirty_files=""
|
|
265
|
-
dirty_files=$(
|
|
261
|
+
dirty_files=$(prizm_git_status_safe "$project_root" || true)
|
|
266
262
|
if [[ -n "$dirty_files" ]]; then
|
|
267
263
|
log_info "Auto-committing remaining session artifacts..."
|
|
268
|
-
|
|
264
|
+
prizm_git_add_all_safe "$project_root" || true
|
|
269
265
|
git -C "$project_root" commit --no-verify --amend --no-edit 2>/dev/null \
|
|
270
266
|
|| git -C "$project_root" commit --no-verify -m "chore($feature_id): include remaining session artifacts" 2>/dev/null \
|
|
271
267
|
|| true
|
|
@@ -385,8 +381,9 @@ finalize_feature_status_after_branch_return() {
|
|
|
385
381
|
local session_id="$3"
|
|
386
382
|
local session_status="$4"
|
|
387
383
|
local max_retries="$5"
|
|
388
|
-
local
|
|
389
|
-
local
|
|
384
|
+
local max_infra_retries="$6"
|
|
385
|
+
local session_dir="$7"
|
|
386
|
+
local base_branch="${8:-main}"
|
|
390
387
|
|
|
391
388
|
local feature_slug="${_SPAWN_FEATURE_SLUG:-}"
|
|
392
389
|
local progress_json="$session_dir/logs/progress.json"
|
|
@@ -422,6 +419,7 @@ finalize_feature_status_after_branch_return() {
|
|
|
422
419
|
--session-status "$session_status" \
|
|
423
420
|
--session-id "$session_id" \
|
|
424
421
|
--max-retries "$max_retries" \
|
|
422
|
+
--max-infra-retries "$max_infra_retries" \
|
|
425
423
|
--action update 2>&1) || {
|
|
426
424
|
log_error "Failed to update feature status: $update_output"
|
|
427
425
|
log_error ".prizmkit/plans/feature-list.json may be out of sync. Manual intervention needed."
|
|
@@ -467,6 +465,8 @@ for feat in data.get('features', []):
|
|
|
467
465
|
--state-dir "$STATE_DIR" \
|
|
468
466
|
--feature-id "$_interrupted_id" \
|
|
469
467
|
--session-status "failed" \
|
|
468
|
+
--max-retries "$MAX_RETRIES" \
|
|
469
|
+
--max-infra-retries "$MAX_INFRA_RETRIES" \
|
|
470
470
|
--action update 2>/dev/null || true
|
|
471
471
|
log_info "Feature $_interrupted_id marked as failed due to interrupt"
|
|
472
472
|
fi
|
|
@@ -497,29 +497,6 @@ check_dependencies() {
|
|
|
497
497
|
prizm_ensure_git_repo "$_project_root"
|
|
498
498
|
}
|
|
499
499
|
|
|
500
|
-
run_log_cleanup() {
|
|
501
|
-
if [[ "$LOG_CLEANUP_ENABLED" != "1" ]]; then
|
|
502
|
-
return 0
|
|
503
|
-
fi
|
|
504
|
-
|
|
505
|
-
local cleanup_result
|
|
506
|
-
cleanup_result=$(python3 "$SCRIPTS_DIR/cleanup-logs.py" \
|
|
507
|
-
--state-dir "$STATE_DIR" \
|
|
508
|
-
--retention-days "$LOG_RETENTION_DAYS" \
|
|
509
|
-
--max-total-mb "$LOG_MAX_TOTAL_MB" 2>/dev/null) || {
|
|
510
|
-
log_warn "Log cleanup failed (continuing)"
|
|
511
|
-
return 0
|
|
512
|
-
}
|
|
513
|
-
|
|
514
|
-
local deleted reclaimed_kb
|
|
515
|
-
deleted=$(echo "$cleanup_result" | python3 -c "import sys,json; print(json.load(sys.stdin).get('deleted_files', 0))" 2>/dev/null || echo "0")
|
|
516
|
-
reclaimed_kb=$(echo "$cleanup_result" | python3 -c "import sys,json; print(int(json.load(sys.stdin).get('reclaimed_bytes', 0)/1024))" 2>/dev/null || echo "0")
|
|
517
|
-
|
|
518
|
-
if [[ "$deleted" -gt 0 ]]; then
|
|
519
|
-
log_info "Log cleanup: deleted $deleted files, reclaimed ${reclaimed_kb}KB"
|
|
520
|
-
fi
|
|
521
|
-
}
|
|
522
|
-
|
|
523
500
|
# ============================================================
|
|
524
501
|
# run-one: Run a single feature with full control
|
|
525
502
|
# ============================================================
|
|
@@ -583,13 +560,14 @@ run_one() {
|
|
|
583
560
|
critic_override="false"
|
|
584
561
|
shift
|
|
585
562
|
;;
|
|
586
|
-
--
|
|
563
|
+
--max-infra-retries)
|
|
587
564
|
shift
|
|
588
565
|
if [[ $# -eq 0 ]]; then
|
|
589
|
-
log_error "--
|
|
566
|
+
log_error "--max-infra-retries requires a value"
|
|
590
567
|
exit 1
|
|
591
568
|
fi
|
|
592
|
-
|
|
569
|
+
prizm_require_positive_int "--max-infra-retries" "$1"
|
|
570
|
+
MAX_INFRA_RETRIES="$1"
|
|
593
571
|
shift
|
|
594
572
|
;;
|
|
595
573
|
F-*|f-*)
|
|
@@ -635,7 +613,6 @@ run_one() {
|
|
|
635
613
|
fi
|
|
636
614
|
|
|
637
615
|
check_dependencies
|
|
638
|
-
run_log_cleanup
|
|
639
616
|
|
|
640
617
|
# Initialize pipeline state if needed (same logic as run_all)
|
|
641
618
|
if [[ ! -f "$STATE_DIR/pipeline.json" ]]; then
|
|
@@ -887,11 +864,6 @@ else:
|
|
|
887
864
|
esac
|
|
888
865
|
log_info "Pipeline mode: ${BOLD}$pipeline_mode${NC} ($_run_one_mode_desc)"
|
|
889
866
|
log_info "Agents: $agent_count (critic: $([ "$critic_enabled" = "true" ] && echo "enabled" || echo "disabled"))"
|
|
890
|
-
if [[ $SESSION_TIMEOUT -gt 0 ]]; then
|
|
891
|
-
log_info "Session timeout: ${SESSION_TIMEOUT}s"
|
|
892
|
-
else
|
|
893
|
-
log_info "Session timeout: none"
|
|
894
|
-
fi
|
|
895
867
|
log_info "Prompt: $bootstrap_prompt"
|
|
896
868
|
log_info "Log: $session_dir/logs/session.log"
|
|
897
869
|
echo -e "${BOLD}════════════════════════════════════════════════════${NC}"
|
|
@@ -916,6 +888,8 @@ else:
|
|
|
916
888
|
--state-dir "$STATE_DIR" \
|
|
917
889
|
--feature-id "$feature_id" \
|
|
918
890
|
--session-status "failed" \
|
|
891
|
+
--max-retries "$MAX_RETRIES" \
|
|
892
|
+
--max-infra-retries "$MAX_INFRA_RETRIES" \
|
|
919
893
|
--action update 2>/dev/null || true
|
|
920
894
|
log_info "Feature $feature_id marked as failed due to interrupt"
|
|
921
895
|
fi
|
|
@@ -949,12 +923,13 @@ else:
|
|
|
949
923
|
if branch_create "$_proj_root" "$_branch_name" "$_source_branch"; then
|
|
950
924
|
_DEV_BRANCH_NAME="$_branch_name"
|
|
951
925
|
else
|
|
952
|
-
|
|
926
|
+
log_error "Failed to create branch: $_branch_name from $_source_branch — aborting to avoid running on $_source_branch"
|
|
927
|
+
return 1
|
|
953
928
|
fi
|
|
954
929
|
|
|
955
930
|
spawn_and_wait_session \
|
|
956
931
|
"$feature_id" "$feature_list" "$session_id" \
|
|
957
|
-
"$bootstrap_prompt" "$session_dir"
|
|
932
|
+
"$bootstrap_prompt" "$session_dir" "$MAX_RETRIES" "$feature_model" "$_ORIGINAL_BRANCH"
|
|
958
933
|
local session_status="$_SPAWN_RESULT"
|
|
959
934
|
|
|
960
935
|
# Merge dev branch back to original on success
|
|
@@ -976,12 +951,11 @@ else:
|
|
|
976
951
|
branch_ensure_return "$_proj_root" "$_ORIGINAL_BRANCH"
|
|
977
952
|
|
|
978
953
|
finalize_feature_status_after_branch_return \
|
|
979
|
-
"$feature_id" "$feature_list" "$session_id" "$session_status"
|
|
954
|
+
"$feature_id" "$feature_list" "$session_id" "$session_status" "$MAX_RETRIES" "$MAX_INFRA_RETRIES" "$session_dir" "$_ORIGINAL_BRANCH"
|
|
980
955
|
|
|
981
956
|
# Commit feature status update on the original branch (after guaranteed return)
|
|
982
957
|
if ! git -C "$_proj_root" diff --quiet "$feature_list" 2>/dev/null; then
|
|
983
|
-
|
|
984
|
-
git -C "$_proj_root" commit --no-verify -m "chore($feature_id): update feature status" 2>/dev/null || true
|
|
958
|
+
prizm_git_commit_paths "$_proj_root" "chore($feature_id): update feature status" "$feature_list"
|
|
985
959
|
fi
|
|
986
960
|
|
|
987
961
|
echo ""
|
|
@@ -1029,7 +1003,6 @@ main() {
|
|
|
1029
1003
|
fi
|
|
1030
1004
|
|
|
1031
1005
|
check_dependencies
|
|
1032
|
-
run_log_cleanup
|
|
1033
1006
|
|
|
1034
1007
|
# Initialize pipeline state if needed
|
|
1035
1008
|
if [[ ! -f "$STATE_DIR/pipeline.json" ]]; then
|
|
@@ -1080,12 +1053,8 @@ main() {
|
|
|
1080
1053
|
if [[ -n "$features_filter" ]]; then
|
|
1081
1054
|
log_info "Features filter: $features_filter"
|
|
1082
1055
|
fi
|
|
1083
|
-
log_info "Max retries per feature: $MAX_RETRIES"
|
|
1084
|
-
|
|
1085
|
-
log_info "Session timeout: ${SESSION_TIMEOUT}s"
|
|
1086
|
-
else
|
|
1087
|
-
log_info "Session timeout: none"
|
|
1088
|
-
fi
|
|
1056
|
+
log_info "Max code retries per feature: $MAX_RETRIES"
|
|
1057
|
+
log_info "Max infrastructure retries per feature: $MAX_INFRA_RETRIES"
|
|
1089
1058
|
log_info "AI CLI: $CLI_CMD (platform: $PLATFORM)"
|
|
1090
1059
|
if [[ -n "${MODEL:-}" ]]; then
|
|
1091
1060
|
log_info "Default Model: $MODEL"
|
|
@@ -1259,28 +1228,38 @@ DEPLOY_PROMPT_EOF
|
|
|
1259
1228
|
fi
|
|
1260
1229
|
|
|
1261
1230
|
# Parse feature info
|
|
1262
|
-
local feature_id feature_title retry_count resume_phase
|
|
1231
|
+
local feature_id feature_title retry_count infra_error_count resume_phase
|
|
1263
1232
|
feature_id=$(echo "$next_feature" | jq -r '.feature_id')
|
|
1264
1233
|
feature_title=$(echo "$next_feature" | jq -r '.title')
|
|
1265
1234
|
retry_count=$(echo "$next_feature" | jq -r '.retry_count // 0')
|
|
1235
|
+
infra_error_count=$(echo "$next_feature" | jq -r '.infra_error_count // 0')
|
|
1266
1236
|
resume_phase=$(echo "$next_feature" | jq -r '.resume_from_phase // "null"')
|
|
1267
1237
|
|
|
1268
1238
|
echo ""
|
|
1269
1239
|
echo -e "${BOLD}────────────────────────────────────────────────────${NC}"
|
|
1270
1240
|
log_info "Feature: ${BOLD}$feature_id${NC} — $feature_title"
|
|
1271
|
-
log_info "
|
|
1241
|
+
log_info "Code retry: $retry_count / $MAX_RETRIES"
|
|
1242
|
+
log_info "Infrastructure retry: $infra_error_count / $MAX_INFRA_RETRIES"
|
|
1272
1243
|
if [[ "$resume_phase" != "null" ]]; then
|
|
1273
1244
|
log_info "Resuming from Phase $resume_phase"
|
|
1274
1245
|
fi
|
|
1275
1246
|
echo -e "${BOLD}────────────────────────────────────────────────────${NC}"
|
|
1276
1247
|
|
|
1277
|
-
#
|
|
1248
|
+
# Auto-commit any uncommitted workspace changes before starting the pipeline.
|
|
1249
|
+
# This ensures a clean baseline without blocking execution.
|
|
1278
1250
|
local _dirty_files=""
|
|
1279
|
-
_dirty_files=$(
|
|
1251
|
+
_dirty_files=$(prizm_git_status_non_bookkeeping "$_proj_root" "$STATE_DIR" "$feature_list" || true)
|
|
1280
1252
|
if [[ -n "$_dirty_files" ]]; then
|
|
1281
|
-
log_info "
|
|
1282
|
-
|
|
1283
|
-
git -C "$_proj_root" commit --no-verify -m "ready for run $feature_id" 2>/dev/null || true
|
|
1253
|
+
log_info "Auto-committing workspace changes before $feature_id..."
|
|
1254
|
+
prizm_git_add_all_safe "$_proj_root"
|
|
1255
|
+
git -C "$_proj_root" commit --no-verify -m "chore: workspace snapshot ready for run $feature_id" 2>/dev/null || true
|
|
1256
|
+
fi
|
|
1257
|
+
|
|
1258
|
+
local _bookkeeping_dirty=""
|
|
1259
|
+
_bookkeeping_dirty=$(prizm_git_status_bookkeeping "$_proj_root" "$STATE_DIR" "$feature_list" || true)
|
|
1260
|
+
if [[ -n "$_bookkeeping_dirty" ]]; then
|
|
1261
|
+
log_info "Committing pending pipeline bookkeeping before $feature_id..."
|
|
1262
|
+
prizm_git_commit_paths "$_proj_root" "chore($feature_id): include pipeline bookkeeping" "$feature_list" "$STATE_DIR"
|
|
1284
1263
|
fi
|
|
1285
1264
|
|
|
1286
1265
|
# Mark feature as in-progress BEFORE creating dev branch
|
|
@@ -1293,8 +1272,7 @@ DEPLOY_PROMPT_EOF
|
|
|
1293
1272
|
--action start >/dev/null 2>&1 || true
|
|
1294
1273
|
# Commit the in_progress status on the original branch
|
|
1295
1274
|
if ! git -C "$_proj_root" diff --quiet "$feature_list" 2>/dev/null; then
|
|
1296
|
-
|
|
1297
|
-
git -C "$_proj_root" commit --no-verify -m "chore($feature_id): mark in_progress" 2>/dev/null || true
|
|
1275
|
+
prizm_git_commit_paths "$_proj_root" "chore($feature_id): mark in_progress" "$feature_list"
|
|
1298
1276
|
fi
|
|
1299
1277
|
|
|
1300
1278
|
# Create per-feature dev branch (from the now-updated original branch)
|
|
@@ -1303,8 +1281,9 @@ DEPLOY_PROMPT_EOF
|
|
|
1303
1281
|
_DEV_BRANCH_NAME="$_feature_branch"
|
|
1304
1282
|
log_info "Dev branch: $_feature_branch"
|
|
1305
1283
|
else
|
|
1306
|
-
|
|
1284
|
+
log_error "Failed to create dev branch: $_feature_branch from $_ORIGINAL_BRANCH — aborting to avoid running on $_ORIGINAL_BRANCH"
|
|
1307
1285
|
_DEV_BRANCH_NAME=""
|
|
1286
|
+
break
|
|
1308
1287
|
fi
|
|
1309
1288
|
|
|
1310
1289
|
# Generate session ID and bootstrap prompt
|
|
@@ -1397,20 +1376,20 @@ DEPLOY_PROMPT_EOF
|
|
|
1397
1376
|
branch_ensure_return "$_proj_root" "$_ORIGINAL_BRANCH"
|
|
1398
1377
|
|
|
1399
1378
|
finalize_feature_status_after_branch_return \
|
|
1400
|
-
"$feature_id" "$feature_list" "$session_id" "$session_status" "$MAX_RETRIES" "$session_dir" "$_ORIGINAL_BRANCH"
|
|
1379
|
+
"$feature_id" "$feature_list" "$session_id" "$session_status" "$MAX_RETRIES" "$MAX_INFRA_RETRIES" "$session_dir" "$_ORIGINAL_BRANCH"
|
|
1401
1380
|
local item_status_after_session="${_SPAWN_ITEM_STATUS:-}"
|
|
1402
1381
|
|
|
1403
1382
|
# Commit feature status update on the original branch (after guaranteed return)
|
|
1404
1383
|
if ! git -C "$_proj_root" diff --quiet "$feature_list" 2>/dev/null; then
|
|
1405
|
-
|
|
1406
|
-
git -C "$_proj_root" commit --no-verify -m "chore($feature_id): update feature status" 2>/dev/null || true
|
|
1384
|
+
prizm_git_commit_paths "$_proj_root" "chore($feature_id): update feature status" "$feature_list"
|
|
1407
1385
|
fi
|
|
1408
1386
|
|
|
1409
1387
|
session_count=$((session_count + 1))
|
|
1410
1388
|
total_subagent_calls=$((total_subagent_calls + _SUBAGENT_COUNT))
|
|
1411
1389
|
|
|
1412
1390
|
# Stop-on-failure: abort only after the task is actually marked failed.
|
|
1413
|
-
# Pending retry outcomes
|
|
1391
|
+
# Pending retry outcomes keep running; both code and infra failures have
|
|
1392
|
+
# bounded budgets in update-feature-status.py.
|
|
1414
1393
|
if [[ "$session_status" != "success" && "$STOP_ON_FAILURE" == "1" && "$item_status_after_session" == "failed" ]]; then
|
|
1415
1394
|
echo ""
|
|
1416
1395
|
log_error "════════════════════════════════════════════════════"
|
|
@@ -1420,7 +1399,7 @@ DEPLOY_PROMPT_EOF
|
|
|
1420
1399
|
log_error "════════════════════════════════════════════════════"
|
|
1421
1400
|
break
|
|
1422
1401
|
elif [[ "$session_status" != "success" && "$STOP_ON_FAILURE" == "1" ]]; then
|
|
1423
|
-
log_info "STOP_ON_FAILURE: $feature_id is ${item_status_after_session:-unknown}; retry budget not exhausted, continuing."
|
|
1402
|
+
log_info "STOP_ON_FAILURE: $feature_id is ${item_status_after_session:-unknown}; code/infra retry budget not exhausted, continuing."
|
|
1424
1403
|
fi
|
|
1425
1404
|
|
|
1426
1405
|
# Brief pause before next iteration
|
|
@@ -1458,20 +1437,20 @@ show_help() {
|
|
|
1458
1437
|
echo " --no-critic Disable critic review (overrides feature-list setting)"
|
|
1459
1438
|
echo " --clean Delete artifacts and reset before running"
|
|
1460
1439
|
echo " --no-reset Skip feature status reset step"
|
|
1461
|
-
echo " --
|
|
1440
|
+
echo " --max-infra-retries N Max infrastructure/provider retries (default: 3)"
|
|
1441
|
+
echo ""
|
|
1442
|
+
echo "Pipeline Options (run all):"
|
|
1443
|
+
echo " --max-infra-retries N Max infrastructure/provider retries (default: 3)"
|
|
1462
1444
|
echo ""
|
|
1463
1445
|
echo "Environment Variables:"
|
|
1464
|
-
echo " MAX_RETRIES Max retries per feature (default: 3)"
|
|
1465
|
-
echo "
|
|
1446
|
+
echo " MAX_RETRIES Max code retries per feature (default: 3)"
|
|
1447
|
+
echo " MAX_INFRA_RETRIES Max infrastructure/provider retries per feature (default: 3)"
|
|
1466
1448
|
echo " AI_CLI AI CLI command name (auto-detected: cbc, claude, or codex)"
|
|
1467
1449
|
echo " MODEL AI model ID (e.g. claude-opus-4.6, claude-sonnet-4.6, claude-haiku-4.5)"
|
|
1468
1450
|
echo " PRIZMKIT_EFFORT AI reasoning effort (low|medium|high|xhigh|max; max is Claude Code only)"
|
|
1469
1451
|
echo " HEARTBEAT_INTERVAL Heartbeat log interval in seconds (default: 30)"
|
|
1470
1452
|
echo " STALE_KILL_THRESHOLD Auto-kill session after N seconds of no progress (default: 900)"
|
|
1471
1453
|
echo " HEARTBEAT_STALE_THRESHOLD Heartbeat stale threshold in seconds (default: 600)"
|
|
1472
|
-
echo " LOG_CLEANUP_ENABLED Run log cleanup before execution (default: 1)"
|
|
1473
|
-
echo " LOG_RETENTION_DAYS Delete logs older than N days (default: 14)"
|
|
1474
|
-
echo " LOG_MAX_TOTAL_MB Keep total logs under N MB (default: 1024)"
|
|
1475
1454
|
echo " PIPELINE_MODE Override mode for all features: lite|standard|full"
|
|
1476
1455
|
echo " ENABLE_CRITIC Enable critic review for all features: true|false"
|
|
1477
1456
|
echo " STOP_ON_FAILURE Stop pipeline when a task exhausts retries: 0|1 (default: 0)"
|
|
@@ -1483,10 +1462,8 @@ show_help() {
|
|
|
1483
1462
|
echo " ./run-feature.sh run F-007 --dry-run # Inspect generated prompt"
|
|
1484
1463
|
echo " ./run-feature.sh run F-007 --dry-run --mode lite # Test lite mode"
|
|
1485
1464
|
echo " ./run-feature.sh run F-007 --resume-phase 6 # Skip to implementation"
|
|
1486
|
-
echo " ./run-feature.sh run F-007 --mode full --timeout 3600 # Full mode, 1h timeout"
|
|
1487
1465
|
echo " ./run-feature.sh run F-007 --clean --mode standard # Clean + run standard"
|
|
1488
1466
|
echo " ./run-feature.sh status # Show pipeline status"
|
|
1489
|
-
echo " MAX_RETRIES=5 SESSION_TIMEOUT=7200 ./run-feature.sh run # Custom config"
|
|
1490
1467
|
echo " MODEL=claude-sonnet-4.6 ./run-feature.sh run # Use Sonnet model"
|
|
1491
1468
|
echo " MODEL=claude-haiku-4.5 ./run-feature.sh test-cli # Test with Haiku"
|
|
1492
1469
|
}
|
|
@@ -1512,6 +1489,16 @@ case "${1:-run}" in
|
|
|
1512
1489
|
_run_features_filter="$1"
|
|
1513
1490
|
shift
|
|
1514
1491
|
;;
|
|
1492
|
+
--max-infra-retries)
|
|
1493
|
+
shift
|
|
1494
|
+
if [[ $# -eq 0 ]]; then
|
|
1495
|
+
log_error "--max-infra-retries requires a value"
|
|
1496
|
+
exit 1
|
|
1497
|
+
fi
|
|
1498
|
+
prizm_require_positive_int "--max-infra-retries" "$1"
|
|
1499
|
+
MAX_INFRA_RETRIES="$1"
|
|
1500
|
+
shift
|
|
1501
|
+
;;
|
|
1515
1502
|
*)
|
|
1516
1503
|
_run_feature_list="$1"
|
|
1517
1504
|
shift
|
|
@@ -23,7 +23,6 @@ set -euo pipefail
|
|
|
23
23
|
# ./run-recovery.sh help Show help
|
|
24
24
|
#
|
|
25
25
|
# Environment Variables:
|
|
26
|
-
# SESSION_TIMEOUT Session timeout in seconds (default: 0 = no limit)
|
|
27
26
|
# AI_CLI AI CLI command name (auto-detected: cbc, claude, or codex)
|
|
28
27
|
# PRIZMKIT_PLATFORM Platform hint for custom AI_CLI wrappers: codebuddy, claude, or codex
|
|
29
28
|
# MODEL AI model to use (e.g. claude-opus-4.6)
|
|
@@ -62,7 +61,6 @@ if [[ -z "$RECOVERY_DETECT_SCRIPT" ]]; then
|
|
|
62
61
|
fi
|
|
63
62
|
|
|
64
63
|
# Configuration
|
|
65
|
-
SESSION_TIMEOUT=${SESSION_TIMEOUT:-0}
|
|
66
64
|
HEARTBEAT_INTERVAL=${HEARTBEAT_INTERVAL:-30}
|
|
67
65
|
STALE_KILL_THRESHOLD=${STALE_KILL_THRESHOLD:-900}
|
|
68
66
|
VERBOSE=${VERBOSE:-0}
|
|
@@ -108,7 +106,6 @@ Options (for 'run' command):
|
|
|
108
106
|
--model M Override AI model (e.g. claude-opus-4.6)
|
|
109
107
|
|
|
110
108
|
Environment Variables:
|
|
111
|
-
SESSION_TIMEOUT Session timeout in seconds (default: 0 = no limit)
|
|
112
109
|
MODEL AI model to use
|
|
113
110
|
PRIZMKIT_EFFORT AI reasoning effort (low|medium|high|xhigh|max; max is Claude Code only)
|
|
114
111
|
VERBOSE Set to 1 for verbose AI CLI output
|
|
@@ -283,13 +280,6 @@ spawn_recovery_session() {
|
|
|
283
280
|
start_progress_parser "$session_log" "$progress_json" "$SCRIPTS_DIR"
|
|
284
281
|
local parser_pid="${_PARSER_PID:-}"
|
|
285
282
|
|
|
286
|
-
# Timeout watchdog
|
|
287
|
-
local watcher_pid=""
|
|
288
|
-
if [[ $SESSION_TIMEOUT -gt 0 ]]; then
|
|
289
|
-
( sleep "$SESSION_TIMEOUT" && kill -TERM "$_SESSION_PID" 2>/dev/null ) &
|
|
290
|
-
watcher_pid=$!
|
|
291
|
-
fi
|
|
292
|
-
|
|
293
283
|
# Heartbeat monitor
|
|
294
284
|
start_heartbeat "$_SESSION_PID" "$session_log" "$progress_json" "$HEARTBEAT_INTERVAL" "$STALE_KILL_THRESHOLD"
|
|
295
285
|
_HEARTBEAT_PID="${_HEARTBEAT_PID:-}"
|
|
@@ -303,17 +293,10 @@ spawn_recovery_session() {
|
|
|
303
293
|
fi
|
|
304
294
|
_SESSION_PID=""
|
|
305
295
|
|
|
306
|
-
#
|
|
307
|
-
[[ -n "$watcher_pid" ]] && kill "$watcher_pid" 2>/dev/null || true
|
|
296
|
+
# Clean up heartbeat and parser
|
|
308
297
|
stop_heartbeat "${_HEARTBEAT_PID:-}"
|
|
309
298
|
_HEARTBEAT_PID=""
|
|
310
299
|
stop_progress_parser "$parser_pid"
|
|
311
|
-
[[ -n "$watcher_pid" ]] && wait "$watcher_pid" 2>/dev/null || true
|
|
312
|
-
|
|
313
|
-
# Map SIGTERM to timeout
|
|
314
|
-
if [[ $exit_code -eq 143 ]]; then
|
|
315
|
-
exit_code=124
|
|
316
|
-
fi
|
|
317
300
|
|
|
318
301
|
# Check for stale-kill
|
|
319
302
|
local stale_kill_marker="$session_dir/logs/stale-kill.json"
|
|
@@ -347,12 +330,6 @@ detect_session_outcome() {
|
|
|
347
330
|
local was_stale_killed=false
|
|
348
331
|
[[ -f "$stale_kill_marker" ]] && was_stale_killed=true
|
|
349
332
|
|
|
350
|
-
if [[ $exit_code -eq 124 ]]; then
|
|
351
|
-
log_warn "Session timed out"
|
|
352
|
-
echo "timed_out"
|
|
353
|
-
return
|
|
354
|
-
fi
|
|
355
|
-
|
|
356
333
|
if [[ "$was_stale_killed" == true ]]; then
|
|
357
334
|
log_warn "Session stale-killed"
|
|
358
335
|
fi
|
|
@@ -611,14 +588,6 @@ cmd_run() {
|
|
|
611
588
|
echo " • Commit manually: git add <files> && git commit"
|
|
612
589
|
echo ""
|
|
613
590
|
;;
|
|
614
|
-
timed_out)
|
|
615
|
-
log_warn "Session timed out before completing."
|
|
616
|
-
echo ""
|
|
617
|
-
echo " Options:"
|
|
618
|
-
echo " • Run recovery again: ./run-recovery.sh"
|
|
619
|
-
echo " • Increase timeout: SESSION_TIMEOUT=3600 ./run-recovery.sh"
|
|
620
|
-
echo ""
|
|
621
|
-
;;
|
|
622
591
|
no_changes)
|
|
623
592
|
log_warn "Session completed but produced no changes."
|
|
624
593
|
echo ""
|