Files
sift/tools/run-plan.sh
T
kyleandClaude Opus 5.5 cfde35397a P5a plan: PDF documents — seven Ornith tasks with acceptance tests first
docs/plans/p5a: 01 kind+fetch limits, 02 pdftext child + runner, 03 dcache PDF tables,
04 pdfpool, 05 pdfdoc render (map/pages/grep), 06 api, 07 digest citations, metrics, CLI,
skill, unit, README. Given tests compile against a panic-only skeleton and fail there for the
intended reasons; PDFs for tests are built by internal/pdftest (given), plus two qpdf-encrypted
fixtures.

Owner changes for the run: vendor go-pdfkit/extract v0.3.0 + reader v0.6.0 (pinned via
tools.go until task 02); AGENTS.md, tools/run-plan.sh (enforces a Co-Authored-By trailer for
the model), docs/implementer-log.md, `make gate`; gofmt (whitespace) on clean.go and guard;
TestUnsupportedType uses a zip now that PDFs are supported; requestDeadline 90→210 s and
writeTimeout 120→240 s for the PDF path's worst case.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-25 20:47:38 -07:00

72 lines
2.9 KiB
Bash
Executable File

#!/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/<plan> [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/<plan> [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
if ! opencode run -m "$model" $flags --title "$milestone/$name" "$prompt" \
> "$runs/$name.log" 2>&1; then
echo "run-plan: opencode exited with an error 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"