prizmkit 1.0.35 → 1.0.45

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (35) hide show
  1. package/bundled/VERSION.json +3 -3
  2. package/bundled/agents/prizm-dev-team-dev.md +11 -11
  3. package/bundled/agents/prizm-dev-team-reviewer.md +10 -10
  4. package/bundled/dev-pipeline/README.md +14 -17
  5. package/bundled/dev-pipeline/assets/prizm-dev-team-integration.md +16 -22
  6. package/bundled/dev-pipeline/launch-bugfix-daemon.sh +8 -0
  7. package/bundled/dev-pipeline/launch-daemon.sh +2 -0
  8. package/bundled/dev-pipeline/lib/worktree.sh +164 -0
  9. package/bundled/dev-pipeline/retry-bug.sh +5 -2
  10. package/bundled/dev-pipeline/retry-feature.sh +5 -2
  11. package/bundled/dev-pipeline/run-bugfix.sh +167 -2
  12. package/bundled/dev-pipeline/run.sh +169 -2
  13. package/bundled/dev-pipeline/scripts/check-session-status.py +3 -1
  14. package/bundled/dev-pipeline/scripts/generate-bootstrap-prompt.py +0 -8
  15. package/bundled/dev-pipeline/scripts/update-bug-status.py +24 -1
  16. package/bundled/dev-pipeline/scripts/update-feature-status.py +3 -2
  17. package/bundled/dev-pipeline/templates/bootstrap-tier1.md +3 -9
  18. package/bundled/dev-pipeline/templates/bootstrap-tier2.md +2 -8
  19. package/bundled/dev-pipeline/templates/bootstrap-tier3.md +36 -43
  20. package/bundled/dev-pipeline/templates/bugfix-bootstrap-prompt.md +1 -1
  21. package/bundled/dev-pipeline/templates/session-status-schema.json +1 -1
  22. package/bundled/dev-pipeline/tests/test_check_session.py +4 -0
  23. package/bundled/dev-pipeline/tests/test_update_feature_status.py +70 -0
  24. package/bundled/dev-pipeline/tests/test_worktree.py +236 -0
  25. package/bundled/dev-pipeline/tests/test_worktree_integration.py +796 -0
  26. package/bundled/skills/_metadata.json +1 -1
  27. package/bundled/skills/prizmkit-implement/SKILL.md +4 -2
  28. package/bundled/team/prizm-dev-team.json +3 -17
  29. package/package.json +1 -1
  30. package/src/clean.js +0 -2
  31. package/src/manifest.js +8 -4
  32. package/src/scaffold.js +69 -3
  33. package/src/upgrade.js +32 -5
  34. package/bundled/agents/prizm-dev-team-coordinator.md +0 -141
  35. package/bundled/agents/prizm-dev-team-pm.md +0 -126
@@ -25,6 +25,8 @@ set -euo pipefail
25
25
  # LOG_CLEANUP_ENABLED Run periodic log cleanup (default: 1)
26
26
  # LOG_RETENTION_DAYS Delete logs older than N days (default: 14)
27
27
  # LOG_MAX_TOTAL_MB Keep total logs under N MB via oldest-first cleanup (default: 1024)
28
+ # USE_WORKTREE Enable git worktree isolation per session (default: 1). Set to 0 to disable.
29
+ # AUTO_PUSH Auto-push to remote after successful worktree merge (default: 0). Set to 1 to enable.
28
30
  # ============================================================
29
31
 
30
32
  SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
@@ -40,6 +42,8 @@ LOG_CLEANUP_ENABLED=${LOG_CLEANUP_ENABLED:-1}
40
42
  LOG_RETENTION_DAYS=${LOG_RETENTION_DAYS:-14}
41
43
  LOG_MAX_TOTAL_MB=${LOG_MAX_TOTAL_MB:-1024}
42
44
  VERBOSE=${VERBOSE:-0}
45
+ USE_WORKTREE=${USE_WORKTREE:-1}
46
+ AUTO_PUSH=${AUTO_PUSH:-0}
43
47
 
44
48
  # Source shared common helpers (CLI/platform detection + logs + deps)
45
49
  source "$SCRIPT_DIR/lib/common.sh"
@@ -48,12 +52,19 @@ prizm_detect_cli_and_platform
48
52
  # Source shared heartbeat library
49
53
  source "$SCRIPT_DIR/lib/heartbeat.sh"
50
54
 
55
+ # Source shared worktree library
56
+ source "$SCRIPT_DIR/lib/worktree.sh"
57
+
51
58
  # Detect stream-json support
52
59
  detect_stream_json_support "$CLI_CMD"
53
60
 
54
61
  # Bug list path (set in main, used by cleanup trap)
55
62
  BUG_LIST=""
56
63
 
64
+ # Active worktree tracking (for cleanup on interrupt)
65
+ _ACTIVE_WORKTREE_PATH=""
66
+ _ACTIVE_WORKTREE_BRANCH=""
67
+
57
68
  # ============================================================
58
69
  # Shared: Spawn AI CLI session and wait for result
