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
  # fix bugs from a .prizmkit/plans/bug-fix-list.json specification.
9
9
  #
10
10
  # Usage:
11
- # ./run-bugfix.sh run [.prizmkit/plans/bug-fix-list.json] Run all bugs
11
+ # ./run-bugfix.sh run [.prizmkit/plans/bug-fix-list.json] [options] Run all bugs
12
12
  # ./run-bugfix.sh run <bug-id> [options] Run a single bug
13
13
  # ./run-bugfix.sh status [.prizmkit/plans/bug-fix-list.json] Show pipeline status
14
14
  # ./run-bugfix.sh reset Clear all state
15
15
  #
16
16
  # Environment Variables:
17
- # MAX_RETRIES Max retries per bug (default: 3)
18
- # SESSION_TIMEOUT Session timeout in seconds (default: 0 = no limit)
17
+ # MAX_RETRIES Max code retries per bug (default: 3)
18
+ # MAX_INFRA_RETRIES Max infrastructure/provider retries per bug (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 bugfix/pipeline-{run_id})
30
27
  # AUTO_PUSH Auto-push to remote after successful bug fix (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.
@@ -42,13 +39,10 @@ SCRIPTS_DIR="$SCRIPT_DIR/scripts"
42
39
 
43
40
  # Configuration
44
41
  MAX_RETRIES=${MAX_RETRIES:-3}
45
- SESSION_TIMEOUT=${SESSION_TIMEOUT:-0}
42
+ MAX_INFRA_RETRIES=${MAX_INFRA_RETRIES:-3}
46
43
  HEARTBEAT_STALE_THRESHOLD=${HEARTBEAT_STALE_THRESHOLD:-600}
47
44
  HEARTBEAT_INTERVAL=${HEARTBEAT_INTERVAL:-30}
48
45
  STALE_KILL_THRESHOLD=${STALE_KILL_THRESHOLD:-900}
49
- LOG_CLEANUP_ENABLED=${LOG_CLEANUP_ENABLED:-1}
50
- LOG_RETENTION_DAYS=${LOG_RETENTION_DAYS:-14}
51
- LOG_MAX_TOTAL_MB=${LOG_MAX_TOTAL_MB:-1024}
52
46
  VERBOSE=${VERBOSE:-0}
53
47
  MODEL=${MODEL:-""}
54
48
  PRIZMKIT_EFFORT=${PRIZMKIT_EFFORT:-""}
