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.
Files changed (46) hide show
  1. package/bundled/VERSION.json +3 -3
  2. package/bundled/dev-pipeline/.env.example +0 -4
  3. package/bundled/dev-pipeline/README.md +9 -39
  4. package/bundled/dev-pipeline/SCHEMA_ANALYSIS.md +0 -4
  5. package/bundled/dev-pipeline/assets/prizm-dev-team-integration.md +0 -2
  6. package/bundled/dev-pipeline/launch-bugfix-daemon.sh +0 -1
  7. package/bundled/dev-pipeline/launch-feature-daemon.sh +2 -3
  8. package/bundled/dev-pipeline/launch-refactor-daemon.sh +0 -1
  9. package/bundled/dev-pipeline/lib/branch.sh +8 -5
  10. package/bundled/dev-pipeline/lib/common.sh +122 -0
  11. package/bundled/dev-pipeline/lib/heartbeat.sh +13 -3
  12. package/bundled/dev-pipeline/run-bugfix.sh +149 -98
  13. package/bundled/dev-pipeline/run-feature.sh +90 -103
  14. package/bundled/dev-pipeline/run-recovery.sh +1 -32
  15. package/bundled/dev-pipeline/run-refactor.sh +148 -99
  16. package/bundled/dev-pipeline/scripts/update-bug-status.py +39 -4
  17. package/bundled/dev-pipeline/scripts/update-feature-status.py +43 -6
  18. package/bundled/dev-pipeline/scripts/update-refactor-status.py +36 -4
  19. package/bundled/dev-pipeline/tests/test-deploy-safety.sh +3 -3
  20. package/bundled/dev-pipeline/tests/test_auto_skip.py +92 -3
  21. package/bundled/dev-pipeline-windows/.env.example +0 -4
  22. package/bundled/dev-pipeline-windows/SCHEMA_ANALYSIS.md +0 -4
  23. package/bundled/dev-pipeline-windows/assets/prizm-dev-team-integration.md +0 -2
  24. package/bundled/dev-pipeline-windows/lib/branch.ps1 +17 -4
  25. package/bundled/dev-pipeline-windows/lib/common.ps1 +9 -0
  26. package/bundled/dev-pipeline-windows/lib/pipeline.ps1 +89 -108
  27. package/bundled/dev-pipeline-windows/run-recovery.ps1 +4 -19
  28. package/bundled/dev-pipeline-windows/scripts/update-bug-status.py +39 -4
  29. package/bundled/dev-pipeline-windows/scripts/update-feature-status.py +43 -6
  30. package/bundled/dev-pipeline-windows/scripts/update-refactor-status.py +36 -4
  31. package/bundled/skills/_metadata.json +1 -1
  32. package/bundled/skills/bugfix-pipeline-launcher/SKILL.md +1 -1
  33. package/bundled/skills/bugfix-pipeline-launcher/references/configuration.md +5 -15
  34. package/bundled/skills/feature-pipeline-launcher/SKILL.md +4 -4
  35. package/bundled/skills/feature-pipeline-launcher/references/configuration.md +3 -13
  36. package/bundled/skills/refactor-pipeline-launcher/SKILL.md +3 -3
  37. package/bundled/skills/refactor-pipeline-launcher/references/configuration.md +4 -14
  38. package/bundled/skills-windows/bugfix-pipeline-launcher/SKILL.md +1 -1
  39. package/bundled/skills-windows/bugfix-pipeline-launcher/references/configuration.md +4 -14
  40. package/bundled/skills-windows/feature-pipeline-launcher/SKILL.md +4 -4
  41. package/bundled/skills-windows/feature-pipeline-launcher/references/configuration.md +2 -12
  42. package/bundled/skills-windows/refactor-pipeline-launcher/SKILL.md +3 -3
  43. package/bundled/skills-windows/refactor-pipeline-launcher/references/configuration.md +4 -14
  44. package/package.json +1 -1
  45. package/bundled/dev-pipeline/scripts/cleanup-logs.py +0 -192
  46. package/bundled/dev-pipeline-windows/scripts/cleanup-logs.py +0 -192
@@ -8,14 +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] Run all refactors
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)
18
- # SESSION_TIMEOUT Session timeout in seconds (default: 0 = no limit)
17
+ # MAX_RETRIES Max code retries per refactor (default: 3)
18
+ # MAX_INFRA_RETRIES Max infrastructure/provider retries per refactor (default: 3)
19
19
  # AI_CLI AI CLI command name (auto-detected: cbc, claude, or codex)
20
20
  # CODEBUDDY_CLI Legacy alias for AI_CLI (deprecated, use AI_CLI instead)
21
21
  # PRIZMKIT_PLATFORM Platform hint for custom AI_CLI wrappers: codebuddy, claude, or codex
