prizmkit 1.1.27 → 1.1.29

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.
@@ -0,0 +1,691 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ # ============================================================
5
+ # dev-pipeline/run-recovery.sh - Recover Interrupted Workflow Sessions
6
+ #
7
+ # Auto-detects which workflow (feature-workflow, bug-fix-workflow,
8
+ # refactor-workflow) was interrupted and what phase it reached,
9
+ # then generates a comprehensive bootstrap prompt and spawns an
10
+ # AI CLI session to complete all remaining phases.
11
+ #
12
+ # Unlike run-feature.sh / run-bugfix.sh / run-refactor.sh, this
13
+ # is a single-shot operation (no pipeline loop) for one detected
14
+ # interrupted workflow.
15
+ #
16
+ # Usage:
17
+ # ./run-recovery.sh Auto-detect and recover
18
+ # ./run-recovery.sh run Same as above
19
+ # ./run-recovery.sh run --dry-run Generate prompt only
20
+ # ./run-recovery.sh run --yes Skip confirmation
21
+ # ./run-recovery.sh run --model <model> Override AI model
22
+ # ./run-recovery.sh detect Detection report only
23
+ # ./run-recovery.sh help Show help
24
+ #
25
+ # Environment Variables:
26
+ # SESSION_TIMEOUT Session timeout in seconds (default: 0 = no limit)
27
+ # AI_CLI AI CLI command name (auto-detected: cbc or claude)
28
+ # PRIZMKIT_PLATFORM Force platform: 'codebuddy' or 'claude' (auto-detected)
29
+ # MODEL AI model to use (e.g. claude-opus-4.6)
30
+ # VERBOSE Set to 1 to enable --verbose on AI CLI
31
+ # HEARTBEAT_INTERVAL Heartbeat log interval in seconds (default: 30)
32
+ # STALE_KILL_THRESHOLD Auto-kill after N seconds of no progress (default: 900)
33
+ # AUTO_PUSH Auto-push after successful recovery (default: 0)
34
+ # ============================================================
35
+
36
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
37
+ PROJECT_ROOT="$(dirname "$SCRIPT_DIR")"
38
+ RECOVERY_STATE_DIR="${PROJECT_ROOT}/.prizmkit/state/recovery"
39
+ SCRIPTS_DIR="$SCRIPT_DIR/scripts"
40
+ RECOVERY_DETECT_SCRIPT="${PROJECT_ROOT}/core/skills/orchestration-skill/workflows/recovery-workflow/scripts/detect-recovery-state.py"
41
+
42
+ # Configuration
43
+ SESSION_TIMEOUT=${SESSION_TIMEOUT:-0}
44
+ HEARTBEAT_INTERVAL=${HEARTBEAT_INTERVAL:-30}
45
+ STALE_KILL_THRESHOLD=${STALE_KILL_THRESHOLD:-900}
46
+ VERBOSE=${VERBOSE:-0}
47
+ MODEL=${MODEL:-""}
48
+ AUTO_PUSH=${AUTO_PUSH:-0}
49
+
50
+ # Source shared common helpers (CLI/platform detection + logs + deps)
51
+ source "$SCRIPT_DIR/lib/common.sh"
52
+ prizm_detect_cli_and_platform
53
+
54
+ # Source shared heartbeat library
55
+ source "$SCRIPT_DIR/lib/heartbeat.sh"
56
+
57
+ # Source shared branch library
58
+ source "$SCRIPT_DIR/lib/branch.sh"
59
+
60
+ # Detect stream-json support
61
+ detect_stream_json_support "$CLI_CMD"
62
+
63
+ # Session tracking (for cleanup)
64
+ _SESSION_DIR=""
65
+ _SESSION_PID=""
66
+
67
+ # ============================================================
68
+ # Help
69
+ # ============================================================
70
+
71
+ show_help() {
72
+ cat <<'HELP'
73
+ Usage: ./run-recovery.sh [command] [options]
74
+
75
+ Commands:
76
+ run Auto-detect interrupted workflow and recover (default)
77
+ detect Show detection report only (no execution)
78
+ help Show this help
79
+
80
+ Options (for 'run' command):
81
+ --dry-run Generate bootstrap prompt without spawning AI session
82
+ --yes Skip user confirmation
83
+ --model M Override AI model (e.g. claude-opus-4.6)
84
+
85
+ Environment Variables:
86
+ SESSION_TIMEOUT Session timeout in seconds (default: 0 = no limit)
87
+ MODEL AI model to use
88
+ VERBOSE Set to 1 for verbose AI CLI output
89
+ HEARTBEAT_INTERVAL Heartbeat log interval in seconds (default: 30)
90
+ STALE_KILL_THRESHOLD Auto-kill after N seconds no progress (default: 900)
91
+ AUTO_PUSH Auto-push after success (default: 0)
92
+
93
+ Examples:
94
+ ./run-recovery.sh # Auto-detect and recover
95
+ ./run-recovery.sh detect # Show what would be recovered
96
+ ./run-recovery.sh run --dry-run # Generate prompt, don't execute
97
+ ./run-recovery.sh run --yes # Skip confirmation prompt
98
+ ./run-recovery.sh run --model claude-opus-4.6
99
+ HELP
100
+ }
101
+
102
+ # ============================================================
103
+ # Cleanup trap
104
+ # ============================================================
105
+
106
+ cleanup() {
107
+ local exit_code=$?
108
+ # Kill any background processes
109
+ if [[ -n "${_SESSION_PID:-}" ]]; then
110
+ kill "$_SESSION_PID" 2>/dev/null || true
111
+ wait "$_SESSION_PID" 2>/dev/null || true
112
+ fi
113
+ # Stop heartbeat if running
114
+ stop_heartbeat "${_HEARTBEAT_PID:-}" 2>/dev/null || true
115
+ # Log recovery session location
116
+ if [[ -n "${_SESSION_DIR:-}" && -d "${_SESSION_DIR:-}" ]]; then
117
+ log_info "Session directory: $_SESSION_DIR"
118
+ if [[ -f "$_SESSION_DIR/logs/session.log" ]]; then
119
+ log_info "Session log: $_SESSION_DIR/logs/session.log"
120
+ fi
121
+ fi
122
+ if [[ $exit_code -ne 0 && $exit_code -ne 130 ]]; then
123
+ log_info "Re-run ./run-recovery.sh to try again."
124
+ fi
125
+ }
126
+ trap cleanup EXIT
127
+
128
+ # ============================================================
129
+ # Detection
130
+ # ============================================================
131
+
132
+ run_detection() {
133
+ local detection_output
134
+ local detect_stderr_file
135
+ detect_stderr_file=$(mktemp)
136
+ detection_output=$(python3 "$RECOVERY_DETECT_SCRIPT" --project-root "$PROJECT_ROOT" 2>"$detect_stderr_file") || {
137
+ log_error "Detection script failed"
138
+ if [[ -s "$detect_stderr_file" ]]; then
139
+ log_error "Details: $(cat "$detect_stderr_file")"
140
+ fi
141
+ rm -f "$detect_stderr_file"
142
+ return 1
143
+ }
144
+ # Show migration warnings or other informational messages from detection
145
+ if [[ -s "$detect_stderr_file" ]]; then
146
+ log_warn "$(cat "$detect_stderr_file")"
147
+ fi
148
+ rm -f "$detect_stderr_file"
149
+ echo "$detection_output"
150
+ }
151
+
152
+ display_report() {
153
+ local detection_json="$1"
154
+
155
+ local detected workflow_type branch phase phase_name reason remaining
156
+ detected=$(echo "$detection_json" | jq -r '.detected // false')
157
+
158
+ if [[ "$detected" != "true" ]]; then
159
+ echo ""
160
+ log_info "No interrupted workflow detected in this workspace."
161
+ echo ""
162
+ echo " To start a new workflow:"
163
+ echo " • /feature-workflow — build features from idea to code"
164
+ echo " • /bug-fix-workflow — fix a specific bug interactively"
165
+ echo " • /refactor-workflow — behavior-preserving code restructuring"
166
+ echo ""
167
+ return 1
168
+ fi
169
+
170
+ workflow_type=$(echo "$detection_json" | jq -r '.workflow_type')
171
+ branch=$(echo "$detection_json" | jq -r '.git.current_branch // "unknown"')
172
+ phase=$(echo "$detection_json" | jq -r '.phase')
173
+ phase_name=$(echo "$detection_json" | jq -r '.phase_name')
174
+ reason=$(echo "$detection_json" | jq -r '.recovery.reason // ""')
175
+ remaining=$(echo "$detection_json" | jq -r '.recovery.remaining_work // ""')
176
+
177
+ local uncommitted staged commits_ahead
178
+ uncommitted=$(echo "$detection_json" | jq -r '.git.uncommitted_files // 0')
179
+ staged=$(echo "$detection_json" | jq -r '.git.staged_files // 0')
180
+ commits_ahead=$(echo "$detection_json" | jq -r '.git.commits_ahead_of_main // 0')
181
+
182
+ local has_changes files_modified files_added test_files
183
+ has_changes=$(echo "$detection_json" | jq -r '.code.has_changes // false')
184
+ files_modified=$(echo "$detection_json" | jq -r '.code.files_modified // 0')
185
+ files_added=$(echo "$detection_json" | jq -r '.code.files_added // 0')
186
+ test_files=$(echo "$detection_json" | jq -r '.code.test_files_touched // 0')
187
+
188
+ echo ""
189
+ echo "═══════════════════════════════════════════════════"
190
+ echo " Recovery Report"
191
+ echo "═══════════════════════════════════════════════════"
192
+ echo ""
193
+ echo " Workflow: $workflow_type"
194
+ echo " Branch: $branch"
195
+ echo " Phase: $phase — $phase_name"
196
+ echo ""
197
+
198
+ if [[ "$has_changes" == "true" ]]; then
199
+ echo " Code Changes:"
200
+ echo " Modified: $files_modified files"
201
+ echo " Added: $files_added files"
202
+ echo " Tests: $test_files test files touched"
203
+ echo ""
204
+ fi
205
+
206
+ echo " Git State:"
207
+ echo " Uncommitted: $uncommitted files"
208
+ echo " Staged: $staged files"
209
+ echo " Ahead: $commits_ahead commits ahead of main"
210
+ echo ""
211
+ echo " Reason: $reason"
212
+ echo " Remaining: $remaining"
213
+ echo ""
214
+
215
+ # Show other interrupted workflows (if any)
216
+ local others
217
+ others=$(echo "$detection_json" | jq -r '.other_interrupted_workflows // [] | .[]' 2>/dev/null)
218
+ if [[ -n "$others" ]]; then
219
+ echo " Note: Other interrupted workflows also detected:"
220
+ echo "$others" | while IFS= read -r w; do
221
+ echo " • $w"
222
+ done
223
+ echo " (Run recovery again after this one completes)"
224
+ echo ""
225
+ fi
226
+
227
+ echo "═══════════════════════════════════════════════════"
228
+ echo ""
229
+
230
+ return 0
231
+ }
232
+
233
+ # ============================================================
234
+ # Session spawn (simplified from run-bugfix.sh)
235
+ # ============================================================
236
+
237
+ spawn_recovery_session() {
238
+ local bootstrap_prompt="$1"
239
+ local session_dir="$2"
240
+ local session_model="${3:-}"
241
+
242
+ local session_log="$session_dir/logs/session.log"
243
+ local progress_json="$session_dir/logs/progress.json"
244
+
245
+ local verbose_flag=""
246
+ if [[ "$VERBOSE" == "1" ]]; then
247
+ verbose_flag="--verbose"
248
+ fi
249
+
250
+ local stream_json_flag=""
251
+ if [[ "$USE_STREAM_JSON" == "true" ]]; then
252
+ stream_json_flag="--output-format stream-json"
253
+ verbose_flag="--verbose"
254
+ fi
255
+
256
+ local model_flag=""
257
+ local effective_model="${session_model:-$MODEL}"
258
+ if [[ -n "$effective_model" ]]; then
259
+ model_flag="--model $effective_model"
260
+ fi
261
+
262
+ # Unset CLAUDECODE to prevent "nested session" error
263
+ unset CLAUDECODE 2>/dev/null || true
264
+
265
+ log_info "Spawning AI CLI session..."
266
+ log_info "CLI: $CLI_CMD"
267
+ [[ -n "$effective_model" ]] && log_info "Model: $effective_model"
268
+ log_info "Prompt: $bootstrap_prompt"
269
+
270
+ case "$CLI_CMD" in
271
+ *claude*)
272
+ "$CLI_CMD" \
273
+ -p "$(cat "$bootstrap_prompt")" \
274
+ --dangerously-skip-permissions \
275
+ $verbose_flag \
276
+ $stream_json_flag \
277
+ $model_flag \
278
+ > "$session_log" 2>&1 &
279
+ ;;
280
+ *)
281
+ # CodeBuddy (cbc) and others: prompt via stdin
282
+ "$CLI_CMD" \
283
+ --print \
284
+ -y \
285
+ $verbose_flag \
286
+ $stream_json_flag \
287
+ $model_flag \
288
+ < "$bootstrap_prompt" \
289
+ > "$session_log" 2>&1 &
290
+ ;;
291
+ esac
292
+ _SESSION_PID=$!
293
+
294
+ # Start progress parser
295
+ start_progress_parser "$session_log" "$progress_json" "$SCRIPTS_DIR"
296
+ local parser_pid="${_PARSER_PID:-}"
297
+
298
+ # Timeout watchdog
299
+ local watcher_pid=""
300
+ if [[ $SESSION_TIMEOUT -gt 0 ]]; then
301
+ ( sleep "$SESSION_TIMEOUT" && kill -TERM "$_SESSION_PID" 2>/dev/null ) &
302
+ watcher_pid=$!
303
+ fi
304
+
305
+ # Heartbeat monitor
306
+ start_heartbeat "$_SESSION_PID" "$session_log" "$progress_json" "$HEARTBEAT_INTERVAL" "$STALE_KILL_THRESHOLD"
307
+ _HEARTBEAT_PID="${_HEARTBEAT_PID:-}"
308
+
309
+ # Wait for session to finish
310
+ local exit_code=0
311
+ if wait "$_SESSION_PID" 2>/dev/null; then
312
+ exit_code=0
313
+ else
314
+ exit_code=$?
315
+ fi
316
+ _SESSION_PID=""
317
+
318
+ # Cleanup background processes
319
+ [[ -n "$watcher_pid" ]] && kill "$watcher_pid" 2>/dev/null || true
320
+ stop_heartbeat "${_HEARTBEAT_PID:-}"
321
+ _HEARTBEAT_PID=""
322
+ stop_progress_parser "$parser_pid"
323
+ [[ -n "$watcher_pid" ]] && wait "$watcher_pid" 2>/dev/null || true
324
+
325
+ # Map SIGTERM to timeout
326
+ if [[ $exit_code -eq 143 ]]; then
327
+ exit_code=124
328
+ fi
329
+
330
+ # Check for stale-kill
331
+ local stale_kill_marker="$session_dir/logs/stale-kill.json"
332
+ if [[ -f "$stale_kill_marker" ]]; then
333
+ log_warn "Session was stale-killed (no progress for too long)"
334
+ fi
335
+
336
+ # Log session summary
337
+ if [[ -f "$session_log" ]]; then
338
+ local final_size final_lines
339
+ final_size=$(wc -c < "$session_log" 2>/dev/null | tr -d ' ')
340
+ final_lines=$(wc -l < "$session_log" 2>/dev/null | tr -d ' ')
341
+ log_info "Session log: $final_lines lines, $((final_size / 1024))KB"
342
+ fi
343
+ log_info "Session exit code: $exit_code"
344
+
345
+ return $exit_code
346
+ }
347
+
348
+ # ============================================================
349
+ # Post-session outcome detection
350
+ # ============================================================
351
+
352
+ detect_session_outcome() {
353
+ local exit_code="$1"
354
+ local session_dir="$2"
355
+ local workflow_type="$3"
356
+ local main_branch="${4:-main}"
357
+
358
+ local stale_kill_marker="$session_dir/logs/stale-kill.json"
359
+ local was_stale_killed=false
360
+ [[ -f "$stale_kill_marker" ]] && was_stale_killed=true
361
+
362
+ if [[ $exit_code -eq 124 ]]; then
363
+ log_warn "Session timed out"
364
+ echo "timed_out"
365
+ return
366
+ fi
367
+
368
+ if [[ "$was_stale_killed" == true ]]; then
369
+ log_warn "Session stale-killed"
370
+ fi
371
+
372
+ # Check for commits
373
+ local has_commits=""
374
+ has_commits=$(git -C "$PROJECT_ROOT" log "${main_branch}..HEAD" --oneline 2>/dev/null | head -1 || true)
375
+
376
+ if [[ -n "$has_commits" ]]; then
377
+ echo "success"
378
+ return
379
+ fi
380
+
381
+ # Check for uncommitted changes
382
+ local uncommitted=""
383
+ uncommitted=$(git -C "$PROJECT_ROOT" status --porcelain 2>/dev/null | head -1 || true)
384
+ if [[ -n "$uncommitted" ]]; then
385
+ # Try auto-commit to preserve work (like run-bugfix.sh does)
386
+ log_info "Uncommitted changes found — attempting to preserve work..."
387
+ git -C "$PROJECT_ROOT" add -A 2>/dev/null || true
388
+ if git -C "$PROJECT_ROOT" commit --no-verify -m "wip(recovery): auto-save interrupted session work" 2>/dev/null; then
389
+ log_info "Auto-saved recovery work as WIP commit"
390
+ echo "success_wip"
391
+ return
392
+ fi
393
+ echo "partial"
394
+ return
395
+ fi
396
+
397
+ if [[ $exit_code -ne 0 ]]; then
398
+ echo "failed"
399
+ else
400
+ echo "no_changes"
401
+ fi
402
+ }
403
+
404
+ # ============================================================
405
+ # Commands
406
+ # ============================================================
407
+
408
+ cmd_detect() {
409
+ prizm_check_common_dependencies
410
+ prizm_ensure_git_repo "$PROJECT_ROOT"
411
+
412
+ local detection_json
413
+ detection_json=$(run_detection) || {
414
+ log_error "Detection failed"
415
+ exit 1
416
+ }
417
+
418
+ display_report "$detection_json"
419
+ }
420
+
421
+ cmd_run() {
422
+ local dry_run=false
423
+ local skip_confirm=false
424
+ local model_override=""
425
+
426
+ # Parse options
427
+ while [[ $# -gt 0 ]]; do
428
+ case "$1" in
429
+ --dry-run)
430
+ dry_run=true
431
+ shift
432
+ ;;
433
+ --yes|-y)
434
+ skip_confirm=true
435
+ shift
436
+ ;;
437
+ --model)
438
+ model_override="$2"
439
+ shift 2
440
+ ;;
441
+ *)
442
+ log_error "Unknown option: $1"
443
+ show_help
444
+ exit 1
445
+ ;;
446
+ esac
447
+ done
448
+
449
+ # Step 1: Check dependencies
450
+ prizm_check_common_dependencies
451
+ prizm_ensure_git_repo "$PROJECT_ROOT"
452
+
453
+ # Step 2: Run detection
454
+ log_info "Detecting interrupted workflow..."
455
+ local detection_json
456
+ detection_json=$(run_detection) || {
457
+ log_error "Detection failed"
458
+ exit 1
459
+ }
460
+
461
+ # Step 3: Display report
462
+ if ! display_report "$detection_json"; then
463
+ exit 0 # No workflow detected, clean exit
464
+ fi
465
+
466
+ local workflow_type phase phase_name main_branch
467
+ workflow_type=$(echo "$detection_json" | jq -r '.workflow_type')
468
+ phase=$(echo "$detection_json" | jq -r '.phase')
469
+ phase_name=$(echo "$detection_json" | jq -r '.phase_name')
470
+
471
+ # Detect main branch (used for post-session commit check)
472
+ main_branch="main"
473
+ if ! git -C "$PROJECT_ROOT" rev-parse --verify main >/dev/null 2>&1; then
474
+ if git -C "$PROJECT_ROOT" rev-parse --verify master >/dev/null 2>&1; then
475
+ main_branch="master"
476
+ fi
477
+ fi
478
+
479
+ # Step 4: User confirmation
480
+ if [[ "$skip_confirm" != true ]]; then
481
+ echo -n "Ready to resume from Phase $phase ($phase_name). Continue? [y/N] "
482
+ read -r answer
483
+ if [[ ! "$answer" =~ ^[Yy]$ ]]; then
484
+ log_info "Recovery cancelled by user."
485
+ exit 0
486
+ fi
487
+ else
488
+ log_info "Confirmation skipped (--yes flag)"
489
+ fi
490
+
491
+ # Step 5: Create session directory
492
+ local timestamp session_id session_dir
493
+ timestamp=$(date +%Y%m%d-%H%M%S)
494
+ session_id="recovery-${workflow_type}-${timestamp}"
495
+ session_dir="$RECOVERY_STATE_DIR/$session_id"
496
+ _SESSION_DIR="$session_dir"
497
+ mkdir -p "$session_dir/logs"
498
+
499
+ # Save detection JSON for reference
500
+ local detection_file="$session_dir/detection.json"
501
+ echo "$detection_json" > "$detection_file"
502
+
503
+ # Step 6: Generate bootstrap prompt
504
+ log_info "Generating recovery bootstrap prompt..."
505
+ local bootstrap_prompt="$session_dir/bootstrap-prompt.md"
506
+ local gen_stderr_file="$session_dir/logs/prompt-gen-stderr.log"
507
+ local gen_output
508
+ gen_output=$(python3 "$SCRIPTS_DIR/generate-recovery-prompt.py" \
509
+ --detection-json "$detection_file" \
510
+ --output "$bootstrap_prompt" \
511
+ --project-root "$PROJECT_ROOT" \
512
+ --session-id "$session_id" \
513
+ 2>"$gen_stderr_file") || {
514
+ log_error "Failed to generate recovery prompt"
515
+ if [[ -s "$gen_stderr_file" ]]; then
516
+ log_error "Details: $(cat "$gen_stderr_file")"
517
+ fi
518
+ exit 1
519
+ }
520
+
521
+ local gen_success
522
+ gen_success=$(echo "$gen_output" | jq -r '.success // false')
523
+ if [[ "$gen_success" != "true" ]]; then
524
+ local gen_error
525
+ gen_error=$(echo "$gen_output" | jq -r '.error // "unknown error"')
526
+ log_error "Prompt generation failed: $gen_error"
527
+ exit 1
528
+ fi
529
+
530
+ local prompt_lines
531
+ prompt_lines=$(wc -l < "$bootstrap_prompt" | tr -d ' ')
532
+ log_success "Bootstrap prompt generated: $prompt_lines lines"
533
+ log_info "Prompt: $bootstrap_prompt"
534
+
535
+ # Step 7: Dry run exits here
536
+ if [[ "$dry_run" == true ]]; then
537
+ echo ""
538
+ log_success "Dry run complete. Inspect the prompt with:"
539
+ echo " cat $bootstrap_prompt"
540
+ echo ""
541
+ echo "To execute recovery:"
542
+ echo " ./run-recovery.sh run --yes"
543
+ exit 0
544
+ fi
545
+
546
+ # Step 8: Record current branch
547
+ local current_branch
548
+ current_branch=$(git -C "$PROJECT_ROOT" branch --show-current 2>/dev/null || echo "unknown")
549
+ log_info "Current branch: $current_branch"
550
+ log_info "NOTE: Recovery continues on current branch (no new branch created)"
551
+
552
+ # Step 9: Spawn AI CLI session
553
+ echo ""
554
+ log_info "Starting recovery session..."
555
+ log_info "Workflow: $workflow_type"
556
+ log_info "Resuming from: Phase $phase ($phase_name)"
557
+ echo ""
558
+
559
+ local exit_code=0
560
+ spawn_recovery_session "$bootstrap_prompt" "$session_dir" "$model_override" || exit_code=$?
561
+
562
+ # Step 10: Detect outcome
563
+ local outcome
564
+ outcome=$(detect_session_outcome "$exit_code" "$session_dir" "$workflow_type" "$main_branch")
565
+
566
+ # Step 11: Post-session report
567
+ echo ""
568
+ echo "═══════════════════════════════════════════════════"
569
+ echo " Recovery Result"
570
+ echo "═══════════════════════════════════════════════════"
571
+ echo ""
572
+ echo " Workflow: $workflow_type"
573
+ echo " Recovered from: Phase $phase ($phase_name)"
574
+ echo " Outcome: $outcome"
575
+ echo " Session log: $session_dir/logs/session.log"
576
+ echo ""
577
+
578
+ case "$outcome" in
579
+ success)
580
+ log_success "Recovery completed successfully!"
581
+ # Auto-push if configured
582
+ if [[ "$AUTO_PUSH" == "1" ]]; then
583
+ log_info "Auto-pushing to remote (AUTO_PUSH=1)..."
584
+ if git -C "$PROJECT_ROOT" push 2>/dev/null; then
585
+ log_success "Pushed to remote"
586
+ else
587
+ log_warn "Push failed — push manually: git push"
588
+ fi
589
+ fi
590
+ echo ""
591
+ echo " Next steps:"
592
+ echo " • Review changes: git log --oneline -5"
593
+ echo " • Run tests: npm test"
594
+ if [[ "$workflow_type" == "bug-fix-workflow" && "$current_branch" == fix/* ]]; then
595
+ echo " • Merge to main: git checkout main && git merge $current_branch"
596
+ fi
597
+ if [[ "$AUTO_PUSH" != "1" ]]; then
598
+ echo " • Push when ready: git push"
599
+ fi
600
+ echo ""
601
+ ;;
602
+ success_wip)
603
+ log_warn "Recovery session interrupted — work auto-saved as WIP commit."
604
+ echo ""
605
+ echo " The session was interrupted but uncommitted changes were saved."
606
+ echo " The WIP commit preserves your progress."
607
+ echo ""
608
+ echo " Next steps:"
609
+ echo " • Run recovery again to complete remaining phases: ./run-recovery.sh"
610
+ echo " • Or review the WIP commit: git log --oneline -1"
611
+ echo ""
612
+ ;;
613
+ partial)
614
+ log_warn "Session completed but has uncommitted changes."
615
+ echo ""
616
+ echo " The session did work but didn't finish committing."
617
+ echo " Check uncommitted changes:"
618
+ echo " git status"
619
+ echo " git diff"
620
+ echo ""
621
+ echo " Options:"
622
+ echo " • Run recovery again: ./run-recovery.sh"
623
+ echo " • Commit manually: git add <files> && git commit"
624
+ echo ""
625
+ ;;
626
+ timed_out)
627
+ log_warn "Session timed out before completing."
628
+ echo ""
629
+ echo " Options:"
630
+ echo " • Run recovery again: ./run-recovery.sh"
631
+ echo " • Increase timeout: SESSION_TIMEOUT=3600 ./run-recovery.sh"
632
+ echo ""
633
+ ;;
634
+ no_changes)
635
+ log_warn "Session completed but produced no changes."
636
+ echo ""
637
+ echo " The session may have already been fully recovered,"
638
+ echo " or the AI couldn't make progress."
639
+ echo " Check the session log for details:"
640
+ echo " cat $session_dir/logs/session.log"
641
+ echo ""
642
+ ;;
643
+ *)
644
+ log_error "Recovery session failed."
645
+ echo ""
646
+ echo " Check session log for errors:"
647
+ echo " cat $session_dir/logs/session.log"
648
+ echo ""
649
+ echo " Options:"
650
+ echo " • Run recovery again: ./run-recovery.sh"
651
+ echo " • Start fresh: /<workflow> command"
652
+ echo ""
653
+ ;;
654
+ esac
655
+
656
+ echo "═══════════════════════════════════════════════════"
657
+
658
+ if [[ "$outcome" == "success" ]]; then
659
+ exit 0
660
+ else
661
+ exit 1
662
+ fi
663
+ }
664
+
665
+ # ============================================================
666
+ # Main
667
+ # ============================================================
668
+
669
+ main() {
670
+ local command="${1:-run}"
671
+ shift || true
672
+
673
+ case "$command" in
674
+ run)
675
+ cmd_run "$@"
676
+ ;;
677
+ detect)
678
+ cmd_detect
679
+ ;;
680
+ help|--help|-h)
681
+ show_help
682
+ ;;
683
+ *)
684
+ log_error "Unknown command: $command"
685
+ show_help
686
+ exit 1
687
+ ;;
688
+ esac
689
+ }
690
+
691
+ main "$@"