@@ -90,8 +84,9 @@ spawn_and_wait_session() {
90
84
  local bootstrap_prompt="$4"
91
85
  local session_dir="$5"
92
86
  local max_retries="$6"
93
- local item_model="${7:-}"
94
- local base_branch="${8:-main}"
87
+ local max_infra_retries="$7"
88
+ local item_model="${8:-}"
89
+ local base_branch="${9:-main}"
95
90
 
96
91
  local session_log="$session_dir/logs/session.log"
97
92
  local progress_json="$session_dir/logs/progress.json"
@@ -114,13 +109,6 @@ spawn_and_wait_session() {
114
109
  start_progress_parser "$session_log" "$progress_json" "$SCRIPTS_DIR"
115
110
  local parser_pid="${_PARSER_PID:-}"
116
111
 
117
- # Timeout watchdog
118
- local watcher_pid=""
119
- if [[ $SESSION_TIMEOUT -gt 0 ]]; then
120
- ( sleep "$SESSION_TIMEOUT" && kill -TERM "$cli_pid" 2>/dev/null ) &
121
- watcher_pid=$!
122
- fi
123
-
124
112
  # Heartbeat monitor (with stale-kill protection)
125
113
  start_heartbeat "$cli_pid" "$session_log" "$progress_json" "$HEARTBEAT_INTERVAL" "$STALE_KILL_THRESHOLD"
126
114
  local heartbeat_pid="${_HEARTBEAT_PID:-}"
@@ -133,23 +121,34 @@ spawn_and_wait_session() {
133
121
  exit_code=$?
134
122
  fi
135
123
 
136
- # Cleanup
137
- [[ -n "$watcher_pid" ]] && kill "$watcher_pid" 2>/dev/null || true
124
+ # Clean up heartbeat and parser
138
125
  stop_heartbeat "$heartbeat_pid"
139
126
  stop_progress_parser "$parser_pid"
140
- [[ -n "$watcher_pid" ]] && wait "$watcher_pid" 2>/dev/null || true
141
127
 
142
128
  # Recover session log from backup if truncated by subagent stdout redirection
143
129
  prizm_recover_session_log "$session_log"
144
130
 
145
- [[ $exit_code -eq 143 ]] && exit_code=124
146
131
 
147
132
  # Check for stale-kill marker (heartbeat killed the process due to no progress)
148
133
  local stale_kill_marker="$session_dir/logs/stale-kill.json"
149
134
  local was_stale_killed=false
135
+ local stale_kill_reason=""
150
136
  if [[ -f "$stale_kill_marker" ]]; then
151
137
  was_stale_killed=true
152
- log_warn "Session was stale-killed by heartbeat monitor (no progress for too long)"
138
+ stale_kill_reason=$(python3 - "$stale_kill_marker" <<'PY' 2>/dev/null || true
139
+ import json, sys
140
+ try:
141
+ with open(sys.argv[1], encoding="utf-8") as fh:
142
+ print(json.load(fh).get("reason") or "")
143
+ except Exception:
144
+ pass
145
+ PY
146
+ )
147
+ if [[ "$stale_kill_reason" == "error_loop" ]]; then
148
+ log_warn "Session was killed by heartbeat monitor due to repeated read-offset/wasted-call errors"
149
+ else
150
+ log_warn "Session was stale-killed by heartbeat monitor (no progress for too long)"
151
+ fi
153
152
  fi
154
153
 
155
154
  local was_infra_error=false
@@ -175,15 +174,16 @@ spawn_and_wait_session() {
175
174
  project_root="$PROJECT_ROOT"
176
175
  local default_branch="$base_branch"
177
176
 
178
- if [[ $exit_code -eq 124 ]]; then
179
- log_warn "Session timed out after ${SESSION_TIMEOUT}s"
180
- session_status="timed_out"
181
- elif [[ "$was_infra_error" == true ]]; then
177
+ if [[ "$was_infra_error" == true ]]; then
182
178
  log_warn "Session failed due to AI CLI/provider infrastructure error"
183
179
  log_warn "Infrastructure errors are retried without consuming code retry budget"
184
180
  session_status="infra_error"
185
181
  elif [[ "$was_stale_killed" == true ]]; then
186
- log_warn "Session stale-killed (no progress for ${STALE_KILL_THRESHOLD}s)"
182
+ if [[ "$stale_kill_reason" == "error_loop" ]]; then
183
+ log_warn "Session killed due to repeated read-offset/wasted-call error loop"
184
+ else
185
+ log_warn "Session stale-killed (no progress for ${STALE_KILL_THRESHOLD}s)"
186
+ fi
187
187
  log_warn "Stale-killed sessions are treated as failed; dev branch is preserved for inspection"
188
188
  session_status="crashed"
189
189
  elif [[ $exit_code -ne 0 ]]; then
@@ -200,10 +200,10 @@ spawn_and_wait_session() {
200
200
  session_status="success"
201
201
  else
202
202
  local uncommitted=""
203
- uncommitted=$(git -C "$project_root" status --porcelain 2>/dev/null | head -1 || true)
203
+ uncommitted=$(prizm_git_status_safe "$project_root" | head -1 || true)
204
204
  if [[ -n "$uncommitted" ]]; then
205
205
  log_warn "Session exited cleanly but produced no commits (uncommitted changes found) — auto-committing..."
206
- git -C "$project_root" add -A 2>/dev/null || true
206
+ prizm_git_add_all_safe "$project_root" || true
207
207
  if git -C "$project_root" commit --no-verify -m "chore($bug_id): auto-commit session work" 2>/dev/null; then
208
208
  log_info "Auto-commit succeeded"
209
209
  session_status="success"
@@ -222,10 +222,10 @@ spawn_and_wait_session() {
222
222
  if [[ "$session_status" == "success" ]]; then
223
223
  if git -C "$project_root" rev-parse --is-inside-work-tree >/dev/null 2>&1; then
224
224
  local dirty_files=""
225
- dirty_files=$(git -C "$project_root" status --porcelain 2>/dev/null || true)
225
+ dirty_files=$(prizm_git_status_safe "$project_root" || true)
226
226
  if [[ -n "$dirty_files" ]]; then
227
227
  log_info "Auto-committing remaining session artifacts..."
228
- git -C "$project_root" add -A 2>/dev/null || true
228
+ prizm_git_add_all_safe "$project_root" || true
229
229
  git -C "$project_root" commit --no-verify --amend --no-edit 2>/dev/null \
230
230
  || git -C "$project_root" commit --no-verify -m "chore($bug_id): include remaining session artifacts" 2>/dev/null \
231
231
  || true
@@ -278,7 +278,20 @@ sys.exit(0)
278
278
  # Subagent detection
279
279
  prizm_detect_subagents "$session_log"
280
280
 
281
- # Update bug status (do NOT commit on dev branch commit happens after merge)
281
+ # Final status updates happen after the caller returns to the original branch.
282
+ _SPAWN_RESULT="$session_status"
283
+ }
284
+
285
+ finalize_bug_status_after_branch_return() {
286
+ local bug_id="$1"
287
+ local bug_list="$2"
288
+ local session_id="$3"
289
+ local session_status="$4"
290
+ local max_retries="$5"
291
+ local max_infra_retries="$6"
292
+
293
+ # Update bug status on the original branch. The caller commits the resulting
294
+ # bug-list diff immediately after this helper returns.
282
295
  local update_output
283
296
  update_output=$(python3 "$SCRIPTS_DIR/update-bug-status.py" \
284
297
  --bug-list "$bug_list" \
@@ -287,14 +300,13 @@ sys.exit(0)
287
300
  --session-status "$session_status" \
288
301
  --session-id "$session_id" \
289
302
  --max-retries "$max_retries" \
303
+ --max-infra-retries "$max_infra_retries" \
290
304
  --action update 2>&1) || {
291
305
  log_error "Failed to update bug status: $update_output"
292
306
  update_output=""
293
307
  }
294
308
 
295
309
  _SPAWN_ITEM_STATUS="$(printf '%s' "$update_output" | prizm_extract_update_new_status)"
296
-
297
- _SPAWN_RESULT="$session_status"
298
310
  }
299
311
 
300
312
  # ============================================================
@@ -334,6 +346,8 @@ for bug in data.get('bugs', []):
334
346
  --state-dir "$STATE_DIR" \
335
347
  --bug-id "$_interrupted_id" \
336
348
  --session-status "failed" \
349
+ --max-retries "$MAX_RETRIES" \
350
+ --max-infra-retries "$MAX_INFRA_RETRIES" \
337
351
  --action update 2>/dev/null || true
338
352
  log_info "Bug $_interrupted_id marked as failed due to interrupt"
339
353
  fi
@@ -364,28 +378,6 @@ check_dependencies() {
364
378
  prizm_ensure_git_repo "$_project_root"
365
379
  }
366
380
 
367
- run_log_cleanup() {
368
- if [[ "$LOG_CLEANUP_ENABLED" != "1" ]]; then
369
- return 0
370
- fi
371
-
372
- local cleanup_result
373
- cleanup_result=$(python3 "$SCRIPTS_DIR/cleanup-logs.py" \
374
- --state-dir "$STATE_DIR" \
375
- --retention-days "$LOG_RETENTION_DAYS" \
376
- --max-total-mb "$LOG_MAX_TOTAL_MB" 2>/dev/null) || {
377
- log_warn "Log cleanup failed (continuing)"
378
- return 0
379
- }
380
-
381
- local deleted reclaimed_kb
382
- deleted=$(echo "$cleanup_result" | python3 -c "import sys,json; print(json.load(sys.stdin).get('deleted_files', 0))" 2>/dev/null || echo "0")
383
- 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")
384
-
385
- if [[ "$deleted" -gt 0 ]]; then
386
- log_info "Log cleanup: deleted $deleted files, reclaimed ${reclaimed_kb}KB"
387
- fi
388
- }
389
381
 
390
382
  # ============================================================
391
383
  # run-one: Run a single bug fix
@@ -405,7 +397,16 @@ run_one() {
405
397
  --dry-run) dry_run=true; shift ;;
406
398
  --clean) do_clean=true; shift ;;
407
399
  --no-reset) no_reset=true; shift ;;
408
- --timeout) shift; SESSION_TIMEOUT="${1:-0}"; shift ;;
400
+ --max-infra-retries)
401
+ shift
402
+ if [[ $# -eq 0 ]]; then
403
+ log_error "--max-infra-retries requires a value"
404
+ exit 1
405
+ fi
406
+ prizm_require_positive_int "--max-infra-retries" "$1"
407
+ MAX_INFRA_RETRIES="$1"
408
+ shift
409
+ ;;
409
410
  --mode)
410
411
  shift
411
412
  if [[ $# -eq 0 ]]; then
@@ -446,7 +447,6 @@ run_one() {
446
447
  fi
447
448
 
448
449
  check_dependencies
449
- run_log_cleanup
450
450
 
451
451
  # Initialize state if needed
452
452
  if [[ ! -f "$STATE_DIR/pipeline.json" ]]; then
@@ -695,6 +695,8 @@ else:
695
695
  --state-dir "$STATE_DIR" \
696
696
  --bug-id "$bug_id" \
697
697
  --session-status "failed" \
698
+ --max-retries "$MAX_RETRIES" \
699
+ --max-infra-retries "$MAX_INFRA_RETRIES" \
698
700
  --action update 2>/dev/null || true
699
701
  log_info "Bug $bug_id marked as failed due to interrupt"
700
702
  fi
@@ -728,12 +730,13 @@ else:
728
730
  if branch_create "$_proj_root" "$_branch_name" "$_source_branch"; then
729
731
  _DEV_BRANCH_NAME="$_branch_name"
730
732
  else
731
- log_warn "Failed to create branch; running session on current branch"
733
+ log_error "Failed to create branch: $_branch_name from $_source_branch — aborting to avoid running on $_source_branch"
734
+ return 1
732
735
  fi
733
736
 
734
737
  spawn_and_wait_session \
735
738
  "$bug_id" "$bug_list" "$session_id" \
736
- "$bootstrap_prompt" "$session_dir" 999 "$bug_model" "$_ORIGINAL_BRANCH"
739
+ "$bootstrap_prompt" "$session_dir" "$MAX_RETRIES" "$MAX_INFRA_RETRIES" "$bug_model" "$_ORIGINAL_BRANCH"
737
740
  local session_status="$_SPAWN_RESULT"
738
741
 
739
742
  # Merge dev branch back to original on success
@@ -743,7 +746,7 @@ else:
743
746
  else
744
747
  log_warn "Auto-merge failed — dev branch preserved: $_DEV_BRANCH_NAME"
745
748
  log_warn "Merge manually: git checkout $_ORIGINAL_BRANCH && git rebase $_DEV_BRANCH_NAME"
746
- _DEV_BRANCH_NAME=""
749
+ session_status="merge_conflict"
747
750
  fi
748
751
  elif [[ -n "$_DEV_BRANCH_NAME" ]]; then
749
752
  # Session failed — preserve dev branch for inspection
@@ -754,10 +757,12 @@ else:
754
757
  # GUARANTEED: always return to original branch regardless of success/failure/merge outcome
755
758
  branch_ensure_return "$_proj_root" "$_ORIGINAL_BRANCH"
756
759
 
760
+ finalize_bug_status_after_branch_return \
761
+ "$bug_id" "$bug_list" "$session_id" "$session_status" "$MAX_RETRIES" "$MAX_INFRA_RETRIES"
762
+
757
763
  # Commit bug status update on the original branch (after guaranteed return)
758
764
  if ! git -C "$_proj_root" diff --quiet "$bug_list" 2>/dev/null; then
759
- git -C "$_proj_root" add "$bug_list"
760
- git -C "$_proj_root" commit --no-verify -m "chore($bug_id): update bug status" 2>/dev/null || true
765
+ prizm_git_commit_paths "$_proj_root" "chore($bug_id): update bug status" "$bug_list"
761
766
  fi
762
767
 
763
768
  echo ""
@@ -778,7 +783,41 @@ else:
778
783
  # ============================================================
779
784
 
780
785
  main() {
781
- local bug_list="${1:-$PRIZMKIT_DIR/plans/bug-fix-list.json}"
786
+ local bug_list="$PRIZMKIT_DIR/plans/bug-fix-list.json"
787
+
788
+ while [[ $# -gt 0 ]]; do
789
+ case "$1" in
790
+ --max-infra-retries)
791
+ shift
792
+ if [[ $# -eq 0 ]]; then
793
+ log_error "--max-infra-retries requires a value"
794
+ exit 1
795
+ fi
796
+ prizm_require_positive_int "--max-infra-retries" "$1"
797
+ MAX_INFRA_RETRIES="$1"
798
+ shift
799
+ ;;
800
+ --)
801
+ shift
802
+ break
803
+ ;;
804
+ -*)
805
+ log_error "Unknown option: $1"
806
+ show_help
807
+ exit 1
808
+ ;;
809
+ *)
810
+ bug_list="$1"
811
+ shift
812
+ ;;
813
+ esac
814
+ done
815
+
816
+ if [[ $# -gt 0 ]]; then
817
+ log_error "Unexpected arguments: $*"
818
+ show_help
819
+ exit 1
820
+ fi
782
821
 
783
822
  if [[ ! "$bug_list" = /* ]]; then
784
823
  bug_list="$(pwd)/$bug_list"
@@ -793,7 +832,6 @@ main() {
793
832
  fi
794
833
 
795
834
  check_dependencies
796
- run_log_cleanup
797
835
 
798
836
  # Initialize pipeline state if needed
799
837
  if [[ ! -f "$STATE_DIR/pipeline.json" ]]; then
@@ -840,12 +878,8 @@ main() {
840
878
  echo -e "${BOLD} Bug-Fix Pipeline Runner Started${NC}"
841
879
  echo -e "${BOLD}════════════════════════════════════════════════════${NC}"
842
880
  log_info "Bug fix list: $bug_list"
843
- log_info "Max retries per bug: $MAX_RETRIES"
844
- if [[ $SESSION_TIMEOUT -gt 0 ]]; then
845
- log_info "Session timeout: ${SESSION_TIMEOUT}s"
846
- else
847
- log_info "Session timeout: none"
848
- fi
881
+ log_info "Max code retries per bug: $MAX_RETRIES"
882
+ log_info "Max infrastructure retries per bug: $MAX_INFRA_RETRIES"
849
883
  log_info "AI CLI: $CLI_CMD (platform: $PLATFORM)"
850
884
  if [[ -n "${MODEL:-}" ]]; then
851
885
  log_info "Default Model: $MODEL"
@@ -885,6 +919,7 @@ main() {
885
919
  --bug-list "$bug_list" \
886
920
  --state-dir "$STATE_DIR" \
887
921
  --max-retries "$MAX_RETRIES" \
922
+ --max-infra-retries "$MAX_INFRA_RETRIES" \
888
923
  --action get_next 2>/dev/null); then
889
924
  log_error "Failed to get next bug"
890
925
  break
@@ -972,27 +1007,40 @@ DEPLOY_PROMPT_EOF
972
1007
  fi
973
1008
 
974
1009
  # Parse bug info
975
- local bug_id bug_title bug_severity retry_count resume_phase
1010
+ local bug_id bug_title bug_severity retry_count infra_error_count resume_phase
976
1011
  bug_id=$(echo "$next_bug" | jq -r '.bug_id')
977
1012
  bug_title=$(echo "$next_bug" | jq -r '.title')
978
1013
  bug_severity=$(echo "$next_bug" | jq -r '.severity')
979
1014
  retry_count=$(echo "$next_bug" | jq -r '.retry_count // 0')
1015
+ infra_error_count=$(echo "$next_bug" | jq -r '.infra_error_count // 0')
980
1016
  resume_phase=$(echo "$next_bug" | jq -r '.resume_from_phase // "null"')
981
1017
 
982
1018
  echo ""
983
1019
  echo -e "${BOLD}────────────────────────────────────────────────────${NC}"
984
1020
  log_info "Bug: ${BOLD}$bug_id${NC} — $bug_title"
985
- log_info "Severity: $bug_severity | Retry: $retry_count / $MAX_RETRIES"
1021
+ log_info "Severity: $bug_severity"
1022
+ log_info "Code retry: $retry_count / $MAX_RETRIES"
1023
+ log_info "Infrastructure retry: $infra_error_count / $MAX_INFRA_RETRIES"
986
1024
  if [[ "$resume_phase" != "null" ]]; then
987
1025
  log_info "Resuming from Phase $resume_phase"
988
1026
  fi
989
1027
  echo -e "${BOLD}────────────────────────────────────────────────────${NC}"
990
1028
 
991
- # Pre-commit any dirty tree from previous iteration
992
- 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
993
- log_info "Dirty working tree detected — committing before $bug_id..."
994
- git -C "$_proj_root" add -A 2>/dev/null || true
995
- git -C "$_proj_root" commit --no-verify -m "chore: capture artifacts before $bug_id session" 2>/dev/null || true
1029
+ # Auto-commit any uncommitted workspace changes before starting the pipeline.
1030
+ # This ensures a clean baseline without blocking execution.
1031
+ local _dirty_files=""
1032
+ _dirty_files=$(prizm_git_status_non_bookkeeping "$_proj_root" "$STATE_DIR" "$bug_list" || true)
1033
+ if [[ -n "$_dirty_files" ]]; then
1034
+ log_info "Auto-committing workspace changes before $bug_id..."
1035
+ prizm_git_add_all_safe "$_proj_root"
1036
+ git -C "$_proj_root" commit --no-verify -m "chore: workspace snapshot ready for run $bug_id" 2>/dev/null || true
1037
+ fi
1038
+
1039
+ local _bookkeeping_dirty=""
1040
+ _bookkeeping_dirty=$(prizm_git_status_bookkeeping "$_proj_root" "$STATE_DIR" "$bug_list" || true)
1041
+ if [[ -n "$_bookkeeping_dirty" ]]; then
1042
+ log_info "Committing pending pipeline bookkeeping before $bug_id..."
1043
+ prizm_git_commit_paths "$_proj_root" "chore($bug_id): include pipeline bookkeeping" "$bug_list" "$STATE_DIR"
996
1044
  fi
997
1045
 
998
1046
  # Mark bug as in-progress BEFORE creating dev branch
@@ -1005,8 +1053,7 @@ DEPLOY_PROMPT_EOF
1005
1053
  --action start >/dev/null 2>&1 || true
1006
1054
  # Commit the in_progress status on the original branch
1007
1055
  if ! git -C "$_proj_root" diff --quiet "$bug_list" 2>/dev/null; then
1008
- git -C "$_proj_root" add "$bug_list" 2>/dev/null || true
1009
- git -C "$_proj_root" commit --no-verify -m "chore($bug_id): mark in_progress" 2>/dev/null || true
1056
+ prizm_git_commit_paths "$_proj_root" "chore($bug_id): mark in_progress" "$bug_list"
1010
1057
  fi
1011
1058
 
1012
1059
  # Create per-bug dev branch (from the now-updated original branch)
@@ -1015,8 +1062,9 @@ DEPLOY_PROMPT_EOF
1015
1062
  _DEV_BRANCH_NAME="$_bug_branch"
1016
1063
  log_info "Dev branch: $_bug_branch"
1017
1064
  else
1018
- log_warn "Failed to create dev branch; running on current branch: $_ORIGINAL_BRANCH"
1065
+ log_error "Failed to create dev branch: $_bug_branch from $_ORIGINAL_BRANCH — aborting to avoid running on $_ORIGINAL_BRANCH"
1019
1066
  _DEV_BRANCH_NAME=""
1067
+ break
1020
1068
  fi
1021
1069
 
1022
1070
  # Generate session
@@ -1086,10 +1134,9 @@ DEPLOY_PROMPT_EOF
1086
1134
 
1087
1135
  spawn_and_wait_session \
1088
1136
  "$bug_id" "$bug_list" "$session_id" \
1089
- "$bootstrap_prompt" "$session_dir" "$MAX_RETRIES" "$bug_model" "$_ORIGINAL_BRANCH"
1137
+ "$bootstrap_prompt" "$session_dir" "$MAX_RETRIES" "$MAX_INFRA_RETRIES" "$bug_model" "$_ORIGINAL_BRANCH"
1090
1138
 
1091
1139
  local session_status="$_SPAWN_RESULT"
1092
- local item_status_after_session="${_SPAWN_ITEM_STATUS:-}"
1093
1140
 
1094
1141
  # Merge per-bug dev branch back to original on success
1095
1142
  if [[ "$session_status" == "success" && -n "$_DEV_BRANCH_NAME" ]]; then
@@ -1098,7 +1145,7 @@ DEPLOY_PROMPT_EOF
1098
1145
  else
1099
1146
  log_warn "Auto-merge failed — dev branch preserved: $_DEV_BRANCH_NAME"
1100
1147
  log_warn "Merge manually: git checkout $_ORIGINAL_BRANCH && git rebase $_DEV_BRANCH_NAME"
1101
- _DEV_BRANCH_NAME=""
1148
+ session_status="merge_conflict"
1102
1149
  fi
1103
1150
  elif [[ -n "$_DEV_BRANCH_NAME" ]]; then
1104
1151
  # Session failed — preserve dev branch for inspection
@@ -1109,10 +1156,13 @@ DEPLOY_PROMPT_EOF
1109
1156
  # GUARANTEED: always return to original branch regardless of success/failure/merge outcome
1110
1157
  branch_ensure_return "$_proj_root" "$_ORIGINAL_BRANCH"
1111
1158
 
1159
+ finalize_bug_status_after_branch_return \
1160
+ "$bug_id" "$bug_list" "$session_id" "$session_status" "$MAX_RETRIES" "$MAX_INFRA_RETRIES"
1161
+ local item_status_after_session="${_SPAWN_ITEM_STATUS:-}"
1162
+
1112
1163
  # Commit bug status update on the original branch (after guaranteed return)
1113
1164
  if ! git -C "$_proj_root" diff --quiet "$bug_list" 2>/dev/null; then
1114
- git -C "$_proj_root" add "$bug_list"
1115
- git -C "$_proj_root" commit --no-verify -m "chore($bug_id): update bug status" 2>/dev/null || true
1165
+ prizm_git_commit_paths "$_proj_root" "chore($bug_id): update bug status" "$bug_list"
1116
1166
  fi
1117
1167
 
1118
1168
  session_count=$((session_count + 1))
@@ -1129,7 +1179,7 @@ DEPLOY_PROMPT_EOF
1129
1179
  log_error "════════════════════════════════════════════════════"
1130
1180
  break
1131
1181
  elif [[ "$session_status" != "success" && "$STOP_ON_FAILURE" == "1" ]]; then
1132
- log_info "STOP_ON_FAILURE: $bug_id is ${item_status_after_session:-unknown}; retry budget not exhausted, continuing."
1182
+ log_info "STOP_ON_FAILURE: $bug_id is ${item_status_after_session:-unknown}; code/infra retry budget not exhausted, continuing."
1133
1183
  fi
1134
1184
 
1135
1185
  # Stuck detection
@@ -1166,7 +1216,7 @@ show_help() {
1166
1216
  echo "Usage: $0 <command> [options]"
1167
1217
  echo ""
1168
1218
  echo "Commands:"
1169
- echo " run [.prizmkit/plans/bug-fix-list.json] Run all bugs by severity/priority order"
1219
+ echo " run [.prizmkit/plans/bug-fix-list.json] [options] Run all bugs by severity/priority order"
1170
1220
  echo " run <bug-id> [options] Run a single bug fix"
1171
1221
  echo " status [.prizmkit/plans/bug-fix-list.json] Show bug fix pipeline status"
1172
1222
  echo " reset Clear all bugfix state"
@@ -1176,14 +1226,17 @@ show_help() {
1176
1226
  echo " --dry-run Generate bootstrap prompt only, don't spawn session"
1177
1227
  echo " --clean Delete artifacts and reset before running"
1178
1228
  echo " --no-reset Skip status reset (preserve retry count)"
1179
- echo " --timeout N Session timeout in seconds (default: 0 = no limit)"
1229
+ echo " --max-infra-retries N Max infrastructure/provider retries (default: 3)"
1180
1230
  echo " --mode <lite|standard|full> Override pipeline mode"
1181
1231
  echo " --critic Enable adversarial critic review"
1182
1232
  echo " --no-critic Disable adversarial critic review"
1183
1233
  echo ""
1234
+ echo "Pipeline Options (run all):"
1235
+ echo " --max-infra-retries N Max infrastructure/provider retries (default: 3)"
1236
+ echo ""
1184
1237
  echo "Environment Variables:"
1185
- echo " MAX_RETRIES Max retries per bug (default: 3)"
1186
- echo " SESSION_TIMEOUT Session timeout in seconds (default: 0 = no limit)"
1238
+ echo " MAX_RETRIES Max code retries per bug (default: 3)"
1239
+ echo " MAX_INFRA_RETRIES Max infrastructure/provider retries per bug (default: 3)"
1187
1240
  echo " MODEL Default AI model (overridden by per-bug model in bug list)"
1188
1241
  echo " PRIZMKIT_EFFORT AI reasoning effort (low|medium|high|xhigh|max; max is Claude Code only)"
1189
1242
  echo " PIPELINE_MODE Default pipeline mode: lite|standard|full (overridden by --mode)"
@@ -1193,9 +1246,6 @@ show_help() {
1193
1246
  echo " HEARTBEAT_INTERVAL Heartbeat log interval in seconds (default: 30)"
1194
1247
  echo " STALE_KILL_THRESHOLD Auto-kill session after N seconds of no progress (default: 900)"
1195
1248
  echo " STOP_ON_FAILURE Stop pipeline when a task exhausts retries: 0|1 (default: 0)"
1196
- echo " LOG_CLEANUP_ENABLED Run log cleanup before execution (default: 1)"
1197
- echo " LOG_RETENTION_DAYS Delete logs older than N days (default: 14)"
1198
- echo " LOG_MAX_TOTAL_MB Keep total logs under N MB (default: 1024)"
1199
1249
  echo ""
1200
1250
  echo "Examples:"
1201
1251
  echo " ./run-bugfix.sh run # Run all bugs"
@@ -1203,7 +1253,6 @@ show_help() {
1203
1253
  echo " ./run-bugfix.sh run B-001 --dry-run # Inspect generated prompt"
1204
1254
  echo " ./run-bugfix.sh run B-001 --clean # Clean artifacts + reset + run"
1205
1255
  echo " ./run-bugfix.sh run B-001 --no-reset # Retry without resetting status"
1206
- echo " ./run-bugfix.sh run B-001 --timeout 3600 # 1h timeout"
1207
1256
  echo " ./run-bugfix.sh status # Show status"
1208
1257
  echo " MAX_RETRIES=5 ./run-bugfix.sh run # Custom retries"
1209
1258
  }
@@ -1214,7 +1263,7 @@ case "${1:-run}" in
1214
1263
  if [[ "${1:-}" =~ ^[Bb]-[0-9]+ ]]; then
1215
1264
  run_one "$@"
1216
1265
  else
1217
- main "${1:-$PRIZMKIT_DIR/plans/bug-fix-list.json}"
1266
+ main "$@"
1218
1267
  fi
1219
1268
  ;;
1220
1269
  status)
@@ -1226,6 +1275,8 @@ case "${1:-run}" in
1226
1275
  python3 "$SCRIPTS_DIR/update-bug-status.py" \
1227
1276
  --bug-list "${2:-$PRIZMKIT_DIR/plans/bug-fix-list.json}" \
1228
1277
  --state-dir "$STATE_DIR" \
1278
+ --max-retries "$MAX_RETRIES" \
1279
+ --max-infra-retries "$MAX_INFRA_RETRIES" \
1229
1280
  --action status
1230
1281
  ;;
1231
1282
  reset)