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
|
@@ -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]
|