96 lines
5.3 KiB
Markdown
96 lines
5.3 KiB
Markdown
# 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 A–F`, `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.
|
|||
|
|
- `launchd/ai.granthi.review.digest.plist` → nightly 03:30 `--digest` run.
|
|||
|
|
- Webhooks: Gitea **system webhook** (admin → hooks) pointed at `http://host.docker.internal:5498/webhook` (the gitea container reaches the host that way), events push + pull_request, secret = `webhookSecret`. Fallback: per-repo hooks on pilot repos.
|
|||
|
|
- Pilot gating: `nirpa/gitea-distro` main branch protection lists `granthi-review` in `status_check_contexts` (alongside the CI build context); `enable_approvals_whitelist`/admin-bypass left so admins can merge over a block.
|
|||
|
|
|
|||
|
|
## 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
|
|||
|
|
```
|