Skill: feiskyer/autonomous-skill (version 1.0.3)

--- name: autonomous-skill description: >- Execute long-running, multi-session tasks autonomously using Claude Code headless mode or in-session hook-based loops. Supports structured task decomposition (for complex projects) and lightweight Ralph-style iteration (for TDD, bug fixing, refactoring). Use this skill whenever the user says "autonomous", "long-running task", "multi-session", "run this in the background", "keep working on this", "batch process", "iterate until done", "ralph loop", or wants any task that requires sustained, unattended execution. --- # Autonomous Skill - Multi-Session Task Execution Execute complex tasks across multiple Claude Code sessions with automatic continuation, progress tracking, and two completion mechanisms (promise tags + checkbox counting). ## Two Execution Modes ### Headless Mode (default) Spawns `claude -p` child sessions in a bash loop. Best for background/unattended work. ```bash bash <skill-dir>/scripts/run-session.sh "Build a REST API" --max-sessions 10 ``` ### Hook Mode (in-session) Uses a Stop hook to intercept session exit and feed the prompt back. Runs inside the current interactive session — no nesting issues. ```bash bash <skill-dir>/scripts/setup-loop.sh "Build a REST API" --max-iterations 10 ``` ## Two Task Strategies ### Structured (default) Full task decomposition: Initializer creates `task_list.md` with phased sub-tasks, Executor picks up and completes them one by one. Best for complex, multi-phase projects. ```bash bash <skill-dir>/scripts/run-session.sh "Build a REST API for todo app" ``` ### Lightweight (`--lightweight`) Ralph-style iteration: same prompt repeated each session, no task decomposition. Best for iterative tasks with clear success criteria (TDD, bug fixing, refactoring). ```bash bash <skill-dir>/scripts/run-session.sh "Fix all failing tests in src/" --lightweight ``` ## Completion Detection Two complementary mechanisms — whichever triggers first wins: 1. **Promise tags** (both modes): The agent outputs `<promise>DONE</promise>` when work is genuinely complete. Default promise is `DONE`; customize with `--completion-promise`. The agent is instructed to only output the promise when the work is truly finished — not to escape the loop. 2. **Checkbox counting** (structured mode only): All `[ ]` items in `task_list.md` are marked `[x]`. ## Directory Layout ``` project-root/ ├── .autonomous/ │ └── <task-name>/ │ ├── task_list.md # Master checklist (structured mode) │ ├── progress.md # Per-session progress log │ ├── .mode # "structured" or "lightweight" │ ├── sessions/ # Transcript logs per session │ │ ├── session-001.log │ │ └── session-002.log │ └── run.lock # Prevents concurrent runs └── .claude/ └── autonomous-loop.local.md # Hook mode state (when active) ``` ## Headless Mode — CLI Reference ```bash bash <skill-dir>/scripts/run-session.sh "task description" [OPTIONS] ``` | Flag | Description | Default | |------|-------------|---------| | `--lightweight` | Ralph-style iteration (no task decomposition) | structured | | `--task-name <name>` | Explicit task name | Auto-generated | | `--continue, -c` | Continue most recent or named task | — | | `--list, -l` | List all tasks with progress | — | | `--completion-promise TEXT` | Promise phrase for completion | DONE | | `--max-sessions N` | Stop after N sessions | Unlimited | | `--max-budget N.NN` | Per-session dollar budget | 5.00 | | `--model <model>` | Model alias or full name | sonnet | | `--fallback-model <m>` | Fallback if primary overloaded | — | | `--effort <level>` | Thinking effort (low/medium/high) | high | | `--no-auto-continue` | Run one session only | — | | `--permission-mode <m>` | Permission mode | auto | | `--add-dir <dirs>` | Extra directories to allow | — | ## Hook Mode — Setup For in-session loops (no child process spawning): ```bash bash <skill-dir>/scripts/setup-loop.sh "task description" [OPTIONS] ``` | Flag | Description | Default | |------|-------------|---------| | `--mode structured\|lightweight` | Task strategy | structured | | `--max-iterations N` | Max loop iterations | Unlimited | | `--completion-promise TEXT` | Promise phrase | DONE | | `--task-name NAME` | Explicit task name | Auto-generated | The hook is registered in `hooks/hooks.json`. When active, the Stop hook reads `.claude/autonomous-loop.local.md` and blocks exit until the promise is detected or max iterations reached. To cancel an active hook-mode loop: `rm .claude/autonomous-loop.local.md` ## Workflow Detail ### Structured Mode 1. **Initializer Agent** — analyzes task, creates phased `task_list.md`, begins work 2. **Executor Agent** — reads task list + progress, verifies previous work, completes next task 3. **Auto-Continue** — checks promise tags + checkboxes; if not done, spawns next session ### Lightweight Mode 1. Same prompt fed each iteration 2. Agent sees its previous work in files and git history 3. Iterates until work is complete and promise tag is output 4. No task_list.md — completion is purely promise-based ### When to Use Which | Scenario | Strategy | Mode | |----------|----------|------| | Build a full application | Structured | Headless | | Fix all failing tests | Lightweight | Either | | Refactor a module | Lightweight | Either | | Multi-phase project | Structured | Headless | | Quick iterative fix | Lightweight | Hook | | Overnight batch work | Structured | Headless | ## Important Constraints 1. **task_list.md is append-only for completions**: Only change `[ ]` → `[x]` 2. **One runner per task**: Lock file prevents concurrent sessions on same task 3. **Project files stay in project root**: `.autonomous/` is only for tracking 4. **Promise integrity**: The agent must not output `<promise>DONE</promise>` until genuinely complete 5. **Cost awareness**: Default per-session budget is $5. Adjust with `--max-budget` ## Troubleshooting | Issue | Solution | |-------|----------| | "Lock file exists" | Previous run crashed. Remove `.autonomous/<task>/run.lock` | | Session keeps failing | Check `sessions/session-NNN.log` for errors | | Nested session error | Script auto-unsets CLAUDECODE; use hook mode as alternative | | Hook loop won't stop | Delete `.claude/autonomous-loop.local.md` | | Task not found | Run `--list` to see available tasks | | Want to restart | Delete the task directory and start fresh | | Cost too high | Lower `--max-budget` or use `--model sonnet` |

