Multi-round PR reviewer for Forgejo (standalone)
  • TypeScript 86.9%
  • Svelte 8.3%
  • JavaScript 2.9%
  • Shell 1.5%
  • Dockerfile 0.4%
Find a file
hibryda 8635b61631
All checks were successful
pr-reviewer CI / tsc --noEmit (push) Successful in 21s
pr-reviewer CI / vitest (push) Successful in 1m6s
pr-reviewer CI / tsc build (push) Successful in 36s
fix(health): cancel/restart 202 (not 503) when only the publish ack times out (#13)
Promise.race(publish, deadline) doesn't cancel the losing redis.publish, so a deadline-timeout means the publish was dispatched (and idempotent cancel/restart may have delivered) → 202 dispatched-unconfirmed, not a misleading 503. PublishDeadlineError distinguishes it from a real Redis rejection (still 503). Deadlines injectable. +CHANGELOG backfill for multi-origin OAuth (#11). full suite 2169.
2026-06-03 18:58:48 +02:00
.claude chore: re-home references for standalone hib/hib-pr-reviewer (spun out of aim2be mono) 2026-06-01 11:15:18 +02:00
.forgejo/workflows ci: run on the instance ubuntu-latest runner (hib org has no aim2be-rework runner) 2026-06-01 11:57:51 +02:00
assets feat(status-page): header logo + /assets/* static-asset route 2026-05-23 04:41:24 +02:00
bin feat(auth): multi-origin viewport OAuth (localhost tunnel + reverse-proxied host) (#11) 2026-06-03 12:52:50 +02:00
deploy feat(rmp): comparative quorum modules — quorum-in-module + distributed quorum (#4) 2026-06-03 00:43:17 +02:00
docs docs: document public reverse-proxy exposure (reviewer.hemoglobina.store) (#12) 2026-06-03 18:04:16 +02:00
scripts feat(scripts): add onboard-repo.sh (prefixed Redis keys + JSON change event) 2026-06-01 12:08:32 +02:00
src fix(health): cancel/restart 202 (not 503) when only the publish ack times out (#13) 2026-06-03 18:58:48 +02:00
tests fix(health): cancel/restart 202 (not 503) when only the publish ack times out (#13) 2026-06-03 18:58:48 +02:00
.dockerignore fix(deploy): ship Redis-prefix migration script in the engine image 2026-05-31 18:05:26 +02:00
.env.example feat(auth): multi-origin viewport OAuth (localhost tunnel + reverse-proxied host) (#11) 2026-06-03 12:52:50 +02:00
.gitignore chore: gitignore .audit/ (nemezis audit output dir) 2026-06-02 06:21:13 +02:00
CHANGELOG.md fix(health): cancel/restart 202 (not 503) when only the publish ack times out (#13) 2026-06-03 18:58:48 +02:00
CLAUDE.md docs: track the RMP migration in CLAUDE.md + TODO.md (steps 1-3 shipped, next step 4) 2026-06-02 18:11:42 +02:00
Dockerfile fix(deploy): ship Redis-prefix migration script in the engine image 2026-05-31 18:05:26 +02:00
package-lock.json fix(rmp): use zod 4 to satisfy the claude-agent-sdk peer (unbreaks the image build) 2026-06-02 16:10:00 +02:00
package.json fix(rmp): use zod 4 to satisfy the claude-agent-sdk peer (unbreaks the image build) 2026-06-02 16:10:00 +02:00
README.md chore: re-home references for standalone hib/hib-pr-reviewer (spun out of aim2be mono) 2026-06-01 11:15:18 +02:00
svelte.config.js feat(svelte-viewport): PR-A scaffold — new viewport on :9730 alongside legacy (#101) 2026-05-27 07:16:38 +02:00
TODO.md docs: track the RMP migration in CLAUDE.md + TODO.md (steps 1-3 shipped, next step 4) 2026-06-02 18:11:42 +02:00
tsconfig.build.json fix: apply post-recovery round-14 findings (1 BLOCKING + 1 MINOR) 2026-05-15 03:37:25 +02:00
tsconfig.json feat(deploy): add idempotent run-once Redis key-prefix migration 2026-05-31 04:14:07 +02:00
tsconfig.refresher.json feat(svelte-viewport): PR-A scaffold — new viewport on :9730 alongside legacy (#101) 2026-05-27 07:16:38 +02:00
tsconfig.svelte.json feat(svelte-viewport): PR-A scaffold — new viewport on :9730 alongside legacy (#101) 2026-05-27 07:16:38 +02:00
vite.config.ts chore(deps): bump vite 5→8, vitest 3→4, plugin-svelte 4→7, coverage-v8 2→4 2026-05-31 04:03:03 +02:00
vitest.config.ts chore(deps): bump vite 5→8, vitest 3→4, plugin-svelte 4→7, coverage-v8 2→4 2026-05-31 04:03:03 +02:00

hib-pr-reviewer

A standalone multi-round PR reviewer for Forgejo. Consumes a Redis-backed queue of PR events, runs the Claude Agent SDK against each diff (optionally as an adversarial quorum), and posts structured findings as Forgejo PR comments — read-only, no push/approve/merge/label rights ever.

Originally developed within the aim2be meta-repo (as a fresh-tree fork of ~/code/vioxen/quanta-pr-reviewer patterns — rule 53, never modifies the upstream); now standalone at ~/code/claude/other/hib-pr-reviewer with its Forgejo home at hib/hib-pr-reviewer. The aim2be NAS deployment that drove its development continues to work by configuring the same code via env vars.

Headline features

  • Multi-round semantics. Round 1 on PR open, Round N on every push. Per-PR state in Redis (carried findings, comment IDs, round counter). Stale round parent comments collapsed into <details> blocks — never deleted.
  • Adversarial quorum (opt-in). Two independent reviewers + one arbiter, each in its own Claude Agent SDK session. Arbiter reconciles, drops unverifiable claims, keeps cross-reviewer agreed findings.
  • Structured bookkeeping (rule 68). Every parent comment carries header + TL;DR + summary + findings table + verdict + provenance footer. Future sessions reconstruct PR state in O(1) skim.
  • Blast-radius context. When fed code-intelligence artefacts (code-intelligence/<sub>/{repo-map,hierarchy,.api-surface}.json), the reviewer's prompt is enriched with downstream-impact paragraphs + sensitive-function flags + numeric blast-radius score.
  • Lazy-wait fresh intel. Auto-enqueues an intel-refresh job when blast-radius detects stale code-intelligence vs the PR base-branch HEAD, then waits for the refresh to complete before reviewing.
  • Memora-backed memory across rounds + PRs. Optional integration with a Memora MCP server. Per-PR + per-repo + per-finding memory; reviewer prompts include recall instructions.
  • Forgejo webhook receiver. HMAC-SHA256 verified, allow-list filtered, dedupe-aware. Forwards events to the bee-queue.
  • Operator status page with live SSE. Recent jobs grouped by PR, 4-tier severity badges (B/M/m/i + colour-only + two-line totals/latest), tri-state PR-link colors (teal merged / violet pending / grey closed-no-merge), outcome icons, settings cog + modal overlay, allow-list editor with dynamic org/repo dropdowns + bulk-add.
  • Force actions. /cancel-all, /recheck-webhook/:jobId, /reset-total, /flush-pressure, /intel-refresh. All structured-logged.
  • Bot identity (dual-token). Operator PAT for reads (PR info, diff, clone). Dedicated bot PAT for writes (post comment, patch prior round, file issue). Falls back to single-token mode when bot token unset.
  • Cancel endpoint. POST /cancel/:jobId → Redis pubsub → SDK abort. In-flight jobs interrupted cleanly.
  • Auto-issue filing. Findings marked defer: true become Forgejo issues with idempotent dedup, recurrence comments on re-surface.
  • Prometheus /metrics + JSON /health + structured /version.
  • Single-image, dual-binary. src/worker.ts (reviewer) + bin/intel-refresher.ts (intel sidecar) ship from the same image.

Quick start

Local dev

npm install
ALLOWED_REPO_OWNERS=my-org \
FORGEJO_TOKEN=<a-PAT-with-PR-comment-scope> \
FORGEJO_BASE_URL=https://git.example.com \
REDIS_URL=redis://localhost:6379 \
npm run dev

Then enqueue a job:

node -e '
const BeeQueue = require("bee-queue");
const q = new BeeQueue("pr-review", { redis: { host: "localhost", port: 6379 } });
q.createJob({
  repo_owner: "my-org",
  repo_name: "my-repo",
  pr_number: 42,
  pr_head_sha: "abcdef1234567",
  pr_base_branch: "main",
  pr_title: "feat: x",
  trigger: "opened",
  enqueued_at: new Date().toISOString(),
}).save().then(() => process.exit());
'

NAS deploy via docker compose

See docs/deployment.md for the full walkthrough. Short version:

# From your laptop, against your NAS reachable as SSH alias `NAS`:
ALLOWED_REPO_OWNERS=my-org \
NAS_DEPLOY_DIR=/home/hibryda/hib-pr-reviewer \
MEMORA_BASE_IMAGE=local/memora-server:0.2.28 \
bash deploy/install-on-nas.sh

The script builds the image locally, streams it to the NAS, materialises the secrets env file from ~/.config/claude/llm-keys.env, brings up the compose stack (6 containers: engine, viewport, svelte-viewport, redis, memora, intel-refresher), and verifies /health.

systemd-user (laptop dev)

cp deploy/systemd/hib-pr-reviewer.service ~/.config/systemd/user/
cp deploy/systemd/hib-pr-reviewer-redis.service ~/.config/systemd/user/
systemctl --user daemon-reload
systemctl --user enable --now hib-pr-reviewer.service

Documentation

Doc Covers
docs/architecture.md System architecture: 4 conceptual modules (Governor / Supervisor / Reviewer / Frontend / Shared) + the bee-queue + Redis message flow.
docs/features.md Comprehensive feature inventory. For each feature: what + where in code + env vars + status-page surface.
docs/deployment.md NAS deploy walkthrough. Container topology, env file, Forgejo webhook setup, bulk-installer for 18 repos.
docs/configuration.md Every env var: name + default + accepted values + description.
docs/multi-tenancy.md Allow-list mechanics, /bot-allowlist endpoint, Redis pub/sub propagation, dynamic dropdowns + bulk-add.
docs/bot-identity.md Dual-token operator + bot model. Forgejo user creation, org permission scoping, token rotation.
docs/status-page.md Operator status page on :9723. Recent-jobs grouping, sev-badges, PR-link colors, outcome icons, settings overlay.
docs/multi-round.md R-cycle semantics, stale-comment collapse, structured bookkeeping, blast-radius, lazy-wait, prGroupKey.
docs/cancel-endpoint.md POST /cancel/:jobId semantics — what cancels and what doesn't.
docs/operations.md Operator workflows: rotate tokens, retry a job, restart a container, bulk-rewire webhooks.

Behavioural rules (rule 58)

  • Read-only on PR source. No push, approve, merge, label, close.
  • Allow-listed targets. Owner + prefix filter applied at boundary (job validation + webhook receiver). Anything else dropped silently.
  • Multi-round. Round 1 on PR open, Round N on each push.
  • One parent comment per round. Stale findings from earlier rounds collapsed, not deleted.
  • Rate-limit respect. Token pool rotates PATs to spread load.
  • No findings spam. Empty rounds say "No new findings."
  • Findings must be actionable. file:line + concrete change or question.
  • Blast-radius context. Reviewer prompt includes /quanta:blast-radius output when wired.
  • Failure quiet. Crashed worker logs the error; never posts a half-baked partial comment.

Tests

npm test                # vitest, no network
npm run typecheck       # tsc --noEmit

1145+ tests cover: prompt builders, diff parser, allow-list, queue plumbing, webhook event mapper, intel freshness, blast-radius loader, comment renderer (rule 68 bookkeeping), collapse marker, issue-filer dedup, status server, SSE plumbing, force actions, irregularity detector, session supervisor, phase ledger, semaphore, repo checkout, retry classifier, pressure resolver, quorum + arbiter happy path, settings overlay propagation.

See also

  • Upstream pattern reference (read-only): ~/code/vioxen/quanta-pr-reviewer/{README.md, CLAUDE.md}.
  • Behavioural rules carried over from the originating aim2be meta-repo, now part of this repo's own operating contract (see CLAUDE.md + ./.claude/rules/):
    • Rule 53 (borrowed reference repos — read-only treatment of ~/code/vioxen/quanta-pr-reviewer).
    • Rule 58 (PR reviewer discipline — read-only, allow-listed, multi-round; the verbatim list lives in CLAUDE.md).
    • Rule 68 (structured bookkeeping — header + TL;DR + findings table + verdict + footer on every round comment).