The Problem With AI Code Review
Let me paint a picture. You push a PR. Within 30 seconds, a bot posts 12 comments:
⚠️ Line 23: Consider using `const` instead of `let`
⚠️ Line 47: This function is 12 lines, consider breaking it up
⚠️ Line 89: Missing JSDoc comment
⚠️ Line 112: Trailing whitespace
Here's the thing — you already know all of this. Your linter knows this. Your formatter knows this. Even your teammate who skimmed the diff for 10 seconds knows this.
What you don't know is whether that useEffect has a race condition. Whether that SQL query is vulnerable to injection. Whether that new API endpoint violates your auth model.
Existing AI code review tools suffer from a fundamental problem: they confuse "can find something to say" with "having something valuable to say." And because they're priced per-seat SaaS products, they have every incentive to seem thorough — which in practice means generating noise.
So I built Critiq.
The Purple Cow: CLI-First, Zero Noise
Critiq is not another GitHub bot you install and configure. It's a CLI tool that does one thing: pipe in a diff, get back a review with at most 3 comments, only on things that actually matter.
$ git diff HEAD | npx critiq-cli
That's it. No signup, no OAuth flow, no pricing page to navigate. Three seconds later:
──────────────────────────────────────────────────
🤖 Critiq Review
Review of PR #42: Fix race condition in user cache
Score: ★★★☆☆ 7/10
WARNING src/cache.ts:88
[bug] medium confidence
竞态条件:getUserCache 在 setUserCache 完成前返回过期数据。
建议在写入完成前使用锁或 Promise 队列。
──────────────────────────────────────────────────
Tokens: 892 (↑840 ↓52)
──────────────────────────────────────────────────
Notice what's not there. No nitpick about variable naming. No complaint about missing JSDoc. No suggestion to extract a function that's perfectly readable inline.
The hard cap of 3 comments isn't a technical limitation — it's a philosophical choice. If Critiq can't find 3 things genuinely wrong with your code, it posts 2. Or 1. Or 0. Because the highest value comment is often the one an AI didn't generate.
Architecture: 1000 Lines of TypeScript, $0/mo
The entire Critiq system — CLI, API worker, GitHub App — is built in under 1000 lines of TypeScript and runs on Cloudflare's free tier. Total monthly infrastructure cost: $0.
Three Components, One Core
┌────────────────────┐ ┌────────────────────┐
│ npm CLI │ │ Cloudflare Worker │
│ (critiq-cli) │ │ │
│ │ │ POST /review │
│ stdin → API │ │ POST /webhook │
│ --pretty/--json │ │ GET /api/reviews │
└────────┬───────────┘ └──────────┬───────────┘
│ │
└──────────┬─────────────────┘
▼
┌────────────────────┐
│ review.ts │
│ prompt.ts │
│ (100 lines total) │
└──────────┬─────────┘
▼
┌────────────────────┐
│ DeepSeek V4 Flash │
│ ~$0.00002/req │
└────────────────────┘
The CLI — esbuild Bundle, 9KB
I chose esbuild over tsc for the CLI build. Why? The output is a single ~9KB file with zero runtime dependencies:
await esbuild.build({ entryPoints: ['src/cli.ts'], outfile: 'dist/cli.js', bundle: true, platform: 'node', target: 'node18', banner: { js: '#!/usr/bin/env node' }, })
The banner line means dist/cli.js is directly executable. npm install -g critiq-cli creates a symlink, the shebang resolves to your local Node, and it just works.
The Prompt — Where the Magic (and Discipline) Lives
The secret sauce isn't the AI model. It's the system prompt that tells DeepSeek to shut up unless it matters:
## Core Principles 1. Low noise, high signal — Default to silence. 2. Empathetic — Assume the author is a capable engineer. 3. Actionable — Every comment must include a concrete suggestion. ## Constraints - Output AT MOST 3 comments total. - Focus ONLY on: real bugs, security, performance regressions. - Skip: formatting, naming conventions, style preferences, JSDoc.
But prompts alone aren't reliable. So there's a hard cap in the code:
const comments = (parsed.comments || []) .filter(c => c.path && c.body) // filter invalid entries .slice(0, 3) // hard cap: never more than 3
Prompt engineering + code enforcement = the zero-noise guarantee isn't a promise, it's a constraint.
Model-Agnostic by Design
Critiq doesn't hardcode DeepSeek. Every API call goes through environment variables:
const apiBase = process.env.CRITIQ_API_BASE || 'https://api.deepseek.com/v1' const model = process.env.CRITIQ_MODEL || 'deepseek-chat'
Want to use OpenAI? Set CRITIQ_API_BASE=https://api.openai.com/v1 and CRITIQ_MODEL=gpt-4o-mini. Want to use a local Ollama server? Set it to http://localhost:11434/v1. The code doesn't care.
The default is DeepSeek V4 Flash — currently priced at roughly $0.00002 per review. At that rate, 50,000 reviews cost $1.
Infrastructure: A Masterclass in Free Tier
Here's the full cost breakdown:
| Service | Purpose | Cost |
|---|---|---|
| Cloudflare Workers | API + Webhook handler | Free (100k req/day) |
| Cloudflare KV | PR state + comment cache | Free (1GB) |
| Cloudflare D1 | Review history database | Free (5GB) |
| Cloudflare Pages | Landing page | Free (unlimited bandwidth) |
| DeepSeek API | AI model | ~$0.00002/review |
| npm | CLI distribution | Free (public) |
| GitHub | Source code + Actions | Free (public) |
| Total | $0/mo |
The only cost that scales is the DeepSeek API — and even then, at $0.00002 per review, you'd need to review 50,000 PRs before spending $1.
The GitHub App: Full Automation in ~200 Lines
Critiq also runs as a GitHub App for automated PR review. When someone opens or updates a PR, a Cloudflare Worker receives the webhook and processes it automatically.
The authentication flow is worth highlighting because it's surprisingly elegant with zero npm dependencies:
async function createAppJWT(appId, privateKeyPEM) { const now = Math.floor(Date.now() / 1000) const payload = { iat: now - 60, exp: now + 600, iss: appId } // RS256 sign using Web Crypto — 100% native const key = await crypto.subtle.importKey( 'pkcs8', pemToArrayBuffer(privateKeyPEM), { name: 'RSASSA-PKCS1-v1_5', hash: 'SHA-256' }, false, ['sign'] ) const sig = await crypto.subtle.sign('RSASSA-PKCS1-v1_5', key, ...) return `${headerB64}.${payloadB64}.${b64url(sig)}` }
The Worker verifies incoming webhooks with constant-time HMAC comparison (preventing timing attacks), exchanges the JWT for an installation access token, fetches the PR diff, runs the review, and posts inline comments back to the PR — all within Cloudflare's Worker runtime, all serverless.
What Critiq Doesn't Do Well
Honesty matters, so let me be up front about the limitations:
- Language-dependent — While Critiq is language-agnostic in theory, the prompt emphasizes "real bugs and security." It works best on Python, JavaScript/TypeScript, Rust, and Go — languages where the AI model has strong training data. For niche languages, results are less reliable.
- No context awareness — Critiq reviews each diff in isolation. It doesn't understand your project's architecture, coding conventions, or business logic. A review that looks "safe" in isolation might violate your project's patterns.
- Chinese-first output — Critiq writes reviews in Chinese by default. This is a deliberate choice (there isn't a good Chinese AI code review tool), but it means monolingual English users get mixed-language output. Easy to fix — just change the prompt. But you have to know to do it.
- No learning loop — Critiq doesn't learn from your feedback (yet). Each review is independent. If you keep rejecting a certain type of comment, there's no mechanism to adjust future reviews.
Why Open Source?
Critiq is MIT-licensed on GitHub. The npm package is critiq-cli.
I believe AI code review should be:
- Accessible — no paywall to get a second opinion on your code
- Private — you control what diff gets sent to which API
- Honest — a tool that says "looks clean" when there's nothing to flag is more valuable than one that invents issues to justify its existence
Try It Now
$ git diff HEAD | npx critiq-cli $ git diff main -- myfile.ts | npx critiq-cli --pr-title "My changes" $ git diff HEAD | npx critiq-cli --json | jq '.comments'
One command. No signup. No configuration beyond setting CRITIQ_API_KEY (any OpenAI-compatible API key works).
The response arrives in 1-3 seconds. If you get 0 comments, that doesn't mean Critiq failed — it means your code is clean.
That's the point.
Technical Takeaways
If you're building your own AI dev tool, here's what I learned:
- Restraint is the feature. In an AI tool, what you choose not to say defines the user experience more than what you do say. A tool that confidently says "looks good" builds more trust than one that always finds something.
- Serverless is viable for AI apps. Cloudflare Workers + D1 + KV can handle tens of thousands of daily AI API calls on the free tier. The request size constraints (100KB body, 10ms CPU per request) aren't blockers when your "heavy lifting" is an external API call.
- Model-agnostic pays off. The ability to swap models via environment variables means you can start with cheap models and upgrade when the economics make sense — without code changes.
- Single-file CLI is underrated. A 9KB self-contained executable that requires zero setup is a powerful distribution model. Developers love
npxandbrew install. They hate "create an account, create an API key, install the app, configure permissions..."
Critiq is available on GitHub and npm. Star it if you find it useful. PRs welcome — especially for improving support in more languages.