prizmkit 1.1.19 → 1.1.21
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 +187 -23
- package/bundled/dev-pipeline/run-bugfix.sh +113 -26
- package/bundled/dev-pipeline/run-feature.sh +113 -27
- package/bundled/dev-pipeline/run-refactor.sh +113 -26
- package/bundled/dev-pipeline/scripts/generate-bootstrap-prompt.py +32 -0
- package/bundled/dev-pipeline/templates/bootstrap-tier1.md +22 -7
- package/bundled/dev-pipeline/templates/bootstrap-tier2.md +22 -7
- package/bundled/dev-pipeline/templates/bootstrap-tier3.md +22 -7
- package/bundled/dev-pipeline/templates/sections/phase-browser-verification.md +22 -7
- package/bundled/skills/_metadata.json +1 -1
- package/package.json +1 -1
- package/src/config.js +71 -7
package/bundled/VERSION.json
CHANGED
|
@@ -2,18 +2,21 @@
|
|
|
2
2
|
# ============================================================
|
|
3
3
|
# dev-pipeline/lib/branch.sh - Git Branch Lifecycle Library
|
|
4
4
|
#
|
|
5
|
-
# Shared by run-feature.sh
|
|
6
|
-
# development. Each pipeline run creates
|
|
7
|
-
# features/bugs commit
|
|
5
|
+
# Shared by run-feature.sh, run-bugfix.sh, and run-refactor.sh
|
|
6
|
+
# for branch-based serial development. Each pipeline run creates
|
|
7
|
+
# a dev branch and all features/bugs/refactors commit on it.
|
|
8
8
|
#
|
|
9
9
|
# Functions:
|
|
10
|
-
# branch_create
|
|
11
|
-
# branch_return
|
|
12
|
-
# branch_merge
|
|
10
|
+
# branch_create — Create and checkout a new branch
|
|
11
|
+
# branch_return — Checkout back to original branch
|
|
12
|
+
# branch_merge — Merge dev branch into original and optionally push
|
|
13
|
+
# branch_ensure_return — Guaranteed return to original branch (try/finally)
|
|
13
14
|
#
|
|
14
15
|
# Environment:
|
|
15
|
-
#
|
|
16
|
-
#
|
|
16
|
+
# _ORIGINAL_BRANCH Set by caller before branch_create
|
|
17
|
+
# _DEV_BRANCH_NAME Set by caller after branch_create
|
|
18
|
+
# DEV_BRANCH Optional custom branch name override
|
|
19
|
+
# AUTO_PUSH Set to 1 to auto-push after successful feature
|
|
17
20
|
# ============================================================
|
|
18
21
|
|
|
19
22
|
# branch_create <project_root> <branch_name> <source_branch>
|
|
@@ -80,11 +83,15 @@ branch_return() {
|
|
|
80
83
|
#
|
|
81
84
|
# Merges dev_branch into original_branch, then optionally pushes.
|
|
82
85
|
# Steps:
|
|
83
|
-
# 1.
|
|
86
|
+
# 1. Stash tracked dirty files (NOT untracked — .prizmkit/state/ is gitignored)
|
|
84
87
|
# 2. Rebase dev_branch onto original_branch (handles diverged main)
|
|
85
88
|
# 3. Fast-forward merge original_branch to rebased dev tip
|
|
86
89
|
# 4. Push to remote if auto_push == "1"
|
|
87
90
|
# 5. Delete dev_branch (local only, it's been merged)
|
|
91
|
+
# 6. Restore stashed files
|
|
92
|
+
#
|
|
93
|
+
# IMPORTANT: On failure, caller MUST still call branch_ensure_return()
|
|
94
|
+
# to guarantee return to the original branch.
|
|
88
95
|
#
|
|
89
96
|
# Returns 0 on success, 1 on failure.
|
|
90
97
|
branch_merge() {
|
|
@@ -93,16 +100,21 @@ branch_merge() {
|
|
|
93
100
|
local original_branch="$3"
|
|
94
101
|
local auto_push="${4:-0}"
|
|
95
102
|
|
|
96
|
-
# Step 1:
|
|
97
|
-
#
|
|
103
|
+
# Step 1: Stash any tracked uncommitted changes so checkout is not blocked.
|
|
104
|
+
# Only stash tracked changes (not untracked). Untracked files like
|
|
105
|
+
# .prizmkit/state/ are gitignored and survive checkout without issue.
|
|
106
|
+
# Using --include-untracked causes stash pop conflicts and can lose
|
|
107
|
+
# state/ files that are needed for pipeline status tracking.
|
|
98
108
|
local had_stash=false
|
|
99
|
-
local
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
109
|
+
local tracked_dirty
|
|
110
|
+
tracked_dirty=$(git -C "$project_root" diff --name-only 2>/dev/null || true)
|
|
111
|
+
local staged_dirty
|
|
112
|
+
staged_dirty=$(git -C "$project_root" diff --cached --name-only 2>/dev/null || true)
|
|
113
|
+
if [[ -n "$tracked_dirty" || -n "$staged_dirty" ]]; then
|
|
114
|
+
if git -C "$project_root" stash push -m "pipeline-merge-stash" 2>/dev/null; then
|
|
103
115
|
had_stash=true
|
|
104
116
|
else
|
|
105
|
-
log_warn "git stash failed — uncommitted changes may not be preserved during merge"
|
|
117
|
+
log_warn "git stash failed — uncommitted tracked changes may not be preserved during merge"
|
|
106
118
|
had_stash=false
|
|
107
119
|
fi
|
|
108
120
|
fi
|
|
@@ -116,14 +128,21 @@ branch_merge() {
|
|
|
116
128
|
log_error "Rebase of $dev_branch onto $original_branch failed — resolve manually:"
|
|
117
129
|
log_error " git rebase --abort # then resolve conflicts and retry"
|
|
118
130
|
git -C "$project_root" rebase --abort 2>/dev/null || true
|
|
119
|
-
|
|
120
|
-
|
|
131
|
+
if [[ "$had_stash" == true ]]; then
|
|
132
|
+
if ! git -C "$project_root" stash pop 2>/dev/null; then
|
|
133
|
+
log_warn "git stash pop failed after rebase abort — run 'git stash list' to check"
|
|
134
|
+
fi
|
|
135
|
+
fi
|
|
121
136
|
return 1
|
|
122
137
|
fi
|
|
123
138
|
# After the rebase we are on dev_branch — checkout original for the fast-forward
|
|
124
139
|
if ! git -C "$project_root" checkout "$original_branch" 2>/dev/null; then
|
|
125
140
|
log_error "Failed to checkout $original_branch for merge"
|
|
126
|
-
[[ "$had_stash" == true ]]
|
|
141
|
+
if [[ "$had_stash" == true ]]; then
|
|
142
|
+
if ! git -C "$project_root" stash pop 2>/dev/null; then
|
|
143
|
+
log_warn "git stash pop failed after checkout failure — run 'git stash list' to check"
|
|
144
|
+
fi
|
|
145
|
+
fi
|
|
127
146
|
return 1
|
|
128
147
|
fi
|
|
129
148
|
|
|
@@ -131,8 +150,11 @@ branch_merge() {
|
|
|
131
150
|
if ! git -C "$project_root" merge --ff-only "$dev_branch" 2>&1; then
|
|
132
151
|
log_error "Merge failed after rebase — this should not happen, resolve manually:"
|
|
133
152
|
log_error " git checkout $original_branch && git rebase $dev_branch"
|
|
134
|
-
|
|
135
|
-
|
|
153
|
+
if [[ "$had_stash" == true ]]; then
|
|
154
|
+
if ! git -C "$project_root" stash pop 2>/dev/null; then
|
|
155
|
+
log_warn "git stash pop failed after merge failure — run 'git stash list' to check"
|
|
156
|
+
fi
|
|
157
|
+
fi
|
|
136
158
|
return 1
|
|
137
159
|
fi
|
|
138
160
|
|
|
@@ -152,8 +174,150 @@ branch_merge() {
|
|
|
152
174
|
git -C "$project_root" branch -d "$dev_branch" 2>/dev/null && \
|
|
153
175
|
log_info "Deleted merged branch: $dev_branch" || true
|
|
154
176
|
|
|
155
|
-
# Step 6: Restore stashed
|
|
156
|
-
[[ "$had_stash" == true ]]
|
|
177
|
+
# Step 6: Restore stashed files
|
|
178
|
+
if [[ "$had_stash" == true ]]; then
|
|
179
|
+
if ! git -C "$project_root" stash pop 2>/dev/null; then
|
|
180
|
+
log_warn "git stash pop failed after merge — stashed changes may be lost. Run 'git stash list' to check."
|
|
181
|
+
fi
|
|
182
|
+
fi
|
|
183
|
+
|
|
184
|
+
return 0
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
# branch_save_wip <project_root> <dev_branch>
|
|
188
|
+
#
|
|
189
|
+
# Saves any uncommitted work-in-progress on the dev branch before returning
|
|
190
|
+
# to the original branch. Called during interrupt/crash cleanup to preserve
|
|
191
|
+
# partially completed AI work that hasn't been committed yet.
|
|
192
|
+
#
|
|
193
|
+
# Commits ALL changes (tracked + untracked, excluding gitignored) with a
|
|
194
|
+
# "wip:" prefix message so it's easy to identify and squash later.
|
|
195
|
+
#
|
|
196
|
+
# Safe to call when the working tree is clean — it simply does nothing.
|
|
197
|
+
# Never fails — errors are logged but the function always returns 0.
|
|
198
|
+
branch_save_wip() {
|
|
199
|
+
local project_root="$1"
|
|
200
|
+
local dev_branch="$2"
|
|
201
|
+
|
|
202
|
+
# Nothing to save if dev_branch is empty
|
|
203
|
+
if [[ -z "$dev_branch" ]]; then
|
|
204
|
+
return 0
|
|
205
|
+
fi
|
|
206
|
+
|
|
207
|
+
# Verify we're actually on the dev branch
|
|
208
|
+
local current_branch
|
|
209
|
+
current_branch=$(git -C "$project_root" rev-parse --abbrev-ref HEAD 2>/dev/null || echo "")
|
|
210
|
+
if [[ "$current_branch" != "$dev_branch" ]]; then
|
|
211
|
+
return 0
|
|
212
|
+
fi
|
|
213
|
+
|
|
214
|
+
# Check if there are any uncommitted changes (tracked or untracked, excluding gitignored)
|
|
215
|
+
local has_changes
|
|
216
|
+
has_changes=$(git -C "$project_root" status --porcelain 2>/dev/null || true)
|
|
217
|
+
if [[ -z "$has_changes" ]]; then
|
|
218
|
+
return 0
|
|
219
|
+
fi
|
|
220
|
+
|
|
221
|
+
log_warn "Saving uncommitted work-in-progress on branch: $dev_branch"
|
|
222
|
+
|
|
223
|
+
# Stage all changes (tracked + untracked, respects .gitignore)
|
|
224
|
+
if ! git -C "$project_root" add -A 2>/dev/null; then
|
|
225
|
+
log_warn "git add -A failed — uncommitted work may be lost on branch switch"
|
|
226
|
+
return 0
|
|
227
|
+
fi
|
|
228
|
+
|
|
229
|
+
# Commit with WIP marker
|
|
230
|
+
if git -C "$project_root" commit --no-verify \
|
|
231
|
+
-m "wip($dev_branch): interrupted — uncommitted work saved" \
|
|
232
|
+
-m "Pipeline was interrupted by signal. This commit preserves work-in-progress." \
|
|
233
|
+
-m "To resume: git checkout $dev_branch" 2>/dev/null; then
|
|
234
|
+
log_info "Saved uncommitted work on branch $dev_branch"
|
|
235
|
+
else
|
|
236
|
+
log_warn "git commit failed — uncommitted work may be lost on branch switch"
|
|
237
|
+
fi
|
|
238
|
+
|
|
239
|
+
return 0
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
# branch_ensure_return <project_root> <original_branch> [dev_branch]
|
|
243
|
+
#
|
|
244
|
+
# GUARANTEED return to the original branch. Like a try/finally block.
|
|
245
|
+
# Must be called in EVERY exit path: success, failure, interrupt, crash.
|
|
246
|
+
# This is the single point of truth for "always go back to original branch".
|
|
247
|
+
#
|
|
248
|
+
# If dev_branch is provided and we're currently on it, any uncommitted
|
|
249
|
+
# work is saved as a WIP commit before switching (via branch_save_wip).
|
|
250
|
+
#
|
|
251
|
+
# Handles:
|
|
252
|
+
# - Saving uncommitted WIP on dev branch (if dev_branch provided)
|
|
253
|
+
# - Aborting any in-progress rebase (leftover from branch_merge failure)
|
|
254
|
+
# - Stashing any tracked dirty files that block checkout
|
|
255
|
+
# - Checking out original_branch
|
|
256
|
+
# - Restoring stashed files
|
|
257
|
+
# - Logging for diagnostics
|
|
258
|
+
#
|
|
259
|
+
# Never fails — errors are logged but the function always returns 0
|
|
260
|
+
# so it can be used in cleanup traps without breaking error handling.
|
|
261
|
+
branch_ensure_return() {
|
|
262
|
+
local project_root="$1"
|
|
263
|
+
local original_branch="$2"
|
|
264
|
+
local dev_branch="${3:-}"
|
|
265
|
+
|
|
266
|
+
# If original_branch is empty or unset, nothing to return to
|
|
267
|
+
if [[ -z "$original_branch" ]]; then
|
|
268
|
+
return 0
|
|
269
|
+
fi
|
|
270
|
+
|
|
271
|
+
# Abort any in-progress rebase (can happen if branch_merge failed mid-way)
|
|
272
|
+
if git -C "$project_root" rebase --show-current-patch >/dev/null 2>&1; then
|
|
273
|
+
log_warn "Aborting in-progress rebase..."
|
|
274
|
+
git -C "$project_root" rebase --abort 2>/dev/null || true
|
|
275
|
+
fi
|
|
276
|
+
|
|
277
|
+
# Check current branch
|
|
278
|
+
local current_branch
|
|
279
|
+
current_branch=$(git -C "$project_root" rev-parse --abbrev-ref HEAD 2>/dev/null || echo "")
|
|
280
|
+
|
|
281
|
+
if [[ "$current_branch" == "$original_branch" ]]; then
|
|
282
|
+
return 0
|
|
283
|
+
fi
|
|
284
|
+
|
|
285
|
+
# Save any uncommitted WIP on dev branch before switching away
|
|
286
|
+
# Use dev_branch if provided; otherwise infer from current_branch
|
|
287
|
+
local _wip_branch="${dev_branch:-$current_branch}"
|
|
288
|
+
if [[ -n "$_wip_branch" && "$_wip_branch" != "$original_branch" ]]; then
|
|
289
|
+
branch_save_wip "$project_root" "$_wip_branch"
|
|
290
|
+
fi
|
|
291
|
+
|
|
292
|
+
log_info "Ensuring return to original branch: $original_branch (currently on: ${current_branch:-unknown})"
|
|
293
|
+
|
|
294
|
+
# Stash any tracked dirty files that would block checkout
|
|
295
|
+
# (branch_save_wip should have committed everything, but this is a safety net
|
|
296
|
+
# in case the commit failed or new files appeared)
|
|
297
|
+
local had_stash=false
|
|
298
|
+
local tracked_dirty
|
|
299
|
+
tracked_dirty=$(git -C "$project_root" diff --name-only 2>/dev/null || true)
|
|
300
|
+
local staged_dirty
|
|
301
|
+
staged_dirty=$(git -C "$project_root" diff --cached --name-only 2>/dev/null || true)
|
|
302
|
+
if [[ -n "$tracked_dirty" || -n "$staged_dirty" ]]; then
|
|
303
|
+
if git -C "$project_root" stash push -m "pipeline-ensure-return-stash" 2>/dev/null; then
|
|
304
|
+
had_stash=true
|
|
305
|
+
fi
|
|
306
|
+
fi
|
|
307
|
+
|
|
308
|
+
# Checkout original branch
|
|
309
|
+
if git -C "$project_root" checkout "$original_branch" 2>/dev/null; then
|
|
310
|
+
log_info "Returned to branch: $original_branch"
|
|
311
|
+
else
|
|
312
|
+
log_error "Failed to checkout $original_branch — manual recovery needed"
|
|
313
|
+
fi
|
|
314
|
+
|
|
315
|
+
# Restore stashed files
|
|
316
|
+
if [[ "$had_stash" == true ]]; then
|
|
317
|
+
if ! git -C "$project_root" stash pop 2>/dev/null; then
|
|
318
|
+
log_warn "git stash pop failed during branch return — stashed changes may be lost. Run 'git stash list' to check."
|
|
319
|
+
fi
|
|
320
|
+
fi
|
|
157
321
|
|
|
158
322
|
return 0
|
|
159
323
|
}
|
|
@@ -254,8 +254,15 @@ if incomplete:
|
|
|
254
254
|
sys.exit(1)
|
|
255
255
|
print('ALL_COMPLETE')
|
|
256
256
|
sys.exit(0)
|
|
257
|
-
" "$checkpoint_file" 2>&1)
|
|
257
|
+
" "$checkpoint_file" 2>&1) || checkpoint_result="CHECK_FAILED"
|
|
258
258
|
local check_exit=$?
|
|
259
|
+
if [[ "$checkpoint_result" == "CHECK_FAILED" ]]; then
|
|
260
|
+
check_exit=2
|
|
261
|
+
elif [[ "$checkpoint_result" == *"INCOMPLETE"* ]]; then
|
|
262
|
+
check_exit=1
|
|
263
|
+
else
|
|
264
|
+
check_exit=0
|
|
265
|
+
fi
|
|
259
266
|
if [[ $check_exit -eq 2 ]]; then
|
|
260
267
|
log_warn "CHECKPOINT_CORRUPTED: workflow-checkpoint.json is not valid JSON"
|
|
261
268
|
elif [[ $check_exit -eq 1 ]]; then
|
|
@@ -270,7 +277,7 @@ sys.exit(0)
|
|
|
270
277
|
# Subagent detection
|
|
271
278
|
prizm_detect_subagents "$session_log"
|
|
272
279
|
|
|
273
|
-
# Update bug status
|
|
280
|
+
# Update bug status (do NOT commit on dev branch — commit happens after merge)
|
|
274
281
|
python3 "$SCRIPTS_DIR/update-bug-status.py" \
|
|
275
282
|
--bug-list "$bug_list" \
|
|
276
283
|
--state-dir "$STATE_DIR" \
|
|
@@ -280,12 +287,6 @@ sys.exit(0)
|
|
|
280
287
|
--max-retries "$max_retries" \
|
|
281
288
|
--action update >/dev/null 2>&1 || true
|
|
282
289
|
|
|
283
|
-
# Commit .prizmkit/plans/bug-fix-list.json status update (pipeline management commit)
|
|
284
|
-
if ! git -C "$project_root" diff --quiet "$bug_list" 2>/dev/null; then
|
|
285
|
-
git -C "$project_root" add "$bug_list"
|
|
286
|
-
git -C "$project_root" commit --no-verify -m "chore($bug_id): update bug status" 2>/dev/null || true
|
|
287
|
-
fi
|
|
288
|
-
|
|
289
290
|
_SPAWN_RESULT="$session_status"
|
|
290
291
|
}
|
|
291
292
|
|
|
@@ -306,13 +307,40 @@ cleanup() {
|
|
|
306
307
|
log_info "Original branch was: $_ORIGINAL_BRANCH"
|
|
307
308
|
fi
|
|
308
309
|
|
|
310
|
+
# Update status of currently in-progress bug to interrupted
|
|
309
311
|
if [[ -n "$BUG_LIST" && -f "$BUG_LIST" ]]; then
|
|
312
|
+
# Find any in-progress bug and mark it as failed
|
|
313
|
+
local _interrupted_id
|
|
314
|
+
_interrupted_id=$(python3 -c "
|
|
315
|
+
import json, sys
|
|
316
|
+
with open(sys.argv[1]) as f:
|
|
317
|
+
data = json.load(f)
|
|
318
|
+
for bug in data.get('bugs', []):
|
|
319
|
+
if bug.get('status') == 'in_progress':
|
|
320
|
+
print(bug['id'])
|
|
321
|
+
break
|
|
322
|
+
" "$BUG_LIST" 2>/dev/null || echo "")
|
|
323
|
+
|
|
324
|
+
if [[ -n "$_interrupted_id" ]]; then
|
|
325
|
+
python3 "$SCRIPTS_DIR/update-bug-status.py" \
|
|
326
|
+
--bug-list "$BUG_LIST" \
|
|
327
|
+
--state-dir "$STATE_DIR" \
|
|
328
|
+
--bug-id "$_interrupted_id" \
|
|
329
|
+
--session-status "failed" \
|
|
330
|
+
--action update 2>/dev/null || true
|
|
331
|
+
log_info "Bug $_interrupted_id marked as failed due to interrupt"
|
|
332
|
+
fi
|
|
333
|
+
|
|
334
|
+
# Pause the pipeline
|
|
310
335
|
python3 "$SCRIPTS_DIR/update-bug-status.py" \
|
|
311
336
|
--bug-list "$BUG_LIST" \
|
|
312
337
|
--state-dir "$STATE_DIR" \
|
|
313
338
|
--action pause 2>/dev/null || true
|
|
314
339
|
fi
|
|
315
340
|
|
|
341
|
+
# GUARANTEED: always return to original branch (save WIP on dev branch first)
|
|
342
|
+
branch_ensure_return "$(cd "$SCRIPT_DIR/.." && pwd)" "$_ORIGINAL_BRANCH" "$_DEV_BRANCH_NAME"
|
|
343
|
+
|
|
316
344
|
log_info "Bug fix pipeline paused. Run './run-bugfix.sh run' to resume."
|
|
317
345
|
exit 130
|
|
318
346
|
}
|
|
@@ -639,6 +667,20 @@ else:
|
|
|
639
667
|
log_info "Development was on branch: $_DEV_BRANCH_NAME"
|
|
640
668
|
fi
|
|
641
669
|
log_info "Session log: $session_dir/logs/session.log"
|
|
670
|
+
|
|
671
|
+
# Update bug status to failed on interrupt
|
|
672
|
+
if [[ -n "$bug_list" && -f "$bug_list" ]]; then
|
|
673
|
+
python3 "$SCRIPTS_DIR/update-bug-status.py" \
|
|
674
|
+
--bug-list "$bug_list" \
|
|
675
|
+
--state-dir "$STATE_DIR" \
|
|
676
|
+
--bug-id "$bug_id" \
|
|
677
|
+
--session-status "failed" \
|
|
678
|
+
--action update 2>/dev/null || true
|
|
679
|
+
log_info "Bug $bug_id marked as failed due to interrupt"
|
|
680
|
+
fi
|
|
681
|
+
|
|
682
|
+
# GUARANTEED: always return to original branch (save WIP on dev branch first)
|
|
683
|
+
branch_ensure_return "$(cd "$SCRIPT_DIR/.." && pwd)" "$_ORIGINAL_BRANCH" "$_DEV_BRANCH_NAME"
|
|
642
684
|
exit 130
|
|
643
685
|
}
|
|
644
686
|
trap cleanup_single_bug SIGINT SIGTERM
|
|
@@ -652,6 +694,20 @@ else:
|
|
|
652
694
|
_source_branch=$(git -C "$_proj_root" rev-parse --abbrev-ref HEAD 2>/dev/null || echo "main")
|
|
653
695
|
_ORIGINAL_BRANCH="$_source_branch"
|
|
654
696
|
|
|
697
|
+
# Mark bug as in-progress BEFORE creating dev branch
|
|
698
|
+
# This ensures the in_progress status commit lands on the original branch,
|
|
699
|
+
# not the dev branch — preventing rebase conflicts in branch_merge later.
|
|
700
|
+
python3 "$SCRIPTS_DIR/update-bug-status.py" \
|
|
701
|
+
--bug-list "$bug_list" \
|
|
702
|
+
--state-dir "$STATE_DIR" \
|
|
703
|
+
--bug-id "$bug_id" \
|
|
704
|
+
--action start >/dev/null 2>&1 || true
|
|
705
|
+
# Commit the in_progress status on the original branch
|
|
706
|
+
if ! git -C "$_proj_root" diff --quiet "$bug_list" 2>/dev/null; then
|
|
707
|
+
git -C "$_proj_root" add "$bug_list" 2>/dev/null || true
|
|
708
|
+
git -C "$_proj_root" commit --no-verify -m "chore($bug_id): mark in_progress" 2>/dev/null || true
|
|
709
|
+
fi
|
|
710
|
+
|
|
655
711
|
local _branch_name="${DEV_BRANCH:-bugfix/${bug_id}-$(date +%s)}"
|
|
656
712
|
if branch_create "$_proj_root" "$_branch_name" "$_source_branch"; then
|
|
657
713
|
_DEV_BRANCH_NAME="$_branch_name"
|
|
@@ -671,18 +727,23 @@ else:
|
|
|
671
727
|
else
|
|
672
728
|
log_warn "Auto-merge failed — dev branch preserved: $_DEV_BRANCH_NAME"
|
|
673
729
|
log_warn "Merge manually: git checkout $_ORIGINAL_BRANCH && git rebase $_DEV_BRANCH_NAME"
|
|
674
|
-
git -C "$_proj_root" checkout "$_ORIGINAL_BRANCH" 2>/dev/null || true
|
|
675
730
|
_DEV_BRANCH_NAME=""
|
|
676
731
|
fi
|
|
677
732
|
elif [[ -n "$_DEV_BRANCH_NAME" ]]; then
|
|
678
|
-
# Session failed —
|
|
679
|
-
if ! git -C "$_proj_root" checkout "$_ORIGINAL_BRANCH" 2>/dev/null; then
|
|
680
|
-
log_warn "Failed to checkout $_ORIGINAL_BRANCH after session failure — staying on dev branch"
|
|
681
|
-
fi
|
|
733
|
+
# Session failed — preserve dev branch for inspection
|
|
682
734
|
log_warn "Session failed — dev branch preserved for inspection: $_DEV_BRANCH_NAME"
|
|
683
735
|
_DEV_BRANCH_NAME=""
|
|
684
736
|
fi
|
|
685
737
|
|
|
738
|
+
# GUARANTEED: always return to original branch regardless of success/failure/merge outcome
|
|
739
|
+
branch_ensure_return "$_proj_root" "$_ORIGINAL_BRANCH"
|
|
740
|
+
|
|
741
|
+
# Commit bug status update on the original branch (after guaranteed return)
|
|
742
|
+
if ! git -C "$_proj_root" diff --quiet "$bug_list" 2>/dev/null; then
|
|
743
|
+
git -C "$_proj_root" add "$bug_list"
|
|
744
|
+
git -C "$_proj_root" commit --no-verify -m "chore($bug_id): update bug status" 2>/dev/null || true
|
|
745
|
+
fi
|
|
746
|
+
|
|
686
747
|
echo ""
|
|
687
748
|
if [[ "$session_status" == "success" ]]; then
|
|
688
749
|
log_success "════════════════════════════════════════════════════"
|
|
@@ -785,6 +846,18 @@ main() {
|
|
|
785
846
|
local total_subagent_calls=0
|
|
786
847
|
|
|
787
848
|
while true; do
|
|
849
|
+
# Safety net: ensure we're on the original branch at the start of each iteration.
|
|
850
|
+
# If a previous iteration's `continue` skipped branch_ensure_return, we could
|
|
851
|
+
# still be on a dev branch. This prevents cascading branch confusion.
|
|
852
|
+
if [[ -n "$_ORIGINAL_BRANCH" ]]; then
|
|
853
|
+
local _cur_branch
|
|
854
|
+
_cur_branch=$(git -C "$_proj_root" rev-parse --abbrev-ref HEAD 2>/dev/null || true)
|
|
855
|
+
if [[ -n "$_cur_branch" && "$_cur_branch" != "$_ORIGINAL_BRANCH" ]]; then
|
|
856
|
+
log_warn "Still on branch $_cur_branch at loop start — returning to $_ORIGINAL_BRANCH"
|
|
857
|
+
branch_ensure_return "$_proj_root" "$_ORIGINAL_BRANCH"
|
|
858
|
+
fi
|
|
859
|
+
fi
|
|
860
|
+
|
|
788
861
|
# Find next bug to process
|
|
789
862
|
local next_bug
|
|
790
863
|
if ! next_bug=$(python3 "$SCRIPTS_DIR/update-bug-status.py" \
|
|
@@ -839,7 +912,21 @@ main() {
|
|
|
839
912
|
git -C "$_proj_root" commit --no-verify -m "chore: capture artifacts before $bug_id session" 2>/dev/null || true
|
|
840
913
|
fi
|
|
841
914
|
|
|
842
|
-
#
|
|
915
|
+
# Mark bug as in-progress BEFORE creating dev branch
|
|
916
|
+
# This ensures the in_progress status commit lands on the original branch,
|
|
917
|
+
# not the dev branch — preventing rebase conflicts in branch_merge later.
|
|
918
|
+
python3 "$SCRIPTS_DIR/update-bug-status.py" \
|
|
919
|
+
--bug-list "$bug_list" \
|
|
920
|
+
--state-dir "$STATE_DIR" \
|
|
921
|
+
--bug-id "$bug_id" \
|
|
922
|
+
--action start >/dev/null 2>&1 || true
|
|
923
|
+
# Commit the in_progress status on the original branch
|
|
924
|
+
if ! git -C "$_proj_root" diff --quiet "$bug_list" 2>/dev/null; then
|
|
925
|
+
git -C "$_proj_root" add "$bug_list" 2>/dev/null || true
|
|
926
|
+
git -C "$_proj_root" commit --no-verify -m "chore($bug_id): mark in_progress" 2>/dev/null || true
|
|
927
|
+
fi
|
|
928
|
+
|
|
929
|
+
# Create per-bug dev branch (from the now-updated original branch)
|
|
843
930
|
local _bug_branch="${DEV_BRANCH:-bugfix/${bug_id}-$(date +%Y%m%d%H%M)}"
|
|
844
931
|
if branch_create "$_proj_root" "$_bug_branch" "$_ORIGINAL_BRANCH"; then
|
|
845
932
|
_DEV_BRANCH_NAME="$_bug_branch"
|
|
@@ -885,6 +972,8 @@ main() {
|
|
|
885
972
|
local gen_output
|
|
886
973
|
gen_output=$(python3 "$SCRIPTS_DIR/generate-bugfix-prompt.py" "${main_prompt_args[@]}" 2>/dev/null) || {
|
|
887
974
|
log_error "Failed to generate bootstrap prompt for $bug_id"
|
|
975
|
+
branch_ensure_return "$_proj_root" "$_ORIGINAL_BRANCH"
|
|
976
|
+
_DEV_BRANCH_NAME=""
|
|
888
977
|
continue
|
|
889
978
|
}
|
|
890
979
|
local bug_model pipeline_mode agent_count critic_enabled
|
|
@@ -907,13 +996,6 @@ main() {
|
|
|
907
996
|
log_info "Bug model: $bug_model"
|
|
908
997
|
fi
|
|
909
998
|
|
|
910
|
-
# Mark bug as in-progress before spawning session
|
|
911
|
-
python3 "$SCRIPTS_DIR/update-bug-status.py" \
|
|
912
|
-
--bug-list "$bug_list" \
|
|
913
|
-
--state-dir "$STATE_DIR" \
|
|
914
|
-
--bug-id "$bug_id" \
|
|
915
|
-
--action start >/dev/null 2>&1 || true
|
|
916
|
-
|
|
917
999
|
# Spawn session
|
|
918
1000
|
log_info "Spawning AI CLI session: $session_id"
|
|
919
1001
|
_SPAWN_RESULT=""
|
|
@@ -931,18 +1013,23 @@ main() {
|
|
|
931
1013
|
else
|
|
932
1014
|
log_warn "Auto-merge failed — dev branch preserved: $_DEV_BRANCH_NAME"
|
|
933
1015
|
log_warn "Merge manually: git checkout $_ORIGINAL_BRANCH && git rebase $_DEV_BRANCH_NAME"
|
|
934
|
-
git -C "$_proj_root" checkout "$_ORIGINAL_BRANCH" 2>/dev/null || true
|
|
935
1016
|
_DEV_BRANCH_NAME=""
|
|
936
1017
|
fi
|
|
937
1018
|
elif [[ -n "$_DEV_BRANCH_NAME" ]]; then
|
|
938
|
-
# Session failed —
|
|
939
|
-
if ! git -C "$_proj_root" checkout "$_ORIGINAL_BRANCH" 2>/dev/null; then
|
|
940
|
-
log_warn "Failed to checkout $_ORIGINAL_BRANCH after session failure — staying on dev branch"
|
|
941
|
-
fi
|
|
1019
|
+
# Session failed — preserve dev branch for inspection
|
|
942
1020
|
log_warn "Session failed — dev branch preserved for inspection: $_DEV_BRANCH_NAME"
|
|
943
1021
|
_DEV_BRANCH_NAME=""
|
|
944
1022
|
fi
|
|
945
1023
|
|
|
1024
|
+
# GUARANTEED: always return to original branch regardless of success/failure/merge outcome
|
|
1025
|
+
branch_ensure_return "$_proj_root" "$_ORIGINAL_BRANCH"
|
|
1026
|
+
|
|
1027
|
+
# Commit bug status update on the original branch (after guaranteed return)
|
|
1028
|
+
if ! git -C "$_proj_root" diff --quiet "$bug_list" 2>/dev/null; then
|
|
1029
|
+
git -C "$_proj_root" add "$bug_list"
|
|
1030
|
+
git -C "$_proj_root" commit --no-verify -m "chore($bug_id): update bug status" 2>/dev/null || true
|
|
1031
|
+
fi
|
|
1032
|
+
|
|
946
1033
|
session_count=$((session_count + 1))
|
|
947
1034
|
total_subagent_calls=$((total_subagent_calls + _SUBAGENT_COUNT))
|
|
948
1035
|
|