#!/bin/sh # Runs the tasks of a plan one after another, each in its own fresh OpenCode session, and stops at # the first task that does not end cleanly. This is the owner's tool; the implementing model never # runs it. Adapted from crossbar's tools/run-plan.sh. # # Usage: tools/run-plan.sh docs/plans/ [FIRST] FIRST is a task number such as 03 # Options (environment): # SIFT_MODEL model to use (default llama.cpp/ornith-1.5-35b-a3b) # OPENCODE_FLAGS extra flags for `opencode run` (default --pure) # DRY_RUN=1 print what would be run and stop set -eu plan="${1:?usage: tools/run-plan.sh docs/plans/ [FIRST]}" first="${2:-00}" model="${SIFT_MODEL:-llama.cpp/ornith-1.5-35b-a3b}" flags="${OPENCODE_FLAGS:---pure}" log=docs/implementer-log.md milestone=$(basename "$plan") runs=".state/runs/$milestone" # The model's own name for the attribution trailer: the part after the provider prefix. mname="${model#*/}" trailer="Co-Authored-By: $mname <$mname@llama.cpp.invalid>" [ -d "$plan" ] || { echo "run-plan: $plan is not a directory" >&2; exit 2; } [ -f "$log" ] || { echo "run-plan: run this from the repository root" >&2; exit 2; } mkdir -p "$runs" for task in "$plan"/[0-9][0-9]-*.md; do name=$(basename "$task" .md) number=$(echo "$name" | cut -c1-2) [ "$number" -ge "$first" ] || continue prompt="Read \`AGENTS.md\`, then read \`$task\` and do exactly that task. Your commit message must end with this exact trailer line: $trailer" if [ "${DRY_RUN:-0}" = 1 ]; then echo "opencode run -m $model $flags --title $milestone/$name \"$prompt\"" continue fi if [ -n "$(git status --porcelain)" ]; then echo "run-plan: the working tree is not clean before $name; stopping" >&2 exit 1 fi before=$(git rev-parse HEAD) echo "=== $milestone/$name: started $(date '+%H:%M:%S')" # shellcheck disable=SC2086 # "$runs/$name.log" 2>&1; then echo "run-plan: opencode exited with an error (or hit SESSION_MAX) in $name; see $runs/$name.log" >&2 exit 1 fi echo "=== $milestone/$name: session ended $(date '+%H:%M:%S')" if [ "$(git rev-parse HEAD)" = "$before" ]; then echo "run-plan: $name made no commit; stopping" >&2 exit 1 fi if [ -n "$(git status --porcelain)" ]; then echo "run-plan: $name left uncommitted changes; stopping" >&2 exit 1 fi if ! git log -1 --format=%B | grep -qxF "$trailer"; then echo "run-plan: $name's commit lacks the trailer '$trailer'; stopping" >&2 exit 1 fi if ! grep -Eq "^\| *$milestone/$name *\|[^|]*\| *done *\|" "$log"; then echo "run-plan: $name has no 'done' row in $log; stopping" >&2 exit 1 fi echo "=== $milestone/$name: ok ($(git rev-parse --short HEAD))" done echo "run-plan: all tasks done"