prizmkit 1.1.91 → 1.1.92
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/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 +150 -39
- package/bundled/dev-pipeline/run-feature.sh +98 -33
- package/bundled/dev-pipeline/run-refactor.sh +150 -39
- 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_auto_skip.py +92 -3
- 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 +87 -29
- 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/package.json +1 -1
|
@@ -8,13 +8,14 @@ set -euo pipefail
|
|
|
8
8
|
# execute refactors from a .prizmkit/plans/refactor-list.json specification.
|
|
9
9
|
#
|
|
10
10
|
# Usage:
|
|
11
|
-
# ./run-refactor.sh run [.prizmkit/plans/refactor-list.json]
|
|
11
|
+
# ./run-refactor.sh run [.prizmkit/plans/refactor-list.json] [options] Run all refactors
|
|
12
12
|
# ./run-refactor.sh run <refactor-id> [options] Run a single refactor
|
|
13
13
|
# ./run-refactor.sh status [.prizmkit/plans/refactor-list.json] Show pipeline status
|
|
14
14
|
# ./run-refactor.sh reset Clear all state
|
|
15
15
|
#
|
|
16
16
|
# Environment Variables:
|
|
17
|
-
# MAX_RETRIES Max retries per refactor (default: 3)
|
|
17
|
+
# MAX_RETRIES Max code retries per refactor (default: 3)
|
|
18
|
+
# MAX_INFRA_RETRIES Max infrastructure/provider retries per refactor (default: 3)
|
|
18
19
|
# SESSION_TIMEOUT Session timeout in seconds (default: 0 = no limit)
|
|
19
20
|
# AI_CLI AI CLI command name (auto-detected: cbc, claude, or codex)
|
|
20
21
|
# CODEBUDDY_CLI Legacy alias for AI_CLI (deprecated, use AI_CLI instead)
|
|
@@ -43,6 +44,7 @@ SCRIPTS_DIR="$SCRIPT_DIR/scripts"
|
|
|
43
44
|
|
|
44
45
|
# Configuration
|
|
45
46
|
MAX_RETRIES=${MAX_RETRIES:-3}
|
|
47
|
+
MAX_INFRA_RETRIES=${MAX_INFRA_RETRIES:-3}
|
|
46
48
|
SESSION_TIMEOUT=${SESSION_TIMEOUT:-0}
|
|
47
49
|
HEARTBEAT_STALE_THRESHOLD=${HEARTBEAT_STALE_THRESHOLD:-600}
|
|
48
50
|
HEARTBEAT_INTERVAL=${HEARTBEAT_INTERVAL:-30}
|
|
@@ -92,8 +94,9 @@ spawn_and_wait_session() {
|
|
|
92
94
|
local bootstrap_prompt="$4"
|
|
93
95
|
local session_dir="$5"
|
|
94
96
|
local max_retries="$6"
|
|
95
|
-
local
|
|
96
|
-
local
|
|
97
|
+
local max_infra_retries="$7"
|
|
98
|
+
local item_model="${8:-}"
|
|
99
|
+
local base_branch="${9:-main}"
|
|
97
100
|
|
|
98
101
|
local session_log="$session_dir/logs/session.log"
|
|
99
102
|
local progress_json="$session_dir/logs/progress.json"
|
|
@@ -149,9 +152,23 @@ spawn_and_wait_session() {
|
|
|
149
152
|
# Check for stale-kill marker (heartbeat killed the process due to no progress)
|
|
150
153
|
local stale_kill_marker="$session_dir/logs/stale-kill.json"
|
|
151
154
|
local was_stale_killed=false
|
|
155
|
+
local stale_kill_reason=""
|
|
152
156
|
if [[ -f "$stale_kill_marker" ]]; then
|
|
153
157
|
was_stale_killed=true
|
|
154
|
-
|
|
158
|
+
stale_kill_reason=$(python3 - "$stale_kill_marker" <<'PY' 2>/dev/null || true
|
|
159
|
+
import json, sys
|
|
160
|
+
try:
|
|
161
|
+
with open(sys.argv[1], encoding="utf-8") as fh:
|
|
162
|
+
print(json.load(fh).get("reason") or "")
|
|
163
|
+
except Exception:
|
|
164
|
+
pass
|
|
165
|
+
PY
|
|
166
|
+
)
|
|
167
|
+
if [[ "$stale_kill_reason" == "error_loop" ]]; then
|
|
168
|
+
log_warn "Session was killed by heartbeat monitor due to repeated read-offset/wasted-call errors"
|
|
169
|
+
else
|
|
170
|
+
log_warn "Session was stale-killed by heartbeat monitor (no progress for too long)"
|
|
171
|
+
fi
|
|
155
172
|
fi
|
|
156
173
|
|
|
157
174
|
local was_infra_error=false
|
|
@@ -185,7 +202,11 @@ spawn_and_wait_session() {
|
|
|
185
202
|
log_warn "Infrastructure errors are retried without consuming code retry budget"
|
|
186
203
|
session_status="infra_error"
|
|
187
204
|
elif [[ "$was_stale_killed" == true ]]; then
|
|
188
|
-
|
|
205
|
+
if [[ "$stale_kill_reason" == "error_loop" ]]; then
|
|
206
|
+
log_warn "Session killed due to repeated read-offset/wasted-call error loop"
|
|
207
|
+
else
|
|
208
|
+
log_warn "Session stale-killed (no progress for ${STALE_KILL_THRESHOLD}s)"
|
|
209
|
+
fi
|
|
189
210
|
log_warn "Stale-killed sessions are treated as failed; dev branch is preserved for inspection"
|
|
190
211
|
session_status="crashed"
|
|
191
212
|
elif [[ $exit_code -ne 0 ]]; then
|
|
@@ -202,10 +223,10 @@ spawn_and_wait_session() {
|
|
|
202
223
|
session_status="success"
|
|
203
224
|
else
|
|
204
225
|
local uncommitted=""
|
|
205
|
-
uncommitted=$(
|
|
226
|
+
uncommitted=$(prizm_git_status_safe "$project_root" | head -1 || true)
|
|
206
227
|
if [[ -n "$uncommitted" ]]; then
|
|
207
228
|
log_warn "Session exited cleanly but produced no commits (uncommitted changes found) — auto-committing..."
|
|
208
|
-
|
|
229
|
+
prizm_git_add_all_safe "$project_root" || true
|
|
209
230
|
if git -C "$project_root" commit --no-verify -m "chore($refactor_id): auto-commit session work" 2>/dev/null; then
|
|
210
231
|
log_info "Auto-commit succeeded"
|
|
211
232
|
session_status="success"
|
|
@@ -224,10 +245,10 @@ spawn_and_wait_session() {
|
|
|
224
245
|
if [[ "$session_status" == "success" ]]; then
|
|
225
246
|
if git -C "$project_root" rev-parse --is-inside-work-tree >/dev/null 2>&1; then
|
|
226
247
|
local dirty_files=""
|
|
227
|
-
dirty_files=$(
|
|
248
|
+
dirty_files=$(prizm_git_status_safe "$project_root" || true)
|
|
228
249
|
if [[ -n "$dirty_files" ]]; then
|
|
229
250
|
log_info "Auto-committing remaining session artifacts..."
|
|
230
|
-
|
|
251
|
+
prizm_git_add_all_safe "$project_root" || true
|
|
231
252
|
git -C "$project_root" commit --no-verify --amend --no-edit 2>/dev/null \
|
|
232
253
|
|| git -C "$project_root" commit --no-verify -m "chore($refactor_id): include remaining session artifacts" 2>/dev/null \
|
|
233
254
|
|| true
|
|
@@ -305,7 +326,20 @@ sys.exit(0)
|
|
|
305
326
|
fi
|
|
306
327
|
fi
|
|
307
328
|
|
|
308
|
-
#
|
|
329
|
+
# Final status updates happen after the caller returns to the original branch.
|
|
330
|
+
_SPAWN_RESULT="$session_status"
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
finalize_refactor_status_after_branch_return() {
|
|
334
|
+
local refactor_id="$1"
|
|
335
|
+
local refactor_list="$2"
|
|
336
|
+
local session_id="$3"
|
|
337
|
+
local session_status="$4"
|
|
338
|
+
local max_retries="$5"
|
|
339
|
+
local max_infra_retries="$6"
|
|
340
|
+
|
|
341
|
+
# Update refactor status on the original branch. The caller commits the
|
|
342
|
+
# resulting refactor-list diff immediately after this helper returns.
|
|
309
343
|
local update_output
|
|
310
344
|
update_output=$(python3 "$SCRIPTS_DIR/update-refactor-status.py" \
|
|
311
345
|
--refactor-list "$refactor_list" \
|
|
@@ -314,14 +348,13 @@ sys.exit(0)
|
|
|
314
348
|
--session-status "$session_status" \
|
|
315
349
|
--session-id "$session_id" \
|
|
316
350
|
--max-retries "$max_retries" \
|
|
351
|
+
--max-infra-retries "$max_infra_retries" \
|
|
317
352
|
--action update 2>&1) || {
|
|
318
353
|
log_error "Failed to update refactor status: $update_output"
|
|
319
354
|
update_output=""
|
|
320
355
|
}
|
|
321
356
|
|
|
322
357
|
_SPAWN_ITEM_STATUS="$(printf '%s' "$update_output" | prizm_extract_update_new_status)"
|
|
323
|
-
|
|
324
|
-
_SPAWN_RESULT="$session_status"
|
|
325
358
|
}
|
|
326
359
|
|
|
327
360
|
# ============================================================
|
|
@@ -361,6 +394,8 @@ for item in data.get('refactors', []):
|
|
|
361
394
|
--state-dir "$STATE_DIR" \
|
|
362
395
|
--refactor-id "$_interrupted_id" \
|
|
363
396
|
--session-status "failed" \
|
|
397
|
+
--max-retries "$MAX_RETRIES" \
|
|
398
|
+
--max-infra-retries "$MAX_INFRA_RETRIES" \
|
|
364
399
|
--action update 2>/dev/null || true
|
|
365
400
|
log_info "Refactor $_interrupted_id marked as failed due to interrupt"
|
|
366
401
|
fi
|
|
@@ -433,6 +468,16 @@ run_one() {
|
|
|
433
468
|
--clean) do_clean=true; shift ;;
|
|
434
469
|
--no-reset) no_reset=true; shift ;;
|
|
435
470
|
--timeout) shift; SESSION_TIMEOUT="${1:-0}"; shift ;;
|
|
471
|
+
--max-infra-retries)
|
|
472
|
+
shift
|
|
473
|
+
if [[ $# -eq 0 ]]; then
|
|
474
|
+
log_error "--max-infra-retries requires a value"
|
|
475
|
+
exit 1
|
|
476
|
+
fi
|
|
477
|
+
prizm_require_positive_int "--max-infra-retries" "$1"
|
|
478
|
+
MAX_INFRA_RETRIES="$1"
|
|
479
|
+
shift
|
|
480
|
+
;;
|
|
436
481
|
--mode)
|
|
437
482
|
shift
|
|
438
483
|
if [[ $# -eq 0 ]]; then
|
|
@@ -725,6 +770,8 @@ else:
|
|
|
725
770
|
--state-dir "$STATE_DIR" \
|
|
726
771
|
--refactor-id "$refactor_id" \
|
|
727
772
|
--session-status "failed" \
|
|
773
|
+
--max-retries "$MAX_RETRIES" \
|
|
774
|
+
--max-infra-retries "$MAX_INFRA_RETRIES" \
|
|
728
775
|
--action update 2>/dev/null || true
|
|
729
776
|
log_info "Refactor $refactor_id marked as failed due to interrupt"
|
|
730
777
|
fi
|
|
@@ -758,12 +805,13 @@ else:
|
|
|
758
805
|
if branch_create "$_proj_root" "$_branch_name" "$_source_branch"; then
|
|
759
806
|
_DEV_BRANCH_NAME="$_branch_name"
|
|
760
807
|
else
|
|
761
|
-
|
|
808
|
+
log_error "Failed to create branch: $_branch_name from $_source_branch — aborting to avoid running on $_source_branch"
|
|
809
|
+
return 1
|
|
762
810
|
fi
|
|
763
811
|
|
|
764
812
|
spawn_and_wait_session \
|
|
765
813
|
"$refactor_id" "$refactor_list" "$session_id" \
|
|
766
|
-
"$bootstrap_prompt" "$session_dir"
|
|
814
|
+
"$bootstrap_prompt" "$session_dir" "$MAX_RETRIES" "$MAX_INFRA_RETRIES" "$refactor_model" "$_ORIGINAL_BRANCH"
|
|
767
815
|
local session_status="$_SPAWN_RESULT"
|
|
768
816
|
|
|
769
817
|
# Merge dev branch back to original on success
|
|
@@ -773,7 +821,7 @@ else:
|
|
|
773
821
|
else
|
|
774
822
|
log_warn "Auto-merge failed — dev branch preserved: $_DEV_BRANCH_NAME"
|
|
775
823
|
log_warn "Merge manually: git checkout $_ORIGINAL_BRANCH && git rebase $_DEV_BRANCH_NAME"
|
|
776
|
-
|
|
824
|
+
session_status="merge_conflict"
|
|
777
825
|
fi
|
|
778
826
|
elif [[ -n "$_DEV_BRANCH_NAME" ]]; then
|
|
779
827
|
# Session failed — preserve dev branch for inspection
|
|
@@ -784,10 +832,12 @@ else:
|
|
|
784
832
|
# GUARANTEED: always return to original branch regardless of success/failure/merge outcome
|
|
785
833
|
branch_ensure_return "$_proj_root" "$_ORIGINAL_BRANCH"
|
|
786
834
|
|
|
835
|
+
finalize_refactor_status_after_branch_return \
|
|
836
|
+
"$refactor_id" "$refactor_list" "$session_id" "$session_status" "$MAX_RETRIES" "$MAX_INFRA_RETRIES"
|
|
837
|
+
|
|
787
838
|
# Commit refactor status update on the original branch (after guaranteed return)
|
|
788
839
|
if ! git -C "$_proj_root" diff --quiet "$refactor_list" 2>/dev/null; then
|
|
789
|
-
|
|
790
|
-
git -C "$_proj_root" commit --no-verify -m "chore($refactor_id): update refactor status" 2>/dev/null || true
|
|
840
|
+
prizm_git_commit_paths "$_proj_root" "chore($refactor_id): update refactor status" "$refactor_list"
|
|
791
841
|
fi
|
|
792
842
|
|
|
793
843
|
echo ""
|
|
@@ -808,7 +858,41 @@ else:
|
|
|
808
858
|
# ============================================================
|
|
809
859
|
|
|
810
860
|
main() {
|
|
811
|
-
local refactor_list="$
|
|
861
|
+
local refactor_list="$PRIZMKIT_DIR/plans/refactor-list.json"
|
|
862
|
+
|
|
863
|
+
while [[ $# -gt 0 ]]; do
|
|
864
|
+
case "$1" in
|
|
865
|
+
--max-infra-retries)
|
|
866
|
+
shift
|
|
867
|
+
if [[ $# -eq 0 ]]; then
|
|
868
|
+
log_error "--max-infra-retries requires a value"
|
|
869
|
+
exit 1
|
|
870
|
+
fi
|
|
871
|
+
prizm_require_positive_int "--max-infra-retries" "$1"
|
|
872
|
+
MAX_INFRA_RETRIES="$1"
|
|
873
|
+
shift
|
|
874
|
+
;;
|
|
875
|
+
--)
|
|
876
|
+
shift
|
|
877
|
+
break
|
|
878
|
+
;;
|
|
879
|
+
-*)
|
|
880
|
+
log_error "Unknown option: $1"
|
|
881
|
+
show_help
|
|
882
|
+
exit 1
|
|
883
|
+
;;
|
|
884
|
+
*)
|
|
885
|
+
refactor_list="$1"
|
|
886
|
+
shift
|
|
887
|
+
;;
|
|
888
|
+
esac
|
|
889
|
+
done
|
|
890
|
+
|
|
891
|
+
if [[ $# -gt 0 ]]; then
|
|
892
|
+
log_error "Unexpected arguments: $*"
|
|
893
|
+
show_help
|
|
894
|
+
exit 1
|
|
895
|
+
fi
|
|
812
896
|
|
|
813
897
|
if [[ ! "$refactor_list" = /* ]]; then
|
|
814
898
|
refactor_list="$(pwd)/$refactor_list"
|
|
@@ -870,7 +954,8 @@ main() {
|
|
|
870
954
|
echo -e "${BOLD} Refactor Pipeline Runner Started${NC}"
|
|
871
955
|
echo -e "${BOLD}════════════════════════════════════════════════════${NC}"
|
|
872
956
|
log_info "Refactor list: $refactor_list"
|
|
873
|
-
log_info "Max retries per refactor: $MAX_RETRIES"
|
|
957
|
+
log_info "Max code retries per refactor: $MAX_RETRIES"
|
|
958
|
+
log_info "Max infrastructure retries per refactor: $MAX_INFRA_RETRIES"
|
|
874
959
|
if [[ $SESSION_TIMEOUT -gt 0 ]]; then
|
|
875
960
|
log_info "Session timeout: ${SESSION_TIMEOUT}s"
|
|
876
961
|
else
|
|
@@ -920,6 +1005,7 @@ main() {
|
|
|
920
1005
|
--refactor-list "$refactor_list" \
|
|
921
1006
|
--state-dir "$STATE_DIR" \
|
|
922
1007
|
--max-retries "$MAX_RETRIES" \
|
|
1008
|
+
--max-infra-retries "$MAX_INFRA_RETRIES" \
|
|
923
1009
|
--action get_next 2>/dev/null); then
|
|
924
1010
|
log_error "Failed to get next refactor"
|
|
925
1011
|
break
|
|
@@ -1007,27 +1093,43 @@ DEPLOY_PROMPT_EOF
|
|
|
1007
1093
|
fi
|
|
1008
1094
|
|
|
1009
1095
|
# Parse refactor info
|
|
1010
|
-
local refactor_id refactor_title refactor_complexity retry_count resume_phase
|
|
1096
|
+
local refactor_id refactor_title refactor_complexity retry_count infra_error_count resume_phase
|
|
1011
1097
|
refactor_id=$(echo "$next_refactor" | jq -r '.refactor_id')
|
|
1012
1098
|
refactor_title=$(echo "$next_refactor" | jq -r '.title')
|
|
1013
1099
|
refactor_complexity=$(echo "$next_refactor" | jq -r '.complexity')
|
|
1014
1100
|
retry_count=$(echo "$next_refactor" | jq -r '.retry_count // 0')
|
|
1101
|
+
infra_error_count=$(echo "$next_refactor" | jq -r '.infra_error_count // 0')
|
|
1015
1102
|
resume_phase=$(echo "$next_refactor" | jq -r '.resume_from_phase // "null"')
|
|
1016
1103
|
|
|
1017
1104
|
echo ""
|
|
1018
1105
|
echo -e "${BOLD}────────────────────────────────────────────────────${NC}"
|
|
1019
1106
|
log_info "Refactor: ${BOLD}$refactor_id${NC} — $refactor_title"
|
|
1020
|
-
log_info "Complexity: $refactor_complexity
|
|
1107
|
+
log_info "Complexity: $refactor_complexity"
|
|
1108
|
+
log_info "Code retry: $retry_count / $MAX_RETRIES"
|
|
1109
|
+
log_info "Infrastructure retry: $infra_error_count / $MAX_INFRA_RETRIES"
|
|
1021
1110
|
if [[ "$resume_phase" != "null" ]]; then
|
|
1022
1111
|
log_info "Resuming from Phase $resume_phase"
|
|
1023
1112
|
fi
|
|
1024
1113
|
echo -e "${BOLD}────────────────────────────────────────────────────${NC}"
|
|
1025
1114
|
|
|
1026
|
-
#
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1115
|
+
# Do not auto-commit an arbitrary dirty baseline on the original branch.
|
|
1116
|
+
# Only pipeline bookkeeping is committed explicitly; user/source WIP,
|
|
1117
|
+
# submodule dirt, specs, and root-level hidden tool temporary worktrees
|
|
1118
|
+
# must never be swept into main by a broad git add.
|
|
1119
|
+
local _dirty_files=""
|
|
1120
|
+
_dirty_files=$(prizm_git_status_non_bookkeeping "$_proj_root" "$STATE_DIR" "$refactor_list" || true)
|
|
1121
|
+
if [[ -n "$_dirty_files" ]]; then
|
|
1122
|
+
log_error "Dirty working tree detected before $refactor_id; refusing to commit it on $_ORIGINAL_BRANCH."
|
|
1123
|
+
log_error "Commit/stash your changes or start from a clean branch, then rerun the pipeline."
|
|
1124
|
+
printf '%s\n' "$_dirty_files" | sed 's/^/ /'
|
|
1125
|
+
break
|
|
1126
|
+
fi
|
|
1127
|
+
|
|
1128
|
+
local _bookkeeping_dirty=""
|
|
1129
|
+
_bookkeeping_dirty=$(prizm_git_status_bookkeeping "$_proj_root" "$STATE_DIR" "$refactor_list" || true)
|
|
1130
|
+
if [[ -n "$_bookkeeping_dirty" ]]; then
|
|
1131
|
+
log_info "Committing pending pipeline bookkeeping before $refactor_id..."
|
|
1132
|
+
prizm_git_commit_paths "$_proj_root" "chore($refactor_id): include pipeline bookkeeping" "$refactor_list" "$STATE_DIR"
|
|
1031
1133
|
fi
|
|
1032
1134
|
|
|
1033
1135
|
# Mark refactor as in-progress BEFORE creating dev branch
|
|
@@ -1040,8 +1142,7 @@ DEPLOY_PROMPT_EOF
|
|
|
1040
1142
|
--action start >/dev/null 2>&1 || true
|
|
1041
1143
|
# Commit the in_progress status on the original branch
|
|
1042
1144
|
if ! git -C "$_proj_root" diff --quiet "$refactor_list" 2>/dev/null; then
|
|
1043
|
-
|
|
1044
|
-
git -C "$_proj_root" commit --no-verify -m "chore($refactor_id): mark in_progress" 2>/dev/null || true
|
|
1145
|
+
prizm_git_commit_paths "$_proj_root" "chore($refactor_id): mark in_progress" "$refactor_list"
|
|
1045
1146
|
fi
|
|
1046
1147
|
|
|
1047
1148
|
# Create per-refactor dev branch (from the now-updated original branch)
|
|
@@ -1050,8 +1151,9 @@ DEPLOY_PROMPT_EOF
|
|
|
1050
1151
|
_DEV_BRANCH_NAME="$_refactor_branch"
|
|
1051
1152
|
log_info "Dev branch: $_refactor_branch"
|
|
1052
1153
|
else
|
|
1053
|
-
|
|
1154
|
+
log_error "Failed to create dev branch: $_refactor_branch from $_ORIGINAL_BRANCH — aborting to avoid running on $_ORIGINAL_BRANCH"
|
|
1054
1155
|
_DEV_BRANCH_NAME=""
|
|
1156
|
+
break
|
|
1055
1157
|
fi
|
|
1056
1158
|
|
|
1057
1159
|
# Generate session
|
|
@@ -1122,7 +1224,7 @@ DEPLOY_PROMPT_EOF
|
|
|
1122
1224
|
|
|
1123
1225
|
spawn_and_wait_session \
|
|
1124
1226
|
"$refactor_id" "$refactor_list" "$session_id" \
|
|
1125
|
-
"$bootstrap_prompt" "$session_dir" "$MAX_RETRIES" "$refactor_model" "$_ORIGINAL_BRANCH"
|
|
1227
|
+
"$bootstrap_prompt" "$session_dir" "$MAX_RETRIES" "$MAX_INFRA_RETRIES" "$refactor_model" "$_ORIGINAL_BRANCH"
|
|
1126
1228
|
|
|
1127
1229
|
# Validate key artifacts after successful session
|
|
1128
1230
|
if [[ "$_SPAWN_RESULT" == "success" ]]; then
|
|
@@ -1135,7 +1237,6 @@ DEPLOY_PROMPT_EOF
|
|
|
1135
1237
|
fi
|
|
1136
1238
|
|
|
1137
1239
|
local session_status="$_SPAWN_RESULT"
|
|
1138
|
-
local item_status_after_session="${_SPAWN_ITEM_STATUS:-}"
|
|
1139
1240
|
|
|
1140
1241
|
# Merge per-refactor dev branch back to original on success
|
|
1141
1242
|
if [[ "$session_status" == "success" && -n "$_DEV_BRANCH_NAME" ]]; then
|
|
@@ -1144,7 +1245,7 @@ DEPLOY_PROMPT_EOF
|
|
|
1144
1245
|
else
|
|
1145
1246
|
log_warn "Auto-merge failed — dev branch preserved: $_DEV_BRANCH_NAME"
|
|
1146
1247
|
log_warn "Merge manually: git checkout $_ORIGINAL_BRANCH && git rebase $_DEV_BRANCH_NAME"
|
|
1147
|
-
|
|
1248
|
+
session_status="merge_conflict"
|
|
1148
1249
|
fi
|
|
1149
1250
|
elif [[ -n "$_DEV_BRANCH_NAME" ]]; then
|
|
1150
1251
|
# Session failed — preserve dev branch for inspection
|
|
@@ -1155,10 +1256,13 @@ DEPLOY_PROMPT_EOF
|
|
|
1155
1256
|
# GUARANTEED: always return to original branch regardless of success/failure/merge outcome
|
|
1156
1257
|
branch_ensure_return "$_proj_root" "$_ORIGINAL_BRANCH"
|
|
1157
1258
|
|
|
1259
|
+
finalize_refactor_status_after_branch_return \
|
|
1260
|
+
"$refactor_id" "$refactor_list" "$session_id" "$session_status" "$MAX_RETRIES" "$MAX_INFRA_RETRIES"
|
|
1261
|
+
local item_status_after_session="${_SPAWN_ITEM_STATUS:-}"
|
|
1262
|
+
|
|
1158
1263
|
# Commit refactor status update on the original branch (after guaranteed return)
|
|
1159
1264
|
if ! git -C "$_proj_root" diff --quiet "$refactor_list" 2>/dev/null; then
|
|
1160
|
-
|
|
1161
|
-
git -C "$_proj_root" commit --no-verify -m "chore($refactor_id): update refactor status" 2>/dev/null || true
|
|
1265
|
+
prizm_git_commit_paths "$_proj_root" "chore($refactor_id): update refactor status" "$refactor_list"
|
|
1162
1266
|
fi
|
|
1163
1267
|
|
|
1164
1268
|
# Stuck detection
|
|
@@ -1185,7 +1289,7 @@ DEPLOY_PROMPT_EOF
|
|
|
1185
1289
|
log_error "════════════════════════════════════════════════════"
|
|
1186
1290
|
break
|
|
1187
1291
|
elif [[ "$session_status" != "success" && "$STOP_ON_FAILURE" == "1" ]]; then
|
|
1188
|
-
log_info "STOP_ON_FAILURE: $refactor_id is ${item_status_after_session:-unknown}; retry budget not exhausted, continuing."
|
|
1292
|
+
log_info "STOP_ON_FAILURE: $refactor_id is ${item_status_after_session:-unknown}; code/infra retry budget not exhausted, continuing."
|
|
1189
1293
|
fi
|
|
1190
1294
|
|
|
1191
1295
|
log_info "Pausing 5s before next refactor..."
|
|
@@ -1201,7 +1305,7 @@ show_help() {
|
|
|
1201
1305
|
echo "Usage: $0 <command> [options]"
|
|
1202
1306
|
echo ""
|
|
1203
1307
|
echo "Commands:"
|
|
1204
|
-
echo " run [.prizmkit/plans/refactor-list.json]
|
|
1308
|
+
echo " run [.prizmkit/plans/refactor-list.json] [options] Run all refactors in dependency-topological order"
|
|
1205
1309
|
echo " run <refactor-id> [options] Run a single refactor"
|
|
1206
1310
|
echo " status [.prizmkit/plans/refactor-list.json] Show refactor pipeline status"
|
|
1207
1311
|
echo " reset Clear all refactor state"
|
|
@@ -1212,12 +1316,17 @@ show_help() {
|
|
|
1212
1316
|
echo " --clean Delete artifacts and reset before running"
|
|
1213
1317
|
echo " --no-reset Skip status reset (preserve retry count)"
|
|
1214
1318
|
echo " --timeout N Session timeout in seconds (default: 0 = no limit)"
|
|
1319
|
+
echo " --max-infra-retries N Max infrastructure/provider retries (default: 3)"
|
|
1215
1320
|
echo " --mode <lite|standard|full> Override pipeline mode"
|
|
1216
1321
|
echo " --critic Enable adversarial critic review"
|
|
1217
1322
|
echo " --no-critic Disable adversarial critic review"
|
|
1218
1323
|
echo ""
|
|
1324
|
+
echo "Pipeline Options (run all):"
|
|
1325
|
+
echo " --max-infra-retries N Max infrastructure/provider retries (default: 3)"
|
|
1326
|
+
echo ""
|
|
1219
1327
|
echo "Environment Variables:"
|
|
1220
|
-
echo " MAX_RETRIES Max retries per refactor (default: 3)"
|
|
1328
|
+
echo " MAX_RETRIES Max code retries per refactor (default: 3)"
|
|
1329
|
+
echo " MAX_INFRA_RETRIES Max infrastructure/provider retries per refactor (default: 3)"
|
|
1221
1330
|
echo " SESSION_TIMEOUT Session timeout in seconds (default: 0 = no limit)"
|
|
1222
1331
|
echo " MODEL Default AI model (overridden by per-refactor model in refactor list)"
|
|
1223
1332
|
echo " PRIZMKIT_EFFORT AI reasoning effort (low|medium|high|xhigh|max; max is Claude Code only)"
|
|
@@ -1249,7 +1358,7 @@ case "${1:-run}" in
|
|
|
1249
1358
|
if [[ "${1:-}" =~ ^[Rr]-[0-9]+ ]]; then
|
|
1250
1359
|
run_one "$@"
|
|
1251
1360
|
else
|
|
1252
|
-
main "
|
|
1361
|
+
main "$@"
|
|
1253
1362
|
fi
|
|
1254
1363
|
;;
|
|
1255
1364
|
status)
|
|
@@ -1261,6 +1370,8 @@ case "${1:-run}" in
|
|
|
1261
1370
|
python3 "$SCRIPTS_DIR/update-refactor-status.py" \
|
|
1262
1371
|
--refactor-list "${2:-$PRIZMKIT_DIR/plans/refactor-list.json}" \
|
|
1263
1372
|
--state-dir "$STATE_DIR" \
|
|
1373
|
+
--max-retries "$MAX_RETRIES" \
|
|
1374
|
+
--max-infra-retries "$MAX_INFRA_RETRIES" \
|
|
1264
1375
|
--action status
|
|
1265
1376
|
;;
|
|
1266
1377
|
reset)
|
|
@@ -16,7 +16,7 @@ Usage:
|
|
|
16
16
|
--bug-list <path> --state-dir <path> \
|
|
17
17
|
--action <get_next|start|update|status|pause|reset|clean|unskip> \
|
|
18
18
|
[--bug-id <id>] [--session-status <status>] \
|
|
19
|
-
[--session-id <id>] [--max-retries <n>]
|
|
19
|
+
[--session-id <id>] [--max-retries <n>] [--max-infra-retries <n>]
|
|
20
20
|
"""
|
|
21
21
|
|
|
22
22
|
import argparse
|
|
@@ -75,7 +75,8 @@ def parse_args():
|
|
|
75
75
|
help="Session outcome status (required for 'update' action)",
|
|
76
76
|
)
|
|
77
77
|
parser.add_argument("--session-id", default=None, help="Session ID (optional, for 'update' action)")
|
|
78
|
-
parser.add_argument("--max-retries", type=int, default=3, help="Maximum retry count (default: 3)")
|
|
78
|
+
parser.add_argument("--max-retries", type=int, default=3, help="Maximum code retry count (default: 3)")
|
|
79
|
+
parser.add_argument("--max-infra-retries", type=int, default=3, help="Maximum infrastructure retry count (default: 3)")
|
|
79
80
|
parser.add_argument("--project-root", default=None, help="Project root directory. Required for 'clean' action.")
|
|
80
81
|
return parser.parse_args()
|
|
81
82
|
|
|
@@ -98,6 +99,9 @@ def load_bug_status(state_dir, bug_id):
|
|
|
98
99
|
"bug_id": bug_id,
|
|
99
100
|
"retry_count": 0,
|
|
100
101
|
"max_retries": 3,
|
|
102
|
+
"max_infra_retries": 3,
|
|
103
|
+
"infra_error_count": 0,
|
|
104
|
+
"last_infra_error_session_id": None,
|
|
101
105
|
"sessions": [],
|
|
102
106
|
"last_session_id": None,
|
|
103
107
|
"resume_from_phase": None,
|
|
@@ -111,6 +115,9 @@ def load_bug_status(state_dir, bug_id):
|
|
|
111
115
|
"bug_id": bug_id,
|
|
112
116
|
"retry_count": 0,
|
|
113
117
|
"max_retries": 3,
|
|
118
|
+
"max_infra_retries": 3,
|
|
119
|
+
"infra_error_count": 0,
|
|
120
|
+
"last_infra_error_session_id": None,
|
|
114
121
|
"sessions": [],
|
|
115
122
|
"last_session_id": None,
|
|
116
123
|
"resume_from_phase": None,
|
|
@@ -234,6 +241,7 @@ def action_get_next(bug_list_data, state_dir):
|
|
|
234
241
|
"title": chosen.get("title", ""),
|
|
235
242
|
"severity": chosen.get("severity", "medium"),
|
|
236
243
|
"retry_count": chosen_status_data.get("retry_count", 0),
|
|
244
|
+
"infra_error_count": chosen_status_data.get("infra_error_count", 0),
|
|
237
245
|
"resume_from_phase": chosen_status_data.get("resume_from_phase", None),
|
|
238
246
|
}
|
|
239
247
|
print(json.dumps(result, indent=2, ensure_ascii=False))
|
|
@@ -248,6 +256,7 @@ def action_update(args, bug_list_path, state_dir):
|
|
|
248
256
|
session_status = args.session_status
|
|
249
257
|
session_id = args.session_id
|
|
250
258
|
max_retries = args.max_retries
|
|
259
|
+
max_infra_retries = args.max_infra_retries
|
|
251
260
|
|
|
252
261
|
if not bug_id:
|
|
253
262
|
error_out("--bug-id is required for 'update' action")
|
|
@@ -262,6 +271,8 @@ def action_update(args, bug_list_path, state_dir):
|
|
|
262
271
|
new_status = get_bug_status_from_list(bug_list_path, bug_id)
|
|
263
272
|
|
|
264
273
|
if session_status == "success":
|
|
274
|
+
bs["infra_error_count"] = 0
|
|
275
|
+
bs["last_infra_error_session_id"] = None
|
|
265
276
|
new_status = "completed"
|
|
266
277
|
bs["resume_from_phase"] = None
|
|
267
278
|
err = update_bug_in_list(bug_list_path, bug_id, "completed")
|
|
@@ -286,11 +297,25 @@ def action_update(args, bug_list_path, state_dir):
|
|
|
286
297
|
error_out("Failed to update .prizmkit/plans/bug-fix-list.json: {}".format(err))
|
|
287
298
|
return
|
|
288
299
|
elif session_status == "infra_error":
|
|
289
|
-
|
|
290
|
-
|
|
300
|
+
# AI CLI/provider outage, auth failure, gateway error, etc.
|
|
301
|
+
# Infra failures do not consume the code retry budget, but they still
|
|
302
|
+
# need their own bounded budget so a flaky provider cannot loop forever.
|
|
303
|
+
infra_error_count = bs.get("infra_error_count", 0) + 1
|
|
304
|
+
bs["infra_error_count"] = infra_error_count
|
|
291
305
|
bs["last_infra_error_session_id"] = session_id
|
|
306
|
+
bs["max_infra_retries"] = max_infra_retries
|
|
307
|
+
bs["degraded_reason"] = "infra_error"
|
|
308
|
+
if session_id:
|
|
309
|
+
bs["last_session_id"] = session_id
|
|
292
310
|
bs["resume_from_phase"] = None
|
|
293
311
|
|
|
312
|
+
if infra_error_count >= max_infra_retries:
|
|
313
|
+
new_status = "failed"
|
|
314
|
+
if session_id:
|
|
315
|
+
bs["last_failed_session_id"] = session_id
|
|
316
|
+
else:
|
|
317
|
+
new_status = "pending"
|
|
318
|
+
|
|
294
319
|
err = update_bug_in_list(bug_list_path, bug_id, new_status)
|
|
295
320
|
if err:
|
|
296
321
|
error_out("Failed to update .prizmkit/plans/bug-fix-list.json: {}".format(err))
|
|
@@ -338,6 +363,8 @@ def action_update(args, bug_list_path, state_dir):
|
|
|
338
363
|
"session_status": session_status,
|
|
339
364
|
"new_status": new_status,
|
|
340
365
|
"retry_count": bs["retry_count"],
|
|
366
|
+
"infra_error_count": bs.get("infra_error_count", 0),
|
|
367
|
+
"max_infra_retries": max_infra_retries,
|
|
341
368
|
"resume_from_phase": bs.get("resume_from_phase"),
|
|
342
369
|
"updated_at": bs["updated_at"],
|
|
343
370
|
}
|
|
@@ -577,6 +604,8 @@ def action_reset(args, bug_list_path, state_dir):
|
|
|
577
604
|
old_retry = bs.get("retry_count", 0)
|
|
578
605
|
|
|
579
606
|
bs["retry_count"] = 0
|
|
607
|
+
bs["infra_error_count"] = 0
|
|
608
|
+
bs["last_infra_error_session_id"] = None
|
|
580
609
|
bs["sessions"] = []
|
|
581
610
|
bs["last_session_id"] = None
|
|
582
611
|
bs["resume_from_phase"] = None
|
|
@@ -652,6 +681,8 @@ def action_clean(args, bug_list_path, state_dir):
|
|
|
652
681
|
old_retry = bs.get("retry_count", 0)
|
|
653
682
|
|
|
654
683
|
bs["retry_count"] = 0
|
|
684
|
+
bs["infra_error_count"] = 0
|
|
685
|
+
bs["last_infra_error_session_id"] = None
|
|
655
686
|
bs["sessions"] = []
|
|
656
687
|
bs["last_session_id"] = None
|
|
657
688
|
bs["resume_from_phase"] = None
|
|
@@ -740,6 +771,8 @@ def action_start(args, bug_list_path, state_dir):
|
|
|
740
771
|
"bug_id": bug_id,
|
|
741
772
|
"old_status": old_status,
|
|
742
773
|
"new_status": "in_progress",
|
|
774
|
+
"retry_count": bs.get("retry_count", 0),
|
|
775
|
+
"infra_error_count": bs.get("infra_error_count", 0),
|
|
743
776
|
"updated_at": bs["updated_at"],
|
|
744
777
|
}
|
|
745
778
|
print(json.dumps(result, indent=2, ensure_ascii=False))
|
|
@@ -816,6 +849,8 @@ def action_unskip(args, bug_list_path, state_dir):
|
|
|
816
849
|
for bid in to_reset:
|
|
817
850
|
bs = load_bug_status(state_dir, bid)
|
|
818
851
|
bs["retry_count"] = 0
|
|
852
|
+
bs["infra_error_count"] = 0
|
|
853
|
+
bs["last_infra_error_session_id"] = None
|
|
819
854
|
bs["sessions"] = []
|
|
820
855
|
bs["last_session_id"] = None
|
|
821
856
|
bs["resume_from_phase"] = None
|