@@ -23,9 +23,6 @@ set -euo pipefail
23
23
  # HEARTBEAT_INTERVAL Heartbeat log interval in seconds (default: 30)
24
24
  # STALE_KILL_THRESHOLD Auto-kill session after N seconds of no progress (default: 900)
25
25
  # HEARTBEAT_STALE_THRESHOLD Heartbeat stale threshold in seconds (default: 600)
26
- # LOG_CLEANUP_ENABLED Run periodic log cleanup (default: 1)
27
- # LOG_RETENTION_DAYS Delete logs older than N days (default: 14)
28
- # LOG_MAX_TOTAL_MB Keep total logs under N MB via oldest-first cleanup (default: 1024)
29
26
  # DEV_BRANCH Custom dev branch name (default: auto-generated refactor/pipeline-{run_id})
30
27
  # AUTO_PUSH Auto-push to remote after successful refactor (default: 0). Set to 1 to enable.
31
28
  # STOP_ON_FAILURE Stop pipeline after a task exhausts all retries (default: 0). Set to 1 to stop.
@@ -43,13 +40,10 @@ SCRIPTS_DIR="$SCRIPT_DIR/scripts"
43
40
 
44
41
  # Configuration
45
42
  MAX_RETRIES=${MAX_RETRIES:-3}
46
- SESSION_TIMEOUT=${SESSION_TIMEOUT:-0}
43
+ MAX_INFRA_RETRIES=${MAX_INFRA_RETRIES:-3}
47
44
  HEARTBEAT_STALE_THRESHOLD=${HEARTBEAT_STALE_THRESHOLD:-600}
48
45
  HEARTBEAT_INTERVAL=${HEARTBEAT_INTERVAL:-30}
49
46
  STALE_KILL_THRESHOLD=${STALE_KILL_THRESHOLD:-900}
50
- LOG_CLEANUP_ENABLED=${LOG_CLEANUP_ENABLED:-1}
51
- LOG_RETENTION_DAYS=${LOG_RETENTION_DAYS:-14}
52
- LOG_MAX_TOTAL_MB=${LOG_MAX_TOTAL_MB:-1024}
53
47
  VERBOSE=${VERBOSE:-0}
54
48
  MODEL=${MODEL:-""}
55
49
  PRIZMKIT_EFFORT=${PRIZMKIT_EFFORT:-""}
