Files
gitea-review-bot/AGENTS.md
T
Keysat 063e6947f7 Redact thread on merge success even if deploy fails; add clear command
A merge whose post-merge deploy failed left the review thread orphaned (redaction was gated on deploy success). Now the thread always redacts on merge success and the deploy-failure warning is posted at top level so it survives. New clear/resolve command redacts an orphaned thread without touching the PR.
2026-06-28 21:17:36 -05:00

7.0 KiB
Raw Blame History

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.md exists, 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-write state/). Logs: docker compose logs -f.
  • Deploy: the Spark's ~/gitea-review-bot is a Gitea clone tracking master; 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.toml is gitignored — refresh it on the Spark separately (scp) like matrix-bridge.
  • 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/no to confirm a merge, agents … to toggle the subagent panel. clear (alias resolve) 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; threaded claude -p review (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); clear redacts 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.exampleMATRIX_* + GITEA_TOKEN (real .env gitignored).
  • Dockerfile · docker-compose.yml · docker-entrypoint.sh — the Spark container (generic image; secrets/config via read-only mounts; entrypoint writes ~/.ssh/config for 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 -p session spawns subagents and presents each output + its own overall recommendation. Beat: bot-orchestrated separate claude -p runs (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 to state/ (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 (NOT yet on the Spark — needs a redeploy: git fetch && git reset --hard origin/master && docker compose up -d --build): 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: redeploy on the Spark, then clear ten31-site's orphaned PR #4 thread (reply clear in it, or reject).