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.
- package/bundled/VERSION.json +3 -3
- package/bundled/agents/prizm-dev-team-dev.md +11 -11
- package/bundled/agents/prizm-dev-team-reviewer.md +10 -10
- package/bundled/dev-pipeline/README.md +14 -17
- package/bundled/dev-pipeline/assets/prizm-dev-team-integration.md +16 -22
- package/bundled/dev-pipeline/launch-bugfix-daemon.sh +8 -0
- package/bundled/dev-pipeline/launch-daemon.sh +2 -0
- package/bundled/dev-pipeline/lib/worktree.sh +164 -0
- package/bundled/dev-pipeline/retry-bug.sh +5 -2
- package/bundled/dev-pipeline/retry-feature.sh +5 -2
- package/bundled/dev-pipeline/run-bugfix.sh +167 -2
- package/bundled/dev-pipeline/run.sh +169 -2
- package/bundled/dev-pipeline/scripts/check-session-status.py +3 -1
- package/bundled/dev-pipeline/scripts/generate-bootstrap-prompt.py +0 -8
- package/bundled/dev-pipeline/scripts/update-bug-status.py +24 -1
- package/bundled/dev-pipeline/scripts/update-feature-status.py +3 -2
- package/bundled/dev-pipeline/templates/bootstrap-tier1.md +3 -9
- package/bundled/dev-pipeline/templates/bootstrap-tier2.md +2 -8
- package/bundled/dev-pipeline/templates/bootstrap-tier3.md +36 -43
- package/bundled/dev-pipeline/templates/bugfix-bootstrap-prompt.md +1 -1
- package/bundled/dev-pipeline/templates/session-status-schema.json +1 -1
- package/bundled/dev-pipeline/tests/test_check_session.py +4 -0
- package/bundled/dev-pipeline/tests/test_update_feature_status.py +70 -0
- package/bundled/dev-pipeline/tests/test_worktree.py +236 -0
- package/bundled/dev-pipeline/tests/test_worktree_integration.py +796 -0
- package/bundled/skills/_metadata.json +1 -1
- package/bundled/skills/prizmkit-implement/SKILL.md +4 -2
- package/bundled/team/prizm-dev-team.json +3 -17
- package/package.json +1 -1
- package/src/clean.js +0 -2
- package/src/manifest.js +8 -4
- package/src/scaffold.js +69 -3
- package/src/upgrade.js +32 -5
- package/bundled/agents/prizm-dev-team-coordinator.md +0 -141
- 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
|
|
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
|
|
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
|
|
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
|
|