@@ -92,8 +86,9 @@ spawn_and_wait_session() {
92
86
  local bootstrap_prompt="$4"
93
87
  local session_dir="$5"
94
88
  local max_retries="$6"
95
- local item_model="${7:-}"
96
- local base_branch="${8:-main}"
89
+ local max_infra_retries="$7"
90
+ local item_model="${8:-}"
91
+ local base_branch="${9:-main}"
97
92
 
98
93
  local session_log="$session_dir/logs/session.log"
99
94
  local progress_json="$session_dir/logs/progress.json"
@@ -116,13 +111,6 @@ spawn_and_wait_session() {
116
111
  start_progress_parser "$session_log" "$progress_json" "$SCRIPTS_DIR"
117
112
  local parser_pid="${_PARSER_PID:-}"
118
113
 
119
- # Timeout watchdog
120
- local watcher_pid=""
121
- if [[ $SESSION_TIMEOUT -gt 0 ]]; then
122
- ( sleep "$SESSION_TIMEOUT" && kill -TERM "$cli_pid" 2>/dev/null ) &
123
- watcher_pid=$!
124
- fi
125
-
126
114
  # Heartbeat monitor (with stale-kill protection)
127
115
  start_heartbeat "$cli_pid" "$session_log" "$progress_json" "$HEARTBEAT_INTERVAL" "$STALE_KILL_THRESHOLD"
128
116
  local heartbeat_pid="${_HEARTBEAT_PID:-}"
@@ -136,22 +124,32 @@ spawn_and_wait_session() {
136
124
  fi
137
125
 
138
126
  # Cleanup
139
- [[ -n "$watcher_pid" ]] && kill "$watcher_pid" 2>/dev/null || true
140
127
  stop_heartbeat "$heartbeat_pid"
141
128
  stop_progress_parser "$parser_pid"
142
- [[ -n "$watcher_pid" ]] && wait "$watcher_pid" 2>/dev/null || true
143
129
 
144
130
  # Recover session log from backup if truncated by subagent stdout redirection
145
131
  prizm_recover_session_log "$session_log"
146
132
 
147
- [[ $exit_code -eq 143 ]] && exit_code=124
148
-
149
133
  # Check for stale-kill marker (heartbeat killed the process due to no progress)
150
134
  local stale_kill_marker="$session_dir/logs/stale-kill.json"
151
135
  local was_stale_killed=false
136
+ local stale_kill_reason=""
152
137
  if [[ -f "$stale_kill_marker" ]]; then
153
138
  was_stale_killed=true
154
- log_warn "Session was stale-killed by heartbeat monitor (no progress for too long)"
139
+ stale_kill_reason=$(python3 - "$stale_kill_marker" <<'PY' 2>/dev/null || true
140
+ import json, sys
141
+ try:
142
+ with open(sys.argv[1], encoding="utf-8") as fh:
143
+ print(json.load(fh).get("reason") or "")
144
+ except Exception:
145
+ pass
146
+ PY
147
+ )
148
+ if [[ "$stale_kill_reason" == "error_loop" ]]; then
149
+ log_warn "Session was killed by heartbeat monitor due to repeated read-offset/wasted-call errors"
150
+ else
151
+ log_warn "Session was stale-killed by heartbeat monitor (no progress for too long)"
152
+ fi
155
153
  fi
156
154
 
157
155
  local was_infra_error=false
@@ -177,15 +175,16 @@ spawn_and_wait_session() {
177
175
  project_root="$PROJECT_ROOT"
178
176
  local default_branch="$base_branch"
179
177
 
180
- if [[ $exit_code -eq 124 ]]; then
181
- log_warn "Session timed out after ${SESSION_TIMEOUT}s"
182
- session_status="timed_out"
183
- elif [[ "$was_infra_error" == true ]]; then
178
+ if [[ "$was_infra_error" == true ]]; then
184
179
  log_warn "Session failed due to AI CLI/provider infrastructure error"
185
180
  log_warn "Infrastructure errors are retried without consuming code retry budget"
186
181
  session_status="infra_error"
187
182
  elif [[ "$was_stale_killed" == true ]]; then
188
- log_warn "Session stale-killed (no progress for ${STALE_KILL_THRESHOLD}s)"
183
+ if [[ "$stale_kill_reason" == "error_loop" ]]; then
184
+ log_warn "Session killed due to repeated read-offset/wasted-call error loop"
185
+ else
186
+ log_warn "Session stale-killed (no progress for ${STALE_KILL_THRESHOLD}s)"
187
+ fi
189
188
  log_warn "Stale-killed sessions are treated as failed; dev branch is preserved for inspection"
190
189
  session_status="crashed"
191
190
  elif [[ $exit_code -ne 0 ]]; then
@@ -202,10 +201,10 @@ spawn_and_wait_session() {
202
201
  session_status="success"
203
202
  else
204
203
  local uncommitted=""
205
- uncommitted=$(git -C "$project_root" status --porcelain 2>/dev/null | head -1 || true)
204
+ uncommitted=$(prizm_git_status_safe "$project_root" | head -1 || true)
206
205
  if [[ -n "$uncommitted" ]]; then
207
206
  log_warn "Session exited cleanly but produced no commits (uncommitted changes found) — auto-committing..."
208
- git -C "$project_root" add -A 2>/dev/null || true
207
+ prizm_git_add_all_safe "$project_root" || true
209
208
  if git -C "$project_root" commit --no-verify -m "chore($refactor_id): auto-commit session work" 2>/dev/null; then
210
209
  log_info "Auto-commit succeeded"
211
210
  session_status="success"
@@ -224,10 +223,10 @@ spawn_and_wait_session() {
224
223
  if [[ "$session_status" == "success" ]]; then
225
224
  if git -C "$project_root" rev-parse --is-inside-work-tree >/dev/null 2>&1; then
226
225
  local dirty_files=""
227
- dirty_files=$(git -C "$project_root" status --porcelain 2>/dev/null || true)
226
+ dirty_files=$(prizm_git_status_safe "$project_root" || true)
228
227
  if [[ -n "$dirty_files" ]]; then
229
228
  log_info "Auto-committing remaining session artifacts..."
230
- git -C "$project_root" add -A 2>/dev/null || true
229
+ prizm_git_add_all_safe "$project_root" || true
231
230
  git -C "$project_root" commit --no-verify --amend --no-edit 2>/dev/null \
232
231
  || git -C "$project_root" commit --no-verify -m "chore($refactor_id): include remaining session artifacts" 2>/dev/null \
233
232
  || true
@@ -305,7 +304,20 @@ sys.exit(0)
305
304
  fi
306
305
  fi
307
306
 
308
- # Update refactor status (do NOT commit on dev branch commit happens after merge)
307
+ # Final status updates happen after the caller returns to the original branch.
308
+ _SPAWN_RESULT="$session_status"
309
+ }
310
+
311
+ finalize_refactor_status_after_branch_return() {
312
+ local refactor_id="$1"
313
+ local refactor_list="$2"
314
+ local session_id="$3"
315
+ local session_status="$4"
316
+ local max_retries="$5"
317
+ local max_infra_retries="$6"
318
+
319
+ # Update refactor status on the original branch. The caller commits the
320
+ # resulting refactor-list diff immediately after this helper returns.
309
321
  local update_output
310
322
  update_output=$(python3 "$SCRIPTS_DIR/update-refactor-status.py" \
311
323
  --refactor-list "$refactor_list" \
@@ -314,14 +326,13 @@ sys.exit(0)
314
326
  --session-status "$session_status" \
315
327
  --session-id "$session_id" \
316
328
  --max-retries "$max_retries" \
329
+ --max-infra-retries "$max_infra_retries" \
317
330
  --action update 2>&1) || {
318
331
  log_error "Failed to update refactor status: $update_output"
319
332
  update_output=""
320
333
  }
321
334
 
322
335
  _SPAWN_ITEM_STATUS="$(printf '%s' "$update_output" | prizm_extract_update_new_status)"
323
-
324
- _SPAWN_RESULT="$session_status"
325
336
  }
326
337
 
327
338
  # ============================================================
@@ -361,6 +372,8 @@ for item in data.get('refactors', []):
361
372
  --state-dir "$STATE_DIR" \
362
373
  --refactor-id "$_interrupted_id" \
363
374
  --session-status "failed" \
375
+ --max-retries "$MAX_RETRIES" \
376
+ --max-infra-retries "$MAX_INFRA_RETRIES" \
364
377
  --action update 2>/dev/null || true
365
378
  log_info "Refactor $_interrupted_id marked as failed due to interrupt"
366
379
  fi
@@ -391,29 +404,6 @@ check_dependencies() {
391
404
  prizm_ensure_git_repo "$_project_root"
392
405
  }
393
406
 
394
- run_log_cleanup() {
395
- if [[ "$LOG_CLEANUP_ENABLED" != "1" ]]; then
396
- return 0
397
- fi
398
-
399
- local cleanup_result
400
- cleanup_result=$(python3 "$SCRIPTS_DIR/cleanup-logs.py" \
401
- --state-dir "$STATE_DIR" \
402
- --retention-days "$LOG_RETENTION_DAYS" \
403
- --max-total-mb "$LOG_MAX_TOTAL_MB" 2>/dev/null) || {
404
- log_warn "Log cleanup failed (continuing)"
405
- return 0
406
- }
407
-
408
- local deleted reclaimed_kb
409
- deleted=$(echo "$cleanup_result" | python3 -c "import sys,json; print(json.load(sys.stdin).get('deleted_files', 0))" 2>/dev/null || echo "0")
410
- 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")
411
-
412
- if [[ "$deleted" -gt 0 ]]; then
413
- log_info "Log cleanup: deleted $deleted files, reclaimed ${reclaimed_kb}KB"
414
- fi
415
- }
416
-
417
407
  # ============================================================
418
408
  # run-one: Run a single refactor
419
409
  # ============================================================
@@ -432,7 +422,16 @@ run_one() {
432
422
  --dry-run) dry_run=true; shift ;;
433
423
  --clean) do_clean=true; shift ;;
434
424
  --no-reset) no_reset=true; shift ;;
435
- --timeout) shift; SESSION_TIMEOUT="${1:-0}"; shift ;;
425
+ --max-infra-retries)
426
+ shift
427
+ if [[ $# -eq 0 ]]; then
428
+ log_error "--max-infra-retries requires a value"
429
+ exit 1
430
+ fi
431
+ prizm_require_positive_int "--max-infra-retries" "$1"
432
+ MAX_INFRA_RETRIES="$1"
433
+ shift
434
+ ;;
436
435
  --mode)
437
436
  shift
438
437
  if [[ $# -eq 0 ]]; then
@@ -473,7 +472,6 @@ run_one() {
473
472
  fi
474
473
 
475
474
  check_dependencies
476
- run_log_cleanup
477
475
 
478
476
  # Initialize state if needed
479
477
  if [[ ! -f "$STATE_DIR/pipeline.json" ]]; then
@@ -725,6 +723,8 @@ else:
725
723
  --state-dir "$STATE_DIR" \
726
724
  --refactor-id "$refactor_id" \
727
725
  --session-status "failed" \
726
+ --max-retries "$MAX_RETRIES" \
727
+ --max-infra-retries "$MAX_INFRA_RETRIES" \
728
728
  --action update 2>/dev/null || true
729
729
  log_info "Refactor $refactor_id marked as failed due to interrupt"
730
730
  fi
@@ -758,12 +758,13 @@ else:
758
758
  if branch_create "$_proj_root" "$_branch_name" "$_source_branch"; then
759
759
  _DEV_BRANCH_NAME="$_branch_name"
760
760
  else
761
- log_warn "Failed to create branch; running session on current branch"
761
+ log_error "Failed to create branch: $_branch_name from $_source_branch — aborting to avoid running on $_source_branch"
762
+ return 1
762
763
  fi
763
764
 
764
765
  spawn_and_wait_session \
765
766
  "$refactor_id" "$refactor_list" "$session_id" \
766
- "$bootstrap_prompt" "$session_dir" 999 "$refactor_model" "$_ORIGINAL_BRANCH"
767
+ "$bootstrap_prompt" "$session_dir" "$MAX_RETRIES" "$MAX_INFRA_RETRIES" "$refactor_model" "$_ORIGINAL_BRANCH"
767
768
  local session_status="$_SPAWN_RESULT"
768
769
 
769
770
  # Merge dev branch back to original on success
@@ -773,7 +774,7 @@ else:
773
774
  else
774
775
  log_warn "Auto-merge failed — dev branch preserved: $_DEV_BRANCH_NAME"
775
776
  log_warn "Merge manually: git checkout $_ORIGINAL_BRANCH && git rebase $_DEV_BRANCH_NAME"
776
- _DEV_BRANCH_NAME=""
777
+ session_status="merge_conflict"
777
778
  fi
778
779
  elif [[ -n "$_DEV_BRANCH_NAME" ]]; then
779
780
  # Session failed — preserve dev branch for inspection
@@ -784,10 +785,12 @@ else:
784
785
  # GUARANTEED: always return to original branch regardless of success/failure/merge outcome
785
786
  branch_ensure_return "$_proj_root" "$_ORIGINAL_BRANCH"
786
787
 
788
+ finalize_refactor_status_after_branch_return \
789
+ "$refactor_id" "$refactor_list" "$session_id" "$session_status" "$MAX_RETRIES" "$MAX_INFRA_RETRIES"
790
+
787
791
  # Commit refactor status update on the original branch (after guaranteed return)
788
792
  if ! git -C "$_proj_root" diff --quiet "$refactor_list" 2>/dev/null; then
789
- git -C "$_proj_root" add "$refactor_list"
790
- git -C "$_proj_root" commit --no-verify -m "chore($refactor_id): update refactor status" 2>/dev/null || true
793
+ prizm_git_commit_paths "$_proj_root" "chore($refactor_id): update refactor status" "$refactor_list"
791
794
  fi
792
795
 
793
796
  echo ""
@@ -808,7 +811,41 @@ else:
808
811
  # ============================================================
809
812
 
810
813
  main() {
811
- local refactor_list="${1:-$PRIZMKIT_DIR/plans/refactor-list.json}"
814
+ local refactor_list="$PRIZMKIT_DIR/plans/refactor-list.json"
815
+
816
+ while [[ $# -gt 0 ]]; do
817
+ case "$1" in
818
+ --max-infra-retries)
819
+ shift
820
+ if [[ $# -eq 0 ]]; then
821
+ log_error "--max-infra-retries requires a value"
822
+ exit 1
823
+ fi
824
+ prizm_require_positive_int "--max-infra-retries" "$1"
825
+ MAX_INFRA_RETRIES="$1"
826
+ shift
827
+ ;;
828
+ --)
829
+ shift
830
+ break
831
+ ;;
832
+ -*)
833
+ log_error "Unknown option: $1"
834
+ show_help
835
+ exit 1
836
+ ;;
837
+ *)
838
+ refactor_list="$1"
839
+ shift
840
+ ;;
841
+ esac
842
+ done
843
+
844
+ if [[ $# -gt 0 ]]; then
845
+ log_error "Unexpected arguments: $*"
846
+ show_help
847
+ exit 1
848
+ fi
812
849
 
813
850
  if [[ ! "$refactor_list" = /* ]]; then
814
851
  refactor_list="$(pwd)/$refactor_list"
@@ -823,7 +860,6 @@ main() {
823
860
  fi
824
861
 
825
862
  check_dependencies
826
- run_log_cleanup
827
863
 
828
864
  # Initialize pipeline state if needed
829
865
  if [[ ! -f "$STATE_DIR/pipeline.json" ]]; then
@@ -870,12 +906,8 @@ main() {
870
906
  echo -e "${BOLD} Refactor Pipeline Runner Started${NC}"
871
907
  echo -e "${BOLD}════════════════════════════════════════════════════${NC}"
872
908
  log_info "Refactor list: $refactor_list"
873
- log_info "Max retries per refactor: $MAX_RETRIES"
874
- if [[ $SESSION_TIMEOUT -gt 0 ]]; then
875
- log_info "Session timeout: ${SESSION_TIMEOUT}s"
876
- else
877
- log_info "Session timeout: none"
878
- fi
909
+ log_info "Max code retries per refactor: $MAX_RETRIES"
910
+ log_info "Max infrastructure retries per refactor: $MAX_INFRA_RETRIES"
879
911
  log_info "AI CLI: $CLI_CMD (platform: $PLATFORM)"
880
912
  if [[ -n "${MODEL:-}" ]]; then
881
913
  log_info "Default Model: $MODEL"
@@ -920,6 +952,7 @@ main() {
920
952
  --refactor-list "$refactor_list" \
921
953
  --state-dir "$STATE_DIR" \
922
954
  --max-retries "$MAX_RETRIES" \
955
+ --max-infra-retries "$MAX_INFRA_RETRIES" \
923
956
  --action get_next 2>/dev/null); then
924
957
  log_error "Failed to get next refactor"
925
958
  break
@@ -1007,27 +1040,40 @@ DEPLOY_PROMPT_EOF
1007
1040
  fi
1008
1041
 
1009
1042
  # Parse refactor info
1010
- local refactor_id refactor_title refactor_complexity retry_count resume_phase
1043
+ local refactor_id refactor_title refactor_complexity retry_count infra_error_count resume_phase
1011
1044
  refactor_id=$(echo "$next_refactor" | jq -r '.refactor_id')
1012
1045
  refactor_title=$(echo "$next_refactor" | jq -r '.title')
1013
1046
  refactor_complexity=$(echo "$next_refactor" | jq -r '.complexity')
1014
1047
  retry_count=$(echo "$next_refactor" | jq -r '.retry_count // 0')
1048
+ infra_error_count=$(echo "$next_refactor" | jq -r '.infra_error_count // 0')
1015
1049
  resume_phase=$(echo "$next_refactor" | jq -r '.resume_from_phase // "null"')
1016
1050
 
1017
1051
  echo ""
1018
1052
  echo -e "${BOLD}────────────────────────────────────────────────────${NC}"
1019
1053
  log_info "Refactor: ${BOLD}$refactor_id${NC} — $refactor_title"
1020
- log_info "Complexity: $refactor_complexity | Retry: $retry_count / $MAX_RETRIES"
1054
+ log_info "Complexity: $refactor_complexity"
1055
+ log_info "Code retry: $retry_count / $MAX_RETRIES"
1056
+ log_info "Infrastructure retry: $infra_error_count / $MAX_INFRA_RETRIES"
1021
1057
  if [[ "$resume_phase" != "null" ]]; then
1022
1058
  log_info "Resuming from Phase $resume_phase"
1023
1059
  fi
1024
1060
  echo -e "${BOLD}────────────────────────────────────────────────────${NC}"
1025
1061
 
1026
- # Pre-commit any dirty tree from previous iteration
1027
- if ! git -C "$_proj_root" diff --quiet HEAD 2>/dev/null || [ -n "$(git -C "$_proj_root" ls-files --others --exclude-standard 2>/dev/null)" ]; then
1028
- log_info "Dirty working tree detected — committing before $refactor_id..."
1029
- git -C "$_proj_root" add -A 2>/dev/null || true
1030
- git -C "$_proj_root" commit --no-verify -m "chore: capture artifacts before $refactor_id session" 2>/dev/null || true
1062
+ # Auto-commit any uncommitted workspace changes before starting the pipeline.
1063
+ # This ensures a clean baseline without blocking execution.
1064
+ local _dirty_files=""
1065
+ _dirty_files=$(prizm_git_status_non_bookkeeping "$_proj_root" "$STATE_DIR" "$refactor_list" || true)
1066
+ if [[ -n "$_dirty_files" ]]; then
1067
+ log_info "Auto-committing workspace changes before $refactor_id..."
1068
+ prizm_git_add_all_safe "$_proj_root"
1069
+ git -C "$_proj_root" commit --no-verify -m "chore: workspace snapshot ready for run $refactor_id" 2>/dev/null || true
1070
+ fi
1071
+
1072
+ local _bookkeeping_dirty=""
1073
+ _bookkeeping_dirty=$(prizm_git_status_bookkeeping "$_proj_root" "$STATE_DIR" "$refactor_list" || true)
1074
+ if [[ -n "$_bookkeeping_dirty" ]]; then
1075
+ log_info "Committing pending pipeline bookkeeping before $refactor_id..."
1076
+ prizm_git_commit_paths "$_proj_root" "chore($refactor_id): include pipeline bookkeeping" "$refactor_list" "$STATE_DIR"
1031
1077
  fi
1032
1078
 
1033
1079
  # Mark refactor as in-progress BEFORE creating dev branch
@@ -1040,8 +1086,7 @@ DEPLOY_PROMPT_EOF
1040
1086
  --action start >/dev/null 2>&1 || true
1041
1087
  # Commit the in_progress status on the original branch
1042
1088
  if ! git -C "$_proj_root" diff --quiet "$refactor_list" 2>/dev/null; then
1043
- git -C "$_proj_root" add "$refactor_list" 2>/dev/null || true
1044
- git -C "$_proj_root" commit --no-verify -m "chore($refactor_id): mark in_progress" 2>/dev/null || true
1089
+ prizm_git_commit_paths "$_proj_root" "chore($refactor_id): mark in_progress" "$refactor_list"
1045
1090
  fi
1046
1091
 
1047
1092
  # Create per-refactor dev branch (from the now-updated original branch)
@@ -1050,8 +1095,9 @@ DEPLOY_PROMPT_EOF
1050
1095
  _DEV_BRANCH_NAME="$_refactor_branch"
1051
1096
  log_info "Dev branch: $_refactor_branch"
1052
1097
  else
1053
- log_warn "Failed to create dev branch; running on current branch: $_ORIGINAL_BRANCH"
1098
+ log_error "Failed to create dev branch: $_refactor_branch from $_ORIGINAL_BRANCH — aborting to avoid running on $_ORIGINAL_BRANCH"
1054
1099
  _DEV_BRANCH_NAME=""
1100
+ break
1055
1101
  fi
1056
1102
 
1057
1103
  # Generate session
@@ -1122,7 +1168,7 @@ DEPLOY_PROMPT_EOF
1122
1168
 
1123
1169
  spawn_and_wait_session \
1124
1170
  "$refactor_id" "$refactor_list" "$session_id" \
1125
- "$bootstrap_prompt" "$session_dir" "$MAX_RETRIES" "$refactor_model" "$_ORIGINAL_BRANCH"
1171
+ "$bootstrap_prompt" "$session_dir" "$MAX_RETRIES" "$MAX_INFRA_RETRIES" "$refactor_model" "$_ORIGINAL_BRANCH"
1126
1172
 
1127
1173
  # Validate key artifacts after successful session
1128
1174
  if [[ "$_SPAWN_RESULT" == "success" ]]; then
@@ -1135,7 +1181,6 @@ DEPLOY_PROMPT_EOF
1135
1181
  fi
1136
1182
 
1137
1183
  local session_status="$_SPAWN_RESULT"
1138
- local item_status_after_session="${_SPAWN_ITEM_STATUS:-}"
1139
1184
 
1140
1185
  # Merge per-refactor dev branch back to original on success
1141
1186
  if [[ "$session_status" == "success" && -n "$_DEV_BRANCH_NAME" ]]; then
@@ -1144,7 +1189,7 @@ DEPLOY_PROMPT_EOF
1144
1189
  else
1145
1190
  log_warn "Auto-merge failed — dev branch preserved: $_DEV_BRANCH_NAME"
1146
1191
  log_warn "Merge manually: git checkout $_ORIGINAL_BRANCH && git rebase $_DEV_BRANCH_NAME"
1147
- _DEV_BRANCH_NAME=""
1192
+ session_status="merge_conflict"
1148
1193
  fi
1149
1194
  elif [[ -n "$_DEV_BRANCH_NAME" ]]; then
1150
1195
  # Session failed — preserve dev branch for inspection
@@ -1155,10 +1200,13 @@ DEPLOY_PROMPT_EOF
1155
1200
  # GUARANTEED: always return to original branch regardless of success/failure/merge outcome
1156
1201
  branch_ensure_return "$_proj_root" "$_ORIGINAL_BRANCH"
1157
1202
 
1203
+ finalize_refactor_status_after_branch_return \
1204
+ "$refactor_id" "$refactor_list" "$session_id" "$session_status" "$MAX_RETRIES" "$MAX_INFRA_RETRIES"
1205
+ local item_status_after_session="${_SPAWN_ITEM_STATUS:-}"
1206
+
1158
1207
  # Commit refactor status update on the original branch (after guaranteed return)
1159
1208
  if ! git -C "$_proj_root" diff --quiet "$refactor_list" 2>/dev/null; then
1160
- git -C "$_proj_root" add "$refactor_list"
1161
- git -C "$_proj_root" commit --no-verify -m "chore($refactor_id): update refactor status" 2>/dev/null || true
1209
+ prizm_git_commit_paths "$_proj_root" "chore($refactor_id): update refactor status" "$refactor_list"
1162
1210
  fi
1163
1211
 
1164
1212
  # Stuck detection
@@ -1185,7 +1233,7 @@ DEPLOY_PROMPT_EOF
1185
1233
  log_error "════════════════════════════════════════════════════"
1186
1234
  break
1187
1235
  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."
1236
+ log_info "STOP_ON_FAILURE: $refactor_id is ${item_status_after_session:-unknown}; code/infra retry budget not exhausted, continuing."
1189
1237
  fi
1190
1238
 
1191
1239
  log_info "Pausing 5s before next refactor..."
@@ -1201,7 +1249,7 @@ show_help() {
1201
1249
  echo "Usage: $0 <command> [options]"
1202
1250
  echo ""
1203
1251
  echo "Commands:"
1204
- echo " run [.prizmkit/plans/refactor-list.json] Run all refactors in dependency-topological order"
1252
+ echo " run [.prizmkit/plans/refactor-list.json] [options] Run all refactors in dependency-topological order"
1205
1253
  echo " run <refactor-id> [options] Run a single refactor"
1206
1254
  echo " status [.prizmkit/plans/refactor-list.json] Show refactor pipeline status"
1207
1255
  echo " reset Clear all refactor state"
@@ -1211,14 +1259,17 @@ show_help() {
1211
1259
  echo " --dry-run Generate bootstrap prompt only, don't spawn session"
1212
1260
  echo " --clean Delete artifacts and reset before running"
1213
1261
  echo " --no-reset Skip status reset (preserve retry count)"
1214
- echo " --timeout N Session timeout in seconds (default: 0 = no limit)"
1262
+ echo " --max-infra-retries N Max infrastructure/provider retries (default: 3)"
1215
1263
  echo " --mode <lite|standard|full> Override pipeline mode"
1216
1264
  echo " --critic Enable adversarial critic review"
1217
1265
  echo " --no-critic Disable adversarial critic review"
1218
1266
  echo ""
1267
+ echo "Pipeline Options (run all):"
1268
+ echo " --max-infra-retries N Max infrastructure/provider retries (default: 3)"
1269
+ echo ""
1219
1270
  echo "Environment Variables:"
1220
- echo " MAX_RETRIES Max retries per refactor (default: 3)"
1221
- echo " SESSION_TIMEOUT Session timeout in seconds (default: 0 = no limit)"
1271
+ echo " MAX_RETRIES Max code retries per refactor (default: 3)"
1272
+ echo " MAX_INFRA_RETRIES Max infrastructure/provider retries per refactor (default: 3)"
1222
1273
  echo " MODEL Default AI model (overridden by per-refactor model in refactor list)"
1223
1274
  echo " PRIZMKIT_EFFORT AI reasoning effort (low|medium|high|xhigh|max; max is Claude Code only)"
1224
1275
  echo " PIPELINE_MODE Default pipeline mode: lite|standard|full (overridden by --mode)"
@@ -1229,15 +1280,11 @@ show_help() {
1229
1280
  echo " HEARTBEAT_INTERVAL Heartbeat log interval in seconds (default: 30)"
1230
1281
  echo " STALE_KILL_THRESHOLD Auto-kill session after N seconds of no progress (default: 900)"
1231
1282
  echo " STOP_ON_FAILURE Stop pipeline when a task exhausts retries: 0|1 (default: 0)"
1232
- echo " LOG_CLEANUP_ENABLED Run log cleanup before execution (default: 1)"
1233
- echo " LOG_RETENTION_DAYS Delete logs older than N days (default: 14)"
1234
- echo " LOG_MAX_TOTAL_MB Keep total logs under N MB (default: 1024)"
1235
1283
  echo ""
1236
1284
  echo "Examples:"
1237
1285
  echo " ./run-refactor.sh run # Run all refactors"
1238
1286
  echo " ./run-refactor.sh run .prizmkit/plans/refactor-list.json # Custom refactor list"
1239
1287
  echo " ./run-refactor.sh run R-001 --dry-run # Inspect generated prompt"
1240
- echo " ./run-refactor.sh run R-001 --timeout 3600 # 1h timeout"
1241
1288
  echo " ./run-refactor.sh status # Show status"
1242
1289
  echo " STRICT_BEHAVIOR_CHECK=0 ./run-refactor.sh run # Skip full test suite"
1243
1290
  echo " MAX_RETRIES=5 ./run-refactor.sh run # Custom retries"
@@ -1249,7 +1296,7 @@ case "${1:-run}" in
1249
1296
  if [[ "${1:-}" =~ ^[Rr]-[0-9]+ ]]; then
1250
1297
  run_one "$@"
1251
1298
  else
1252
- main "${1:-$PRIZMKIT_DIR/plans/refactor-list.json}"
1299
+ main "$@"
1253
1300
  fi
1254
1301
  ;;
1255
1302
  status)
@@ -1261,6 +1308,8 @@ case "${1:-run}" in
1261
1308
  python3 "$SCRIPTS_DIR/update-refactor-status.py" \
1262
1309
  --refactor-list "${2:-$PRIZMKIT_DIR/plans/refactor-list.json}" \
1263
1310
  --state-dir "$STATE_DIR" \
1311
+ --max-retries "$MAX_RETRIES" \
1312
+ --max-infra-retries "$MAX_INFRA_RETRIES" \
1264
1313
  --action status
1265
1314
  ;;
1266
1315
  reset)