path: hooks/hooks.json
content: {
  "description": "Autonomous skill stop hook for multi-session loops",
  "hooks": {
    "Stop": [
      {
        "hooks": [
          {
            "type": "command",
            "command": "${CLAUDE_PLUGIN_ROOT}/skills/autonomous-skill/hooks/stop-hook.sh"
          }
        ]
      }
    ]
  }
}


path: hooks/stop-hook.sh
content: #!/bin/bash
#
# Autonomous Skill - Stop Hook
# Intercepts session exit when an autonomous loop is active.
# Checks for task completion (checkboxes + promise tags) and either
# blocks exit (feeding the prompt back) or allows it.
#

set -euo pipefail

HOOK_INPUT=$(cat)
STATE_FILE=".claude/autonomous-loop.local.md"

# No active loop → allow exit
if [[ ! -f "$STATE_FILE" ]]; then
  exit 0
fi

# ── Parse state file frontmatter ───────────────────────────────────────
FRONTMATTER=$(sed -n '/^---$/,/^---$/{ /^---$/d; p; }' "$STATE_FILE")

ITERATION=$(echo "$FRONTMATTER" | grep '^iteration:' | sed 's/iteration: *//')
MAX_ITERATIONS=$(echo "$FRONTMATTER" | grep '^max_iterations:' | sed 's/max_iterations: *//')
COMPLETION_PROMISE=$(echo "$FRONTMATTER" | grep '^completion_promise:' | sed 's/completion_promise: *//' | sed 's/^"\(.*\)"$/\1/')
MODE=$(echo "$FRONTMATTER" | grep '^mode:' | sed 's/mode: *//')
TASK_DIR=$(echo "$FRONTMATTER" | grep '^task_dir:' | sed 's/task_dir: *//' | sed 's/^"\(.*\)"$/\1/')

# Validate numeric fields
if [[ ! "$ITERATION" =~ ^[0-9]+$ ]] || [[ ! "$MAX_ITERATIONS" =~ ^[0-9]+$ ]]; then
  echo "Warning: Autonomous loop state corrupted. Stopping." >&2
  rm -f "$STATE_FILE"
  exit 0
fi

# ── Check max iterations ───────────────────────────────────────────────
if [[ $MAX_ITERATIONS -gt 0 ]] && [[ $ITERATION -ge $MAX_ITERATIONS ]]; then
  echo "Autonomous loop: Reached max iterations ($MAX_ITERATIONS). Stopping."
  rm -f "$STATE_FILE"
  exit 0
fi

# ── Get transcript and check for completion ────────────────────────────
TRANSCRIPT_PATH=$(echo "$HOOK_INPUT" | jq -r '.transcript_path' 2>/dev/null || echo "")

LAST_OUTPUT=""
if [[ -n "$TRANSCRIPT_PATH" ]] && [[ -f "$TRANSCRIPT_PATH" ]]; then
  LAST_LINE=$(grep '"role":"assistant"' "$TRANSCRIPT_PATH" | tail -1 || echo "")
  if [[ -n "$LAST_LINE" ]]; then
    LAST_OUTPUT=$(echo "$LAST_LINE" | jq -r '
      .message.content |
      map(select(.type == "text")) |
      map(.text) |
      join("\n")
    ' 2>/dev/null || echo "")
  fi
fi

# ── Check promise-based completion ─────────────────────────────────────
if [[ -n "$COMPLETION_PROMISE" ]] && [[ "$COMPLETION_PROMISE" != "null" ]] && [[ -n "$LAST_OUTPUT" ]]; then
  PROMISE_TEXT=$(echo "$LAST_OUTPUT" | perl -0777 -pe 's/.*?<promise>(.*?)<\/promise>.*/$1/s; s/^\s+|\s+$//g; s/\s+/ /g' 2>/dev/null || echo "")
  if [[ -n "$PROMISE_TEXT" ]] && [[ "$PROMISE_TEXT" = "$COMPLETION_PROMISE" ]]; then
    echo "Autonomous loop: Completion promise detected — <promise>$COMPLETION_PROMISE</promise>"
    rm -f "$STATE_FILE"
    exit 0
  fi
fi

# ── Check task-list completion (structured mode only) ──────────────────
if [[ "$MODE" != "lightweight" ]] && [[ -n "$TASK_DIR" ]] && [[ -f "$TASK_DIR/task_list.md" ]]; then
  TOTAL=$(grep -c '^\- \[' "$TASK_DIR/task_list.md" 2>/dev/null || echo "0")
  DONE_COUNT=$(grep -c '^\- \[x\]' "$TASK_DIR/task_list.md" 2>/dev/null || echo "0")
  if [[ "$DONE_COUNT" -eq "$TOTAL" ]] && [[ "$TOTAL" -gt 0 ]]; then
    echo "Autonomous loop: All $TOTAL tasks complete."
    rm -f "$STATE_FILE"
    exit 0
  fi
fi

# ── Not complete — continue loop ───────────────────────────────────────
NEXT_ITERATION=$((ITERATION + 1))

# Extract prompt (everything after closing ---)
PROMPT_TEXT=$(awk '/^---$/{i++; next} i>=2' "$STATE_FILE")

if [[ -z "$PROMPT_TEXT" ]]; then
  echo "Warning: Autonomous loop state missing prompt. Stopping." >&2
  rm -f "$STATE_FILE"
  exit 0
fi

# Update iteration counter
TEMP_FILE="${STATE_FILE}.tmp.$$"
sed "s/^iteration: .*/iteration: $NEXT_ITERATION/" "$STATE_FILE" > "$TEMP_FILE"
mv "$TEMP_FILE" "$STATE_FILE"

# Build status message
if [[ "$MODE" == "lightweight" ]]; then
  STATUS_MSG="Autonomous loop iteration $NEXT_ITERATION"
else
  if [[ -n "$TASK_DIR" ]] && [[ -f "$TASK_DIR/task_list.md" ]]; then
    TOTAL=$(grep -c '^\- \[' "$TASK_DIR/task_list.md" 2>/dev/null || echo "?")
    DONE_COUNT=$(grep -c '^\- \[x\]' "$TASK_DIR/task_list.md" 2>/dev/null || echo "?")
    STATUS_MSG="Autonomous loop iteration $NEXT_ITERATION | Progress: $DONE_COUNT/$TOTAL"
  else
    STATUS_MSG="Autonomous loop iteration $NEXT_ITERATION"
  fi
fi

if [[ -n "$COMPLETION_PROMISE" ]] && [[ "$COMPLETION_PROMISE" != "null" ]]; then
  STATUS_MSG="$STATUS_MSG | Complete: output <promise>$COMPLETION_PROMISE</promise>"
fi

# Block exit, feed prompt back
jq -n \
  --arg prompt "$PROMPT_TEXT" \
  --arg msg "$STATUS_MSG" \
  '{
    "decision": "block",
    "reason": $prompt,
    "systemMessage": $msg
  }'

