Files
granthi-review/README.md
T

112 lines
6.9 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# granthi-review
Estate-wide AI code review for Granthi (Gitea) forges. One central
webhook-driven service — every repo gets reviews with **zero per-repo CI
setup**. Powered by shre-router.
## Architecture
```
Gitea (peer :3030) ──webhook (push, pull_request, HMAC-signed)──▶ granthi-review (:5498)
per-repo serialization queue, global LLM concurrency 1
diff fetch (PR .diff / per-commit .diff) + CLAUDE.md/CONTRIBUTING.md head
shre-router POST /v1/chat (agentId: code-reviewer)
strict-JSON rubric → 8 axes + findings + verdict
commit status 'granthi-review' + PR comment (scorecard) + sqlite ledger
GET /review/{owner}/{repo} (history dashboard)
nightly per-agent digests → ~/.granthi-review/digests/
```
Components (all in `src/`):
- `server.js` — HTTP server: `POST /webhook`, `GET /health`, `GET /review/:owner/:repo`; `--digest` CLI mode.
- `lib/webhook.js` — HMAC verification (`X-Gitea-Signature`, sha256 over raw body) + event → job mapping. Handles `push` and `pull_request` (opened / synchronize / reopened).
- `lib/queue.js` — per-repo serialization; global mutex around LLM calls (concurrency 1).
- `lib/review.js` — the pipeline. Diff capped at 60KB (truncation logged and disclosed to the model and in the comment).
- `lib/prompt.js` — rubric prompt requiring strict JSON: 8 axes (`correctness, security, tests_coverage, design_simplicity, performance, style, breaking_changes, docs`), each `{score 0-100, rationale}`, plus `overall`, `grade AF`, `findings[]`, `verdict`.
- `lib/jsonExtract.js` — tolerant JSON extractor (fences, prose, `<think>` blocks, trailing commas) + one retry on parse failure.
- `lib/verdict.js`**service-side verdict derivation is authoritative**: `fail` iff a finding has severity ∈ {critical, high} AND confidence = confirmed in axes correctness/security (untagged confirmed criticals also block, conservatively).
- `lib/trailers.js``Co-Authored-By` trailer parser for agent attribution.
- `lib/db.js` — sqlite ledger (`node:sqlite`, zero deps): repo, sha, pr, pusher, authors, trailers, axes, grade, findings, timings.
- `lib/digest.js` — nightly per-agent digests (score trend, recurring finding categories, last N reviews).
Dependencies: **none**. Node ≥ 22.13 (uses `node:sqlite`, global `fetch`).
## Review posture (approved)
**BLOCK on confirmed-critical, with human override.**
- Commit status context: `granthi-review`. verdict pass → `success`; warn → `success` with warning description; fail → `failure`.
- Branch protection on pilot repos requires the `granthi-review` context but does **not** apply to admins — an admin merge is the override. The blocking comment says so explicitly.
- **Fail-open with visibility**: if the router/LLM is down or output is unparseable after retry, the service posts status `warning` with "review unavailable" — it never blocks silently and never fakes a pass.
- Bare pushes (no PR): commit status + ledger + dashboard only. Gitea 1.27 has no commit-comment API endpoint, so no comment is posted for pushes.
## Config
`~/.granthi-review/config.json` (chmod 600):
```json
{
"port": 5498,
"forgeBaseUrl": "http://localhost:3030",
"botToken": "<shre-reviewer API token: write:repository,write:issue>",
"webhookSecret": "<shared HMAC secret>",
"routerUrl": "http://localhost:5497",
"model": "anthropic/claude-sonnet-4-6",
"tenantId": "nirlab",
"publicBaseUrl": "http://localhost:5498"
}
```
Bot identity: Gitea user `shre-reviewer` (created via `gitea admin user create`).
## Deploy (Mac dev tier)
- `launchd/ai.granthi.review.plist``~/Library/LaunchAgents/`, KeepAlive service on :5498, running from the `~/Services/granthi-review` clone.
- `launchd/ai.granthi.review.digest.plist` → nightly 03:30 `--digest` run.
- Webhooks (all target `http://host.docker.internal:5498/webhook`, events push + pull_request, secret = `webhookSecret`):
- **Per-repo hooks** on the pilot repos: `nirpa/gitea-distro`, `nirpa/granthi-staging-infra`, `nirpa/granthi-review`.
- **Admin default hook** (`POST /api/v1/admin/hooks`) — Gitea 1.27's API can only create *default* hooks (copied into repos created after the hook exists), not *system* hooks, so every **new** repo is auto-wired but pre-existing repos need a per-repo hook. To cover the whole estate in one shot, create the same hook as a **system webhook** via the admin web UI (Site Administration → Integrations → Webhooks → System Webhooks) — API tokens cannot do it in 1.27.
- Pilot gating: `nirpa/gitea-distro` main branch protection lists `granthi-review` in `status_check_contexts` (alongside `build-branded-image / build`); `block_admin_merge_override` is false, so admins can merge over a block (the override path).
### Router integration notes (learned live)
- The shre-router request MUST carry `tools:false` and `raw:true`, otherwise keyword fast-paths (current-info briefing, store-resolver, retail dispatch) hijack review prompts whose diff content mentions github/sales/store-ish words and return non-review text.
- The domain preflight can 400 (`agent_mismatch`) when the intent classifier misreads a diff; attempt 2 retries as the c-suite `fallbackAgentId` (default `architect`), which bypasses the tier-gated preflight.
- The router may serve a different model than requested (`_shre.model` is recorded in the ledger's `model` column).
### Known v1 limitations
- The review queue is in-memory: a service restart drops queued/in-flight reviews, leaving that sha's `granthi-review` status at `pending`. Re-push (or push an empty commit) to re-trigger.
- Bare pushes get status + ledger only (no Gitea commit-comment API).
- Reviews for repos outside the pilot set require the bot (`shre-reviewer`) to have write access before statuses/comments can post; grant collaborator write when adding repos.
## Adding the Reviews tab later (Granthi overlay)
The dashboard page `GET /review/{owner}/{repo}` is designed to be iframed or
linked as a repo tab. In the gitea-distro overlay, add an extra-tabs template
(`custom/templates/custom/extra_tabs.tmpl`):
```html
<a class="item" href="http://<review-host>:5498/review/{{.Repository.Owner.Name}}/{{.Repository.Name}}" target="_blank">
{{svg "octicon-checklist"}} Reviews
</a>
```
(For prod, front the service behind the granthi domain and use a relative
path instead of the host-port URL.)
## Tests
```
npm test # node --test: JSON extractor, verdict derivation, trailer parser
```
<!-- live-test marker 20260818 -->