148 lines
6.1 KiB
Markdown
148 lines
6.1 KiB
Markdown
# 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 needs `main` checked out, which conflicts
|
|
with any agent working in a git worktree. Merges stay a human step.
|
|
- **Run `Seed Energy` or `Restart Service`.** The first spends real
|
|
provider credit (`?samples=5` is 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 and `md5sum` before 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.
|