exit 0


path: scripts/run-session.sh
content: #!/bin/bash
#
# Autonomous Skill - Session Runner
# Executes Claude Code in headless mode with auto-continuation
#
# Supports two modes:
#   structured  - Full task decomposition with task_list.md (default)
#   lightweight - Same prompt repeated, Ralph-style iteration
#
# Usage:
#   ./run-session.sh "task description"
#   ./run-session.sh "fix tests" --lightweight
#   ./run-session.sh --task-name <name> --continue
#   ./run-session.sh --list
#

set -euo pipefail

# ── Configuration ──────────────────────────────────────────────────────
AUTO_CONTINUE_DELAY=3
MAX_TURNS_INIT=50
MAX_TURNS_EXEC=100
DEFAULT_MODEL="sonnet"
DEFAULT_MAX_BUDGET="5.00"
DEFAULT_EFFORT="high"
DEFAULT_PERMISSION_MODE="bypassPermissions"
DEFAULT_COMPLETION_PROMISE="DONE"

# Resolve skill directory
if [ -n "${CLAUDE_PLUGIN_ROOT:-}" ]; then
    SKILL_DIR="${CLAUDE_PLUGIN_ROOT}/skills/autonomous-skill"
else
    SKILL_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
fi

AUTONOMOUS_DIR=".autonomous"

# Allow spawning claude -p from within an interactive Claude Code session.
# Without this, Claude Code refuses to launch nested sessions.
unset CLAUDECODE 2>/dev/null || true

# ── Colors ─────────────────────────────────────────────────────────────
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
CYAN='\033[0;36m'
DIM='\033[2m'
NC='\033[0m'

print_header()  { echo -e "\n${BLUE}══════════════════════════════════════════${NC}"; echo -e "${BLUE}  $1${NC}"; echo -e "${BLUE}══════════════════════════════════════════${NC}"; }
print_success() { echo -e "${GREEN}  [ok] $1${NC}"; }
print_warning() { echo -e "${YELLOW}  [!!] $1${NC}"; }
print_error()   { echo -e "${RED}  [err] $1${NC}"; }
print_info()    { echo -e "${CYAN}  [..] $1${NC}"; }

# ── Helpers ────────────────────────────────────────────────────────────

show_help() {
    cat <<'HELP'
Autonomous Skill - Session Runner

Usage:
  run-session.sh "task description"              Start new structured task
  run-session.sh "fix tests" --lightweight       Start lightweight (Ralph-style) task
  run-session.sh --task-name <n> --continue      Continue specific task
  run-session.sh --list                          List all tasks

Modes:
  --lightweight          Ralph-style: same prompt repeated, no task decomposition.
                         Best for iterative tasks (TDD, bug fixing, refactoring).
  (default)              Structured: full task decomposition with task_list.md.
                         Best for complex, multi-phase projects.

Options:
  --task-name <name>         Explicit task name
  --continue, -c             Continue existing task
  --completion-promise TEXT  Promise phrase to signal completion (default: DONE)
  --no-auto-continue         Single session only
  --max-sessions N           Limit total sessions (0 = unlimited)
  --max-budget N.NN          Per-session dollar budget (default: 5.00)
  --model <model>            Model alias or ID (default: sonnet)
  --fallback-model <m>       Fallback model if primary overloaded
  --effort <level>           Thinking effort: low|medium|high (default: high)
  --permission-mode <m>      Permission mode (default: bypassPermissions)
  --add-dir <dirs>           Extra directories to allow access
  --list, -l                 List all tasks with progress
  --help, -h                 Show this help

Completion:
  Sessions end when the agent outputs <promise>DONE</promise> (or your custom
  promise). In structured mode, completion also triggers when all task_list.md
  checkboxes are marked [x]. Use --completion-promise to change the phrase.
HELP
}

generate_task_name() {
    local desc="${1:-}"
    local result
    result=$(echo "$desc" | tr '[:upper:]' '[:lower:]' | sed 's/[^a-z0-9]/-/g' | sed 's/--*/-/g' | cut -c1-30 | sed 's/^-//' | sed 's/-$//')
    if [ -z "$result" ]; then
        result="task-$(date +%Y%m%d-%H%M%S)"
        print_warning "Using generated name: $result"
    fi
    echo "$result"
}

