#!/usr/bin/env bash # sx-fix-up.sh — spin up ONE claude fix-loop in tmux, driven by an agentic-sx # branch in the rose-ash/sx-review forge (git-sx / gitea-sx / agentic-sx). # # This tests the git→gitea→agentic→tmux wiring: the forge holds the agent # branch + briefing; this launcher READS that briefing from the forge (proving # the forge drives the session), then materialises a git worktree and a tmux # window running claude on the corresponding real branch. # # Usage: ./scripts/sx-fix-up.sh [interval] # forge-agent agentic-sx agent branch in the forge, e.g. ws-W14 # briefing.md file under plans/agent-briefings/ with the detailed work plan # interval optional /loop interval (e.g. 15m); omit for self-paced # # Example: ./scripts/sx-fix-up.sh ws-W14 sx-gate-loop.md # # The real code branch is loops/sx- in worktree # /root/rose-ash-loops/sx-. Commits are LOCAL by default — the briefing # decides whether to push. Watch: tmux a -t sx-fix ; stop: ./scripts/sx-fix-down.sh set -euo pipefail ROOT="$(cd "$(dirname "$0")/.." && pwd)"; cd "$ROOT" AGENT="${1:?usage: sx-fix-up.sh [interval]}" BRIEFING_MD="${2:?briefing file under plans/agent-briefings/ required}" INTERVAL="${3:-}" SLUG="$(echo "$AGENT" | tr '[:upper:]' '[:lower:]' | tr -c 'a-z0-9' '-' | sed 's/-*$//')" SESSION="sx-fix" WT="/root/rose-ash-loops/sx-${SLUG}" BRANCH="loops/sx-${SLUG}" BIN="hosts/ocaml/_build/default/bin/sx_server.exe" BOOT_WAIT=20 [ -f "plans/agent-briefings/$BRIEFING_MD" ] || { echo "no such briefing: plans/agent-briefings/$BRIEFING_MD"; exit 1; } # --- 1. read the briefing identity FROM the forge (the agentic-sx → launch link) --- echo "Querying the forge for agent '$AGENT'..." FORGE_OUT="$( { echo '(epoch 1)' for f in spec/stdlib.sx lib/r7rs.sx lib/persist/event.sx lib/persist/backend.sx \ lib/persist/log.sx lib/persist/kv.sx lib/artdag/dag.sx \ lib/datalog/tokenizer.sx lib/datalog/parser.sx lib/datalog/unify.sx \ lib/datalog/db.sx lib/datalog/builtins.sx lib/datalog/aggregates.sx \ lib/datalog/strata.sx lib/datalog/eval.sx lib/datalog/api.sx lib/datalog/magic.sx \ lib/git/object.sx lib/git/ref.sx lib/git/dag.sx lib/git/worktree.sx \ lib/git/diff.sx lib/git/merge.sx lib/git/porcelain.sx \ lib/relations/schema.sx lib/relations/engine.sx lib/relations/api.sx \ lib/relations/explain.sx lib/relations/federation.sx lib/relations/tree.sx \ lib/agentic/schema.sx lib/agentic/branch.sx lib/gitea/repo.sx; do echo "(load \"$f\")" done echo '(epoch 2)' echo '(load "plans/sx-review/forge-build.sxsrc")' echo '(epoch 3)' echo "(eval \"(str (agentic/briefing-title (agentic/briefing-of fb-sp \\\"$AGENT\\\")) \\\" | \\\" (agentic/briefing-goal (agentic/briefing-of fb-sp \\\"$AGENT\\\")))\")" } | timeout 300 "$BIN" 2>/dev/null | grep -A1 '(ok-len 3' | tail -1 )" if [ -z "$FORGE_OUT" ]; then echo "FAILED: could not read briefing for '$AGENT' from the forge." >&2 exit 1 fi echo "Forge briefing: $FORGE_OUT" # --- 2. materialise the worktree + branch off architecture --- if [ -d "$WT/.git" ] || [ -f "$WT/.git" ]; then echo "worktree exists: $WT" else if git show-ref --verify --quiet "refs/heads/$BRANCH"; then git worktree add "$WT" "$BRANCH" >/dev/null else git worktree add -b "$BRANCH" "$WT" architecture >/dev/null fi echo "worktree created: $WT on $BRANCH" fi # permissions so the loop doesn't stall on prompts (mirrors sx-loops-up.sh) mkdir -p "$WT/.claude" cat > "$WT/.claude/settings.local.json" <<'SETTINGS' { "permissions": { "allow": [ "mcp__sx-tree__sx_summarise","mcp__sx-tree__sx_read_tree","mcp__sx-tree__sx_read_subtree", "mcp__sx-tree__sx_find_all","mcp__sx-tree__sx_find_across","mcp__sx-tree__sx_validate", "mcp__sx-tree__sx_write_file","mcp__sx-tree__sx_eval","mcp__sx-tree__sx_harness_eval", "mcp__sx-tree__sx_build","mcp__sx-tree__sx_test","mcp__sx-tree__sx_diff_branch", "mcp__sx-tree__sx_changed","mcp__sx-tree__sx_comp_list","mcp__sx-tree__sx_comp_usage", "Bash(node *)","Bash(python3 *)","Bash(bash *)","Bash(cp *)","Bash(git *)","Bash(timeout *)" ] }, "enabledMcpjsonServers": ["sx-tree","rose-ash-services","hs-test"] } SETTINGS # --- 3. tmux window + claude + /loop briefing --- if ! tmux has-session -t "$SESSION" 2>/dev/null; then tmux new-session -d -s "$SESSION" -n "$SLUG" -c "$WT" else tmux new-window -t "$SESSION" -n "$SLUG" -c "$WT" fi tmux send-keys -t "$SESSION:$SLUG" "claude" C-m echo "waiting ${BOOT_WAIT}s for claude to boot..." sleep "$BOOT_WAIT" PRE="/loop "; [ -n "$INTERVAL" ] && PRE="/loop $INTERVAL " CMD="${PRE}Read plans/agent-briefings/$BRIEFING_MD (forge agent $AGENT: $FORGE_OUT) and do ONE iteration per fire: pick the first unchecked [ ], implement, test, commit LOCALLY (no push), tick the box, prepend one dated line to the Progress log, then stop. You are on branch $BRANCH in worktree $WT. Obey the briefing's hard guardrails — test-only, no semantics edits, no push." tmux send-keys -t "$SESSION:$SLUG" "$CMD" sleep 0.5 tmux send-keys -t "$SESSION:$SLUG" Enter echo "" echo "Launched fix-loop '$SLUG' (forge agent $AGENT) in tmux '$SESSION'." echo " Attach: tmux a -t $SESSION" echo " Watch: tmux capture-pane -t $SESSION:$SLUG -p | tail -40" echo " Stop: tmux kill-window -t $SESSION:$SLUG (or ./scripts/sx-fix-down.sh)" echo " Branch: $BRANCH Worktree: $WT (commits LOCAL — review before push)"