Defect o: When tests FAIL, the detail is at most 20 lines total. - Fixed ERROR prefix: pytest prints 'ERROR ' (not 'ERRORS ') - Truncation with '... and N more' when summary exceeds 20 lines - Fallback (last 20 lines) unchanged
scripts/ — shared agent mechanics
Small scripts for the things every agent working on this repo ends up doing by hand, with the reasons attached. Usable from Claude Code, from opencode, or from a terminal — they take no agent-specific input and print plain text.
Each of these encodes something that cost real time to work out at least once and is not guessable from the code.
preflight.sh — refuse a start that will waste an hour
scripts/preflight.sh [--allow-shared] [--base main]
Run it before any multi-step work. Exit 0 means safe to proceed; exit 1 prints the reason and the fix.
It exists because of one run on 2026-09-10 that burned most of a session, and its four checks are ordered by how much time each failure actually cost — not by severity.
It prints the stash stack unconditionally, first, even when it passes.
That is the whole point. In that run an executor stashed its own partial work
with a descriptive message, came back to a clean tree, concluded "subagent
edits are not persisting", and re-fired subagents twice against that theory.
The work was in stash@{0} throughout. An unexpectedly clean tree is far more
often a stash than a broken editor, and an agent that sees the stack cannot
invent the tooling theory in the first place.
The stack is also shared across every worktree of this repo on this
machine — about twenty of them. Another session pushing an entry shifts
every index, and git stash pop deletes on success, so a wrong index is
destructive. Hence the printed reminder to apply by SHA.
Wrong-branch and shared-checkout checks. The same run executed on
docs/admin-screenshots — a merged PR's leftover branch — so every edit
landed somewhere nobody was looking, against a tree missing two merged PRs,
and the operator's git pull origin main failed against it. The branch check
is deliberately not "must equal main": a feature branch cut from an
up-to-date base is fine, so it tests whether origin/<base> is an ancestor.
A leftover branch from a merged PR fails that; a fresh feature branch passes.
--allow-shared skips the worktree requirement for the case where working in
the primary checkout is genuinely intended.
sandbox.sh — throwaway router on 8081
scripts/sandbox.sh start [worktree] # defaults to $PWD; waits for /health
scripts/sandbox.sh stop
scripts/sandbox.sh status
Runs a second router instance so the admin portal can be exercised against real data without touching production.
8080 is production, always — it is baked into opencode.json, the
systemd unit, every curl example in CLAUDE.md, and the admin frontend's
own fetches. A throwaway binds 8081. This script cannot bind 8080.
It only kills a pid it recorded itself. Never pkill uvicorn or
kill-by-port here: Restart=always will fight you, and on this machine that
process is the operator's model access.
Why it passes both a cwd and a PYTHONPATH. cwd decides
config/config.yaml and the relative router.db; PYTHONPATH decides
which src/ runs and which admin/frontend/*.html is served, because
admin.py resolves _REPO_ROOT from Path(__file__).parent.parent.
Pointing both at a worktree keeps every write — including admin writes to
config.local.yaml — inside that worktree.
To exercise the portal against the real catalog and decision history, clone the production DB into the worktree first:
sqlite3 router.db "VACUUM INTO '<worktree>/router.db'"
VACUUM INTO rather than cp: the live DB is WAL-mode with an active
writer, so a plain copy can be torn.
mkpr.sh — cut a gitea PR with a long body
scripts/mkpr.sh <body.md> "<title>" [head] [base]
tea pr create has no --description-file, only --description, so a
long body has to arrive through $(cat ...). This wraps that, and refuses
to open a PR whose head branch is unpushed, has diverged from its remote,
or is not actually ahead of the base — tea will cheerfully do all three
and leave you with an empty or misleading PR.
Set MKPR_REPO to target a repo other than alee/6krrt.
verify_commit.py — check a commit is valid, clean, and tested
python3 scripts/verify_commit.py [--repo DIR] [--require-clean] SHA
Checks that a commit exists (PASS), working tree is clean (PASS/FAIL with --require-clean), and pytest passes on the files the commit touched (against the committed tree, via git archive). Output is one PASS|FAIL|SKIP line per check, at most 40 lines.
Used as the default plan_tick_gate command: the opencode guardrails plugin
refuses to tick a todo while this script reports FAIL on HEAD.
oc_dispatch_audit.py — audit orchestrator dispatch calls
python3 scripts/oc_dispatch_audit.py <session_id> [--worktree <path>]
Opens an orchestrator session's transcript and flags every task() or call_omo_agent call that is DEAD (no category, subagent_type, AND task_id), BANNED (subagent_type starts with oh-my-claudecode:), NOWT (missing WORKTREE: line when worktree is given), or IDLE (child session with 0 tool calls).
An orchestrator runs this before reporting a wave done.
Things these scripts deliberately do not do
- Merge to
main. A merge needsmainchecked out, which conflicts with any agent working in a git worktree. Merges stay a human step. - Run
Seed EnergyorRestart Service. The first spends real provider credit (?samples=5is 65+ billed completions); the second restarts the production systemd unit whichever instance is asked. Read them in source rather than invoking them to find out. - Touch
config/config.local.yaml. It is gitignored, never committed, and holds the live classifier settings — there is no copy in git to restore from. Back it up andmd5sumbefore and after if a task must write to it.
Tests
PYTHONPATH=src .venv/bin/python -m pytest tests/ -q
tests/conftest.py sets ROUTER_IGNORE_LOCAL_CONFIG, so the suite ignores
whichever config.local.yaml the machine has. Before that, a deployment
with a configured classifier.mode failed nine tests that had nothing to do
with its changes. A test that genuinely wants overlay merging must pass
include_overlay=True to load_config explicitly.