Files
6krrt/scripts/README.md
2026-10-04 01:14:32 -04:00

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.