59
70
  # ============================================================
@@ -65,6 +76,7 @@ spawn_and_wait_session() {
65
76
  local bootstrap_prompt="$4"
66
77
  local session_dir="$5"
67
78
  local max_retries="$6"
79
+ local worktree_path="${7:-}"
68
80
 
69
81
  local session_log="$session_dir/logs/session.log"
70
82
  local progress_json="$session_dir/logs/progress.json"
@@ -88,6 +100,14 @@ spawn_and_wait_session() {
88
100
  # within an existing Claude Code session (e.g. via launch-bugfix-daemon.sh).
89
101
  unset CLAUDECODE 2>/dev/null || true
90
102
 
103
+ # If worktree path is provided, cd into it for the AI CLI session
104
+ local _saved_pwd=""
105
+ if [[ -n "$worktree_path" && -d "$worktree_path" ]]; then
106
+ _saved_pwd="$(pwd)"
107
+ cd "$worktree_path"
108
+ log_info "Running AI CLI in worktree: $worktree_path"
109
+ fi
110
+
91
111
  case "$CLI_CMD" in
92
112
  *claude*)
93
113
  # Claude Code: prompt via -p, --dangerously-skip-permissions for auto-accept
@@ -113,6 +133,11 @@ spawn_and_wait_session() {
113
133
  esac
114
134
  local cli_pid=$!
115
135
 
136
+ # Restore original directory if we changed it
137
+ if [[ -n "$_saved_pwd" ]]; then
138
+ cd "$_saved_pwd"
139
+ fi
140
+
116
141
  # Start progress parser (no-op if stream-json not supported)
117
142
  start_progress_parser "$session_log" "$progress_json" "$SCRIPTS_DIR"
118
143
  local parser_pid="${_PARSER_PID:-}"
@@ -206,6 +231,15 @@ cleanup() {
206
231
  # Kill all child processes (claude-internal, heartbeat, progress parser, etc.)
207
232
  kill 0 2>/dev/null || true
208
233
 
234
+ # Clean up active worktree if any
235
+ if [[ -n "$_ACTIVE_WORKTREE_PATH" ]]; then
236
+ local _project_root
237
+ _project_root="$(cd "$SCRIPT_DIR/.." && pwd)"
238
+ worktree_cleanup "$_project_root" "$_ACTIVE_WORKTREE_PATH" "$_ACTIVE_WORKTREE_BRANCH"
239
+ _ACTIVE_WORKTREE_PATH=""
240
+ _ACTIVE_WORKTREE_BRANCH=""
241
+ fi
242
+
209
243
  if [[ -n "$BUG_LIST" && -f "$BUG_LIST" ]]; then
210
244
  python3 "$SCRIPTS_DIR/update-bug-status.py" \
211
245
  --bug-list "$BUG_LIST" \
@@ -386,17 +420,80 @@ sys.exit(1)
386
420
  echo ""
387
421
  log_warn "Interrupted. Killing session..."
388
422
  kill 0 2>/dev/null || true
423
+ # Clean up active worktree if any
424
+ if [[ -n "$_ACTIVE_WORKTREE_PATH" ]]; then
425
+ local _proj_root
426
+ _proj_root="$(cd "$SCRIPT_DIR/.." && pwd)"
427
+ worktree_cleanup "$_proj_root" "$_ACTIVE_WORKTREE_PATH" "$_ACTIVE_WORKTREE_BRANCH"
428
+ _ACTIVE_WORKTREE_PATH=""
429
+ _ACTIVE_WORKTREE_BRANCH=""
430
+ fi
389
431
  log_info "Session log: $session_dir/logs/session.log"
390
432
  exit 130
391
433
  }
392
434
  trap cleanup_single_bug SIGINT SIGTERM
393
435
 
394
436
  _SPAWN_RESULT=""
437
+
438
+ # Worktree lifecycle: create worktree before session if enabled
439
+ local _wt_path="" _wt_branch=""
440
+ if [[ "$USE_WORKTREE" == "1" ]]; then
441
+ local _proj_root
442
+ _proj_root="$(cd "$SCRIPT_DIR/.." && pwd)"
443
+ local _wt_base="$STATE_DIR/worktrees"
444
+ local _source_branch
445
+ _source_branch=$(git -C "$_proj_root" rev-parse --abbrev-ref HEAD 2>/dev/null || echo "main")
446
+
447
+ # Prune stale worktrees before creating new one
448
+ worktree_prune_stale "$_proj_root"
449
+
450
+ _WORKTREE_PATH=""
451
+ _WORKTREE_BRANCH=""
452
+ if worktree_create "$_proj_root" "$_wt_base" "$session_id" "$_source_branch"; then
453
+ _wt_path="$_WORKTREE_PATH"
454
+ _wt_branch="$_WORKTREE_BRANCH"
455
+ _ACTIVE_WORKTREE_PATH="$_wt_path"
456
+ _ACTIVE_WORKTREE_BRANCH="$_wt_branch"
457
+ else
458
+ log_warn "Failed to create worktree; running session in main working tree"
459
+ fi
460
+ fi
461
+
395
462
  spawn_and_wait_session \
396
463
  "$bug_id" "$bug_list" "$session_id" \
397
- "$bootstrap_prompt" "$session_dir" 999
464
+ "$bootstrap_prompt" "$session_dir" 999 "$_wt_path"
398
465
  local session_status="$_SPAWN_RESULT"
399
466
 
467
+ # Worktree lifecycle: merge and cleanup after session
468
+ if [[ -n "$_wt_path" && -n "$_wt_branch" ]]; then
469
+ local _proj_root
470
+ _proj_root="$(cd "$SCRIPT_DIR/.." && pwd)"
471
+ local _target_branch
472
+ _target_branch=$(git -C "$_proj_root" rev-parse --abbrev-ref HEAD 2>/dev/null || echo "main")
473
+
474
+ if [[ "$session_status" == "success" ]]; then
475
+ _MERGE_RESULT=""
476
+ worktree_merge "$_proj_root" "$_wt_branch" "$_target_branch" "$bug_id" "$session_id" || true
477
+ if [[ "$_MERGE_RESULT" == "success" ]]; then
478
+ if [[ "$AUTO_PUSH" == "1" ]]; then
479
+ log_info "AUTO_PUSH enabled; pushing to remote..."
480
+ git -C "$_proj_root" push 2>/dev/null || log_warn "Auto-push failed"
481
+ fi
482
+ worktree_cleanup "$_proj_root" "$_wt_path" "$_wt_branch"
483
+ elif [[ "$_MERGE_RESULT" == "conflict" ]]; then
484
+ session_status="merge_conflict"
485
+ _SPAWN_RESULT="merge_conflict"
486
+ log_warn "Worktree branch preserved for manual conflict resolution: $_wt_branch"
487
+ else
488
+ worktree_cleanup "$_proj_root" "$_wt_path" "$_wt_branch"
489
+ fi
490
+ else
491
+ worktree_cleanup "$_proj_root" "$_wt_path" "$_wt_branch"
492
+ fi
493
+ _ACTIVE_WORKTREE_PATH=""
494
+ _ACTIVE_WORKTREE_BRANCH=""
495
+ fi
496
+
400
497
  echo ""
401
498
  if [[ "$session_status" == "success" ]]; then
402
499
  log_success "════════════════════════════════════════════════════"
@@ -432,6 +529,13 @@ main() {
432
529
  check_dependencies
433
530
  run_log_cleanup
434
531
 
532
+ # Prune stale worktree references at startup
533
+ if [[ "$USE_WORKTREE" == "1" ]]; then
534
+ local _prune_root
535
+ _prune_root="$(cd "$SCRIPT_DIR/.." && pwd)"
536
+ worktree_prune_stale "$_prune_root"
537
+ fi
538
+
435
539
  # Initialize pipeline state if needed
436
540
  if [[ ! -f "$STATE_DIR/pipeline.json" ]]; then
437
541
  log_info "Initializing bugfix pipeline state..."
@@ -557,9 +661,70 @@ os.replace(tmp, target)
557
661
  # Spawn session
558
662
  log_info "Spawning AI CLI session: $session_id"
559
663
  _SPAWN_RESULT=""
664
+
665
+ # Worktree lifecycle: create worktree before session if enabled
666
+ local _wt_path="" _wt_branch=""
667
+ if [[ "$USE_WORKTREE" == "1" ]]; then
668
+ local _proj_root
669
+ _proj_root="$(cd "$SCRIPT_DIR/.." && pwd)"
670
+ local _wt_base="$STATE_DIR/worktrees"
671
+ local _source_branch
672
+ _source_branch=$(git -C "$_proj_root" rev-parse --abbrev-ref HEAD 2>/dev/null || echo "main")
673
+
674
+ _WORKTREE_PATH=""
675
+ _WORKTREE_BRANCH=""
676
+ if worktree_create "$_proj_root" "$_wt_base" "$session_id" "$_source_branch"; then
677
+ _wt_path="$_WORKTREE_PATH"
678
+ _wt_branch="$_WORKTREE_BRANCH"
679
+ _ACTIVE_WORKTREE_PATH="$_wt_path"
680
+ _ACTIVE_WORKTREE_BRANCH="$_wt_branch"
681
+ else
682
+ log_warn "Failed to create worktree; running session in main working tree"
683
+ fi
684
+ fi
685
+
560
686
  spawn_and_wait_session \
561
687
  "$bug_id" "$bug_list" "$session_id" \
562
- "$bootstrap_prompt" "$session_dir" "$MAX_RETRIES"
688
+ "$bootstrap_prompt" "$session_dir" "$MAX_RETRIES" "$_wt_path"
689
+
690
+ # Worktree lifecycle: merge and cleanup after session
691
+ if [[ -n "$_wt_path" && -n "$_wt_branch" ]]; then
692
+ local _proj_root
693
+ _proj_root="$(cd "$SCRIPT_DIR/.." && pwd)"
694
+ local _target_branch
695
+ _target_branch=$(git -C "$_proj_root" rev-parse --abbrev-ref HEAD 2>/dev/null || echo "main")
696
+ local _session_status="$_SPAWN_RESULT"
697
+
698
+ if [[ "$_session_status" == "success" ]]; then
699
+ _MERGE_RESULT=""
700
+ worktree_merge "$_proj_root" "$_wt_branch" "$_target_branch" "$bug_id" "$session_id" || true
701
+ if [[ "$_MERGE_RESULT" == "success" ]]; then
702
+ if [[ "$AUTO_PUSH" == "1" ]]; then
703
+ log_info "AUTO_PUSH enabled; pushing to remote..."
704
+ git -C "$_proj_root" push 2>/dev/null || log_warn "Auto-push failed"
705
+ fi
706
+ worktree_cleanup "$_proj_root" "$_wt_path" "$_wt_branch"
707
+ elif [[ "$_MERGE_RESULT" == "conflict" ]]; then
708
+ _SPAWN_RESULT="merge_conflict"
709
+ log_warn "Worktree branch preserved for manual conflict resolution: $_wt_branch"
710
+ # Update bug status to merge_conflict
711
+ python3 "$SCRIPTS_DIR/update-bug-status.py" \
712
+ --bug-list "$bug_list" \
713
+ --state-dir "$STATE_DIR" \
714
+ --bug-id "$bug_id" \
715
+ --session-status "merge_conflict" \
716
+ --session-id "$session_id" \
717
+ --max-retries "$MAX_RETRIES" \
718
+ --action update >/dev/null 2>&1 || true
719
+ else
720
+ worktree_cleanup "$_proj_root" "$_wt_path" "$_wt_branch"
721
+ fi
722
+ else
723
+ worktree_cleanup "$_proj_root" "$_wt_path" "$_wt_branch"
724
+ fi
725
+ _ACTIVE_WORKTREE_PATH=""
726
+ _ACTIVE_WORKTREE_BRANCH=""
727
+ fi
563
728
 
564
729
  session_count=$((session_count + 1))
565
730
 
@@ -28,6 +28,8 @@ set -euo pipefail
28
28
  # LOG_RETENTION_DAYS Delete logs older than N days (default: 14)
29
29
  # LOG_MAX_TOTAL_MB Keep total logs under N MB via oldest-first cleanup (default: 1024)
30
30
  # PIPELINE_MODE Override mode for all features: lite|standard|full|self-evolve (used by daemon)
31
+ # USE_WORKTREE Enable git worktree isolation per session (default: 1). Set to 0 to disable.
32
+ # AUTO_PUSH Auto-push to remote after successful worktree merge (default: 0). Set to 1 to enable.
31
33
  # ============================================================
32
34
 
33
35
  SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
@@ -44,6 +46,8 @@ LOG_RETENTION_DAYS=${LOG_RETENTION_DAYS:-14}
44
46
  LOG_MAX_TOTAL_MB=${LOG_MAX_TOTAL_MB:-1024}
45
47
  VERBOSE=${VERBOSE:-0}
46
48
  MODEL=${MODEL:-""}
49
+ USE_WORKTREE=${USE_WORKTREE:-1}
50
+ AUTO_PUSH=${AUTO_PUSH:-0}
47
51
 
48
52
  # Source shared common helpers (CLI/platform detection + logs + deps)
49
53
  source "$SCRIPT_DIR/lib/common.sh"
@@ -52,12 +56,19 @@ prizm_detect_cli_and_platform
52
56
  # Source shared heartbeat library
53
57
  source "$SCRIPT_DIR/lib/heartbeat.sh"
54
58
 
59
+ # Source shared worktree library
60
+ source "$SCRIPT_DIR/lib/worktree.sh"
61
+
55
62
  # Detect stream-json support
56
63
  detect_stream_json_support "$CLI_CMD"
57
64
 
58
65
  # Feature list path (set in main, used by cleanup trap)
59
66
  FEATURE_LIST=""
60
67
 
68
+ # Active worktree tracking (for cleanup on interrupt)
69
+ _ACTIVE_WORKTREE_PATH=""
70
+ _ACTIVE_WORKTREE_BRANCH=""
71
+
61
72
  # ============================================================
62
73
  # Shared: Spawn an AI CLI session and wait for result
63
74
  # ============================================================
@@ -72,6 +83,7 @@ FEATURE_LIST=""
72
83
  # $4 - bootstrap_prompt (path)
73
84
  # $5 - session_dir
74
85
  # $6 - max_retries (for status update)
86
+ # $7 - worktree_path (optional; if set, AI CLI runs inside this directory)
75
87
  spawn_and_wait_session() {
76
88
  local feature_id="$1"
77
89
  local feature_list="$2"
@@ -79,6 +91,7 @@ spawn_and_wait_session() {
79
91
  local bootstrap_prompt="$4"
80
92
  local session_dir="$5"
81
93
  local max_retries="$6"
94
+ local worktree_path="${7:-}"
82
95
 
83
96
  local session_log="$session_dir/logs/session.log"
84
97
  local progress_json="$session_dir/logs/progress.json"
@@ -103,6 +116,14 @@ spawn_and_wait_session() {
103
116
  # within an existing Claude Code session (e.g. via launch-daemon.sh).
104
117
  unset CLAUDECODE 2>/dev/null || true
105
118
 
119
+ # If worktree path is provided, cd into it for the AI CLI session
120
+ local _saved_pwd=""
121
+ if [[ -n "$worktree_path" && -d "$worktree_path" ]]; then
122
+ _saved_pwd="$(pwd)"
123
+ cd "$worktree_path"
124
+ log_info "Running AI CLI in worktree: $worktree_path"
125
+ fi
126
+
106
127
  case "$CLI_CMD" in
107
128
  *claude*)
108
129
  # Claude Code: prompt via -p argument, --dangerously-skip-permissions for auto-accept
@@ -128,6 +149,11 @@ spawn_and_wait_session() {
128
149
  esac
129
150
  local cbc_pid=$!
130
151
 
152
+ # Restore original directory if we changed it
153
+ if [[ -n "$_saved_pwd" ]]; then
154
+ cd "$_saved_pwd"
155
+ fi
156
+
131
157
  # Start progress parser (no-op if stream-json not supported)
132
158
  start_progress_parser "$session_log" "$progress_json" "$SCRIPTS_DIR"
133
159
  local parser_pid="${_PARSER_PID:-}"
@@ -254,6 +280,15 @@ cleanup() {
254
280
  # Kill all child processes (claude-internal, heartbeat, progress parser, etc.)
255
281
  kill 0 2>/dev/null || true
256
282
 
283
+ # Clean up active worktree if any
284
+ if [[ -n "$_ACTIVE_WORKTREE_PATH" ]]; then
285
+ local _project_root
286
+ _project_root="$(cd "$SCRIPT_DIR/.." && pwd)"
287
+ worktree_cleanup "$_project_root" "$_ACTIVE_WORKTREE_PATH" "$_ACTIVE_WORKTREE_BRANCH"
288
+ _ACTIVE_WORKTREE_PATH=""
289
+ _ACTIVE_WORKTREE_BRANCH=""
290
+ fi
291
+
257
292
  if [[ -n "$FEATURE_LIST" && -f "$FEATURE_LIST" ]]; then
258
293
  python3 "$SCRIPTS_DIR/update-feature-status.py" \
259
294
  --feature-list "$FEATURE_LIST" \
@@ -588,17 +623,80 @@ sys.exit(1)
588
623
  log_warn "Interrupted. Killing session..."
589
624
  # Kill all child processes
590
625
  kill 0 2>/dev/null || true
626
+ # Clean up active worktree if any
627
+ if [[ -n "$_ACTIVE_WORKTREE_PATH" ]]; then
628
+ local _proj_root
629
+ _proj_root="$(cd "$SCRIPT_DIR/.." && pwd)"
630
+ worktree_cleanup "$_proj_root" "$_ACTIVE_WORKTREE_PATH" "$_ACTIVE_WORKTREE_BRANCH"
631
+ _ACTIVE_WORKTREE_PATH=""
632
+ _ACTIVE_WORKTREE_BRANCH=""
633
+ fi
591
634
  log_info "Session log: $session_dir/logs/session.log"
592
635
  exit 130
593
636
  }
594
637
  trap cleanup_single_feature SIGINT SIGTERM
595
638
 
596
639
  _SPAWN_RESULT=""
640
+
641
+ # Worktree lifecycle: create worktree before session if enabled
642
+ local _wt_path="" _wt_branch=""
643
+ if [[ "$USE_WORKTREE" == "1" ]]; then
644
+ local _proj_root
645
+ _proj_root="$(cd "$SCRIPT_DIR/.." && pwd)"
646
+ local _wt_base="$STATE_DIR/worktrees"
647
+ local _source_branch
648
+ _source_branch=$(git -C "$_proj_root" rev-parse --abbrev-ref HEAD 2>/dev/null || echo "main")
649
+
650
+ # Prune stale worktrees before creating new one
651
+ worktree_prune_stale "$_proj_root"
652
+
653
+ _WORKTREE_PATH=""
654
+ _WORKTREE_BRANCH=""
655
+ if worktree_create "$_proj_root" "$_wt_base" "$session_id" "$_source_branch"; then
656
+ _wt_path="$_WORKTREE_PATH"
657
+ _wt_branch="$_WORKTREE_BRANCH"
658
+ _ACTIVE_WORKTREE_PATH="$_wt_path"
659
+ _ACTIVE_WORKTREE_BRANCH="$_wt_branch"
660
+ else
661
+ log_warn "Failed to create worktree; running session in main working tree"
662
+ fi
663
+ fi
664
+
597
665
  spawn_and_wait_session \
598
666
  "$feature_id" "$feature_list" "$session_id" \
599
- "$bootstrap_prompt" "$session_dir" 999
667
+ "$bootstrap_prompt" "$session_dir" 999 "$_wt_path"
600
668
  local session_status="$_SPAWN_RESULT"
601
669
 
670
+ # Worktree lifecycle: merge and cleanup after session
671
+ if [[ -n "$_wt_path" && -n "$_wt_branch" ]]; then
672
+ local _proj_root
673
+ _proj_root="$(cd "$SCRIPT_DIR/.." && pwd)"
674
+ local _target_branch
675
+ _target_branch=$(git -C "$_proj_root" rev-parse --abbrev-ref HEAD 2>/dev/null || echo "main")
676
+
677
+ if [[ "$session_status" == "success" ]]; then
678
+ _MERGE_RESULT=""
679
+ worktree_merge "$_proj_root" "$_wt_branch" "$_target_branch" "$feature_id" "$session_id" || true
680
+ if [[ "$_MERGE_RESULT" == "success" ]]; then
681
+ if [[ "$AUTO_PUSH" == "1" ]]; then
682
+ log_info "AUTO_PUSH enabled; pushing to remote..."
683
+ git -C "$_proj_root" push 2>/dev/null || log_warn "Auto-push failed"
684
+ fi
685
+ worktree_cleanup "$_proj_root" "$_wt_path" "$_wt_branch"
686
+ elif [[ "$_MERGE_RESULT" == "conflict" ]]; then
687
+ session_status="merge_conflict"
688
+ _SPAWN_RESULT="merge_conflict"
689
+ log_warn "Worktree branch preserved for manual conflict resolution: $_wt_branch"
690
+ else
691
+ worktree_cleanup "$_proj_root" "$_wt_path" "$_wt_branch"
692
+ fi
693
+ else
694
+ worktree_cleanup "$_proj_root" "$_wt_path" "$_wt_branch"
695
+ fi
696
+ _ACTIVE_WORKTREE_PATH=""
697
+ _ACTIVE_WORKTREE_BRANCH=""
698
+ fi
699
+
602
700
  echo ""
603
701
  if [[ "$session_status" == "success" ]]; then
604
702
  # Self-evolve mode: run framework validation after successful session
@@ -668,6 +766,13 @@ main() {
668
766
  check_dependencies
669
767
  run_log_cleanup
670
768
 
769
+ # Prune stale worktree references at startup
770
+ if [[ "$USE_WORKTREE" == "1" ]]; then
771
+ local _prune_root
772
+ _prune_root="$(cd "$SCRIPT_DIR/.." && pwd)"
773
+ worktree_prune_stale "$_prune_root"
774
+ fi
775
+
671
776
  # Initialize pipeline state if needed
672
777
  if [[ ! -f "$STATE_DIR/pipeline.json" ]]; then
673
778
  log_info "Initializing pipeline state..."
@@ -842,11 +947,73 @@ os.replace(tmp, target)
842
947
  # Spawn session and wait
843
948
  log_info "Spawning AI CLI session: $session_id"
844
949
  _SPAWN_RESULT=""
950
+
951
+ # Worktree lifecycle: create worktree before session if enabled
952
+ local _wt_path="" _wt_branch=""
953
+ if [[ "$USE_WORKTREE" == "1" ]]; then
954
+ local _proj_root
955
+ _proj_root="$(cd "$SCRIPT_DIR/.." && pwd)"
956
+ local _wt_base="$STATE_DIR/worktrees"
957
+ local _source_branch
958
+ _source_branch=$(git -C "$_proj_root" rev-parse --abbrev-ref HEAD 2>/dev/null || echo "main")
959
+
960
+ _WORKTREE_PATH=""
961
+ _WORKTREE_BRANCH=""
962
+ if worktree_create "$_proj_root" "$_wt_base" "$session_id" "$_source_branch"; then
963
+ _wt_path="$_WORKTREE_PATH"
964
+ _wt_branch="$_WORKTREE_BRANCH"
965
+ _ACTIVE_WORKTREE_PATH="$_wt_path"
966
+ _ACTIVE_WORKTREE_BRANCH="$_wt_branch"
967
+ else
968
+ log_warn "Failed to create worktree; running session in main working tree"
969
+ fi
970
+ fi
971
+
845
972
  spawn_and_wait_session \
846
973
  "$feature_id" "$feature_list" "$session_id" \
847
- "$bootstrap_prompt" "$session_dir" "$MAX_RETRIES"
974
+ "$bootstrap_prompt" "$session_dir" "$MAX_RETRIES" "$_wt_path"
848
975
  local session_status="$_SPAWN_RESULT"
849
976
 
977
+ # Worktree lifecycle: merge and cleanup after session
978
+ if [[ -n "$_wt_path" && -n "$_wt_branch" ]]; then
979
+ local _proj_root
980
+ _proj_root="$(cd "$SCRIPT_DIR/.." && pwd)"
981
+ local _target_branch
982
+ _target_branch=$(git -C "$_proj_root" rev-parse --abbrev-ref HEAD 2>/dev/null || echo "main")
983
+
984
+ if [[ "$session_status" == "success" ]]; then
985
+ _MERGE_RESULT=""
986
+ worktree_merge "$_proj_root" "$_wt_branch" "$_target_branch" "$feature_id" "$session_id" || true
987
+ if [[ "$_MERGE_RESULT" == "success" ]]; then
988
+ if [[ "$AUTO_PUSH" == "1" ]]; then
989
+ log_info "AUTO_PUSH enabled; pushing to remote..."
990
+ git -C "$_proj_root" push 2>/dev/null || log_warn "Auto-push failed"
991
+ fi
992
+ worktree_cleanup "$_proj_root" "$_wt_path" "$_wt_branch"
993
+ elif [[ "$_MERGE_RESULT" == "conflict" ]]; then
994
+ session_status="merge_conflict"
995
+ _SPAWN_RESULT="merge_conflict"
996
+ log_warn "Worktree branch preserved for manual conflict resolution: $_wt_branch"
997
+ # Update feature status to merge_conflict
998
+ python3 "$SCRIPTS_DIR/update-feature-status.py" \
999
+ --feature-list "$feature_list" \
1000
+ --state-dir "$STATE_DIR" \
1001
+ --feature-id "$feature_id" \
1002
+ --session-status "merge_conflict" \
1003
+ --session-id "$session_id" \
1004
+ --max-retries "$MAX_RETRIES" \
1005
+ --action update >/dev/null 2>&1 || true
1006
+ else
1007
+ worktree_cleanup "$_proj_root" "$_wt_path" "$_wt_branch"
1008
+ fi
1009
+ else
1010
+ # Non-success: cleanup worktree
1011
+ worktree_cleanup "$_proj_root" "$_wt_path" "$_wt_branch"
1012
+ fi
1013
+ _ACTIVE_WORKTREE_PATH=""
1014
+ _ACTIVE_WORKTREE_BRANCH=""
1015
+ fi
1016
+
850
1017
  session_count=$((session_count + 1))
851
1018
 
852
1019
  # Brief pause before next iteration
@@ -66,7 +66,7 @@ def determine_status(data):
66
66
  """Determine the single-line status string from the parsed data.
67
67
 
68
68
  Returns one of: success, partial_resumable, partial_not_resumable,
69
- failed, commit_missing, docs_missing.
69
+ failed, commit_missing, docs_missing, merge_conflict.
70
70
  """
71
71
  status = data.get("status", "")
72
72
 
@@ -84,6 +84,8 @@ def determine_status(data):
84
84
  return "commit_missing"
85
85
  elif status == "docs_missing":
86
86
  return "docs_missing"
87
+ elif status == "merge_conflict":
88
+ return "merge_conflict"
87
89
  else:
88
90
  # Unknown status value — treat as crashed
89
91
  return "crashed"
@@ -408,12 +408,6 @@ def build_replacements(args, feature, features, global_context, script_dir):
408
408
  )
409
409
 
410
410
  # Agent definitions are .md files in the platform-specific agents dir
411
- coordinator_subagent = os.path.join(
412
- agents_dir, "prizm-dev-team-coordinator.md",
413
- )
414
- pm_subagent = os.path.join(
415
- agents_dir, "prizm-dev-team-pm.md",
416
- )
417
411
  dev_subagent = os.path.join(
418
412
  agents_dir, "prizm-dev-team-dev.md",
419
413
  )
@@ -475,8 +469,6 @@ def build_replacements(args, feature, features, global_context, script_dir):
475
469
  ),
476
470
  "{{GLOBAL_CONTEXT}}": format_global_context(global_context),
477
471
  "{{TEAM_CONFIG_PATH}}": team_config_path,
478
- "{{COORDINATOR_SUBAGENT_PATH}}": coordinator_subagent,
479
- "{{PM_SUBAGENT_PATH}}": pm_subagent,
480
472
  "{{DEV_SUBAGENT_PATH}}": dev_subagent,
481
473
  "{{REVIEWER_SUBAGENT_PATH}}": reviewer_subagent,
482
474
  "{{VALIDATOR_SCRIPTS_DIR}}": validator_scripts_dir,
@@ -39,6 +39,7 @@ SESSION_STATUS_VALUES = [
39
39
  "failed",
40
40
  "crashed",
41
41
  "timed_out",
42
+ "merge_conflict",
42
43
  ]
43
44
 
44
45
  TERMINAL_STATUSES = {"completed", "failed", "skipped", "needs_info"}
@@ -237,6 +238,25 @@ def action_update(args, bug_list_path, state_dir):
237
238
  if err:
238
239
  error_out("Failed to update bug-fix-list.json: {}".format(err))
239
240
  return
241
+ elif session_status == "merge_conflict":
242
+ # Degraded outcome: keep artifacts for retry (branch preserves work)
243
+ bs["retry_count"] = bs.get("retry_count", 0) + 1
244
+
245
+ if bs["retry_count"] >= max_retries:
246
+ bs["status"] = "failed"
247
+ target_status = "failed"
248
+ else:
249
+ bs["status"] = "merge_conflict"
250
+ target_status = "merge_conflict"
251
+
252
+ bs["resume_from_phase"] = None
253
+ bs["sessions"] = []
254
+ bs["last_session_id"] = None
255
+
256
+ err = update_bug_in_list(bug_list_path, bug_id, target_status)
257
+ if err:
258
+ error_out("Failed to update bug-fix-list.json: {}".format(err))
259
+ return
240
260
  else:
241
261
  bs["retry_count"] = bs.get("retry_count", 0) + 1
242
262
 
@@ -285,7 +305,10 @@ def action_update(args, bug_list_path, state_dir):
285
305
  "resume_from_phase": bs.get("resume_from_phase"),
286
306
  "updated_at": bs["updated_at"],
287
307
  }
288
- if session_status != "success":
308
+ if session_status == "merge_conflict":
309
+ summary["degraded_reason"] = "merge_conflict"
310
+ summary["restart_policy"] = "finalization_retry"
311
+ elif session_status != "success":
289
312
  summary["restart_policy"] = "full_restart"
290
313
  summary["cleanup_performed"] = cleaned
291
314
 
@@ -44,6 +44,7 @@ SESSION_STATUS_VALUES = [
44
44
  "timed_out",
45
45
  "commit_missing",
46
46
  "docs_missing",
47
+ "merge_conflict",
47
48
  ]
48
49
 
49
50
  TERMINAL_STATUSES = {"completed", "failed", "skipped"}
@@ -441,7 +442,7 @@ def action_update(args, feature_list_path, state_dir):
441
442
  if err:
442
443
  error_out("Failed to update feature-list.json: {}".format(err))
443
444
  return
444
- elif session_status in ("commit_missing", "docs_missing"):
445
+ elif session_status in ("commit_missing", "docs_missing", "merge_conflict"):
445
446
  # Degraded outcome: keep artifacts for retry and expose specific status.
446
447
  fs["retry_count"] = fs.get("retry_count", 0) + 1
447
448
 
@@ -509,7 +510,7 @@ def action_update(args, feature_list_path, state_dir):
509
510
  "resume_from_phase": fs.get("resume_from_phase"),
510
511
  "updated_at": fs["updated_at"],
511
512
  }
512
- if session_status in ("commit_missing", "docs_missing"):
513
+ if session_status in ("commit_missing", "docs_missing", "merge_conflict"):
513
514
  summary["degraded_reason"] = session_status
514
515
  summary["restart_policy"] = "finalization_retry"
515
516
  elif session_status != "success":
@@ -72,7 +72,7 @@ If MISSING — build it now:
72
72
  - **Section 1 — Feature Brief**: feature description + acceptance criteria (copy from above)
73
73
  - **Section 2 — Project Structure**: output of relevant `ls src/` calls
74
74
  - **Section 3 — Prizm Context**: content of root.prizm and relevant L1/L2 docs
75
- - **Section 4 — Existing Source Files**: full content of each related file as code block
75
+ - **Section 4 — Existing Source Files**: **full verbatim content** of each related file in fenced code blocks (with `### path/to/file` heading and line count). Include ALL files needed for implementation and review — downstream phases read this section instead of re-reading individual source files
76
76
  - **Section 5 — Existing Tests**: full content of related test files as code block
77
77
 
78
78
  ### Phase 2: Plan & Tasks
@@ -81,7 +81,7 @@ If MISSING — build it now:
81
81
  ls .prizmkit/specs/{{FEATURE_SLUG}}/ 2>/dev/null
82
82
  ```
83
83
 
84
- If plan.md or tasks.md missing, write them directly (no PM needed):
84
+ If plan.md or tasks.md missing, write them directly:
85
85
  - `plan.md`: key components, data flow, files to create/modify (under 80 lines)
86
86
  - `tasks.md`: checklist with `[ ]` checkboxes, each task = one implementable unit
87
87
 
@@ -129,15 +129,9 @@ Stage any `.prizm-docs/` changes produced: `git add .prizm-docs/`
129
129
  ### Phase 5: Commit
130
130
 
131
131
  - Run `/prizmkit-summarize` → archive to REGISTRY.md
132
- - Mark feature complete:
133
- ```bash
134
- python3 {{VALIDATOR_SCRIPTS_DIR}}/update-feature-status.py \
135
- --feature-list "{{FEATURE_LIST_PATH}}" \
136
- --state-dir "{{PROJECT_ROOT}}/dev-pipeline/state" \
137
- --feature-id "{{FEATURE_ID}}" --session-id "{{SESSION_ID}}" --action complete
138
- ```
139
132
  - Run `/prizmkit-committer` → `feat({{FEATURE_ID}}): {{FEATURE_TITLE}}`, do NOT push
140
133
  - MANDATORY: commit must be done via `/prizmkit-committer` skill. Do NOT run manual `git add`/`git commit` as a substitute.
134
+ - Do NOT run `update-feature-status.py` here — the pipeline runner handles feature-list.json updates automatically after session exit.
141
135
 
142
136
  ---
143
137