prizmkit 1.1.20 → 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/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
|
|
|
@@ -308,8 +308,15 @@ if incomplete:
|
|
|
308
308
|
sys.exit(1)
|
|
309
309
|
print('ALL_COMPLETE')
|
|
310
310
|
sys.exit(0)
|
|
311
|
-
" "$checkpoint_file" 2>&1)
|
|
311
|
+
" "$checkpoint_file" 2>&1) || checkpoint_result="CHECK_FAILED"
|
|
312
312
|
local check_exit=$?
|
|
313
|
+
if [[ "$checkpoint_result" == "CHECK_FAILED" ]]; then
|
|
314
|
+
check_exit=2
|
|
315
|
+
elif [[ "$checkpoint_result" == *"INCOMPLETE"* ]]; then
|
|
316
|
+
check_exit=1
|
|
317
|
+
else
|
|
318
|
+
check_exit=0
|
|
319
|
+
fi
|
|
313
320
|
if [[ $check_exit -eq 2 ]]; then
|
|
314
321
|
log_warn "CHECKPOINT_CORRUPTED: workflow-checkpoint.json is not valid JSON"
|
|
315
322
|
elif [[ $check_exit -eq 1 ]]; then
|
|
@@ -352,7 +359,7 @@ sys.exit(0)
|
|
|
352
359
|
fi
|
|
353
360
|
fi
|
|
354
361
|
|
|
355
|
-
# Update feature status
|
|
362
|
+
# Update feature status (do NOT commit on dev branch — commit happens after merge)
|
|
356
363
|
local update_output
|
|
357
364
|
update_output=$(python3 "$SCRIPTS_DIR/update-feature-status.py" \
|
|
358
365
|
--feature-list "$feature_list" \
|
|
@@ -366,12 +373,6 @@ sys.exit(0)
|
|
|
366
373
|
log_error ".prizmkit/plans/feature-list.json may be out of sync. Manual intervention needed."
|
|
367
374
|
}
|
|
368
375
|
|
|
369
|
-
# Commit feature status update (pipeline management commit)
|
|
370
|
-
if ! git -C "$project_root" diff --quiet "$feature_list" 2>/dev/null; then
|
|
371
|
-
git -C "$project_root" add "$feature_list"
|
|
372
|
-
git -C "$project_root" commit --no-verify -m "chore($feature_id): update feature status" 2>/dev/null || true
|
|
373
|
-
fi
|
|
374
|
-
|
|
375
376
|
# Return status via global variable (avoids $() swallowing stdout)
|
|
376
377
|
_SPAWN_RESULT="$session_status"
|
|
377
378
|
}
|
|
@@ -393,13 +394,40 @@ cleanup() {
|
|
|
393
394
|
log_info "Original branch was: $_ORIGINAL_BRANCH"
|
|
394
395
|
fi
|
|
395
396
|
|
|
397
|
+
# Update status of currently in-progress feature to interrupted
|
|
396
398
|
if [[ -n "$FEATURE_LIST" && -f "$FEATURE_LIST" ]]; then
|
|
399
|
+
# Find any in-progress feature and mark it as interrupted
|
|
400
|
+
local _interrupted_id
|
|
401
|
+
_interrupted_id=$(python3 -c "
|
|
402
|
+
import json, sys
|
|
403
|
+
with open(sys.argv[1]) as f:
|
|
404
|
+
data = json.load(f)
|
|
405
|
+
for feat in data.get('features', []):
|
|
406
|
+
if feat.get('status') == 'in_progress':
|
|
407
|
+
print(feat['id'])
|
|
408
|
+
break
|
|
409
|
+
" "$FEATURE_LIST" 2>/dev/null || echo "")
|
|
410
|
+
|
|
411
|
+
if [[ -n "$_interrupted_id" ]]; then
|
|
412
|
+
python3 "$SCRIPTS_DIR/update-feature-status.py" \
|
|
413
|
+
--feature-list "$FEATURE_LIST" \
|
|
414
|
+
--state-dir "$STATE_DIR" \
|
|
415
|
+
--feature-id "$_interrupted_id" \
|
|
416
|
+
--session-status "failed" \
|
|
417
|
+
--action update 2>/dev/null || true
|
|
418
|
+
log_info "Feature $_interrupted_id marked as failed due to interrupt"
|
|
419
|
+
fi
|
|
420
|
+
|
|
421
|
+
# Pause the pipeline (mark remaining pending items)
|
|
397
422
|
python3 "$SCRIPTS_DIR/update-feature-status.py" \
|
|
398
423
|
--feature-list "$FEATURE_LIST" \
|
|
399
424
|
--state-dir "$STATE_DIR" \
|
|
400
425
|
--action pause 2>/dev/null || true
|
|
401
426
|
fi
|
|
402
427
|
|
|
428
|
+
# GUARANTEED: always return to original branch (save WIP on dev branch first)
|
|
429
|
+
branch_ensure_return "$(cd "$SCRIPT_DIR/.." && pwd)" "$_ORIGINAL_BRANCH" "$_DEV_BRANCH_NAME"
|
|
430
|
+
|
|
403
431
|
log_info "Pipeline paused. Run './run-feature.sh run' to resume."
|
|
404
432
|
exit 130
|
|
405
433
|
}
|
|
@@ -814,6 +842,20 @@ else:
|
|
|
814
842
|
log_info "Development was on branch: $_DEV_BRANCH_NAME"
|
|
815
843
|
fi
|
|
816
844
|
log_info "Session log: $session_dir/logs/session.log"
|
|
845
|
+
|
|
846
|
+
# Update feature status to failed on interrupt
|
|
847
|
+
if [[ -n "$feature_list" && -f "$feature_list" ]]; then
|
|
848
|
+
python3 "$SCRIPTS_DIR/update-feature-status.py" \
|
|
849
|
+
--feature-list "$feature_list" \
|
|
850
|
+
--state-dir "$STATE_DIR" \
|
|
851
|
+
--feature-id "$feature_id" \
|
|
852
|
+
--session-status "failed" \
|
|
853
|
+
--action update 2>/dev/null || true
|
|
854
|
+
log_info "Feature $feature_id marked as failed due to interrupt"
|
|
855
|
+
fi
|
|
856
|
+
|
|
857
|
+
# GUARANTEED: always return to original branch (save WIP on dev branch first)
|
|
858
|
+
branch_ensure_return "$(cd "$SCRIPT_DIR/.." && pwd)" "$_ORIGINAL_BRANCH" "$_DEV_BRANCH_NAME"
|
|
817
859
|
exit 130
|
|
818
860
|
}
|
|
819
861
|
trap cleanup_single_feature SIGINT SIGTERM
|
|
@@ -827,6 +869,20 @@ else:
|
|
|
827
869
|
_source_branch=$(git -C "$_proj_root" rev-parse --abbrev-ref HEAD 2>/dev/null || echo "main")
|
|
828
870
|
_ORIGINAL_BRANCH="$_source_branch"
|
|
829
871
|
|
|
872
|
+
# Mark feature as in-progress BEFORE creating dev branch
|
|
873
|
+
# This ensures the in_progress status commit lands on the original branch,
|
|
874
|
+
# not the dev branch — preventing rebase conflicts in branch_merge later.
|
|
875
|
+
python3 "$SCRIPTS_DIR/update-feature-status.py" \
|
|
876
|
+
--feature-list "$feature_list" \
|
|
877
|
+
--state-dir "$STATE_DIR" \
|
|
878
|
+
--feature-id "$feature_id" \
|
|
879
|
+
--action start >/dev/null 2>&1 || true
|
|
880
|
+
# Commit the in_progress status on the original branch
|
|
881
|
+
if ! git -C "$_proj_root" diff --quiet "$feature_list" 2>/dev/null; then
|
|
882
|
+
git -C "$_proj_root" add "$feature_list" 2>/dev/null || true
|
|
883
|
+
git -C "$_proj_root" commit --no-verify -m "chore($feature_id): mark in_progress" 2>/dev/null || true
|
|
884
|
+
fi
|
|
885
|
+
|
|
830
886
|
local _branch_name="${DEV_BRANCH:-dev/${feature_id}-$(date +%Y%m%d%H%M)}"
|
|
831
887
|
if branch_create "$_proj_root" "$_branch_name" "$_source_branch"; then
|
|
832
888
|
_DEV_BRANCH_NAME="$_branch_name"
|
|
@@ -846,18 +902,23 @@ else:
|
|
|
846
902
|
else
|
|
847
903
|
log_warn "Auto-merge failed — dev branch preserved: $_DEV_BRANCH_NAME"
|
|
848
904
|
log_warn "Merge manually: git checkout $_ORIGINAL_BRANCH && git rebase $_DEV_BRANCH_NAME"
|
|
849
|
-
git -C "$_proj_root" checkout "$_ORIGINAL_BRANCH" 2>/dev/null || true
|
|
850
905
|
_DEV_BRANCH_NAME=""
|
|
851
906
|
fi
|
|
852
907
|
elif [[ -n "$_DEV_BRANCH_NAME" ]]; then
|
|
853
|
-
# Session failed —
|
|
854
|
-
if ! git -C "$_proj_root" checkout "$_ORIGINAL_BRANCH" 2>/dev/null; then
|
|
855
|
-
log_warn "Failed to checkout $_ORIGINAL_BRANCH after session failure — staying on dev branch"
|
|
856
|
-
fi
|
|
908
|
+
# Session failed — preserve dev branch for inspection
|
|
857
909
|
log_warn "Session failed — dev branch preserved for inspection: $_DEV_BRANCH_NAME"
|
|
858
910
|
_DEV_BRANCH_NAME=""
|
|
859
911
|
fi
|
|
860
912
|
|
|
913
|
+
# GUARANTEED: always return to original branch regardless of success/failure/merge outcome
|
|
914
|
+
branch_ensure_return "$_proj_root" "$_ORIGINAL_BRANCH"
|
|
915
|
+
|
|
916
|
+
# Commit feature status update on the original branch (after guaranteed return)
|
|
917
|
+
if ! git -C "$_proj_root" diff --quiet "$feature_list" 2>/dev/null; then
|
|
918
|
+
git -C "$_proj_root" add "$feature_list"
|
|
919
|
+
git -C "$_proj_root" commit --no-verify -m "chore($feature_id): update feature status" 2>/dev/null || true
|
|
920
|
+
fi
|
|
921
|
+
|
|
861
922
|
echo ""
|
|
862
923
|
if [[ "$session_status" == "success" ]]; then
|
|
863
924
|
log_success "════════════════════════════════════════════════════"
|
|
@@ -980,6 +1041,18 @@ main() {
|
|
|
980
1041
|
local total_subagent_calls=0
|
|
981
1042
|
|
|
982
1043
|
while true; do
|
|
1044
|
+
# Safety net: ensure we're on the original branch at the start of each iteration.
|
|
1045
|
+
# If a previous iteration's `continue` skipped branch_ensure_return, we could
|
|
1046
|
+
# still be on a dev branch. This prevents cascading branch confusion.
|
|
1047
|
+
if [[ -n "$_ORIGINAL_BRANCH" ]]; then
|
|
1048
|
+
local _cur_branch
|
|
1049
|
+
_cur_branch=$(git -C "$_proj_root" rev-parse --abbrev-ref HEAD 2>/dev/null || true)
|
|
1050
|
+
if [[ -n "$_cur_branch" && "$_cur_branch" != "$_ORIGINAL_BRANCH" ]]; then
|
|
1051
|
+
log_warn "Still on branch $_cur_branch at loop start — returning to $_ORIGINAL_BRANCH"
|
|
1052
|
+
branch_ensure_return "$_proj_root" "$_ORIGINAL_BRANCH"
|
|
1053
|
+
fi
|
|
1054
|
+
fi
|
|
1055
|
+
|
|
983
1056
|
# Check for stuck features
|
|
984
1057
|
local stuck_result
|
|
985
1058
|
stuck_result=$(python3 "$SCRIPTS_DIR/detect-stuck.py" \
|
|
@@ -1081,7 +1154,21 @@ print(count)
|
|
|
1081
1154
|
git -C "$_proj_root" commit --no-verify -m "ready for run $feature_id" 2>/dev/null || true
|
|
1082
1155
|
fi
|
|
1083
1156
|
|
|
1084
|
-
#
|
|
1157
|
+
# Mark feature as in-progress BEFORE creating dev branch
|
|
1158
|
+
# This ensures the in_progress status commit lands on the original branch,
|
|
1159
|
+
# not the dev branch — preventing rebase conflicts in branch_merge later.
|
|
1160
|
+
python3 "$SCRIPTS_DIR/update-feature-status.py" \
|
|
1161
|
+
--feature-list "$feature_list" \
|
|
1162
|
+
--state-dir "$STATE_DIR" \
|
|
1163
|
+
--feature-id "$feature_id" \
|
|
1164
|
+
--action start >/dev/null 2>&1 || true
|
|
1165
|
+
# Commit the in_progress status on the original branch
|
|
1166
|
+
if ! git -C "$_proj_root" diff --quiet "$feature_list" 2>/dev/null; then
|
|
1167
|
+
git -C "$_proj_root" add "$feature_list" 2>/dev/null || true
|
|
1168
|
+
git -C "$_proj_root" commit --no-verify -m "chore($feature_id): mark in_progress" 2>/dev/null || true
|
|
1169
|
+
fi
|
|
1170
|
+
|
|
1171
|
+
# Create per-feature dev branch (from the now-updated original branch)
|
|
1085
1172
|
local _feature_branch="${DEV_BRANCH:-dev/${feature_id}-$(date +%Y%m%d%H%M)}"
|
|
1086
1173
|
if branch_create "$_proj_root" "$_feature_branch" "$_ORIGINAL_BRANCH"; then
|
|
1087
1174
|
_DEV_BRANCH_NAME="$_feature_branch"
|
|
@@ -1127,6 +1214,8 @@ print(count)
|
|
|
1127
1214
|
local gen_output
|
|
1128
1215
|
gen_output=$(python3 "$SCRIPTS_DIR/generate-bootstrap-prompt.py" "${main_prompt_args[@]}" 2>/dev/null) || {
|
|
1129
1216
|
log_error "Failed to generate bootstrap prompt for $feature_id"
|
|
1217
|
+
branch_ensure_return "$_proj_root" "$_ORIGINAL_BRANCH"
|
|
1218
|
+
_DEV_BRANCH_NAME=""
|
|
1130
1219
|
continue
|
|
1131
1220
|
}
|
|
1132
1221
|
local feature_model pipeline_mode agent_count critic_enabled
|
|
@@ -1146,13 +1235,6 @@ print(count)
|
|
|
1146
1235
|
log_info "Pipeline mode: ${BOLD}$pipeline_mode${NC} ($_mode_desc)"
|
|
1147
1236
|
log_info "Agents: $agent_count (critic: $([ "$critic_enabled" = "true" ] && echo "enabled" || echo "disabled"))"
|
|
1148
1237
|
|
|
1149
|
-
# Mark feature as in-progress before spawning session
|
|
1150
|
-
python3 "$SCRIPTS_DIR/update-feature-status.py" \
|
|
1151
|
-
--feature-list "$feature_list" \
|
|
1152
|
-
--state-dir "$STATE_DIR" \
|
|
1153
|
-
--feature-id "$feature_id" \
|
|
1154
|
-
--action start >/dev/null 2>&1 || true
|
|
1155
|
-
|
|
1156
1238
|
# Spawn session and wait
|
|
1157
1239
|
prizm_log_bootstrap_prompt "$bootstrap_prompt" "$feature_id"
|
|
1158
1240
|
log_info "Spawning AI CLI session: $session_id"
|
|
@@ -1173,19 +1255,23 @@ print(count)
|
|
|
1173
1255
|
else
|
|
1174
1256
|
log_warn "Auto-merge failed — dev branch preserved: $_DEV_BRANCH_NAME"
|
|
1175
1257
|
log_warn "Merge manually: git checkout $_ORIGINAL_BRANCH && git rebase $_DEV_BRANCH_NAME"
|
|
1176
|
-
# Return to original branch; state/ files are untracked and persist across checkout
|
|
1177
|
-
git -C "$_proj_root" checkout "$_ORIGINAL_BRANCH" 2>/dev/null || true
|
|
1178
1258
|
_DEV_BRANCH_NAME=""
|
|
1179
1259
|
fi
|
|
1180
1260
|
elif [[ -n "$_DEV_BRANCH_NAME" ]]; then
|
|
1181
|
-
# Session failed —
|
|
1182
|
-
if ! git -C "$_proj_root" checkout "$_ORIGINAL_BRANCH" 2>/dev/null; then
|
|
1183
|
-
log_warn "Failed to checkout $_ORIGINAL_BRANCH after session failure — staying on dev branch"
|
|
1184
|
-
fi
|
|
1261
|
+
# Session failed — preserve dev branch for inspection
|
|
1185
1262
|
log_warn "Session failed — dev branch preserved for inspection: $_DEV_BRANCH_NAME"
|
|
1186
1263
|
_DEV_BRANCH_NAME=""
|
|
1187
1264
|
fi
|
|
1188
1265
|
|
|
1266
|
+
# GUARANTEED: always return to original branch regardless of success/failure/merge outcome
|
|
1267
|
+
branch_ensure_return "$_proj_root" "$_ORIGINAL_BRANCH"
|
|
1268
|
+
|
|
1269
|
+
# Commit feature status update on the original branch (after guaranteed return)
|
|
1270
|
+
if ! git -C "$_proj_root" diff --quiet "$feature_list" 2>/dev/null; then
|
|
1271
|
+
git -C "$_proj_root" add "$feature_list"
|
|
1272
|
+
git -C "$_proj_root" commit --no-verify -m "chore($feature_id): update feature status" 2>/dev/null || true
|
|
1273
|
+
fi
|
|
1274
|
+
|
|
1189
1275
|
session_count=$((session_count + 1))
|
|
1190
1276
|
total_subagent_calls=$((total_subagent_calls + _SUBAGENT_COUNT))
|
|
1191
1277
|
|
|
@@ -262,8 +262,15 @@ if incomplete:
|
|
|
262
262
|
sys.exit(1)
|
|
263
263
|
print('ALL_COMPLETE')
|
|
264
264
|
sys.exit(0)
|
|
265
|
-
" "$checkpoint_file" 2>&1)
|
|
265
|
+
" "$checkpoint_file" 2>&1) || checkpoint_result="CHECK_FAILED"
|
|
266
266
|
local check_exit=$?
|
|
267
|
+
if [[ "$checkpoint_result" == "CHECK_FAILED" ]]; then
|
|
268
|
+
check_exit=2
|
|
269
|
+
elif [[ "$checkpoint_result" == *"INCOMPLETE"* ]]; then
|
|
270
|
+
check_exit=1
|
|
271
|
+
else
|
|
272
|
+
check_exit=0
|
|
273
|
+
fi
|
|
267
274
|
if [[ $check_exit -eq 2 ]]; then
|
|
268
275
|
log_warn "CHECKPOINT_CORRUPTED: workflow-checkpoint.json is not valid JSON"
|
|
269
276
|
elif [[ $check_exit -eq 1 ]]; then
|
|
@@ -297,7 +304,7 @@ sys.exit(0)
|
|
|
297
304
|
fi
|
|
298
305
|
fi
|
|
299
306
|
|
|
300
|
-
# Update refactor status
|
|
307
|
+
# Update refactor status (do NOT commit on dev branch — commit happens after merge)
|
|
301
308
|
python3 "$SCRIPTS_DIR/update-refactor-status.py" \
|
|
302
309
|
--refactor-list "$refactor_list" \
|
|
303
310
|
--state-dir "$STATE_DIR" \
|
|
@@ -307,12 +314,6 @@ sys.exit(0)
|
|
|
307
314
|
--max-retries "$max_retries" \
|
|
308
315
|
--action update >/dev/null 2>&1 || true
|
|
309
316
|
|
|
310
|
-
# Commit .prizmkit/plans/refactor-list.json status update (pipeline management commit)
|
|
311
|
-
if ! git -C "$project_root" diff --quiet "$refactor_list" 2>/dev/null; then
|
|
312
|
-
git -C "$project_root" add "$refactor_list"
|
|
313
|
-
git -C "$project_root" commit --no-verify -m "chore($refactor_id): update refactor status" 2>/dev/null || true
|
|
314
|
-
fi
|
|
315
|
-
|
|
316
317
|
_SPAWN_RESULT="$session_status"
|
|
317
318
|
}
|
|
318
319
|
|
|
@@ -333,13 +334,40 @@ cleanup() {
|
|
|
333
334
|
log_info "Original branch was: $_ORIGINAL_BRANCH"
|
|
334
335
|
fi
|
|
335
336
|
|
|
337
|
+
# Update status of currently in-progress refactor to interrupted
|
|
336
338
|
if [[ -n "$REFACTOR_LIST" && -f "$REFACTOR_LIST" ]]; then
|
|
339
|
+
# Find any in-progress refactor and mark it as failed
|
|
340
|
+
local _interrupted_id
|
|
341
|
+
_interrupted_id=$(python3 -c "
|
|
342
|
+
import json, sys
|
|
343
|
+
with open(sys.argv[1]) as f:
|
|
344
|
+
data = json.load(f)
|
|
345
|
+
for item in data.get('refactors', []):
|
|
346
|
+
if item.get('status') == 'in_progress':
|
|
347
|
+
print(item['id'])
|
|
348
|
+
break
|
|
349
|
+
" "$REFACTOR_LIST" 2>/dev/null || echo "")
|
|
350
|
+
|
|
351
|
+
if [[ -n "$_interrupted_id" ]]; then
|
|
352
|
+
python3 "$SCRIPTS_DIR/update-refactor-status.py" \
|
|
353
|
+
--refactor-list "$REFACTOR_LIST" \
|
|
354
|
+
--state-dir "$STATE_DIR" \
|
|
355
|
+
--refactor-id "$_interrupted_id" \
|
|
356
|
+
--session-status "failed" \
|
|
357
|
+
--action update 2>/dev/null || true
|
|
358
|
+
log_info "Refactor $_interrupted_id marked as failed due to interrupt"
|
|
359
|
+
fi
|
|
360
|
+
|
|
361
|
+
# Pause the pipeline
|
|
337
362
|
python3 "$SCRIPTS_DIR/update-refactor-status.py" \
|
|
338
363
|
--refactor-list "$REFACTOR_LIST" \
|
|
339
364
|
--state-dir "$STATE_DIR" \
|
|
340
365
|
--action pause 2>/dev/null || true
|
|
341
366
|
fi
|
|
342
367
|
|
|
368
|
+
# GUARANTEED: always return to original branch (save WIP on dev branch first)
|
|
369
|
+
branch_ensure_return "$(cd "$SCRIPT_DIR/.." && pwd)" "$_ORIGINAL_BRANCH" "$_DEV_BRANCH_NAME"
|
|
370
|
+
|
|
343
371
|
log_info "Refactor pipeline paused. Run './run-refactor.sh run' to resume."
|
|
344
372
|
exit 130
|
|
345
373
|
}
|
|
@@ -669,6 +697,20 @@ else:
|
|
|
669
697
|
log_info "Development was on branch: $_DEV_BRANCH_NAME"
|
|
670
698
|
fi
|
|
671
699
|
log_info "Session log: $session_dir/logs/session.log"
|
|
700
|
+
|
|
701
|
+
# Update refactor status to failed on interrupt
|
|
702
|
+
if [[ -n "$refactor_list" && -f "$refactor_list" ]]; then
|
|
703
|
+
python3 "$SCRIPTS_DIR/update-refactor-status.py" \
|
|
704
|
+
--refactor-list "$refactor_list" \
|
|
705
|
+
--state-dir "$STATE_DIR" \
|
|
706
|
+
--refactor-id "$refactor_id" \
|
|
707
|
+
--session-status "failed" \
|
|
708
|
+
--action update 2>/dev/null || true
|
|
709
|
+
log_info "Refactor $refactor_id marked as failed due to interrupt"
|
|
710
|
+
fi
|
|
711
|
+
|
|
712
|
+
# GUARANTEED: always return to original branch (save WIP on dev branch first)
|
|
713
|
+
branch_ensure_return "$(cd "$SCRIPT_DIR/.." && pwd)" "$_ORIGINAL_BRANCH" "$_DEV_BRANCH_NAME"
|
|
672
714
|
exit 130
|
|
673
715
|
}
|
|
674
716
|
trap cleanup_single_refactor SIGINT SIGTERM
|
|
@@ -682,6 +724,20 @@ else:
|
|
|
682
724
|
_source_branch=$(git -C "$_proj_root" rev-parse --abbrev-ref HEAD 2>/dev/null || echo "main")
|
|
683
725
|
_ORIGINAL_BRANCH="$_source_branch"
|
|
684
726
|
|
|
727
|
+
# Mark refactor as in-progress BEFORE creating dev branch
|
|
728
|
+
# This ensures the in_progress status commit lands on the original branch,
|
|
729
|
+
# not the dev branch — preventing rebase conflicts in branch_merge later.
|
|
730
|
+
python3 "$SCRIPTS_DIR/update-refactor-status.py" \
|
|
731
|
+
--refactor-list "$refactor_list" \
|
|
732
|
+
--state-dir "$STATE_DIR" \
|
|
733
|
+
--refactor-id "$refactor_id" \
|
|
734
|
+
--action start >/dev/null 2>&1 || true
|
|
735
|
+
# Commit the in_progress status on the original branch
|
|
736
|
+
if ! git -C "$_proj_root" diff --quiet "$refactor_list" 2>/dev/null; then
|
|
737
|
+
git -C "$_proj_root" add "$refactor_list" 2>/dev/null || true
|
|
738
|
+
git -C "$_proj_root" commit --no-verify -m "chore($refactor_id): mark in_progress" 2>/dev/null || true
|
|
739
|
+
fi
|
|
740
|
+
|
|
685
741
|
local _branch_name="${DEV_BRANCH:-refactor/${refactor_id}-$(date +%s)}"
|
|
686
742
|
if branch_create "$_proj_root" "$_branch_name" "$_source_branch"; then
|
|
687
743
|
_DEV_BRANCH_NAME="$_branch_name"
|
|
@@ -701,18 +757,23 @@ else:
|
|
|
701
757
|
else
|
|
702
758
|
log_warn "Auto-merge failed — dev branch preserved: $_DEV_BRANCH_NAME"
|
|
703
759
|
log_warn "Merge manually: git checkout $_ORIGINAL_BRANCH && git rebase $_DEV_BRANCH_NAME"
|
|
704
|
-
git -C "$_proj_root" checkout "$_ORIGINAL_BRANCH" 2>/dev/null || true
|
|
705
760
|
_DEV_BRANCH_NAME=""
|
|
706
761
|
fi
|
|
707
762
|
elif [[ -n "$_DEV_BRANCH_NAME" ]]; then
|
|
708
|
-
# Session failed —
|
|
709
|
-
if ! git -C "$_proj_root" checkout "$_ORIGINAL_BRANCH" 2>/dev/null; then
|
|
710
|
-
log_warn "Failed to checkout $_ORIGINAL_BRANCH after session failure — staying on dev branch"
|
|
711
|
-
fi
|
|
763
|
+
# Session failed — preserve dev branch for inspection
|
|
712
764
|
log_warn "Session failed — dev branch preserved for inspection: $_DEV_BRANCH_NAME"
|
|
713
765
|
_DEV_BRANCH_NAME=""
|
|
714
766
|
fi
|
|
715
767
|
|
|
768
|
+
# GUARANTEED: always return to original branch regardless of success/failure/merge outcome
|
|
769
|
+
branch_ensure_return "$_proj_root" "$_ORIGINAL_BRANCH"
|
|
770
|
+
|
|
771
|
+
# Commit refactor status update on the original branch (after guaranteed return)
|
|
772
|
+
if ! git -C "$_proj_root" diff --quiet "$refactor_list" 2>/dev/null; then
|
|
773
|
+
git -C "$_proj_root" add "$refactor_list"
|
|
774
|
+
git -C "$_proj_root" commit --no-verify -m "chore($refactor_id): update refactor status" 2>/dev/null || true
|
|
775
|
+
fi
|
|
776
|
+
|
|
716
777
|
echo ""
|
|
717
778
|
if [[ "$session_status" == "success" ]]; then
|
|
718
779
|
log_success "════════════════════════════════════════════════════"
|
|
@@ -820,6 +881,18 @@ main() {
|
|
|
820
881
|
local total_subagent_calls=0
|
|
821
882
|
|
|
822
883
|
while true; do
|
|
884
|
+
# Safety net: ensure we're on the original branch at the start of each iteration.
|
|
885
|
+
# If a previous iteration's `continue` skipped branch_ensure_return, we could
|
|
886
|
+
# still be on a dev branch. This prevents cascading branch confusion.
|
|
887
|
+
if [[ -n "$_ORIGINAL_BRANCH" ]]; then
|
|
888
|
+
local _cur_branch
|
|
889
|
+
_cur_branch=$(git -C "$_proj_root" rev-parse --abbrev-ref HEAD 2>/dev/null || true)
|
|
890
|
+
if [[ -n "$_cur_branch" && "$_cur_branch" != "$_ORIGINAL_BRANCH" ]]; then
|
|
891
|
+
log_warn "Still on branch $_cur_branch at loop start — returning to $_ORIGINAL_BRANCH"
|
|
892
|
+
branch_ensure_return "$_proj_root" "$_ORIGINAL_BRANCH"
|
|
893
|
+
fi
|
|
894
|
+
fi
|
|
895
|
+
|
|
823
896
|
# Find next refactor to process (dependency-topological order)
|
|
824
897
|
local next_refactor
|
|
825
898
|
if ! next_refactor=$(python3 "$SCRIPTS_DIR/update-refactor-status.py" \
|
|
@@ -874,7 +947,21 @@ main() {
|
|
|
874
947
|
git -C "$_proj_root" commit --no-verify -m "chore: capture artifacts before $refactor_id session" 2>/dev/null || true
|
|
875
948
|
fi
|
|
876
949
|
|
|
877
|
-
#
|
|
950
|
+
# Mark refactor as in-progress BEFORE creating dev branch
|
|
951
|
+
# This ensures the in_progress status commit lands on the original branch,
|
|
952
|
+
# not the dev branch — preventing rebase conflicts in branch_merge later.
|
|
953
|
+
python3 "$SCRIPTS_DIR/update-refactor-status.py" \
|
|
954
|
+
--refactor-list "$refactor_list" \
|
|
955
|
+
--state-dir "$STATE_DIR" \
|
|
956
|
+
--refactor-id "$refactor_id" \
|
|
957
|
+
--action start >/dev/null 2>&1 || true
|
|
958
|
+
# Commit the in_progress status on the original branch
|
|
959
|
+
if ! git -C "$_proj_root" diff --quiet "$refactor_list" 2>/dev/null; then
|
|
960
|
+
git -C "$_proj_root" add "$refactor_list" 2>/dev/null || true
|
|
961
|
+
git -C "$_proj_root" commit --no-verify -m "chore($refactor_id): mark in_progress" 2>/dev/null || true
|
|
962
|
+
fi
|
|
963
|
+
|
|
964
|
+
# Create per-refactor dev branch (from the now-updated original branch)
|
|
878
965
|
local _refactor_branch="${DEV_BRANCH:-refactor/${refactor_id}-$(date +%Y%m%d%H%M)}"
|
|
879
966
|
if branch_create "$_proj_root" "$_refactor_branch" "$_ORIGINAL_BRANCH"; then
|
|
880
967
|
_DEV_BRANCH_NAME="$_refactor_branch"
|
|
@@ -920,6 +1007,8 @@ main() {
|
|
|
920
1007
|
local gen_output
|
|
921
1008
|
gen_output=$(python3 "$SCRIPTS_DIR/generate-refactor-prompt.py" "${main_prompt_args[@]}" 2>/dev/null) || {
|
|
922
1009
|
log_error "Failed to generate bootstrap prompt for $refactor_id"
|
|
1010
|
+
branch_ensure_return "$_proj_root" "$_ORIGINAL_BRANCH"
|
|
1011
|
+
_DEV_BRANCH_NAME=""
|
|
923
1012
|
continue
|
|
924
1013
|
}
|
|
925
1014
|
local refactor_model pipeline_mode agent_count critic_enabled
|
|
@@ -943,13 +1032,6 @@ main() {
|
|
|
943
1032
|
log_info "Refactor model: $refactor_model"
|
|
944
1033
|
fi
|
|
945
1034
|
|
|
946
|
-
# Mark refactor as in-progress before spawning session
|
|
947
|
-
python3 "$SCRIPTS_DIR/update-refactor-status.py" \
|
|
948
|
-
--refactor-list "$refactor_list" \
|
|
949
|
-
--state-dir "$STATE_DIR" \
|
|
950
|
-
--refactor-id "$refactor_id" \
|
|
951
|
-
--action start >/dev/null 2>&1 || true
|
|
952
|
-
|
|
953
1035
|
# Spawn session
|
|
954
1036
|
log_info "Spawning AI CLI session: $session_id"
|
|
955
1037
|
_SPAWN_RESULT=""
|
|
@@ -979,18 +1061,23 @@ main() {
|
|
|
979
1061
|
else
|
|
980
1062
|
log_warn "Auto-merge failed — dev branch preserved: $_DEV_BRANCH_NAME"
|
|
981
1063
|
log_warn "Merge manually: git checkout $_ORIGINAL_BRANCH && git rebase $_DEV_BRANCH_NAME"
|
|
982
|
-
git -C "$_proj_root" checkout "$_ORIGINAL_BRANCH" 2>/dev/null || true
|
|
983
1064
|
_DEV_BRANCH_NAME=""
|
|
984
1065
|
fi
|
|
985
1066
|
elif [[ -n "$_DEV_BRANCH_NAME" ]]; then
|
|
986
|
-
# Session failed —
|
|
987
|
-
if ! git -C "$_proj_root" checkout "$_ORIGINAL_BRANCH" 2>/dev/null; then
|
|
988
|
-
log_warn "Failed to checkout $_ORIGINAL_BRANCH after session failure — staying on dev branch"
|
|
989
|
-
fi
|
|
1067
|
+
# Session failed — preserve dev branch for inspection
|
|
990
1068
|
log_warn "Session failed — dev branch preserved for inspection: $_DEV_BRANCH_NAME"
|
|
991
1069
|
_DEV_BRANCH_NAME=""
|
|
992
1070
|
fi
|
|
993
1071
|
|
|
1072
|
+
# GUARANTEED: always return to original branch regardless of success/failure/merge outcome
|
|
1073
|
+
branch_ensure_return "$_proj_root" "$_ORIGINAL_BRANCH"
|
|
1074
|
+
|
|
1075
|
+
# Commit refactor status update on the original branch (after guaranteed return)
|
|
1076
|
+
if ! git -C "$_proj_root" diff --quiet "$refactor_list" 2>/dev/null; then
|
|
1077
|
+
git -C "$_proj_root" add "$refactor_list"
|
|
1078
|
+
git -C "$_proj_root" commit --no-verify -m "chore($refactor_id): update refactor status" 2>/dev/null || true
|
|
1079
|
+
fi
|
|
1080
|
+
|
|
994
1081
|
# Stuck detection
|
|
995
1082
|
if python3 "$SCRIPTS_DIR/detect-stuck.py" \
|
|
996
1083
|
--state-dir "$STATE_DIR" \
|
|
@@ -1314,6 +1314,36 @@ def build_replacements(args, feature, features, global_context, script_dir):
|
|
|
1314
1314
|
if isinstance(testing_config, dict):
|
|
1315
1315
|
coverage_target = str(testing_config.get("coverage_target", 80))
|
|
1316
1316
|
|
|
1317
|
+
# Detect dev server port from package.json
|
|
1318
|
+
dev_port = "3000" # Default fallback
|
|
1319
|
+
try:
|
|
1320
|
+
pkg_path = os.path.join(project_root, "package.json")
|
|
1321
|
+
if os.path.isfile(pkg_path):
|
|
1322
|
+
with open(pkg_path, "r", encoding="utf-8") as f:
|
|
1323
|
+
pkg = json.load(f)
|
|
1324
|
+
dev_script = pkg.get("scripts", {}).get("dev", "")
|
|
1325
|
+
# Extract -p <port> from dev script
|
|
1326
|
+
port_match = re.search(r"-p\s+(\d+)", dev_script)
|
|
1327
|
+
if port_match:
|
|
1328
|
+
dev_port = port_match.group(1)
|
|
1329
|
+
else:
|
|
1330
|
+
# Fallback: try NEXT_PUBLIC_SITE_URL from .env files
|
|
1331
|
+
for env_file in [".env.local", ".env"]:
|
|
1332
|
+
env_path = os.path.join(project_root, env_file)
|
|
1333
|
+
if os.path.isfile(env_path):
|
|
1334
|
+
with open(env_path, "r", encoding="utf-8") as ef:
|
|
1335
|
+
for line in ef:
|
|
1336
|
+
m = re.match(
|
|
1337
|
+
r"NEXT_PUBLIC_SITE_URL\s*=\s*.*?:([0-9]+)", line.strip()
|
|
1338
|
+
)
|
|
1339
|
+
if m:
|
|
1340
|
+
dev_port = m.group(1)
|
|
1341
|
+
break
|
|
1342
|
+
if dev_port != "3000":
|
|
1343
|
+
break
|
|
1344
|
+
except Exception:
|
|
1345
|
+
pass # Keep default 3000 on any error
|
|
1346
|
+
dev_url = f"http://localhost:{dev_port}"
|
|
1317
1347
|
|
|
1318
1348
|
replacements = {
|
|
1319
1349
|
"{{RUN_ID}}": args.run_id,
|
|
@@ -1356,6 +1386,8 @@ def build_replacements(args, feature, features, global_context, script_dir):
|
|
|
1356
1386
|
"{{TEST_CMD}}": test_cmd,
|
|
1357
1387
|
"{{BASELINE_FAILURES}}": baseline_failures,
|
|
1358
1388
|
"{{COVERAGE_TARGET}}": coverage_target,
|
|
1389
|
+
"{{DEV_PORT}}": dev_port,
|
|
1390
|
+
"{{DEV_URL}}": dev_url,
|
|
1359
1391
|
}
|
|
1360
1392
|
|
|
1361
1393
|
return replacements, effective_resume, browser_enabled
|
|
@@ -194,17 +194,32 @@ You MUST execute this phase. Do NOT skip it. Do NOT mark it as completed without
|
|
|
194
194
|
You know this project's tech stack. Detect and start the dev server yourself:
|
|
195
195
|
|
|
196
196
|
1. Identify the dev server start command from project config (`package.json` scripts, `Makefile`, `docker-compose.yml`, etc.)
|
|
197
|
-
2.
|
|
197
|
+
2. **Detect the dev server port** — use the pre-detected port from pipeline if available, otherwise extract from project config. Do NOT hardcode or guess the port:
|
|
198
198
|
```bash
|
|
199
|
-
|
|
199
|
+
# Use pipeline-injected port if available, otherwise extract from package.json
|
|
200
|
+
DEV_PORT={{DEV_PORT}}
|
|
201
|
+
# If DEV_PORT is still a placeholder, detect at runtime:
|
|
202
|
+
if [ "$DEV_PORT" = "{{DEV_PORT}}" ]; then
|
|
203
|
+
DEV_PORT=$(node -e "const s=require('./package.json').scripts.dev; const m=s.match(/-p\s+(\d+)/); console.log(m?m[1]:'')")
|
|
204
|
+
if [ -z "$DEV_PORT" ]; then
|
|
205
|
+
DEV_PORT=$(echo "$NEXT_PUBLIC_SITE_URL" | sed -nE 's|.*:([0-9]+).*|\1|p')
|
|
206
|
+
fi
|
|
207
|
+
DEV_PORT=${DEV_PORT:-3000}
|
|
208
|
+
fi
|
|
209
|
+
echo "Detected DEV_PORT=$DEV_PORT"
|
|
200
210
|
```
|
|
201
|
-
3.
|
|
211
|
+
3. Verify the port is available:
|
|
212
|
+
```bash
|
|
213
|
+
lsof -ti:$DEV_PORT 2>/dev/null && echo "PORT_IN_USE" || echo "PORT_FREE"
|
|
214
|
+
```
|
|
215
|
+
4. Start the dev server in background, capture PID:
|
|
202
216
|
```bash
|
|
203
217
|
<start-command> &
|
|
204
218
|
DEV_SERVER_PID=$!
|
|
205
219
|
```
|
|
206
|
-
|
|
207
|
-
|
|
220
|
+
5. Wait for server to be ready: poll `http://localhost:$DEV_PORT` with `curl -s -o /dev/null -w "%{http_code}"` until it returns 200 or 302 (max 30 seconds, 2s interval)
|
|
221
|
+
6. Open the app in playwright-cli: `playwright-cli open http://localhost:$DEV_PORT`
|
|
222
|
+
7. If the page requires authentication, use playwright-cli to register a test user and log in first
|
|
208
223
|
|
|
209
224
|
**Step 2 — Verification**:
|
|
210
225
|
|
|
@@ -217,14 +232,14 @@ Take a final screenshot for evidence.
|
|
|
217
232
|
**Step 3 — Cleanup (REQUIRED — you started it, you stop it)**:
|
|
218
233
|
|
|
219
234
|
1. Kill the dev server process: `kill $DEV_SERVER_PID 2>/dev/null || true`
|
|
220
|
-
2. Verify port is released: `lsof -ti
|
|
235
|
+
2. Verify port is released: `lsof -ti:$DEV_PORT | xargs kill -9 2>/dev/null || true`
|
|
221
236
|
|
|
222
237
|
**Step 4 — Reporting**:
|
|
223
238
|
|
|
224
239
|
Append results to `context-snapshot.md`:
|
|
225
240
|
```
|
|
226
241
|
## Browser Verification
|
|
227
|
-
URL:
|
|
242
|
+
URL: http://localhost:$DEV_PORT
|
|
228
243
|
Dev Server Command: <actual command used>
|
|
229
244
|
Steps executed: [list]
|
|
230
245
|
Screenshot: [path]
|
|
@@ -292,17 +292,32 @@ You MUST execute this phase. Do NOT skip it. Do NOT mark it as completed without
|
|
|
292
292
|
You know this project's tech stack. Detect and start the dev server yourself:
|
|
293
293
|
|
|
294
294
|
1. Identify the dev server start command from project config (`package.json` scripts, `Makefile`, `docker-compose.yml`, etc.)
|
|
295
|
-
2.
|
|
295
|
+
2. **Detect the dev server port** — use the pre-detected port from pipeline if available, otherwise extract from project config. Do NOT hardcode or guess the port:
|
|
296
296
|
```bash
|
|
297
|
-
|
|
297
|
+
# Use pipeline-injected port if available, otherwise extract from package.json
|
|
298
|
+
DEV_PORT={{DEV_PORT}}
|
|
299
|
+
# If DEV_PORT is still a placeholder, detect at runtime:
|
|
300
|
+
if [ "$DEV_PORT" = "{{DEV_PORT}}" ]; then
|
|
301
|
+
DEV_PORT=$(node -e "const s=require('./package.json').scripts.dev; const m=s.match(/-p\s+(\d+)/); console.log(m?m[1]:'')")
|
|
302
|
+
if [ -z "$DEV_PORT" ]; then
|
|
303
|
+
DEV_PORT=$(echo "$NEXT_PUBLIC_SITE_URL" | sed -nE 's|.*:([0-9]+).*|\1|p')
|
|
304
|
+
fi
|
|
305
|
+
DEV_PORT=${DEV_PORT:-3000}
|
|
306
|
+
fi
|
|
307
|
+
echo "Detected DEV_PORT=$DEV_PORT"
|
|
298
308
|
```
|
|
299
|
-
3.
|
|
309
|
+
3. Verify the port is available:
|
|
310
|
+
```bash
|
|
311
|
+
lsof -ti:$DEV_PORT 2>/dev/null && echo "PORT_IN_USE" || echo "PORT_FREE"
|
|
312
|
+
```
|
|
313
|
+
4. Start the dev server in background, capture PID:
|
|
300
314
|
```bash
|
|
301
315
|
<start-command> &
|
|
302
316
|
DEV_SERVER_PID=$!
|
|
303
317
|
```
|
|
304
|
-
|
|
305
|
-
|
|
318
|
+
5. Wait for server to be ready: poll `http://localhost:$DEV_PORT` with `curl -s -o /dev/null -w "%{http_code}"` until it returns 200 or 302 (max 30 seconds, 2s interval)
|
|
319
|
+
6. Open the app in playwright-cli: `playwright-cli open http://localhost:$DEV_PORT`
|
|
320
|
+
7. If the page requires authentication, use playwright-cli to register a test user and log in first
|
|
306
321
|
|
|
307
322
|
**Step 2 — Verification**:
|
|
308
323
|
|
|
@@ -315,14 +330,14 @@ Take a final screenshot for evidence.
|
|
|
315
330
|
**Step 3 — Cleanup (REQUIRED — you started it, you stop it)**:
|
|
316
331
|
|
|
317
332
|
1. Kill the dev server process: `kill $DEV_SERVER_PID 2>/dev/null || true`
|
|
318
|
-
2. Verify port is released: `lsof -ti
|
|
333
|
+
2. Verify port is released: `lsof -ti:$DEV_PORT | xargs kill -9 2>/dev/null || true`
|
|
319
334
|
|
|
320
335
|
**Step 4 — Reporting**:
|
|
321
336
|
|
|
322
337
|
Append results to `context-snapshot.md`:
|
|
323
338
|
```
|
|
324
339
|
## Browser Verification
|
|
325
|
-
URL:
|
|
340
|
+
URL: http://localhost:$DEV_PORT
|
|
326
341
|
Dev Server Command: <actual command used>
|
|
327
342
|
Steps executed: [list]
|
|
328
343
|
Screenshot: [path]
|
|
@@ -364,17 +364,32 @@ You MUST execute this phase. Do NOT skip it. Do NOT mark it as completed without
|
|
|
364
364
|
You know this project's tech stack. Detect and start the dev server yourself:
|
|
365
365
|
|
|
366
366
|
1. Identify the dev server start command from project config (`package.json` scripts, `Makefile`, `docker-compose.yml`, etc.)
|
|
367
|
-
2.
|
|
367
|
+
2. **Detect the dev server port** — use the pre-detected port from pipeline if available, otherwise extract from project config. Do NOT hardcode or guess the port:
|
|
368
368
|
```bash
|
|
369
|
-
|
|
369
|
+
# Use pipeline-injected port if available, otherwise extract from package.json
|
|
370
|
+
DEV_PORT={{DEV_PORT}}
|
|
371
|
+
# If DEV_PORT is still a placeholder, detect at runtime:
|
|
372
|
+
if [ "$DEV_PORT" = "{{DEV_PORT}}" ]; then
|
|
373
|
+
DEV_PORT=$(node -e "const s=require('./package.json').scripts.dev; const m=s.match(/-p\s+(\d+)/); console.log(m?m[1]:'')")
|
|
374
|
+
if [ -z "$DEV_PORT" ]; then
|
|
375
|
+
DEV_PORT=$(echo "$NEXT_PUBLIC_SITE_URL" | sed -nE 's|.*:([0-9]+).*|\1|p')
|
|
376
|
+
fi
|
|
377
|
+
DEV_PORT=${DEV_PORT:-3000}
|
|
378
|
+
fi
|
|
379
|
+
echo "Detected DEV_PORT=$DEV_PORT"
|
|
370
380
|
```
|
|
371
|
-
3.
|
|
381
|
+
3. Verify the port is available:
|
|
382
|
+
```bash
|
|
383
|
+
lsof -ti:$DEV_PORT 2>/dev/null && echo "PORT_IN_USE" || echo "PORT_FREE"
|
|
384
|
+
```
|
|
385
|
+
4. Start the dev server in background, capture PID:
|
|
372
386
|
```bash
|
|
373
387
|
<start-command> &
|
|
374
388
|
DEV_SERVER_PID=$!
|
|
375
389
|
```
|
|
376
|
-
|
|
377
|
-
|
|
390
|
+
5. Wait for server to be ready: poll `http://localhost:$DEV_PORT` with `curl -s -o /dev/null -w "%{http_code}"` until it returns 200 or 302 (max 30 seconds, 2s interval)
|
|
391
|
+
6. Open the app in playwright-cli: `playwright-cli open http://localhost:$DEV_PORT`
|
|
392
|
+
7. If the page requires authentication, use playwright-cli to register a test user and log in first
|
|
378
393
|
|
|
379
394
|
**Step 2 — Verification**:
|
|
380
395
|
|
|
@@ -387,14 +402,14 @@ Take a final screenshot for evidence.
|
|
|
387
402
|
**Step 3 — Cleanup (REQUIRED — you started it, you stop it)**:
|
|
388
403
|
|
|
389
404
|
1. Kill the dev server process: `kill $DEV_SERVER_PID 2>/dev/null || true`
|
|
390
|
-
2. Verify port is released: `lsof -ti
|
|
405
|
+
2. Verify port is released: `lsof -ti:$DEV_PORT | xargs kill -9 2>/dev/null || true`
|
|
391
406
|
|
|
392
407
|
**Step 4 — Reporting**:
|
|
393
408
|
|
|
394
409
|
Append results to `context-snapshot.md`:
|
|
395
410
|
```
|
|
396
411
|
## Browser Verification
|
|
397
|
-
URL:
|
|
412
|
+
URL: http://localhost:$DEV_PORT
|
|
398
413
|
Dev Server Command: <actual command used>
|
|
399
414
|
Steps executed: [list]
|
|
400
415
|
Screenshot: [path]
|
|
@@ -7,17 +7,32 @@ You MUST execute this phase. Do NOT skip it. Do NOT mark it as completed without
|
|
|
7
7
|
You know this project's tech stack. Detect and start the dev server yourself:
|
|
8
8
|
|
|
9
9
|
1. Identify the dev server start command from project config (`package.json` scripts, `Makefile`, `docker-compose.yml`, etc.)
|
|
10
|
-
2.
|
|
10
|
+
2. **Detect the dev server port** — use the pre-detected port from pipeline if available, otherwise extract from project config. Do NOT hardcode or guess the port:
|
|
11
11
|
```bash
|
|
12
|
-
|
|
12
|
+
# Use pipeline-injected port if available, otherwise extract from package.json
|
|
13
|
+
DEV_PORT={{DEV_PORT}}
|
|
14
|
+
# If DEV_PORT is still a placeholder, detect at runtime:
|
|
15
|
+
if [ "$DEV_PORT" = "{{DEV_PORT}}" ]; then
|
|
16
|
+
DEV_PORT=$(node -e "const s=require('./package.json').scripts.dev; const m=s.match(/-p\s+(\d+)/); console.log(m?m[1]:'')")
|
|
17
|
+
if [ -z "$DEV_PORT" ]; then
|
|
18
|
+
DEV_PORT=$(echo "$NEXT_PUBLIC_SITE_URL" | sed -nE 's|.*:([0-9]+).*|\1|p')
|
|
19
|
+
fi
|
|
20
|
+
DEV_PORT=${DEV_PORT:-3000}
|
|
21
|
+
fi
|
|
22
|
+
echo "Detected DEV_PORT=$DEV_PORT"
|
|
13
23
|
```
|
|
14
|
-
3.
|
|
24
|
+
3. Verify the port is available:
|
|
25
|
+
```bash
|
|
26
|
+
lsof -ti:$DEV_PORT 2>/dev/null && echo "PORT_IN_USE" || echo "PORT_FREE"
|
|
27
|
+
```
|
|
28
|
+
4. Start the dev server in background, capture PID:
|
|
15
29
|
```bash
|
|
16
30
|
<start-command> &
|
|
17
31
|
DEV_SERVER_PID=$!
|
|
18
32
|
```
|
|
19
|
-
|
|
20
|
-
|
|
33
|
+
5. Wait for server to be ready: poll `http://localhost:$DEV_PORT` with `curl -s -o /dev/null -w "%{http_code}"` until it returns 200 or 302 (max 30 seconds, 2s interval)
|
|
34
|
+
6. Open the app in playwright-cli: `playwright-cli open http://localhost:$DEV_PORT`
|
|
35
|
+
7. If the page requires authentication, use playwright-cli to register a test user and log in first
|
|
21
36
|
|
|
22
37
|
**Step 2 — Verification**:
|
|
23
38
|
|
|
@@ -31,14 +46,14 @@ Take a final screenshot for evidence.
|
|
|
31
46
|
**Step 3 — Cleanup (REQUIRED — you started it, you stop it)**:
|
|
32
47
|
|
|
33
48
|
1. Kill the dev server process: `kill $DEV_SERVER_PID 2>/dev/null || true`
|
|
34
|
-
2. Verify port is released: `lsof -ti
|
|
49
|
+
2. Verify port is released: `lsof -ti:$DEV_PORT | xargs kill -9 2>/dev/null || true`
|
|
35
50
|
|
|
36
51
|
**Step 4 — Reporting**:
|
|
37
52
|
|
|
38
53
|
Append results to `context-snapshot.md`:
|
|
39
54
|
```
|
|
40
55
|
## Browser Verification
|
|
41
|
-
URL:
|
|
56
|
+
URL: http://localhost:$DEV_PORT
|
|
42
57
|
Dev Server Command: <actual command used>
|
|
43
58
|
Steps executed: [list]
|
|
44
59
|
Screenshot: [path]
|