#!/usr/bin/env bash set -euo pipefail # # session-resume.sh — Crash recovery for dead orchestrator sessions # # Usage: # session-resume.sh [--project ] [--clean-lock] SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" source "$SCRIPT_DIR/_lib.sh" # ─── Parse arguments ───────────────────────────────────────────────────────── PROJECT="." CLEAN_LOCK=false while [[ $# -gt 0 ]]; do case "$1" in --project) PROJECT="$2"; shift 2 ;; --clean-lock) CLEAN_LOCK=true; shift ;; -h|--help) echo "Usage: session-resume.sh [--project ] [--clean-lock]" exit 0 ;; *) echo "Unknown option: $1" >&2; exit 1 ;; esac done _require_jq # ─── Check session lock ───────────────────────────────────────────────────── lock_data="" has_lock=false if lock_data="$(session_lock_read "$PROJECT" 2>/dev/null)"; then has_lock=true fi if [[ "$has_lock" == true ]]; then lock_pid="$(echo "$lock_data" | jq -r '.pid // 0')" lock_sid="$(echo "$lock_data" | jq -r '.session_id // "unknown"')" lock_rt="$(echo "$lock_data" | jq -r '.runtime // "unknown"')" lock_start="$(echo "$lock_data" | jq -r '.started_at // ""')" lock_milestone="$(echo "$lock_data" | jq -r '.milestone_id // ""')" if is_pid_alive "$lock_pid"; then echo -e "${C_YELLOW}Session $lock_sid is still running (PID $lock_pid).${C_RESET}" echo "Use 'mosaic coord status' to check session health." exit 0 fi # Session is dead echo "" echo -e "${C_RED}${C_BOLD}CRASH RECOVERY — Session $lock_sid ($lock_rt)${C_RESET}" echo "===========================================" echo "" if [[ -n "$lock_start" ]]; then echo -e " ${C_CYAN}Session started:${C_RESET} $lock_start" fi echo -e " ${C_CYAN}Session died:${C_RESET} PID $lock_pid is not running" [[ -n "$lock_milestone" ]] && echo -e " ${C_CYAN}Active milestone:${C_RESET} $lock_milestone" echo "" else # No lock — check mission.json for last session info if [[ -f "$(mission_path "$PROJECT")" ]]; then mission="$(load_mission "$PROJECT")" session_count="$(echo "$mission" | jq '.sessions | length')" if (( session_count > 0 )); then last_idx=$(( session_count - 1 )) last_sid="$(echo "$mission" | jq -r ".sessions[$last_idx].session_id")" last_reason="$(echo "$mission" | jq -r ".sessions[$last_idx].ended_reason // \"unknown\"")" echo -e "${C_DIM}No session lock found. Last session: $last_sid (ended: $last_reason)${C_RESET}" echo "Use 'mosaic coord continue' to generate a continuation prompt." exit 0 fi fi echo -e "${C_DIM}No session state found.${C_RESET}" exit 4 fi # ─── Detect dirty state ───────────────────────────────────────────────────── echo -e "${C_BOLD}Dirty State:${C_RESET}" dirty_files="" if git -C "$PROJECT" rev-parse --is-inside-work-tree &>/dev/null; then dirty_files="$(git -C "$PROJECT" status --porcelain 2>/dev/null || true)" fi if [[ -n "$dirty_files" ]]; then echo " Modified files:" echo "$dirty_files" | head -20 | while IFS= read -r line; do echo " $line" done file_count="$(echo "$dirty_files" | wc -l)" if (( file_count > 20 )); then echo " ... and $(( file_count - 20 )) more" fi else echo -e " ${C_GREEN}Working tree is clean.${C_RESET}" fi # Check for in-progress tasks inprog_count=0 task_counts="$(count_tasks_md "$PROJECT")" inprog_count="$(echo "$task_counts" | jq '.in_progress')" if (( inprog_count > 0 )); then echo -e " ${C_YELLOW}$inprog_count task(s) still marked in-progress in TASKS.md${C_RESET}" fi echo "" # ─── Recovery actions ──────────────────────────────────────────────────────── echo -e "${C_BOLD}Recovery Actions:${C_RESET}" if [[ -n "$dirty_files" ]]; then echo " 1. Review changes: git diff" echo " 2. If good: git add -A && git commit -m \"wip: partial work from crashed session\"" echo " 3. If bad: git checkout ." fi echo " 4. Clean lock: mosaic coord resume --clean-lock" echo " 5. Generate prompt: mosaic coord continue" echo "" # ─── Clean lock if requested ───────────────────────────────────────────────── if [[ "$CLEAN_LOCK" == true ]]; then echo -e "${C_CYAN}Cleaning session lock...${C_RESET}" # Update mission.json with crash info mp="$(mission_path "$PROJECT")" if [[ -f "$mp" && "$has_lock" == true ]]; then updated="$(jq \ --arg sid "$lock_sid" \ --arg ts "$(iso_now)" \ '(.sessions[] | select(.session_id == $sid)) |= . + { ended_at: $ts, ended_reason: "crashed" }' "$mp")" write_json "$mp" "$updated" echo " Updated mission.json: session $lock_sid marked as crashed" fi session_lock_clear "$PROJECT" echo " Cleared session.lock" echo "" echo -e "${C_GREEN}Lock cleared. Generate continuation prompt with: mosaic coord continue${C_RESET}" fi # ─── Generate resume prompt ───────────────────────────────────────────────── if [[ "$CLEAN_LOCK" != true ]]; then echo "---" echo "" echo -e "${C_BOLD}Resume Prompt (paste to new session):${C_RESET}" echo "" mission_name="" mission_id="" if [[ -f "$(mission_path "$PROJECT")" ]]; then mission="$(load_mission "$PROJECT")" mission_name="$(echo "$mission" | jq -r '.name')" mission_id="$(echo "$mission" | jq -r '.mission_id')" quality_gates="$(echo "$mission" | jq -r '.quality_gates // "—"')" project_path="$(echo "$mission" | jq -r '.project_path')" fi task_counts="$(count_tasks_md "$PROJECT")" tasks_done="$(echo "$task_counts" | jq '.done')" tasks_total="$(echo "$task_counts" | jq '.total')" next_task="$(find_next_task "$PROJECT")" cat <