Files
6krrt/scripts/README.md
adlee-was-taken 7f73a00c78 feat(scripts): preflight gate, plus the git rules that would have caught it
A run on 2026-09-10 burned most of a session on three failures, all
mechanical and all preventable by one check at the start.

The expensive one was not the wrong branch. 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} the whole time.

So preflight.sh prints the stash stack UNCONDITIONALLY and first, pass or
fail. An agent that sees the stack cannot invent the tooling theory. The
checks are ordered by how much time each failure actually cost, not by
severity:

  0. stash stack, always printed
  1. not the shared primary checkout (git-dir vs git-common-dir)
  2. on a branch that contains origin/<base>
  3. clean working tree
  4. not behind origin/<base>

Check 2 is deliberately not "must equal main". A feature branch cut from
an up-to-date base is fine; what caused the incident was a LEFTOVER
branch from a merged PR (docs/admin-screenshots), which fails the
ancestor test while a fresh feature branch passes. Verified against the
real commit: `git merge-base --is-ancestor origin/main 5b7ae6d` fails.

AGENTS.md gains the rules the incident earned, the first of which is the
one that matters:

  - If the tree looks unexpectedly clean, run `git stash list` BEFORE
    concluding anything about your tooling.
  - Never bare `git stash` / `pop`. ~20 worktrees on this machine share
    ONE stack; another session pushing an entry shifts every index, and
    pop deletes on success, so a wrong index is unrecoverable.
  - Work in a worktree, not the shared checkout the operator is using.
  - The plan is the contract. If an approved item looks wrong, STOP and
    say so rather than implementing the alternative -- the same run
    re-derived a design that had been explicitly reviewed and rejected,
    because the rejected version sounded more reasonable in isolation.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VRQXz5SYZYVWscxS1QqF6U
2026-09-09 22:34:05 -04:00

5.1 KiB

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.

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.