Paste or upload code and get a senior-engineer-style review: 🐛 bugs with concrete fixes, 💡 design suggestions, ⏱️ algorithmic complexity analysis, and a 0–100 health score — every finding grounded in real line numbers from your file.
- 📤 Upload or paste code — drag a file onto the editor or paste directly, with a synced line-number gutter
- 🤖 AI-powered review — bugs, warnings, suggestions, and praise, each pinned to the exact line
- 🔧 Concrete fixes — every bug/warning finding includes a suggested code fix, not vague advice
- ⏱️ Complexity analysis — Big-O time & space, explained in plain language
- 📊 Health score — a 0–100 score with a rubric behind it, not vibes
- 🎯 Optional review focus — steer the reviewer toward security, performance, readability, etc.
- 🔌 Bring your own model — OpenAI, OpenRouter, OmniRoute, Ollama, or anything OpenAI-compatible
Same file, two runs. The reviewer catches a real syntax bug (an unmatched closing brace), scores it accordingly — then after the fix, correctly recognizes the code as clean and bumps the score to 95.
| Layer | Tech |
|---|---|
| Framework | Next.js 14 (App Router) |
| Language | TypeScript |
| Styling | Tailwind CSS |
| AI | OpenAI API (or any OpenAI-compatible endpoint — local or gateway-routed) |
npm install
cp .env.example .env.local
# edit .env.local — add your API key (see provider options below)
npm run devOpen http://localhost:3000, paste some code (or drop a file onto the editor panel), optionally give it a focus like security or performance, and hit Run review. 🎉
The app reads three env vars — set the two/three that match your provider and nothing else needs to change in the code.
OPENAI_API_KEY=sk-...
OPENAI_MODEL=gpt-4o-miniOmniRoute is a free, self-hosted AI gateway that puts one OpenAI-compatible endpoint in front of 280+ providers (Claude, GPT, Gemini, DeepSeek, Kimi, and more) — handy if you want to point this app at a Claude model without an Anthropic key, or want automatic fallback across providers.
1. Install it:
npm install -g omniroute
omniroute
# API: http://localhost:20128/v1
# Dashboard: http://localhost:201282. Get an API key:
Open the dashboard at http://localhost:20128 → Endpoints, and copy the key shown there (this is OmniRoute's own gateway key, not a provider key — you connect individual providers separately from the Dashboard's Providers tab).
3. Point Sentinel at it — in .env.local:
OPENAI_API_KEY=your-omniroute-key
OPENAI_BASE_URL=http://localhost:20128/v1
OPENAI_MODEL=kr/claude-sonnet-4.5 # or any model id from `omniroute models --search <term>`
⚠️ Note the/v1on the base URL — the dashboard root (/home) is not the API endpoint.
4. Restart:
npm run devEnv vars are only read at process start, so a restart is required after any .env.local change.
OPENAI_API_KEY=sk-or-v1-...
OPENAI_BASE_URL=https://openrouter.ai/api/v1
OPENAI_MODEL=openai/gpt-oss-20b:freeSign up at openrouter.ai/keys — no card required for :free-suffixed models.
OPENAI_API_KEY=not-needed # any non-empty string
OPENAI_BASE_URL=http://localhost:11434/v1
OPENAI_MODEL=llama3.1The server logs a masked fingerprint of the key it's using on every request:
[openai] using key sk-abc…7055 (len 35) via http://localhost:20128/v1
401/ missing auth → the key isn't loading. Check.env.localis at the project root (next topackage.json), has no quotes/extra spaces around the value, and that you restartednpm run devafter editing it.502/ parse error → the model responded, but not in valid JSON. The server console (dev mode) prints the raw response above the error — the reviewer already retries once withoutresponse_formatand strips stray markdown fences, but a very small/undertrained model may still misbehave.
app/
api/review/route.ts → validates input, calls the model, returns JSON
page.tsx → split-pane UI (editor left, review right)
layout.tsx, globals.css
components/
CodeInput.tsx → editor pane: line numbers, upload, severity gutter rail
ReviewPanel.tsx → results pane: summary, score, complexity, findings
FindingCard.tsx → single finding (severity, detail, suggested fix)
ScoreGauge.tsx → circular 0–100 score indicator
lib/
prompts.ts → 🧠 system prompt + user-prompt builder (the prompt engineering core)
openai.ts → OpenAI client wrapper, retries, JSON parsing/validation
types.ts → shared TypeScript types for the review contract
ui.ts → severity color/label metadata, filename→language guess
This is the part worth reading if you're studying the repo rather than just running it.
- Line-numbered source — every line of submitted code is numbered (
12 | const x = 1) before it reaches the model, so findings can cite real lines instead of guessing them. - A closed JSON contract — the system prompt defines an exact output schema and the API call requests
response_format: json_object. If a model/gateway rejects that param, the client automatically retries once without it, and strips markdown fences before parsing — this is what makes it resilient across OpenAI, OpenRouter, OmniRoute-routed Claude, and local models. - Calibration rules, not just a task description — the prompt explicitly tells the model to prefer a few high-confidence findings over noise, to never invent a line number, and to always include a concrete fix for anything rated bug/warning. This is the single biggest lever for review quality — a vague prompt produces a vague review.
- A scoring rubric — the 0–100 health score is anchored to explicit bands (90–100 production-ready, 70–89 minor issues only, etc.) so scores are comparable across different pieces of code instead of being mood-based.
- Low temperature (0.2) — code review should be consistent and grounded, not creative.
See lib/prompts.ts for the full system prompt.
Dark, IDE-inspired theme (IBM Plex Mono/Sans, a deep blue-black base, and a 4-color semantic severity palette — red/amber/violet/green for bug/warning/suggestion/praise). The signature UI element is the severity gutter rail: after a review runs, small colored ticks appear next to the exact lines with findings, mirroring an editor's error gutter — so you can spot where the issues cluster before reading a single card.
MAX_CODE_LENGTHinapp/api/review/route.tscaps requests at 20,000 characters — raise it for larger files, at the cost of latency and token spend.- Stateless by design — no review history or persistence. Add a database if you want to keep past reviews.
- For very large files, consider chunking by function/class and merging findings rather than one giant single-shot review.
Built with 🧠 + ☕. PRs and issues welcome.

