4ed3410eb4
Balance responsiveness with cloud agent runtimes that exceeded the previous two-minute limit. Co-authored-by: Cursor <cursoragent@cursor.com>
6.2 KiB
6.2 KiB
gitea-pr-review-bot
Central webhook service that reviews Gitea pull requests using the Cursor SDK and posts a full Gitea review (summary + inline comments).
This bot is designed to run once for many repositories in Bram/* instead of duplicating workflows in every repo.
What This Bot Does
- Listens to Gitea webhook events.
- Triggers on:
pull_requestwith actionopenedpull_requestorpull_request_review_requestwith actionreview_requestedwhen requested reviewer is the bot user
- Loads PR metadata and changed files from Gitea.
- Builds a review prompt (including optional repo-specific rule files).
- Calls Cursor Cloud Agent (
Agent.prompt) for structured review output. - Validates and posts a single consolidated Gitea review.
- Removes itself from requested reviewers after successful review.
- Prevents duplicate processing by dedupe key:
{owner}/{repo}#{pr_number}#{head_sha}
High-Level Flow
src/server.tsreceives webhook payload.src/webhook/verify-signature.tsvalidates HMAC (WEBHOOK_SECRET).src/webhook/event-router.tsaccepts or ignores event by type/action.src/run/review-runner.tsorchestrates full review run.src/config/load-repo-config.tsloads optional per-repo overrides.src/gitea/client.tsfetches PR + files and writes reviews.src/prompt/build-review-prompt.tsassembles review instructions/context.src/cursor/review-agent.tsinvokes Cursor SDK in cloud mode.src/cursor/review-schema.tsvalidates structured JSON response.src/gitea/review-api.tsposts review and handles inline comment fallback.src/gitea/reviewer-api.tsremoves bot reviewer from PR.
Architecture Diagram
flowchart TD
A[Gitea Webhook] --> B[src/server.ts]
B --> C[verify-signature.ts]
C --> D[event-router.ts]
D --> E[review-runner.ts]
E --> F[dedupe-store.ts]
E --> G[should-process-event.ts]
E --> H[load-repo-config.ts]
E --> I[gitea/client.ts]
E --> J[build-review-prompt.ts]
E --> K[review-agent.ts]
K --> L[Cursor Cloud Agent.prompt]
K --> M[review-schema.ts]
E --> N[review-api.ts]
N --> O[POST /pulls/{index}/reviews]
N --> P[DELETE prior bot reviews]
E --> Q[reviewer-api.ts]
Q --> R[PATCH reviewers remove bot]
Project Structure
Server and Webhook Layer
src/server.ts- HTTP server,
/healthz,/webhooks/gitea - Signature check + event routing + response codes
- HTTP server,
src/webhook/verify-signature.ts- SHA256 HMAC validation with timing-safe compare
src/webhook/event-router.ts- Converts raw webhook headers/payload into supported routed events
Domain Logic
src/domain/should-process-event.ts- Loop guard (skip bot-originated events)
- Repo enable/disable check
- Label skip logic
- Base branch filtering
src/domain/dedupe-store.ts- In-memory TTL dedupe store for idempotency
Gitea Integration
src/gitea/client.ts- Typed wrapper around Gitea REST endpoints
- Pull details/files/reviews, create/delete review, patch pull
- Optional content-file loading from repository
src/gitea/review-api.ts- Delete prior bot reviews
- Post one consolidated review
- Validate inline comments against changed-file paths and positions
- Summary-only fallback if inline set is invalid
src/gitea/reviewer-api.ts- Remove bot from requested reviewers list
Cursor Integration
src/cursor/review-agent.ts- Calls Cursor SDK
Agent.promptin cloud runtime - Enforces review timeout
- Extracts text from SDK response formats
- Calls Cursor SDK
src/cursor/review-schema.ts- Zod schema for strict review JSON contract:
verdict,event,body,comments[]
Prompting and Config
src/prompt/build-review-prompt.ts- Builds complete PR review prompt from title/body/files/patches/rules
src/config/load-repo-config.ts- Reads
.gitea/pr-review-bot.ymlwhen present - Loads optional repo-local rule files:
AGENTS.mddocs/pr-review.md.cursor/skills/pr-review/SKILL.md.cursor/skills/pr-review/gitea.md
- Reads
Orchestration and Utilities
src/run/review-runner.ts- Main end-to-end review execution
- Dedupe, retry wrappers, posting, reviewer self-removal
src/run/retry.ts- Exponential backoff retry helper for transient failures
src/types/events.ts- Gitea webhook payload typings
Scripts and Deployment
scripts/dry-run.ts- Manual run by
owner/repo/pr
- Manual run by
Dockerfile- Production container image
docker-compose.yml- Local/hosted compose deployment
docs/setup.md- Installation and webhook setup
docs/operations.md- Runtime behavior, failure handling, rollback
Environment Variables
Required:
CURSOR_API_KEYGITEA_TOKENGITEA_BASE_URLGITEA_BOT_LOGINWEBHOOK_SECRETPORT
Optional:
DEFAULT_BASE_BRANCH(default:main)MAX_INLINE_COMMENTS(default:5)REVIEW_TIMEOUT_MS(default:300000, 5 minutes)DEDUPE_TTL_SECONDS(default:1800)LOG_LEVEL(default:info; options:debug,info,warn,error)
See .env.example.
Review Output Contract
The Cursor response must be strict JSON:
verdict:approve | request_changes | commentevent:APPROVE | REQUEST_CHANGES | COMMENTbody: stringcomments: array of:path(changed file path)new_position(integer >= 1)body(comment text)
Post-validation rules:
- Invalid path/position in inline comments => post summary-only review.
- Inline comments are clamped to configured maximum.
Getting Started
- Copy env template:
cp .env.example .env
- Fill secrets/tokens.
- Install:
npm install
- Run locally:
npm run dev
- Verify health:
curl http://localhost:8787/healthz
Useful Commands
- Type check:
npm run check - Build:
npm run build - Start built app:
npm run start - Dry run:
npm run dry-run -- <owner> <repo> <pr_number> [head_sha]
Operational Notes
- Keep bot credentials scoped to least privilege.
- Do not log tokens or raw auth headers.
- Dedupe is in-memory (resets on restart).
- Best deployment model is a central service with org-level webhook and per-repo opt-out config.
- Logs are structured JSON to simplify filtering in Docker/log collectors.