AI-powered GitHub Pull Request reviewer. Sign in, paste a PR URL, and get inline code comments, a quality score, and a merge recommendation β posted directly back to GitHub.
PRHawk also learns your team's coding conventions from merged PR history and applies them as additional review rules on every future review.
π Live: frontend on Vercel (pr-hawk.vercel.app) Β· backend API on Render.
- Account-based access β email/password sign-up & login (powered by better-auth, sessions stored in MongoDB). The dashboard is gated behind authentication.
- Automated inline comments β bugs, security issues, performance regressions, style violations, and suggestions, pinned to the exact line.
- Risk summary β overall quality score (0β100), highest-risk changes, and an
approve / request_changes / commentmerge decision. - Convention learning β analyses your repo's last 10 merged PRs and extracts team-specific rules that are applied on all future reviews.
- Bring your own keys β users can supply their own GitHub token and OpenRouter key in the UI (Settings), so reviews run under their own identity and quota instead of the server's.
- Graceful posting β if the GitHub token can't post the review (e.g. insufficient scope), the analysis is still returned to the UI with a clear warning instead of failing.
- CLI mode β run reviews and convention learning from the terminal without the web server or auth.
| Layer | Stack |
|---|---|
| Frontend | React 19, Vite, React Router v7, better-auth React client, lucide-react |
| Backend | Node + Express 5, TypeScript (run directly via tsx), Octokit, OpenAI SDK (Groq / OpenRouter), Zod |
| Auth & data | better-auth (email/password) + MongoDB |
| Hosting | Vercel (frontend) Β· Render (backend) Β· MongoDB Atlas |
PRHawk/
βββ backend/ # Express REST API + CLI (TypeScript)
β βββ src/
β β βββ index.ts # Entry point: server or CLI; CORS + routes
β β βββ config.ts # Environment-variable configuration
β β βββ types.ts # Shared TypeScript types
β β βββ lib/
β β β βββ auth.ts # better-auth instance (email/password, cookies)
β β βββ db/
β β β βββ index.ts # MongoClient (used by better-auth)
β β βββ github/ # GitHub API integration (Octokit)
β β β βββ client.ts # Octokit client factory (central or custom token)
β β β βββ diffParser.ts # Parses unified diffs β added line numbers
β β β βββ prService.ts # Fetches PR metadata & file contents
β β β βββ reviewPublishers.ts # Posts reviews & comments to GitHub
β β βββ llm/
β β β βββ groq.ts # OpenAI-compatible client (Groq / OpenRouter)
β β βββ review/
β β β βββ prompts.ts # System & user prompt builders
β β β βββ schema.ts # Zod schemas for structured LLM output
β β β βββ reviewer.ts # Orchestrates the review pipeline
β β βββ conventions/
β β βββ learner.ts # Extracts team conventions from merged PRs
β β βββ store.ts # Reads/writes conventions/rules.json
β βββ .env.example # β copy to backend/.env and fill in
βββ frontend/ # React + Vite web UI
βββ src/
βββ main.jsx # Mounts <App/> inside <BrowserRouter>
βββ App.jsx # Routes: /login, /signup, protected dashboard
βββ lib/
β βββ api.js # Wrappers around the backend REST API
β βββ auth.js # better-auth React client (authClient)
β βββ storageKeys.js # localStorage keys + per-session cleanup
βββ hooks/
β βββ usePersistentState.js # localStorage-backed React state
βββ pages/
β βββ LoginPage.jsx
β βββ SignupPage.jsx
βββ components/
βββ AuthGuard.jsx # Redirects to /login when no session
βββ Header.jsx Β· ToastContainer.jsx
βββ ReviewTab.jsx + review/ # input, loader, summary, findings
βββ RulesTab.jsx + rules/ # toolbar, add/edit, learn panel
βββ OnboardingOverlay.jsx + onboarding/ # settings / custom keys
cp backend/.env.example backend/.env| Variable | Required | Description |
|---|---|---|
OPENROUTER_API_KEY |
One of these two | API key for OpenRouter. When set, OpenRouter is the LLM provider. |
GROQ_API_KEY |
One of these two | API key for Groq. Used when OPENROUTER_API_KEY is not set. |
GITHUB_TOKEN |
β Yes | GitHub PAT used to fetch PRs and post reviews. Classic token with repo (private) or public_repo (public). |
MONGODB_URL |
β Yes | MongoDB connection string for better-auth (users/sessions). The server fails to start if unset. e.g. mongodb+srv://β¦ |
BETTER_AUTH_SECRET |
β Yes | Secret used to sign sessions. Generate with openssl rand -base64 32. |
BETTER_AUTH_URL |
β Yes | Public base URL of this backend (e.g. http://localhost:3000 locally, the Render URL in prod). |
MODEL |
No | Override the LLM model. Defaults to google/gemini-2.5-flash (OpenRouter) or llama-3.3-70b-versatile (Groq). |
PORT |
No | Port the Express server listens on. Defaults to 3000. (Render injects this β don't hardcode it there.) |
APP_SECRET |
No | Legacy x-access-key gate, predates better-auth. Leave empty. |
cp frontend/.env.example frontend/.env # optional for local dev| Variable | Description |
|---|---|
VITE_API_BASE_URL |
Base URL of the backend API. Leave empty for local dev (requests stay relative and use the Vite proxy β localhost:3000). Set to the backend's public URL for production builds. |
You'll need a MongoDB instance (local or Atlas free tier) for auth.
cd backend
npm install
npm start # Express server at http://localhost:3000cd frontend
npm install
npm run dev # Vite dev server, proxies /api/* to :3000Open http://localhost:5173, create an account, and you're in.
cd frontend && npm run build # Outputs to frontend/dist/
cd ../backend && npm start # Also serves frontend/dist as static filesA single backend process can serve both the API and the built frontend when they're co-located.
PRHawk is deployed as two services on different domains, so a few things are wired specifically for that:
Backend (Render)
- Root Directory:
backendΒ· Build:npm installΒ· Start:npm start(no compile step βtsxruns TypeScript directly). - Env vars: all the backend vars above, including
MONGODB_URL,BETTER_AUTH_SECRET, andBETTER_AUTH_URL(= the Render URL). Don't setPORT. - In MongoDB Atlas, allow Render's network access (
0.0.0.0/0or Render's IPs).
Frontend (Vercel)
- Root Directory:
frontendΒ· Build:npm run buildΒ· Output:dist. - Set
VITE_API_BASE_URLto the Render backend URL (or commit it infrontend/.env.production).
Cross-domain auth β because the two run on different domains:
- The backend's CORS allows the frontend origin and sends
Access-Control-Allow-Credentials: true(allowed origins are listed inbackend/src/index.ts; trusted origins inbackend/src/lib/auth.ts). - Session cookies are issued with
SameSite=None; Secureso the browser sends them cross-site. Browsers that block third-party cookies may still reject them β the most robust alternative is to put both behind one domain (a Vercel rewrite of/api/*, or sub-domains withcrossSubDomainCookies).
Reviews and convention learning can run from the terminal without the web server or authentication.
cd backend
# Review a pull request
npm run review https://github.com/owner/repo/pull/123
# Learn conventions from a repository's merged PR history
npm run learn owner/repo
# or
npm run learn https://github.com/owner/repo- The React app renders
/loginand/signup; every other route is wrapped inAuthGuard, which checks the better-auth session and redirects to/loginwhen there isn't one. - Sign-up/sign-in call the backend's
/api/auth/*routes (handled by better-auth), which store users and sessions in MongoDB and set a session cookie. - Per-user dashboard state (last review, active tab) lives in
localStorageand is cleared on login/logout so it never leaks between accounts on a shared browser.
- You paste a GitHub PR URL into the UI (or pass it to the CLI).
- The backend fetches the PR metadata and changed files via the GitHub API.
- Each file's unified diff is parsed to identify the exact added/modified line numbers.
- The full file content at the PR's head commit is fetched for context.
- Saved team conventions are loaded from
conventions/rules.json. - A structured prompt (diff, numbered file content, addable lines, conventions) is sent to the LLM.
- The LLM returns JSON (validated against a Zod schema) with inline comments and a risk summary.
- Comments referencing lines outside the "addable" set are discarded to prevent ghost comments.
- The review is posted to the PR as inline comments + a summary. If posting fails (token permissions), the analysis is still returned to the UI with a
postWarning.
- You provide a repository (
owner/repo) in the UI or CLI. - The backend fetches recent closed PRs and keeps the 10 most recently merged.
- Their diff patches are sent to the LLM.
- The LLM extracts recurring patterns β naming, error handling, testing expectations, etc. β as concrete rules with severity tags.
- Rules are saved to
backend/conventions/rules.jsonand applied to all future reviews.
| Use case | Token |
|---|---|
| Review public repos | Classic token with public_repo scope |
| Review private repos | Classic token with repo scope (and access to the repo) |
| Review another account's repo | That account's token; for private repos you must be a collaborator |
β οΈ Fine-grained tokens are bound to a single resource owner and can only write to repos that owner controls β they cannot post reviews to a repo owned by a different account, even a public one. Use a classic token withpublic_repo/repofor cross-account reviews. Reviews are posted withevent: "COMMENT", so reviewing your own PR works (GitHub only blocks self-approval).