7.2 KiB
gitea-review-bot: AGENTS.md
gitea-review-bot is a thin Matrix bot that runs an AI PR-review-and-merge workflow for your Gitea
repos, one review room per repo. It is a separate bot from matrix-bridge (which turns Matrix
messages into interactive Claude Code sessions): this one's single responsibility is reviewing
pull requests — so every room it's in is a review room and there is no manual "ignore this room"
wiring. Invite it to a room, it auto-joins and maps the room to a repo by name, then for that repo
it polls Gitea PRs, posts a headless claude -p review as a Matrix thread, and lets you merge/reject
from the thread.
Inbox check: At session start, if
~/Projects/standards/INBOX.mdexists, scan it for items tagged(gitea-review-bot)and surface them before proposing next steps; triage with/triage.
Stack
Python + matrix-nio, one thin Docker container on the Spark (same shape as matrix-bridge and
the ten31-database intake bot). No framework (Maubot rejected — see Decisions). The heavy work —
the claude -p review and any deploy — runs on the Mac over SSH, reusing matrix-bridge's proven
wrappers (scripts/ask-claude.sh for the review, scripts/deploy-site.sh for publish). State is
flat JSON in a writable state/ mount.
Placement
| Dimension | Call |
|---|---|
| Host | Spark, plain Docker container (NOT Start9/s9pk) |
| Runtime | Long-running service: matrix-nio sync + a Gitea poll loop over mapped repos |
| Model routing | claude -p on the Mac via the Spark→Mac SSH seam (subscription); the review session spawns subagents (reviewer / adjudicator / security-auditor) |
| Data layer | Flat JSON in state/ (room→repo map + enabled agents + reviewed-PR heads + thread roots) |
| Interface | Matrix — one review room per repo (+ phone) |
| Repo home | Local + Gitea (ssh://git@immense-voyage.local:59916/grant/gitea-review-bot.git) |
| Sensitivity | Sends PR diffs to claude -p (subscription). Fine for code review; flag the boundary before pointing it at a sensitive repo. |
Commands
- Run (container, on the Spark): from
~/gitea-review-bot,docker compose up -d --build(host networking,restart: unless-stopped; read-only mounts of.env/config.toml/SSH key, read-writestate/). Logs:docker compose logs -f. - Deploy: the Spark's
~/gitea-review-botis a Gitea clone trackingmaster; deploy =git fetch && git reset --hard origin/master && docker compose up -d --build(or a Spark Control Update tile, once added — captured in the inbox).config.tomlis gitignored — refresh it on the Spark separately (scp) like matrix-bridge. Reach the Spark from the Mac asssh modelo32(the NVIDIA Sync alias — hostspark-32d0, usermodelo, key-based; injected into~/.ssh/configvia anInclude.modelo27/spark-27eais a second Spark on another LAN).reset --hardis safe:.env/config.toml/state/are gitignored. - Onboard a repo: create a Matrix room named like the repo, invite this bot → it auto-joins,
maps the room to
<owner>/<roomname>(+~/Projects/<roomname>on the Mac), and posts an onboarding message. Pick review agents in-chat:agents +reviewer +security -adjudicator. - In a review room:
merge/reject/clear(inside a PR's thread, or<cmd> <n>by number),yes/noto confirm a merge,agents …to toggle the subagent panel.clear(aliasresolve) redacts an orphaned thread without touching the PR.
Layout
src/bot.py— the bot: matrix-nio sync; auto-join + in-room auto-map (room→repo by name); a Gitea poll loop per mapped repo; threadedclaude -preview (subagent panel); merge/reject/deploy in-thread; whole-thread redaction on resolve (server-enumerated, restart-proof) — a merge always redacts (a failed post-merge deploy leaves a top-level warning, not an orphaned thread);clearredacts without touching the PR.config.example.toml— homeserver,[mac](ssh alias + the reused matrix-bridge wrapper paths),[gitea](api_base/owner/verify_tls),[defaults], optional[repo.<name>]deploy overrides..env.example—MATRIX_*+GITEA_TOKEN(real.envgitignored).Dockerfile·docker-compose.yml·docker-entrypoint.sh— the Spark container (generic image; secrets/config via read-only mounts; entrypoint writes~/.ssh/configfor the Mac alias).state/— gitignored runtime JSON (rooms.json: room map + agents + heads + thread roots).
Decisions
- Separate bot from matrix-bridge (single responsibility = PR review). Beat: extending matrix-bridge with review-room special-casing. Reopens if the two bots' logic heavily overlaps.
- Thin matrix-nio container, NOT Maubot. Reevaluated 2026-06-28: Maubot helps with the Matrix
plumbing we've already solved, not the SSH/
claude -p/poll logic that's the actual weight, and it reintroduces a web-UI/management layer (Spark Control is the dashboard). Reopens at ~6+ bots or a non-developer web-management need; the lighter step first is a shared "bot kit" library. - Subagent panel (Option B): the lead
claude -psession spawns subagents and presents each output + its own overall recommendation. Beat: bot-orchestrated separateclaude -pruns (more deterministic but 3× the sessions + more bot code). Reopens if headless subagent spawning is flaky. - Panel composition is per-room, set in chat (onboarding message +
agents +/-), not config. - Reuse matrix-bridge's Mac wrappers + Spark→Mac SSH key (don't duplicate the seam).
- Auto-map by name: room
<x>→ Gitea<owner>/<x>+~/Projects/<x>; mapping persists tostate/(mirrors matrix-bridge D14). One room per repo.
Sovereignty
Reviews send PR diffs to claude -p (the subscription), not a frontier API on payload data; that's
acceptable for code review of these repos. Before pointing the bot at a repo with sensitive content,
revisit this — local inference via Spark Control would be the path.
Current state
LIVE and proven. Bot runs on the Spark (docker compose up -d), @reviewer maps each review room
to its repo, and the subagent panel is configured in-chat. Proven end-to-end on ten31-site PR #4
(2026-06-28): threaded review → merge + yes → force_merge → auto-publish → thread redacted — and
the previously-unproven bit, headless claude -p spawning the reviewer/adjudicator/security
subagents, works. The ten31-site colleague is onboarded; the pipeline is in steady use.
This session's hardening (DEPLOYED to the Spark at 063e694): a merge now always redacts the
thread on merge success; a failed post-merge deploy posts its "deploy manually" warning at top
level so it survives the redaction instead of orphaning the thread (the bug PR #4 hit when the Mac
was left off master). New clear/resolve command redacts an orphaned thread without touching
the PR.
Deploy gotcha (still true): the Spark needs a dedicated per-repo Gitea deploy key + a Host
alias (the default immense-voyage.local block uses matrix-bridge's key); Gitea won't reuse one SSH
key across repos' deploy keys. A Spark Control tile is captured in the inbox. Next: clear
ten31-site's orphaned PR #4 thread (reply clear in its thread). Future redeploys: ssh modelo32.