| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136 |
- #!/usr/bin/env bash
- # ff-run.sh - whole-run replay/status for a fleetflow run.
- #
- # resume: replay every packet in <run>/manifest.json through ff-spawn, IN
- # MANIFEST ORDER, sequential. Unchanged packets cache-hit (ff-spawn exit 3)
- # and are reported "cached"; changed/new packets run live. A per-lane summary
- # goes to stderr; a JSON result array goes to stdout. Exit 0 if every lane is
- # ok or cached, 10 if any lane failed.
- # status: convenience alias for ff-status (its JSON on stdout, identical exit).
- # stdout: the JSON result list (resume) / ff-status JSON (status). stderr: chatter.
- #
- # Exit codes: 0 all ok/cached | 2 usage | 10 a lane failed
- set -u
- . "$(dirname "${BASH_SOURCE[0]}")/_env.sh"
- FF_VERSION="1.1.0"
- usage() {
- cat <<'EOF'
- Usage: ff-run.sh resume --run NAME [--repo PATH]
- ff-run.sh status --run NAME [--repo PATH]
- resume --run NAME replay every packet in <run>/manifest.json through
- ff-spawn, in order. Unchanged packets cache-hit ("cached"),
- changed/new ones run live. JSON result list on stdout.
- status --run NAME alias for ff-status (run status JSON on stdout).
- --repo PATH repo root (default: git toplevel of cwd)
- EXAMPLES
- ff-run.sh resume --run currency
- ff-run.sh resume --run currency --repo /path/to/repo | jq '.[] | select(.status!="cached")'
- ff-run.sh status --run currency | jq '.lanes | length'
- EOF
- }
- err() { echo "ff-run: $*" >&2; }
- HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
- SPAWN="$HERE/ff-spawn.sh"
- MODE="" RUN="" REPO=""
- [ $# -gt 0 ] || { err "a subcommand is required (resume|status)"; usage >&2; exit 2; }
- case "$1" in
- resume) MODE="resume"; shift ;;
- status) MODE="status"; shift ;;
- -h|--help) usage; exit 0 ;;
- *) err "unknown subcommand: $1"; usage >&2; exit 2 ;;
- esac
- while [ $# -gt 0 ]; do
- case "$1" in
- --run) RUN="${2:-}"; shift 2 ;;
- --repo) REPO="${2:-}"; shift 2 ;;
- -h|--help) usage; exit 0 ;;
- *) err "unknown argument: $1"; usage >&2; exit 2 ;;
- esac
- done
- command -v jq >/dev/null || { err "jq required"; exit 2; }
- command -v git >/dev/null || { err "git required"; exit 2; }
- [ -n "$RUN" ] || { err "--run required"; usage >&2; exit 2; }
- [ -n "$REPO" ] || REPO="$(git rev-parse --show-toplevel 2>/dev/null)" || true
- [ -n "$REPO" ] && [ -d "$REPO" ] || { err "not in a git repo (or --repo invalid)"; exit 2; }
- # status = alias for ff-status (hand off and let it own exit codes).
- # exec THROUGH bash: a direct exec needs the +x bit, which zip installs and
- # mode-dropping checkouts lose — Git Bash fakes modes, so only Linux notices.
- if [ "$MODE" = "status" ]; then
- if [ -n "$REPO" ]; then exec bash "$HERE/ff-status.sh" --run "$RUN" --repo "$REPO"; fi
- exec bash "$HERE/ff-status.sh" --run "$RUN"
- fi
- RUNDIR="$REPO/.fleetflow/$RUN"
- MANIFEST="$RUNDIR/manifest.json"
- [ -f "$MANIFEST" ] || { err "no manifest at $MANIFEST (run ff-spawn first)"; exit 2; }
- # Snapshot the packets ONCE, before replay. ff-spawn upserts each packet on the
- # way in (remove-then-append), which REORDERS the live manifest - so re-reading
- # .packets[$i] mid-loop would drift and revisit the same packet. The snapshot is
- # the replay contract: spawn order = the order captured here, frozen.
- PACKETS="$(jq -c '.packets' "$MANIFEST" 2>/dev/null)"
- N="$(printf '%s' "$PACKETS" | jq -r 'length' 2>/dev/null)"
- [ "${N:-0}" -gt 0 ] 2>/dev/null || { err "manifest has no packets to replay"; exit 2; }
- err "resume: replaying $N packet(s) from $MANIFEST (sequential)"
- err " # id brain status"
- err " -- ----------------------- -------- --------"
- RESULTS="[]"
- ANY_FAIL=0
- i=0
- while [ "$i" -lt "$N" ]; do
- pid="$(printf '%s' "$PACKETS" | jq -r ".[$i].id")"
- pbrain="$(printf '%s' "$PACKETS" | jq -r ".[$i].brain")"
- # imported native packets are terminal facts, not replayable ("native" is not a
- # spawnable brain - ff-spawn rejects it). ff-run resume SKIPS them; to continue
- # from an imported result, spawn a fresh lane with a real brain. See ff-import.
- if [ "$pbrain" = "native" ]; then
- err " $((i+1)) $(printf '%-23s' "$pid") native imported (skipped)"
- RESULTS="$(jq -nc --argjson R "$RESULTS" --arg id "$pid" --arg s "imported" --argjson rc 0 \
- '$R + [{id:$id,status:$s,rc:$rc}]')"
- i=$((i+1)); continue
- fi
- pphase="$(printf '%s' "$PACKETS" | jq -r ".[$i].phase // \"build\"")"
- ppf="$(printf '%s' "$PACKETS" | jq -r ".[$i].prompt_file")"
- pwt="$(printf '%s' "$PACKETS" | jq -r ".[$i].worktree // false")"
- pmt="$(printf '%s' "$PACKETS" | jq -r ".[$i].max_turns // 100")"
- peff="$(printf '%s' "$PACKETS" | jq -r ".[$i].effort // \"\"")"
- psch="$(printf '%s' "$PACKETS" | jq -r ".[$i].schema // \"\"")"
- # worktree is a boolean string ("true"/"false"); both are non-empty, so gate on
- # the literal value rather than ${pwt:+...} (which would always fire).
- WT_FLAG=""; [ "$pwt" = "true" ] && WT_FLAG="1"
- bash "$SPAWN" --run "$RUN" --id "$pid" --brain "$pbrain" --phase "$pphase" \
- --prompt-file "$ppf" --max-turns "$pmt" --repo "$REPO" \
- ${WT_FLAG:+--worktree} ${peff:+--effort "$peff"} ${psch:+--schema "$psch"} \
- >/dev/null 2>>"$RUNDIR/$pid.resume.err"
- rc=$?
- case "$rc" in
- 0) status="ran" ;;
- 3) status="cached" ;;
- *) status="failed"; ANY_FAIL=1 ;;
- esac
- err " $((i+1)) $(printf '%-23s' "$pid") $(printf '%-8s' "$pbrain") $status${rc:+ (rc=$rc)}"
- RESULTS="$(jq -nc --argjson R "$RESULTS" --arg id "$pid" --arg s "$status" --argjson rc "$rc" \
- '$R + [{id:$id,status:$s,rc:$rc}]')"
- i=$((i+1))
- done
- RAN="$(printf '%s' "$RESULTS" | jq -r '[.[]|select(.status=="ran")]|length')"
- CACHED="$(printf '%s' "$RESULTS" | jq -r '[.[]|select(.status=="cached")]|length')"
- FAILED="$(printf '%s' "$RESULTS" | jq -r '[.[]|select(.status=="failed")]|length')"
- err " --"
- err " summary: $RAN ran, $CACHED cached, $FAILED failed"
- printf '%s\n' "$RESULTS"
- [ "$ANY_FAIL" = 1 ] && exit 10
- exit 0
|