validate_task_name() {
    local name="$1"
    if [[ "$name" == *".."* ]] || [[ "$name" == *"/"* ]] || [[ "$name" == *"\\"* ]]; then
        print_error "Invalid task name: '$name' (path traversal characters)"
        return 1
    fi
    if [ -z "$name" ]; then
        print_error "Task name cannot be empty"
        return 1
    fi
    if [[ "$name" == -* ]]; then
        print_error "Task name cannot start with a hyphen"
        return 1
    fi
    return 0
}

check_dependencies() {
    if ! command -v claude &> /dev/null; then
        print_error "'claude' CLI not found. Install: https://claude.ai/code"
        exit 1
    fi
}

# ── Lock file management ──────────────────────────────────────────────

acquire_lock() {
    local task_dir="$1"
    local lock_file="$task_dir/run.lock"
    if [ -f "$lock_file" ]; then
        local lock_pid
        lock_pid=$(cat "$lock_file" 2>/dev/null || echo "unknown")
        if kill -0 "$lock_pid" 2>/dev/null; then
            print_error "Task is already running (PID $lock_pid)"
            print_info "If this is stale, remove: $lock_file"
            return 1
        else
            print_warning "Stale lock found (PID $lock_pid not running). Removing."
            rm -f "$lock_file"
        fi
    fi
    echo $$ > "$lock_file"
    return 0
}

release_lock() {
    local task_dir="$1"
    rm -f "$task_dir/run.lock"
}

# ── Promise detection ─────────────────────────────────────────────────

check_promise_in_log() {
    local log_file="$1"
    local promise="$2"
    # Check for <promise>TEXT</promise> in the session log
    if grep -q "<promise>${promise}</promise>" "$log_file" 2>/dev/null; then
        return 0
    fi
    return 1
}

# ── Task listing ───────────────────────────────────────────────────────

