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
package/bundled/VERSION.json
CHANGED
|
@@ -211,18 +211,21 @@ branch_save_wip() {
|
|
|
211
211
|
return 0
|
|
212
212
|
fi
|
|
213
213
|
|
|
214
|
-
# Check if there are any uncommitted changes (tracked or untracked,
|
|
214
|
+
# Check if there are any uncommitted changes (tracked or untracked,
|
|
215
|
+
# excluding gitignored and root-level hidden tool temporary worktrees)
|
|
215
216
|
local has_changes
|
|
216
|
-
has_changes=$(
|
|
217
|
+
has_changes=$(prizm_git_status_safe "$project_root" || true)
|
|
217
218
|
if [[ -z "$has_changes" ]]; then
|
|
218
219
|
return 0
|
|
219
220
|
fi
|
|
220
221
|
|
|
221
222
|
log_warn "Saving uncommitted work-in-progress on branch: $dev_branch"
|
|
222
223
|
|
|
223
|
-
# Stage all changes (tracked + untracked, respects .gitignore)
|
|
224
|
-
|
|
225
|
-
|
|
224
|
+
# Stage all changes (tracked + untracked, respects .gitignore), excluding
|
|
225
|
+
# root-level hidden tool temporary worktrees so nested agent repos are not
|
|
226
|
+
# recorded as accidental gitlinks in the user's project.
|
|
227
|
+
if ! prizm_git_add_all_safe "$project_root"; then
|
|
228
|
+
log_warn "git add failed — uncommitted work may be lost on branch switch"
|
|
226
229
|
return 0
|
|
227
230
|
fi
|
|
228
231
|
|
|
@@ -23,6 +23,128 @@ log_warn() { echo -e "${YELLOW}[WARN]${NC} $(date '+%Y-%m-%d %H:%M:%S') $*
|
|
|
23
23
|
log_error() { echo -e "${RED}[ERROR]${NC} $(date '+%Y-%m-%d %H:%M:%S') $*"; }
|
|
24
24
|
log_success() { echo -e "${GREEN}[SUCCESS]${NC} $(date '+%Y-%m-%d %H:%M:%S') $*"; }
|
|
25
25
|
|
|
26
|
+
prizm_require_positive_int() {
|
|
27
|
+
local option_name="$1"
|
|
28
|
+
local option_value="$2"
|
|
29
|
+
if [[ ! "$option_value" =~ ^[1-9][0-9]*$ ]]; then
|
|
30
|
+
log_error "$option_name must be a positive integer: $option_value"
|
|
31
|
+
exit 1
|
|
32
|
+
fi
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
# Ignore temporary git worktrees created under root-level hidden AI tool dirs
|
|
36
|
+
# matching .<tool>/worktree(s), without hiding normal source directories.
|
|
37
|
+
PRIZM_GIT_HIDDEN_TOOL_WORKTREE_EXCLUDES=(
|
|
38
|
+
':(top,exclude,glob).*/worktree'
|
|
39
|
+
':(top,exclude,glob).*/worktree/**'
|
|
40
|
+
':(top,exclude,glob).*/worktrees'
|
|
41
|
+
':(top,exclude,glob).*/worktrees/**'
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
prizm_git_status_safe() {
|
|
45
|
+
local project_root="$1"
|
|
46
|
+
git -C "$project_root" status --porcelain -- . \
|
|
47
|
+
"${PRIZM_GIT_HIDDEN_TOOL_WORKTREE_EXCLUDES[@]}" 2>/dev/null
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
prizm_git_add_all_safe() {
|
|
51
|
+
local project_root="$1"
|
|
52
|
+
git -C "$project_root" add -A -- . \
|
|
53
|
+
"${PRIZM_GIT_HIDDEN_TOOL_WORKTREE_EXCLUDES[@]}" 2>/dev/null
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
prizm_git_relative_path() {
|
|
57
|
+
local project_root="$1"
|
|
58
|
+
local target_path="$2"
|
|
59
|
+
python3 - "$project_root" "$target_path" <<'PY' 2>/dev/null || true
|
|
60
|
+
import os
|
|
61
|
+
import sys
|
|
62
|
+
root = os.path.abspath(sys.argv[1])
|
|
63
|
+
target = os.path.abspath(sys.argv[2])
|
|
64
|
+
print(os.path.relpath(target, root).replace(os.sep, "/"))
|
|
65
|
+
PY
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
prizm_git_status_non_bookkeeping() {
|
|
69
|
+
local project_root="$1"
|
|
70
|
+
local state_dir="$2"
|
|
71
|
+
local list_path="$3"
|
|
72
|
+
|
|
73
|
+
local state_rel list_rel
|
|
74
|
+
state_rel="$(prizm_git_relative_path "$project_root" "$state_dir")"
|
|
75
|
+
list_rel="$(prizm_git_relative_path "$project_root" "$list_path")"
|
|
76
|
+
|
|
77
|
+
local line path
|
|
78
|
+
while IFS= read -r line; do
|
|
79
|
+
[[ -n "$line" ]] || continue
|
|
80
|
+
path="${line:3}"
|
|
81
|
+
if [[ "$path" == *" -> "* ]]; then
|
|
82
|
+
path="${path##* -> }"
|
|
83
|
+
fi
|
|
84
|
+
path="${path%\"}"
|
|
85
|
+
path="${path#\"}"
|
|
86
|
+
path="${path#./}"
|
|
87
|
+
if [[ -n "$list_rel" && "$path" == "$list_rel" ]]; then
|
|
88
|
+
continue
|
|
89
|
+
fi
|
|
90
|
+
if [[ -n "$state_rel" && ( "$path" == "$state_rel" || "$path" == "$state_rel/"* ) ]]; then
|
|
91
|
+
continue
|
|
92
|
+
fi
|
|
93
|
+
printf '%s\n' "$line"
|
|
94
|
+
done < <(prizm_git_status_safe "$project_root")
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
prizm_git_status_bookkeeping() {
|
|
98
|
+
local project_root="$1"
|
|
99
|
+
local state_dir="$2"
|
|
100
|
+
local list_path="$3"
|
|
101
|
+
|
|
102
|
+
local state_rel list_rel
|
|
103
|
+
state_rel="$(prizm_git_relative_path "$project_root" "$state_dir")"
|
|
104
|
+
list_rel="$(prizm_git_relative_path "$project_root" "$list_path")"
|
|
105
|
+
|
|
106
|
+
local line path
|
|
107
|
+
while IFS= read -r line; do
|
|
108
|
+
[[ -n "$line" ]] || continue
|
|
109
|
+
path="${line:3}"
|
|
110
|
+
if [[ "$path" == *" -> "* ]]; then
|
|
111
|
+
path="${path##* -> }"
|
|
112
|
+
fi
|
|
113
|
+
path="${path%\"}"
|
|
114
|
+
path="${path#\"}"
|
|
115
|
+
path="${path#./}"
|
|
116
|
+
if [[ -n "$list_rel" && "$path" == "$list_rel" ]]; then
|
|
117
|
+
printf '%s\n' "$line"
|
|
118
|
+
continue
|
|
119
|
+
fi
|
|
120
|
+
if [[ -n "$state_rel" && ( "$path" == "$state_rel" || "$path" == "$state_rel/"* ) ]]; then
|
|
121
|
+
printf '%s\n' "$line"
|
|
122
|
+
fi
|
|
123
|
+
done < <(prizm_git_status_safe "$project_root")
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
prizm_git_commit_paths() {
|
|
127
|
+
local project_root="$1"
|
|
128
|
+
local message="$2"
|
|
129
|
+
shift 2
|
|
130
|
+
|
|
131
|
+
local paths=()
|
|
132
|
+
local path rel_path
|
|
133
|
+
for path in "$@"; do
|
|
134
|
+
[[ -n "$path" && -e "$path" ]] || continue
|
|
135
|
+
rel_path="$(prizm_git_relative_path "$project_root" "$path")"
|
|
136
|
+
[[ -n "$rel_path" ]] || continue
|
|
137
|
+
paths+=("$rel_path")
|
|
138
|
+
done
|
|
139
|
+
[[ ${#paths[@]} -gt 0 ]] || return 0
|
|
140
|
+
|
|
141
|
+
git -C "$project_root" add -- "${paths[@]}" 2>/dev/null || true
|
|
142
|
+
if ! git -C "$project_root" diff --cached --quiet -- "${paths[@]}" 2>/dev/null; then
|
|
143
|
+
git -C "$project_root" commit --no-verify -m "$message" -- "${paths[@]}" 2>/dev/null || true
|
|
144
|
+
fi
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
|
|
26
148
|
# ============================================================
|
|
27
149
|
# Path resolution (single source of truth)
|
|
28
150
|
# ============================================================
|
|
@@ -46,6 +46,7 @@ start_heartbeat() {
|
|
|
46
46
|
local prev_progress_signature=""
|
|
47
47
|
local prev_error_loop_signature=""
|
|
48
48
|
local stale_seconds=0
|
|
49
|
+
local stale_reason="stale_session"
|
|
49
50
|
while kill -0 "$cli_pid" 2>/dev/null; do
|
|
50
51
|
sleep "$heartbeat_interval"
|
|
51
52
|
elapsed=$((elapsed + heartbeat_interval))
|
|
@@ -240,16 +241,20 @@ PY
|
|
|
240
241
|
# child activity as the primary progress signals — the parent process is
|
|
241
242
|
# still running and children may still be producing output.
|
|
242
243
|
if [[ "$error_loop_detected" == "true" ]]; then
|
|
244
|
+
stale_reason="error_loop"
|
|
243
245
|
stale_seconds=$effective_stale_kill_threshold
|
|
244
246
|
elif [[ "$log_truncated" == "true" ]]; then
|
|
247
|
+
stale_reason="stale_session"
|
|
245
248
|
if [[ $progress_advanced -gt 0 || $child_growth -gt 0 ]]; then
|
|
246
249
|
stale_seconds=0
|
|
247
250
|
else
|
|
248
251
|
stale_seconds=$((stale_seconds + heartbeat_interval))
|
|
249
252
|
fi
|
|
250
253
|
elif [[ $growth -le 0 && $child_growth -eq 0 && $progress_advanced -eq 0 ]]; then
|
|
254
|
+
stale_reason="stale_session"
|
|
251
255
|
stale_seconds=$((stale_seconds + heartbeat_interval))
|
|
252
256
|
else
|
|
257
|
+
stale_reason="stale_session"
|
|
253
258
|
stale_seconds=0
|
|
254
259
|
fi
|
|
255
260
|
|
|
@@ -333,14 +338,19 @@ PY
|
|
|
333
338
|
# stale window to surface stuck agents promptly.
|
|
334
339
|
if [[ $effective_stale_kill_threshold -gt 0 && $stale_seconds -ge $effective_stale_kill_threshold ]]; then
|
|
335
340
|
local stale_mins=$((stale_seconds / 60))
|
|
336
|
-
|
|
337
|
-
|
|
341
|
+
if [[ "$stale_reason" == "error_loop" ]]; then
|
|
342
|
+
echo -e " ${RED}[HEARTBEAT]${NC} ${mins}m${secs}s | log: ${size_display} | ${RED}STALE-KILL: repeated read-offset/wasted-call error loop (threshold: ${effective_stale_kill_threshold}s)${NC}"
|
|
343
|
+
echo -e " ${RED}[HEARTBEAT]${NC} Killing AI CLI process $cli_pid (error loop)..."
|
|
344
|
+
else
|
|
345
|
+
echo -e " ${RED}[HEARTBEAT]${NC} ${mins}m${secs}s | log: ${size_display} | ${RED}STALE-KILL: no progress for ${stale_mins}m (threshold: ${effective_stale_kill_threshold}s)${NC}"
|
|
346
|
+
echo -e " ${RED}[HEARTBEAT]${NC} Killing AI CLI process $cli_pid (stale session)..."
|
|
347
|
+
fi
|
|
338
348
|
# Write the marker before killing. Some CLIs exit quickly, and the
|
|
339
349
|
# parent runner may stop this heartbeat process immediately after
|
|
340
350
|
# wait(1) returns.
|
|
341
351
|
local _marker_dir
|
|
342
352
|
_marker_dir="$(dirname "$session_log")"
|
|
343
|
-
echo "{\"killed_at\": \"$(date -u +%Y-%m-%dT%H:%M:%SZ)\", \"reason\": \"
|
|
353
|
+
echo "{\"killed_at\": \"$(date -u +%Y-%m-%dT%H:%M:%SZ)\", \"reason\": \"${stale_reason}\", \"stale_seconds\": $stale_seconds, \"threshold\": $effective_stale_kill_threshold}" > "$_marker_dir/stale-kill.json" 2>/dev/null || true
|
|
344
354
|
kill -TERM "$cli_pid" 2>/dev/null || true
|
|
345
355
|
# Give process 10s to exit gracefully, then force kill
|
|
346
356
|
local stale_kill_grace_seconds="${STALE_KILL_GRACE_SECONDS:-10}"
|
|
@@ -8,13 +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]
|
|
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)
|
|
17
|
+
# MAX_RETRIES Max code retries per bug (default: 3)
|
|
18
|
+
# MAX_INFRA_RETRIES Max infrastructure/provider retries per bug (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)
|
|
@@ -42,6 +43,7 @@ SCRIPTS_DIR="$SCRIPT_DIR/scripts"
|
|
|
42
43
|
|
|
43
44
|
# Configuration
|
|
44
45
|
MAX_RETRIES=${MAX_RETRIES:-3}
|
|
46
|
+
MAX_INFRA_RETRIES=${MAX_INFRA_RETRIES:-3}
|
|
45
47
|
SESSION_TIMEOUT=${SESSION_TIMEOUT:-0}
|
|
46
48
|
HEARTBEAT_STALE_THRESHOLD=${HEARTBEAT_STALE_THRESHOLD:-600}
|
|
47
49
|
HEARTBEAT_INTERVAL=${HEARTBEAT_INTERVAL:-30}
|
|
@@ -90,8 +92,9 @@ spawn_and_wait_session() {
|
|
|
90
92
|
local bootstrap_prompt="$4"
|
|
91
93
|
local session_dir="$5"
|
|
92
94
|
local max_retries="$6"
|
|
93
|
-
local
|
|
94
|
-
local
|
|
95
|
+
local max_infra_retries="$7"
|
|
96
|
+
local item_model="${8:-}"
|
|
97
|
+
local base_branch="${9:-main}"
|
|
95
98
|
|
|
96
99
|
local session_log="$session_dir/logs/session.log"
|
|
97
100
|
local progress_json="$session_dir/logs/progress.json"
|
|
@@ -147,9 +150,23 @@ spawn_and_wait_session() {
|
|
|
147
150
|
# Check for stale-kill marker (heartbeat killed the process due to no progress)
|
|
148
151
|
local stale_kill_marker="$session_dir/logs/stale-kill.json"
|
|
149
152
|
local was_stale_killed=false
|
|
153
|
+
local stale_kill_reason=""
|
|
150
154
|
if [[ -f "$stale_kill_marker" ]]; then
|
|
151
155
|
was_stale_killed=true
|
|
152
|
-
|
|
156
|
+
stale_kill_reason=$(python3 - "$stale_kill_marker" <<'PY' 2>/dev/null || true
|
|
157
|
+
import json, sys
|
|
158
|
+
try:
|
|
159
|
+
with open(sys.argv[1], encoding="utf-8") as fh:
|
|
160
|
+
print(json.load(fh).get("reason") or "")
|
|
161
|
+
except Exception:
|
|
162
|
+
pass
|
|
163
|
+
PY
|
|
164
|
+
)
|
|
165
|
+
if [[ "$stale_kill_reason" == "error_loop" ]]; then
|
|
166
|
+
log_warn "Session was killed by heartbeat monitor due to repeated read-offset/wasted-call errors"
|
|
167
|
+
else
|
|
168
|
+
log_warn "Session was stale-killed by heartbeat monitor (no progress for too long)"
|
|
169
|
+
fi
|
|
153
170
|
fi
|
|
154
171
|
|
|
155
172
|
local was_infra_error=false
|
|
@@ -183,7 +200,11 @@ spawn_and_wait_session() {
|
|
|
183
200
|
log_warn "Infrastructure errors are retried without consuming code retry budget"
|
|
184
201
|
session_status="infra_error"
|
|
185
202
|
elif [[ "$was_stale_killed" == true ]]; then
|
|
186
|
-
|
|
203
|
+
if [[ "$stale_kill_reason" == "error_loop" ]]; then
|
|
204
|
+
log_warn "Session killed due to repeated read-offset/wasted-call error loop"
|
|
205
|
+
else
|
|
206
|
+
log_warn "Session stale-killed (no progress for ${STALE_KILL_THRESHOLD}s)"
|
|
207
|
+
fi
|
|
187
208
|
log_warn "Stale-killed sessions are treated as failed; dev branch is preserved for inspection"
|
|
188
209
|
session_status="crashed"
|
|
189
210
|
elif [[ $exit_code -ne 0 ]]; then
|
|
@@ -200,10 +221,10 @@ spawn_and_wait_session() {
|
|
|
200
221
|
session_status="success"
|
|
201
222
|
else
|
|
202
223
|
local uncommitted=""
|
|
203
|
-
uncommitted=$(
|
|
224
|
+
uncommitted=$(prizm_git_status_safe "$project_root" | head -1 || true)
|
|
204
225
|
if [[ -n "$uncommitted" ]]; then
|
|
205
226
|
log_warn "Session exited cleanly but produced no commits (uncommitted changes found) — auto-committing..."
|
|
206
|
-
|
|
227
|
+
prizm_git_add_all_safe "$project_root" || true
|
|
207
228
|
if git -C "$project_root" commit --no-verify -m "chore($bug_id): auto-commit session work" 2>/dev/null; then
|
|
208
229
|
log_info "Auto-commit succeeded"
|
|
209
230
|
session_status="success"
|
|
@@ -222,10 +243,10 @@ spawn_and_wait_session() {
|
|
|
222
243
|
if [[ "$session_status" == "success" ]]; then
|
|
223
244
|
if git -C "$project_root" rev-parse --is-inside-work-tree >/dev/null 2>&1; then
|
|
224
245
|
local dirty_files=""
|
|
225
|
-
dirty_files=$(
|
|
246
|
+
dirty_files=$(prizm_git_status_safe "$project_root" || true)
|
|
226
247
|
if [[ -n "$dirty_files" ]]; then
|
|
227
248
|
log_info "Auto-committing remaining session artifacts..."
|
|
228
|
-
|
|
249
|
+
prizm_git_add_all_safe "$project_root" || true
|
|
229
250
|
git -C "$project_root" commit --no-verify --amend --no-edit 2>/dev/null \
|
|
230
251
|
|| git -C "$project_root" commit --no-verify -m "chore($bug_id): include remaining session artifacts" 2>/dev/null \
|
|
231
252
|
|| true
|
|
@@ -278,7 +299,20 @@ sys.exit(0)
|
|
|
278
299
|
# Subagent detection
|
|
279
300
|
prizm_detect_subagents "$session_log"
|
|
280
301
|
|
|
281
|
-
#
|
|
302
|
+
# Final status updates happen after the caller returns to the original branch.
|
|
303
|
+
_SPAWN_RESULT="$session_status"
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
finalize_bug_status_after_branch_return() {
|
|
307
|
+
local bug_id="$1"
|
|
308
|
+
local bug_list="$2"
|
|
309
|
+
local session_id="$3"
|
|
310
|
+
local session_status="$4"
|
|
311
|
+
local max_retries="$5"
|
|
312
|
+
local max_infra_retries="$6"
|
|
313
|
+
|
|
314
|
+
# Update bug status on the original branch. The caller commits the resulting
|
|
315
|
+
# bug-list diff immediately after this helper returns.
|
|
282
316
|
local update_output
|
|
283
317
|
update_output=$(python3 "$SCRIPTS_DIR/update-bug-status.py" \
|
|
284
318
|
--bug-list "$bug_list" \
|
|
@@ -287,14 +321,13 @@ sys.exit(0)
|
|
|
287
321
|
--session-status "$session_status" \
|
|
288
322
|
--session-id "$session_id" \
|
|
289
323
|
--max-retries "$max_retries" \
|
|
324
|
+
--max-infra-retries "$max_infra_retries" \
|
|
290
325
|
--action update 2>&1) || {
|
|
291
326
|
log_error "Failed to update bug status: $update_output"
|
|
292
327
|
update_output=""
|
|
293
328
|
}
|
|
294
329
|
|
|
295
330
|
_SPAWN_ITEM_STATUS="$(printf '%s' "$update_output" | prizm_extract_update_new_status)"
|
|
296
|
-
|
|
297
|
-
_SPAWN_RESULT="$session_status"
|
|
298
331
|
}
|
|
299
332
|
|
|
300
333
|
# ============================================================
|
|
@@ -334,6 +367,8 @@ for bug in data.get('bugs', []):
|
|
|
334
367
|
--state-dir "$STATE_DIR" \
|
|
335
368
|
--bug-id "$_interrupted_id" \
|
|
336
369
|
--session-status "failed" \
|
|
370
|
+
--max-retries "$MAX_RETRIES" \
|
|
371
|
+
--max-infra-retries "$MAX_INFRA_RETRIES" \
|
|
337
372
|
--action update 2>/dev/null || true
|
|
338
373
|
log_info "Bug $_interrupted_id marked as failed due to interrupt"
|
|
339
374
|
fi
|
|
@@ -406,6 +441,16 @@ run_one() {
|
|
|
406
441
|
--clean) do_clean=true; shift ;;
|
|
407
442
|
--no-reset) no_reset=true; shift ;;
|
|
408
443
|
--timeout) shift; SESSION_TIMEOUT="${1:-0}"; shift ;;
|
|
444
|
+
--max-infra-retries)
|
|
445
|
+
shift
|
|
446
|
+
if [[ $# -eq 0 ]]; then
|
|
447
|
+
log_error "--max-infra-retries requires a value"
|
|
448
|
+
exit 1
|
|
449
|
+
fi
|
|
450
|
+
prizm_require_positive_int "--max-infra-retries" "$1"
|
|
451
|
+
MAX_INFRA_RETRIES="$1"
|
|
452
|
+
shift
|
|
453
|
+
;;
|
|
409
454
|
--mode)
|
|
410
455
|
shift
|
|
411
456
|
if [[ $# -eq 0 ]]; then
|
|
@@ -695,6 +740,8 @@ else:
|
|
|
695
740
|
--state-dir "$STATE_DIR" \
|
|
696
741
|
--bug-id "$bug_id" \
|
|
697
742
|
--session-status "failed" \
|
|
743
|
+
--max-retries "$MAX_RETRIES" \
|
|
744
|
+
--max-infra-retries "$MAX_INFRA_RETRIES" \
|
|
698
745
|
--action update 2>/dev/null || true
|
|
699
746
|
log_info "Bug $bug_id marked as failed due to interrupt"
|
|
700
747
|
fi
|
|
@@ -728,12 +775,13 @@ else:
|
|
|
728
775
|
if branch_create "$_proj_root" "$_branch_name" "$_source_branch"; then
|
|
729
776
|
_DEV_BRANCH_NAME="$_branch_name"
|
|
730
777
|
else
|
|
731
|
-
|
|
778
|
+
log_error "Failed to create branch: $_branch_name from $_source_branch — aborting to avoid running on $_source_branch"
|
|
779
|
+
return 1
|
|
732
780
|
fi
|
|
733
781
|
|
|
734
782
|
spawn_and_wait_session \
|
|
735
783
|
"$bug_id" "$bug_list" "$session_id" \
|
|
736
|
-
"$bootstrap_prompt" "$session_dir"
|
|
784
|
+
"$bootstrap_prompt" "$session_dir" "$MAX_RETRIES" "$MAX_INFRA_RETRIES" "$bug_model" "$_ORIGINAL_BRANCH"
|
|
737
785
|
local session_status="$_SPAWN_RESULT"
|
|
738
786
|
|
|
739
787
|
# Merge dev branch back to original on success
|
|
@@ -743,7 +791,7 @@ else:
|
|
|
743
791
|
else
|
|
744
792
|
log_warn "Auto-merge failed — dev branch preserved: $_DEV_BRANCH_NAME"
|
|
745
793
|
log_warn "Merge manually: git checkout $_ORIGINAL_BRANCH && git rebase $_DEV_BRANCH_NAME"
|
|
746
|
-
|
|
794
|
+
session_status="merge_conflict"
|
|
747
795
|
fi
|
|
748
796
|
elif [[ -n "$_DEV_BRANCH_NAME" ]]; then
|
|
749
797
|
# Session failed — preserve dev branch for inspection
|
|
@@ -754,10 +802,12 @@ else:
|
|
|
754
802
|
# GUARANTEED: always return to original branch regardless of success/failure/merge outcome
|
|
755
803
|
branch_ensure_return "$_proj_root" "$_ORIGINAL_BRANCH"
|
|
756
804
|
|
|
805
|
+
finalize_bug_status_after_branch_return \
|
|
806
|
+
"$bug_id" "$bug_list" "$session_id" "$session_status" "$MAX_RETRIES" "$MAX_INFRA_RETRIES"
|
|
807
|
+
|
|
757
808
|
# Commit bug status update on the original branch (after guaranteed return)
|
|
758
809
|
if ! git -C "$_proj_root" diff --quiet "$bug_list" 2>/dev/null; then
|
|
759
|
-
|
|
760
|
-
git -C "$_proj_root" commit --no-verify -m "chore($bug_id): update bug status" 2>/dev/null || true
|
|
810
|
+
prizm_git_commit_paths "$_proj_root" "chore($bug_id): update bug status" "$bug_list"
|
|
761
811
|
fi
|
|
762
812
|
|
|
763
813
|
echo ""
|
|
@@ -778,7 +828,41 @@ else:
|
|
|
778
828
|
# ============================================================
|
|
779
829
|
|
|
780
830
|
main() {
|
|
781
|
-
local bug_list="$
|
|
831
|
+
local bug_list="$PRIZMKIT_DIR/plans/bug-fix-list.json"
|
|
832
|
+
|
|
833
|
+
while [[ $# -gt 0 ]]; do
|
|
834
|
+
case "$1" in
|
|
835
|
+
--max-infra-retries)
|
|
836
|
+
shift
|
|
837
|
+
if [[ $# -eq 0 ]]; then
|
|
838
|
+
log_error "--max-infra-retries requires a value"
|
|
839
|
+
exit 1
|
|
840
|
+
fi
|
|
841
|
+
prizm_require_positive_int "--max-infra-retries" "$1"
|
|
842
|
+
MAX_INFRA_RETRIES="$1"
|
|
843
|
+
shift
|
|
844
|
+
;;
|
|
845
|
+
--)
|
|
846
|
+
shift
|
|
847
|
+
break
|
|
848
|
+
;;
|
|
849
|
+
-*)
|
|
850
|
+
log_error "Unknown option: $1"
|
|
851
|
+
show_help
|
|
852
|
+
exit 1
|
|
853
|
+
;;
|
|
854
|
+
*)
|
|
855
|
+
bug_list="$1"
|
|
856
|
+
shift
|
|
857
|
+
;;
|
|
858
|
+
esac
|
|
859
|
+
done
|
|
860
|
+
|
|
861
|
+
if [[ $# -gt 0 ]]; then
|
|
862
|
+
log_error "Unexpected arguments: $*"
|
|
863
|
+
show_help
|
|
864
|
+
exit 1
|
|
865
|
+
fi
|
|
782
866
|
|
|
783
867
|
if [[ ! "$bug_list" = /* ]]; then
|
|
784
868
|
bug_list="$(pwd)/$bug_list"
|
|
@@ -840,7 +924,8 @@ main() {
|
|
|
840
924
|
echo -e "${BOLD} Bug-Fix Pipeline Runner Started${NC}"
|
|
841
925
|
echo -e "${BOLD}════════════════════════════════════════════════════${NC}"
|
|
842
926
|
log_info "Bug fix list: $bug_list"
|
|
843
|
-
log_info "Max retries per bug: $MAX_RETRIES"
|
|
927
|
+
log_info "Max code retries per bug: $MAX_RETRIES"
|
|
928
|
+
log_info "Max infrastructure retries per bug: $MAX_INFRA_RETRIES"
|
|
844
929
|
if [[ $SESSION_TIMEOUT -gt 0 ]]; then
|
|
845
930
|
log_info "Session timeout: ${SESSION_TIMEOUT}s"
|
|
846
931
|
else
|
|
@@ -885,6 +970,7 @@ main() {
|
|
|
885
970
|
--bug-list "$bug_list" \
|
|
886
971
|
--state-dir "$STATE_DIR" \
|
|
887
972
|
--max-retries "$MAX_RETRIES" \
|
|
973
|
+
--max-infra-retries "$MAX_INFRA_RETRIES" \
|
|
888
974
|
--action get_next 2>/dev/null); then
|
|
889
975
|
log_error "Failed to get next bug"
|
|
890
976
|
break
|
|
@@ -972,27 +1058,43 @@ DEPLOY_PROMPT_EOF
|
|
|
972
1058
|
fi
|
|
973
1059
|
|
|
974
1060
|
# Parse bug info
|
|
975
|
-
local bug_id bug_title bug_severity retry_count resume_phase
|
|
1061
|
+
local bug_id bug_title bug_severity retry_count infra_error_count resume_phase
|
|
976
1062
|
bug_id=$(echo "$next_bug" | jq -r '.bug_id')
|
|
977
1063
|
bug_title=$(echo "$next_bug" | jq -r '.title')
|
|
978
1064
|
bug_severity=$(echo "$next_bug" | jq -r '.severity')
|
|
979
1065
|
retry_count=$(echo "$next_bug" | jq -r '.retry_count // 0')
|
|
1066
|
+
infra_error_count=$(echo "$next_bug" | jq -r '.infra_error_count // 0')
|
|
980
1067
|
resume_phase=$(echo "$next_bug" | jq -r '.resume_from_phase // "null"')
|
|
981
1068
|
|
|
982
1069
|
echo ""
|
|
983
1070
|
echo -e "${BOLD}────────────────────────────────────────────────────${NC}"
|
|
984
1071
|
log_info "Bug: ${BOLD}$bug_id${NC} — $bug_title"
|
|
985
|
-
log_info "Severity: $bug_severity
|
|
1072
|
+
log_info "Severity: $bug_severity"
|
|
1073
|
+
log_info "Code retry: $retry_count / $MAX_RETRIES"
|
|
1074
|
+
log_info "Infrastructure retry: $infra_error_count / $MAX_INFRA_RETRIES"
|
|
986
1075
|
if [[ "$resume_phase" != "null" ]]; then
|
|
987
1076
|
log_info "Resuming from Phase $resume_phase"
|
|
988
1077
|
fi
|
|
989
1078
|
echo -e "${BOLD}────────────────────────────────────────────────────${NC}"
|
|
990
1079
|
|
|
991
|
-
#
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
1080
|
+
# Do not auto-commit an arbitrary dirty baseline on the original branch.
|
|
1081
|
+
# Only pipeline bookkeeping is committed explicitly; user/source WIP,
|
|
1082
|
+
# submodule dirt, specs, and root-level hidden tool temporary worktrees
|
|
1083
|
+
# must never be swept into main by a broad git add.
|
|
1084
|
+
local _dirty_files=""
|
|
1085
|
+
_dirty_files=$(prizm_git_status_non_bookkeeping "$_proj_root" "$STATE_DIR" "$bug_list" || true)
|
|
1086
|
+
if [[ -n "$_dirty_files" ]]; then
|
|
1087
|
+
log_error "Dirty working tree detected before $bug_id; refusing to commit it on $_ORIGINAL_BRANCH."
|
|
1088
|
+
log_error "Commit/stash your changes or start from a clean branch, then rerun the pipeline."
|
|
1089
|
+
printf '%s\n' "$_dirty_files" | sed 's/^/ /'
|
|
1090
|
+
break
|
|
1091
|
+
fi
|
|
1092
|
+
|
|
1093
|
+
local _bookkeeping_dirty=""
|
|
1094
|
+
_bookkeeping_dirty=$(prizm_git_status_bookkeeping "$_proj_root" "$STATE_DIR" "$bug_list" || true)
|
|
1095
|
+
if [[ -n "$_bookkeeping_dirty" ]]; then
|
|
1096
|
+
log_info "Committing pending pipeline bookkeeping before $bug_id..."
|
|
1097
|
+
prizm_git_commit_paths "$_proj_root" "chore($bug_id): include pipeline bookkeeping" "$bug_list" "$STATE_DIR"
|
|
996
1098
|
fi
|
|
997
1099
|
|
|
998
1100
|
# Mark bug as in-progress BEFORE creating dev branch
|
|
@@ -1005,8 +1107,7 @@ DEPLOY_PROMPT_EOF
|
|
|
1005
1107
|
--action start >/dev/null 2>&1 || true
|
|
1006
1108
|
# Commit the in_progress status on the original branch
|
|
1007
1109
|
if ! git -C "$_proj_root" diff --quiet "$bug_list" 2>/dev/null; then
|
|
1008
|
-
|
|
1009
|
-
git -C "$_proj_root" commit --no-verify -m "chore($bug_id): mark in_progress" 2>/dev/null || true
|
|
1110
|
+
prizm_git_commit_paths "$_proj_root" "chore($bug_id): mark in_progress" "$bug_list"
|
|
1010
1111
|
fi
|
|
1011
1112
|
|
|
1012
1113
|
# Create per-bug dev branch (from the now-updated original branch)
|
|
@@ -1015,8 +1116,9 @@ DEPLOY_PROMPT_EOF
|
|
|
1015
1116
|
_DEV_BRANCH_NAME="$_bug_branch"
|
|
1016
1117
|
log_info "Dev branch: $_bug_branch"
|
|
1017
1118
|
else
|
|
1018
|
-
|
|
1119
|
+
log_error "Failed to create dev branch: $_bug_branch from $_ORIGINAL_BRANCH — aborting to avoid running on $_ORIGINAL_BRANCH"
|
|
1019
1120
|
_DEV_BRANCH_NAME=""
|
|
1121
|
+
break
|
|
1020
1122
|
fi
|
|
1021
1123
|
|
|
1022
1124
|
# Generate session
|
|
@@ -1086,10 +1188,9 @@ DEPLOY_PROMPT_EOF
|
|
|
1086
1188
|
|
|
1087
1189
|
spawn_and_wait_session \
|
|
1088
1190
|
"$bug_id" "$bug_list" "$session_id" \
|
|
1089
|
-
"$bootstrap_prompt" "$session_dir" "$MAX_RETRIES" "$bug_model" "$_ORIGINAL_BRANCH"
|
|
1191
|
+
"$bootstrap_prompt" "$session_dir" "$MAX_RETRIES" "$MAX_INFRA_RETRIES" "$bug_model" "$_ORIGINAL_BRANCH"
|
|
1090
1192
|
|
|
1091
1193
|
local session_status="$_SPAWN_RESULT"
|
|
1092
|
-
local item_status_after_session="${_SPAWN_ITEM_STATUS:-}"
|
|
1093
1194
|
|
|
1094
1195
|
# Merge per-bug dev branch back to original on success
|
|
1095
1196
|
if [[ "$session_status" == "success" && -n "$_DEV_BRANCH_NAME" ]]; then
|
|
@@ -1098,7 +1199,7 @@ DEPLOY_PROMPT_EOF
|
|
|
1098
1199
|
else
|
|
1099
1200
|
log_warn "Auto-merge failed — dev branch preserved: $_DEV_BRANCH_NAME"
|
|
1100
1201
|
log_warn "Merge manually: git checkout $_ORIGINAL_BRANCH && git rebase $_DEV_BRANCH_NAME"
|
|
1101
|
-
|
|
1202
|
+
session_status="merge_conflict"
|
|
1102
1203
|
fi
|
|
1103
1204
|
elif [[ -n "$_DEV_BRANCH_NAME" ]]; then
|
|
1104
1205
|
# Session failed — preserve dev branch for inspection
|
|
@@ -1109,10 +1210,13 @@ DEPLOY_PROMPT_EOF
|
|
|
1109
1210
|
# GUARANTEED: always return to original branch regardless of success/failure/merge outcome
|
|
1110
1211
|
branch_ensure_return "$_proj_root" "$_ORIGINAL_BRANCH"
|
|
1111
1212
|
|
|
1213
|
+
finalize_bug_status_after_branch_return \
|
|
1214
|
+
"$bug_id" "$bug_list" "$session_id" "$session_status" "$MAX_RETRIES" "$MAX_INFRA_RETRIES"
|
|
1215
|
+
local item_status_after_session="${_SPAWN_ITEM_STATUS:-}"
|
|
1216
|
+
|
|
1112
1217
|
# Commit bug status update on the original branch (after guaranteed return)
|
|
1113
1218
|
if ! git -C "$_proj_root" diff --quiet "$bug_list" 2>/dev/null; then
|
|
1114
|
-
|
|
1115
|
-
git -C "$_proj_root" commit --no-verify -m "chore($bug_id): update bug status" 2>/dev/null || true
|
|
1219
|
+
prizm_git_commit_paths "$_proj_root" "chore($bug_id): update bug status" "$bug_list"
|
|
1116
1220
|
fi
|
|
1117
1221
|
|
|
1118
1222
|
session_count=$((session_count + 1))
|
|
@@ -1129,7 +1233,7 @@ DEPLOY_PROMPT_EOF
|
|
|
1129
1233
|
log_error "════════════════════════════════════════════════════"
|
|
1130
1234
|
break
|
|
1131
1235
|
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."
|
|
1236
|
+
log_info "STOP_ON_FAILURE: $bug_id is ${item_status_after_session:-unknown}; code/infra retry budget not exhausted, continuing."
|
|
1133
1237
|
fi
|
|
1134
1238
|
|
|
1135
1239
|
# Stuck detection
|
|
@@ -1166,7 +1270,7 @@ show_help() {
|
|
|
1166
1270
|
echo "Usage: $0 <command> [options]"
|
|
1167
1271
|
echo ""
|
|
1168
1272
|
echo "Commands:"
|
|
1169
|
-
echo " run [.prizmkit/plans/bug-fix-list.json]
|
|
1273
|
+
echo " run [.prizmkit/plans/bug-fix-list.json] [options] Run all bugs by severity/priority order"
|
|
1170
1274
|
echo " run <bug-id> [options] Run a single bug fix"
|
|
1171
1275
|
echo " status [.prizmkit/plans/bug-fix-list.json] Show bug fix pipeline status"
|
|
1172
1276
|
echo " reset Clear all bugfix state"
|
|
@@ -1177,12 +1281,17 @@ show_help() {
|
|
|
1177
1281
|
echo " --clean Delete artifacts and reset before running"
|
|
1178
1282
|
echo " --no-reset Skip status reset (preserve retry count)"
|
|
1179
1283
|
echo " --timeout N Session timeout in seconds (default: 0 = no limit)"
|
|
1284
|
+
echo " --max-infra-retries N Max infrastructure/provider retries (default: 3)"
|
|
1180
1285
|
echo " --mode <lite|standard|full> Override pipeline mode"
|
|
1181
1286
|
echo " --critic Enable adversarial critic review"
|
|
1182
1287
|
echo " --no-critic Disable adversarial critic review"
|
|
1183
1288
|
echo ""
|
|
1289
|
+
echo "Pipeline Options (run all):"
|
|
1290
|
+
echo " --max-infra-retries N Max infrastructure/provider retries (default: 3)"
|
|
1291
|
+
echo ""
|
|
1184
1292
|
echo "Environment Variables:"
|
|
1185
|
-
echo " MAX_RETRIES Max retries per bug (default: 3)"
|
|
1293
|
+
echo " MAX_RETRIES Max code retries per bug (default: 3)"
|
|
1294
|
+
echo " MAX_INFRA_RETRIES Max infrastructure/provider retries per bug (default: 3)"
|
|
1186
1295
|
echo " SESSION_TIMEOUT Session timeout in seconds (default: 0 = no limit)"
|
|
1187
1296
|
echo " MODEL Default AI model (overridden by per-bug model in bug list)"
|
|
1188
1297
|
echo " PRIZMKIT_EFFORT AI reasoning effort (low|medium|high|xhigh|max; max is Claude Code only)"
|
|
@@ -1214,7 +1323,7 @@ case "${1:-run}" in
|
|
|
1214
1323
|
if [[ "${1:-}" =~ ^[Bb]-[0-9]+ ]]; then
|
|
1215
1324
|
run_one "$@"
|
|
1216
1325
|
else
|
|
1217
|
-
main "
|
|
1326
|
+
main "$@"
|
|
1218
1327
|
fi
|
|
1219
1328
|
;;
|
|
1220
1329
|
status)
|
|
@@ -1226,6 +1335,8 @@ case "${1:-run}" in
|
|
|
1226
1335
|
python3 "$SCRIPTS_DIR/update-bug-status.py" \
|
|
1227
1336
|
--bug-list "${2:-$PRIZMKIT_DIR/plans/bug-fix-list.json}" \
|
|
1228
1337
|
--state-dir "$STATE_DIR" \
|
|
1338
|
+
--max-retries "$MAX_RETRIES" \
|
|
1339
|
+
--max-infra-retries "$MAX_INFRA_RETRIES" \
|
|
1229
1340
|
--action status
|
|
1230
1341
|
;;
|
|
1231
1342
|
reset)
|