From the combined Ornith guidance (§8.2), checked against OpenCode 1.18.30's schema and permission docs (last matching rule wins). Reads and edits outside the repository are denied, except OpenCode's own tool-output and temporary directories; web access, questions, subagents and doom-loop prompts are denied; edits to AGENTS.md, CLAUDE.md, opencode.json, the design documents, specs, plans and tools/ are denied; shell commands are allowed except push, reset, rebase, amend, switch, checkout, find /, locate, sudo, rm -rf / and curl/wget. A denied call continues the loop (experimental.continue_loop_on_deny, untested with Ornith). steps = 400 is a backstop. No custom prompt: OpenCode already reads AGENTS.md, and a prompt may replace its tool guidance. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
78 lines
3.6 KiB
Bash
Executable File
78 lines
3.6 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.
|
|
#
|
|
# Usage: tools/run-plan.sh docs/plans/M2a [FIRST] FIRST is a task number such as 04
|
|
# Options (environment):
|
|
# OPENCODE_MODEL model to use (default straylight/laguna-s-2.1; OpenCode's own default differs).
|
|
# The part after the provider (`ornith-1.5-35b-a3b`) names the model in each
|
|
# commit's `Co-Authored-By: <model> <agent@wntrmute.dev>` trailer and in the log.
|
|
# OPENCODE_FLAGS extra flags for `opencode run`, for example "--pure"
|
|
# Each session runs as the `implement` agent of ./opencode.json: its permissions deny edits to the
|
|
# plans and design documents, reads outside the repository, subagents and pushes.
|
|
# DRY_RUN=1 print what would be run and stop
|
|
set -eu
|
|
|
|
plan="${1:?usage: tools/run-plan.sh docs/plans/<milestone> [FIRST]}"
|
|
first="${2:-00}"
|
|
model="${OPENCODE_MODEL:-straylight/laguna-s-2.1}"
|
|
agent="${model##*/}"
|
|
trailer="Co-Authored-By: $agent <agent@wntrmute.dev>"
|
|
log=docs/implementer-log.md
|
|
milestone=$(basename "$plan")
|
|
runs=".state/runs/$milestone"
|
|
|
|
[ -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 \`$task\` and do exactly that task. You are $agent: write $agent in the log row's Model column, and end the commit message with this line, exactly: $trailer"
|
|
|
|
if [ "${DRY_RUN:-0}" = 1 ]; then
|
|
echo "opencode run --agent implement -m $model ${OPENCODE_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')"
|
|
# A new `opencode run` is a new session: nothing carries over from the task before.
|
|
# shellcheck disable=SC2086
|
|
if ! opencode run --agent implement -m "$model" ${OPENCODE_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')"
|
|
|
|
# The task must have left exactly what AGENTS.md asks for: a commit carrying the trailer, a
|
|
# clean tree, and a `done` row in the log. Anything else needs a person, so stop.
|
|
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 line '$trailer'; stopping" >&2
|
|
exit 1
|
|
fi
|
|
if ! grep -Eq "^\| *$milestone/$name *\|[^|]*\| *done *\|" "$log"; then
|
|
echo "run-plan: no 'done' row for $milestone/$name in $log; stopping" >&2
|
|
grep -E "^\| *$milestone/$name *\|" "$log" >&2 || true
|
|
exit 1
|
|
fi
|
|
echo "=== $milestone/$name: done ($(git log -1 --format=%h))"
|
|
done
|
|
echo "run-plan: every task from $first on ended cleanly. Ready for review."
|