list_tasks() {
    print_header "AUTONOMOUS TASKS"

    if [ ! -d "$AUTONOMOUS_DIR" ]; then
        print_warning "No tasks found ($AUTONOMOUS_DIR/ does not exist)"
        return
    fi

    local found=0
    for task_dir in "$AUTONOMOUS_DIR"/*/; do
        [ -d "$task_dir" ] || continue
        local task_name
        task_name=$(basename "$task_dir")
        local task_list="$task_dir/task_list.md"
        local mode_file="$task_dir/.mode"

        # Detect mode
        local mode="structured"
        [ -f "$mode_file" ] && mode=$(cat "$mode_file")

        if [ "$mode" == "lightweight" ]; then
            echo -e "  ${CYAN}lite${NC}  $task_name"
        elif [ -f "$task_list" ]; then
            local total done_count percent
            total=$(grep -c '^\- \[' "$task_list" 2>/dev/null || echo "0")
            done_count=$(grep -c '^\- \[x\]' "$task_list" 2>/dev/null || echo "0")
            percent=0
            [ "$total" -gt 0 ] && percent=$((done_count * 100 / total))

            if [ "$done_count" -eq "$total" ] && [ "$total" -gt 0 ]; then
                echo -e "  ${GREEN}done${NC}  $task_name  ($done_count/$total)"
            else
                echo -e "  ${YELLOW}${percent}%${NC}   $task_name  ($done_count/$total)"
            fi
        else
            echo -e "  ${RED}???${NC}   $task_name  (no task_list.md)"
        fi

        [ -f "$task_dir/run.lock" ] && echo -e "        ${DIM}(currently running)${NC}"
        found=$((found + 1))
    done

    [ "$found" -eq 0 ] && print_warning "No tasks found in $AUTONOMOUS_DIR/"
    echo ""
}

# ── Progress helpers ───────────────────────────────────────────────────

get_progress() {
    local task_dir="$1"
    if [ -f "$task_dir/task_list.md" ]; then
        local total done_count
        total=$(grep -c '^\- \[' "$task_dir/task_list.md" 2>/dev/null || echo "0")
        done_count=$(grep -c '^\- \[x\]' "$task_dir/task_list.md" 2>/dev/null || echo "0")
        echo "$done_count/$total"
    else
        echo "—"
    fi
}

is_complete() {
    local task_dir="$1"
    if [ -f "$task_dir/task_list.md" ]; then
        local total done_count
        total=$(grep -c '^\- \[' "$task_dir/task_list.md" 2>/dev/null || echo "0")
        done_count=$(grep -c '^\- \[x\]' "$task_dir/task_list.md" 2>/dev/null || echo "0")
        [ "$done_count" -eq "$total" ] && [ "$total" -gt 0 ]
    else
        return 1
    fi
}

task_exists() {
    [ -f "$AUTONOMOUS_DIR/$1/task_list.md" ]
}

# ── Session runners ────────────────────────────────────────────────────

build_claude_args() {
    local -a args=()
    args+=(--output-format stream-json --verbose)
    args+=(--model "$opt_model")
    args+=(--effort "$opt_effort")
    args+=(--max-budget-usd "$opt_max_budget")
    args+=(--permission-mode "$opt_permission_mode")
    args+=(--no-session-persistence)

    [ -n "$opt_fallback_model" ] && args+=(--fallback-model "$opt_fallback_model")
    [ -n "$opt_add_dir" ] && args+=(--add-dir "$opt_add_dir")

    echo "${args[@]}"
}

run_initializer() {
    local task_name="$1"
    local task_desc="$2"
    local task_dir="$AUTONOMOUS_DIR/$task_name"
    local session_num="$3"

    mkdir -p "$task_dir/sessions"

    local log_file="$task_dir/sessions/session-$(printf '%03d' "$session_num").log"

    print_info "Mode: Initializer"
    print_info "Task: $task_desc"
    print_info "Directory: $task_dir"
    print_info "Log: $log_file"

    local init_prompt
    init_prompt=$(sed "s|{TASK_DIR}|$task_dir|g" "$SKILL_DIR/templates/initializer-prompt.md")

    local claude_args
    claude_args=$(build_claude_args)

    local exit_code=0
    claude -p "Task: $task_desc
Task Name: $task_name
Task Directory: $task_dir
Completion Promise: $opt_completion_promise

$init_prompt" \
        $claude_args \
        --max-turns $MAX_TURNS_INIT \
        --append-system-prompt "You are the Initializer Agent. Create task_list.md and progress.md in $task_dir/. Project files go in their normal locations. When ALL work is genuinely complete, output <promise>$opt_completion_promise</promise>." \
        2>&1 | tee "$log_file" || exit_code=$?

    return $exit_code
}

run_executor() {
    local task_name="$1"
    local task_dir="$AUTONOMOUS_DIR/$task_name"
    local session_num="$2"

    local log_file="$task_dir/sessions/session-$(printf '%03d' "$session_num").log"

    print_info "Mode: Executor"
    print_info "Directory: $task_dir"
    print_info "Log: $log_file"

    local task_list progress_notes
    task_list=$(cat "$task_dir/task_list.md" 2>/dev/null || echo "No task list found")
    progress_notes=$(cat "$task_dir/progress.md" 2>/dev/null || echo "No progress notes yet")

    local exec_prompt
    exec_prompt=$(sed "s|{TASK_DIR}|$task_dir|g" "$SKILL_DIR/templates/executor-prompt.md")

    local claude_args
    claude_args=$(build_claude_args)

    local exit_code=0
    claude -p "Continue working on the task.
Task Name: $task_name
Task Directory: $task_dir
Completion Promise: $opt_completion_promise

Current task_list.md:
$task_list

Previous progress notes:
$progress_notes

$exec_prompt" \
        $claude_args \
        --max-turns $MAX_TURNS_EXEC \
        --append-system-prompt "You are the Executor Agent. Complete tasks and update files in $task_dir/. Project files go in their normal locations. When ALL tasks are genuinely complete, output <promise>$opt_completion_promise</promise>." \
        2>&1 | tee "$log_file" || exit_code=$?

    return $exit_code
}

run_lightweight() {
    local task_name="$1"
    local task_desc="$2"
    local task_dir="$AUTONOMOUS_DIR/$task_name"
    local session_num="$3"

    mkdir -p "$task_dir/sessions"

    local log_file="$task_dir/sessions/session-$(printf '%03d' "$session_num").log"

    print_info "Mode: Lightweight (iteration $session_num)"
    print_info "Log: $log_file"

    local claude_args
    claude_args=$(build_claude_args)

    local exit_code=0
    claude -p "$task_desc

---
You are in iteration $session_num of an iterative development loop.
Check existing files and git history to see your previous work.
Continue improving until the task is genuinely complete.

When the task is FULLY complete and verified, output exactly:
  <promise>$opt_completion_promise</promise>

Do NOT output the promise tag until everything is genuinely done and verified.
If tests exist, they must all pass. If builds exist, they must succeed.
Keep iterating until quality is right." \
        $claude_args \
        --max-turns $MAX_TURNS_EXEC \
        --append-system-prompt "You are in a lightweight autonomous loop (iteration $session_num). Work on the task, check your previous work in files/git. Output <promise>$opt_completion_promise</promise> ONLY when genuinely complete." \
        2>&1 | tee "$log_file" || exit_code=$?

    return $exit_code
}

# ── Main ───────────────────────────────────────────────────────────────

main() {
    local task_desc=""
    local task_name=""
    local auto_continue=true
    local max_sessions=0
    local continue_mode=false
    local opt_model="$DEFAULT_MODEL"
    local opt_fallback_model=""
    local opt_max_budget="$DEFAULT_MAX_BUDGET"
    local opt_effort="$DEFAULT_EFFORT"
    local opt_permission_mode="$DEFAULT_PERMISSION_MODE"
    local opt_completion_promise="$DEFAULT_COMPLETION_PROMISE"
    local opt_add_dir=""
    local opt_lightweight=false

    # Parse arguments
    while [[ $# -gt 0 ]]; do
        case $1 in
            --help|-h)       show_help; exit 0 ;;
            --list|-l)       list_tasks; exit 0 ;;
            --task-name|-n)  task_name="${2:-}"; shift 2 ;;
            --continue|-c)   continue_mode=true; shift ;;
            --no-auto-continue) auto_continue=false; shift ;;
            --max-sessions)  max_sessions="${2:-0}"; shift 2 ;;
            --max-budget)    opt_max_budget="${2:-$DEFAULT_MAX_BUDGET}"; shift 2 ;;
            --model)         opt_model="${2:-$DEFAULT_MODEL}"; shift 2 ;;
            --fallback-model) opt_fallback_model="${2:-}"; shift 2 ;;
            --effort)        opt_effort="${2:-$DEFAULT_EFFORT}"; shift 2 ;;
            --permission-mode) opt_permission_mode="${2:-$DEFAULT_PERMISSION_MODE}"; shift 2 ;;
            --completion-promise) opt_completion_promise="${2:-$DEFAULT_COMPLETION_PROMISE}"; shift 2 ;;
            --add-dir)       opt_add_dir="${2:-}"; shift 2 ;;
            --lightweight)   opt_lightweight=true; shift ;;
            *)               task_desc="$1"; shift ;;
        esac
    done

    # Determine task name
    if [ -z "$task_name" ] && [ -n "$task_desc" ]; then
        task_name=$(generate_task_name "$task_desc")
        print_info "Generated task name: $task_name"
    fi

    # Handle --continue without explicit name
    if [ -z "$task_name" ]; then
        if [ "$continue_mode" = true ]; then
            if [ -d "$AUTONOMOUS_DIR" ]; then
                task_name=$(ls -t "$AUTONOMOUS_DIR" 2>/dev/null | head -1) || true
            fi
            if [ -z "$task_name" ]; then
                print_error "No task name provided and no existing tasks found"
                exit 1
            fi
            print_info "Continuing most recent task: $task_name"
        else
            print_error "No task description or name provided"
            show_help
            exit 1
        fi
    fi

    validate_task_name "$task_name" || exit 1
    check_dependencies

    local task_dir="$AUTONOMOUS_DIR/$task_name"
    mkdir -p "$task_dir/sessions"

    # Save mode marker for --list display
    if [ "$opt_lightweight" = true ]; then
        echo "lightweight" > "$task_dir/.mode"
    elif [ ! -f "$task_dir/.mode" ]; then
        echo "structured" > "$task_dir/.mode"
    fi

    # Detect mode from existing task if continuing
    if [ "$continue_mode" = true ] && [ -f "$task_dir/.mode" ]; then
        local saved_mode
        saved_mode=$(cat "$task_dir/.mode")
        if [ "$saved_mode" == "lightweight" ]; then
            opt_lightweight=true
        fi
    fi

    # Acquire lock
    if ! acquire_lock "$task_dir"; then
        exit 1
    fi
    trap 'release_lock "$task_dir"; echo ""; print_warning "Progress saved in $task_dir/"; echo "  Resume: $0 --task-name $task_name --continue"' EXIT

    print_info "Completion promise: <promise>$opt_completion_promise</promise>"
    if [ "$opt_lightweight" = true ]; then
        print_info "Mode: lightweight (Ralph-style iteration)"
    else
        print_info "Mode: structured (task decomposition)"
    fi

    local session_num=1
    local retry_count=0
    local max_retries=1

    # ── Session loop ───────────────────────────────────────────────────
    while true; do
        print_header "SESSION $session_num  [$task_name]"

        if [ "$opt_lightweight" = false ] && task_exists "$task_name"; then
            print_info "Progress: $(get_progress "$task_dir")"
        fi

        # Run the appropriate agent
        local exit_code=0
        local log_file="$task_dir/sessions/session-$(printf '%03d' "$session_num").log"

        if [ "$opt_lightweight" = true ]; then
            # Lightweight mode: same prompt, no task decomposition
            run_lightweight "$task_name" "$task_desc" "$session_num" || exit_code=$?
        elif task_exists "$task_name"; then
            run_executor "$task_name" "$session_num" || exit_code=$?
        else
            if [ -z "$task_desc" ]; then
                print_error "Task '$task_name' not found and no description provided"
                exit 1
            fi
            run_initializer "$task_name" "$task_desc" "$session_num" || exit_code=$?
        fi

        # Handle session failure
        if [ $exit_code -ne 0 ]; then
            print_error "Session exited with code $exit_code"
            if [ $retry_count -lt $max_retries ]; then
                retry_count=$((retry_count + 1))
                print_warning "Retrying (attempt $retry_count/$max_retries)..."
                sleep 2
                continue
            else
                print_error "Max retries reached. Stopping."
                exit 1
            fi
        fi
        retry_count=0

        # ── Check completion (promise OR checkboxes) ───────────────────
        echo ""

        # 1. Check promise-based completion
        if [ -f "$log_file" ] && check_promise_in_log "$log_file" "$opt_completion_promise"; then
            print_header "PROMISE FULFILLED: <promise>$opt_completion_promise</promise>"
            print_success "Task '$task_name' completed in $session_num session(s)"
            print_info "Results in: $task_dir/"
            exit 0
        fi

        # 2. Check task-list completion (structured mode only)
        if [ "$opt_lightweight" = false ]; then
            print_info "Progress after session $session_num: $(get_progress "$task_dir")"
            if is_complete "$task_dir"; then
                print_header "ALL TASKS COMPLETED"
                print_success "Task '$task_name' finished in $session_num session(s)"
                print_info "Results in: $task_dir/"
                exit 0
            fi
        else
            print_info "Lightweight iteration $session_num complete (no promise detected)"
        fi

        # Check max sessions
        if [ $max_sessions -gt 0 ] && [ $session_num -ge $max_sessions ]; then
            print_warning "Reached session limit ($max_sessions)"
            print_info "Resume later: $0 --task-name $task_name --continue"
            exit 0
        fi

        # Auto-continue
        if [ "$auto_continue" = true ]; then
            echo ""
            print_info "Next session in ${AUTO_CONTINUE_DELAY}s... (Ctrl+C to stop)"
            sleep $AUTO_CONTINUE_DELAY
        else
            print_info "Auto-continue disabled."
            exit 0
        fi

        session_num=$((session_num + 1))
    done
}

main "$@"


path: scripts/setup-loop.sh
content: #!/bin/bash
#
# Autonomous Skill - Loop Setup Script
# Creates state file for in-session hook-based autonomous loop.
# Called by SKILL.md when user wants hook mode instead of headless mode.
#
# Usage:
#   setup-loop.sh "task description" [OPTIONS]
#
# Options:
#   --mode structured|lightweight    Loop mode (default: structured)
#   --max-iterations N               Max iterations (default: 0 = unlimited)
#   --completion-promise TEXT        Promise phrase (default: DONE)
#   --task-name NAME                 Explicit task name
#

set -euo pipefail

# ── Parse arguments ────────────────────────────────────────────────────
PROMPT_PARTS=()
MAX_ITERATIONS=0
COMPLETION_PROMISE="DONE"
MODE="structured"
TASK_NAME=""

while [[ $# -gt 0 ]]; do
  case $1 in
    -h|--help)
      cat <<'EOF'
Autonomous Loop Setup

Usage:
  setup-loop.sh "task description" [OPTIONS]

Options:
  --mode structured|lightweight  Loop mode (default: structured)
  --max-iterations N             Max iterations (0 = unlimited)
  --completion-promise TEXT       Promise phrase (default: DONE)
  --task-name NAME               Explicit task name

Modes:
  structured   - Full task decomposition with task_list.md tracking
  lightweight  - Same prompt repeated, Ralph-style iteration
EOF
      exit 0
      ;;
    --max-iterations)
      [[ -z "${2:-}" ]] && { echo "Error: --max-iterations requires a number" >&2; exit 1; }
      MAX_ITERATIONS="$2"; shift 2 ;;
    --completion-promise)
      [[ -z "${2:-}" ]] && { echo "Error: --completion-promise requires text" >&2; exit 1; }
      COMPLETION_PROMISE="$2"; shift 2 ;;
    --mode)
      [[ -z "${2:-}" ]] && { echo "Error: --mode requires structured or lightweight" >&2; exit 1; }
      MODE="$2"; shift 2 ;;
    --task-name)
      [[ -z "${2:-}" ]] && { echo "Error: --task-name requires a name" >&2; exit 1; }
      TASK_NAME="$2"; shift 2 ;;
    *)
      PROMPT_PARTS+=("$1"); shift ;;
  esac
done

PROMPT="${PROMPT_PARTS[*]}"

if [[ -z "$PROMPT" ]]; then
  echo "Error: No task description provided" >&2
  echo "Usage: setup-loop.sh \"Your task description\" [OPTIONS]" >&2
  exit 1
fi

# Generate task name if not provided
if [[ -z "$TASK_NAME" ]]; then
  TASK_NAME=$(echo "$PROMPT" | tr '[:upper:]' '[:lower:]' | sed 's/[^a-z0-9]/-/g' | sed 's/--*/-/g' | cut -c1-30 | sed 's/^-//' | sed 's/-$//')
  [[ -z "$TASK_NAME" ]] && TASK_NAME="task-$(date +%Y%m%d-%H%M%S)"
fi

TASK_DIR=".autonomous/$TASK_NAME"

# Set up task directory for structured mode
if [[ "$MODE" == "structured" ]]; then
  mkdir -p "$TASK_DIR/sessions"
fi

# Create state file
mkdir -p .claude

# Quote values for YAML
if [[ -n "$COMPLETION_PROMISE" ]]; then
  PROMISE_YAML="\"$COMPLETION_PROMISE\""
else
  PROMISE_YAML="null"
fi

cat > .claude/autonomous-loop.local.md <<EOF
---
active: true
iteration: 1
max_iterations: $MAX_ITERATIONS
completion_promise: $PROMISE_YAML
mode: $MODE
task_name: "$TASK_NAME"
task_dir: "$TASK_DIR"
started_at: "$(date -u +%Y-%m-%dT%H:%M:%SZ)"
---

$PROMPT
EOF

# Output setup message
cat <<EOF
Autonomous loop activated in this session.

Mode: $MODE
Task: $TASK_NAME
Iteration: 1
Max iterations: $(if [[ $MAX_ITERATIONS -gt 0 ]]; then echo $MAX_ITERATIONS; else echo "unlimited"; fi)
Completion: output <promise>$COMPLETION_PROMISE</promise> when genuinely done

The stop hook is now active. When you try to exit, the same prompt
will be fed back. Your previous work persists in files, creating an
iterative improvement loop.

To cancel: delete .claude/autonomous-loop.local.md

EOF

# Build the prompt with mode-appropriate context
if [[ "$MODE" == "structured" ]]; then
  if [[ -f "$TASK_DIR/task_list.md" ]]; then
    echo "Continuing structured task. Read $TASK_DIR/task_list.md and $TASK_DIR/progress.md for current state."
  else
    echo "New structured task. Create $TASK_DIR/task_list.md with phased sub-tasks, then begin work."
  fi
  echo ""
  echo "Task directory: $TASK_DIR"
else
  echo "Lightweight mode: iterate on the task until complete."
fi

echo ""
echo "When ALL work is complete and verified, output:"
echo "  <promise>$COMPLETION_PROMISE</promise>"
echo ""
echo "ONLY output the promise when it is genuinely true. Do not lie to exit the loop."
echo ""
echo "$PROMPT"


path: templates/executor-prompt.md
content: ## YOUR ROLE - EXECUTOR AGENT

You are continuing a multi-session autonomous task. You have NO memory of
previous sessions — your state comes entirely from the files below.

All tracking files are in **{TASK_DIR}/**. Project files go in their normal locations.

---

## 1. Orient (MANDATORY)

Before doing anything, understand where things stand:

1. Read `{TASK_DIR}/task_list.md` — your master checklist
2. Read `{TASK_DIR}/progress.md` — what previous sessions did and what they recommend
3. List project files to see what exists
4. If code project: check git log, run build/tests to verify nothing is broken

## 2. Verify Previous Work

The last session may have introduced issues. Before new work:
- If tests exist, run them
- If it's a build project, verify it compiles
- If anything is broken, fix it first and note in progress.md
- If a task was marked `[x]` but is actually broken, mark it back to `[ ]`

## 3. Pick Next Task

Find the first unchecked `[ ]` task in `{TASK_DIR}/task_list.md`. Tasks are
ordered by dependency — trust the order unless something is explicitly blocked.

If a task is blocked, add `(blocked: reason)` after it and move to the next one.

## 4. Execute

1. Implement the task thoroughly
2. Follow existing patterns in the codebase
3. Test your work
4. Don't over-engineer — do what the task asks

## 5. Update Tracking

After completing and verifying a task:

**task_list.md** — Change ONLY the checkbox: `[ ]` → `[x]`
- NEVER delete, reorder, or edit task descriptions
- Update the Meta section's completed count

**progress.md** — Append a new session entry:
```markdown
## Session N - [YYYY-MM-DD HH:MM]

### Accomplished
- Completed Task N: [what you did]

### Issues
- [Any problems encountered]

### Next Session Should
- Continue with Task N+1: [description]
- [Important context for the next agent]

### Status: M/T (P%)
```

## 6. Continue or End

**Continue** if: context has capacity, next task is related, you have momentum.
**End** if: context is filling up, next task is complex, work should be reviewed.

If continuing, go back to step 3.

## Before Ending

- All files saved (task_list.md and progress.md updated)
- No half-finished work — if you can't complete a task, don't mark it done
- progress.md has clear guidance for the next session
- Code is in a clean, working state

---

**Your goal:** Complete tasks with quality. One well-done task is better than
three broken ones. You have unlimited sessions — focus on steady progress.

## Completion Signal

When ALL tasks in task_list.md are marked `[x]` and verified working, output
this exact tag to signal completion:

<promise>{COMPLETION_PROMISE}</promise>

where {COMPLETION_PROMISE} is the value from the "Completion Promise" field above
(defaults to DONE). Only output this when everything is genuinely finished.
Do NOT output it to escape the loop — the loop continues until real completion.


path: templates/initializer-prompt.md
content: ## YOUR ROLE - INITIALIZER AGENT

You are the first agent in an autonomous multi-session workflow. Your job:
analyze the task, create a structured breakdown, and set up tracking files.

All tracking files go in **{TASK_DIR}/** (the Task Directory).
Project files (source code, configs, etc.) go in their normal project locations.

---

## 1. Understand the Task

Read the task description carefully. Identify:
- Scope and deliverables
- Dependencies and constraints
- What "done" looks like

## 2. Create task_list.md

**Path:** `{TASK_DIR}/task_list.md`

Break the task into concrete, independently-verifiable sub-tasks grouped by phase:

```markdown
# Task List: [Task Name]

## Meta
- Created: [YYYY-MM-DD HH:MM]
- Task Directory: {TASK_DIR}
- Total Tasks: [N]
- Completed: 0/[N] (0%)

## Tasks

### Phase 1: Foundation
- [ ] Task 1: [Clear, actionable description]
- [ ] Task 2: [Clear, actionable description]

### Phase 2: Core Implementation
- [ ] Task 3: [Clear, actionable description]

### Phase 3: Integration & Testing
- [ ] Task 4: [Clear, actionable description]

### Phase 4: Polish & Documentation
- [ ] Task 5: [Clear, actionable description]

## Notes
- [Architecture decisions, constraints, dependencies]
```

**Guidelines:**
- Simple tasks: 10-20 sub-tasks. Medium: 20-50. Complex: 50-100+
- Each task must be completable in roughly one session
- Use actionable language ("Implement X", "Add Y") not vague language ("Think about X")
- Order by dependency — later tasks can depend on earlier ones
- **CRITICAL**: Once created, task descriptions are immutable. Future sessions may only change `[ ]` to `[x]`.

## 3. Create progress.md

**Path:** `{TASK_DIR}/progress.md`

```markdown
# Progress Log

## Task Info
- Task Name: [name]
- Task Directory: {TASK_DIR}
- Started: [YYYY-MM-DD HH:MM]

## Session 1 (Initializer) - [YYYY-MM-DD HH:MM]

### Accomplished
- Created task_list.md with [N] tasks
- [Any setup work done]

### Next Session Should
- Start with Task 1: [description]
- [Any context the next agent needs]

### Status: 0/[N] (0%)
```

## 4. Set Up Project Structure (if applicable)

If the task involves creating files or code, set up the initial directory structure
and configuration in the **project root** (not in {TASK_DIR}).

## 5. Optionally Begin Work

If time and context permit, start executing Task 1. Mark it `[x]` only after
verification, and update progress.md.

## Before Ending

Verify:
- [ ] `{TASK_DIR}/task_list.md` exists with all sub-tasks
- [ ] `{TASK_DIR}/progress.md` documents this session
- [ ] Tasks are ordered by dependency
- [ ] Each task is clear enough for a fresh agent to execute without ambiguity
- [ ] Environment is in a clean, buildable state

## Completion Signal

If you managed to complete ALL tasks in this session (small tasks sometimes
finish in one go), output this exact tag to signal completion:

<promise>{COMPLETION_PROMISE}</promise>

where {COMPLETION_PROMISE} is the value from the "Completion Promise" field above
(defaults to DONE). Only output this when everything is genuinely finished and verified.
Do NOT output it prematurely — the loop is designed to continue until real completion.


path: templates/task-list-template.md
content: # Task List: [PROJECT NAME]

## Meta
- Created: [YYYY-MM-DD HH:MM]
- Task Directory: [TASK_DIR]
- Total Tasks: [N]
- Completed: 0/[N] (0%)

## Tasks

### Phase 1: Foundation & Setup
- [ ] Task 1: [Description]
- [ ] Task 2: [Description]
- [ ] Task 3: [Description]

### Phase 2: Core Implementation
- [ ] Task 4: [Description]
- [ ] Task 5: [Description]
- [ ] Task 6: [Description]

### Phase 3: Integration & Testing
- [ ] Task 7: [Description]
- [ ] Task 8: [Description]

### Phase 4: Polish & Documentation
- [ ] Task 9: [Description]
- [ ] Task 10: [Description]

## Notes

### Architecture Decisions
- [Decision 1]

### Dependencies
- [Dependency 1]

### Known Issues
- [Issue 1]


Security scan verdicts — ClawScan: suspicious | static analysis: clean | VirusTotal: suspicious | SkillSpector: suspicious (HIGH)