Six months ago I got fed up with my AI code review tool.
Two problems.
- It charged roughly 3.5× the raw OpenAI API cost while pretending that wasn't happening.
- It kept flagging phantom SQL injection in a codebase that literally had no SQL, while completely missing a real authorization bypass in the same pull request.
That combination broke my trust.
So I cancelled my subscription and started building my own.
Today I'm launching LGTM (Looks Good To Meow).
This isn't a launch post about features.
It's about the architecture, because that's the interesting part.
The one-prompt failure mode
The obvious implementation looks like this:
Give one LLM a giant system prompt telling it to review security, bugs, performance, readability, best practices and documentation all at once.
I built exactly that first.
It didn't work.
The model spread its attention across every category equally.
That meant:
- Critical security issues got buried beneath variable naming suggestions.
- Performance regressions were mixed together with documentation comments.
- The review became 30+ unordered findings.
Beta users simply stopped reading after the first few comments.
I tried making the prompt more aggressive.
Prioritize security above everything else.
That solved one problem and created another.
Security became better.
Everything else became dramatically worse.
The fundamental issue remained.
One prompt was trying to solve six different problems.
Six agents in parallel
Instead of making one model do everything, I split the review into six independent reviewers.
| Agent | Responsibility |
|---|---|
| 🔒 Security | Auth, injection, secrets, XSS, unsafe deserialization |
| 🐞 Bugs | Correctness, null handling, race conditions |
| ⚡ Performance | N+1 queries, quadratic loops, allocations |
| 📖 Readability | Naming, function size, cognitive complexity |
| ✅ Best Practices | Language idioms and architectural patterns |
| 📝 Documentation | JSDoc, docstrings, README updates |
Each receives:
- the same PR diff
- its own specialized system prompt
- zero knowledge of the other reviewers
Every agent returns structured JSON.
{
"agent": "security",
"findings": [
{
"severity": "high",
"file": "src/api/user.ts",
"line": 47,
"title": "Missing authorization check",
"description": "DELETE endpoint allows any authenticated user to delete accounts.",
"suggested_fix": "Verify ownership or admin role before deletion."
}
]
}
Six reports come back simultaneously.
That is where the interesting part begins.
The synthesizer
Simply concatenating six reports produces garbage.
Real pull requests can easily end up with 40–60 findings.
Nobody reads that.
The synthesizer converts six raw reports into one review.
It performs four jobs.
1. Deduplication
Security and bug reviewers frequently report the same issue.
Example:
- Missing null check
- Unsafe input validation
- Missing authorization
Instead of showing duplicates, the synthesizer groups findings by:
- file
- line
- category
and keeps the strongest explanation.
2. Conflict resolution
Sometimes two reviewers disagree.
For example:
Security says:
Add rate limiting.
Performance says:
Remove rate limiting to improve latency.
They're both technically correct.
So I built a simple precedence system.
Security
>
Correctness
>
Performance
>
Style
When conflicts occur, the final report still includes both viewpoints so the developer can override the decision.
3. Ranking
Ordering matters more than people realize.
Nobody wants this:
- Rename variable
- Missing JSDoc
- Catastrophic auth bypass
Instead, findings are sorted by:
Severity
→ Category Weight
→ File
→ Line
Critical issues always appear first.
4. Final verdict
Finally the synthesizer emits one overall review.
Possible outputs:
- ✅ Approve
- 🟡 Request Changes
- ❌ Block
based entirely on blocker findings.
Why only show 10 findings?
One surprising lesson from beta testing:
People don't read long reviews.
The dashboard originally showed everything.
Average review length:
27 findings
Almost nobody reached the bottom.
Today LGTM only surfaces the top 10 findings plus a summary.
Everything else stays available inside the dashboard.
Ten wasn't chosen mathematically.
It was chosen because users actually read ten.
Building the synthesizer took longer than everything else
Early versions had weird behavior.
The longest report usually won.
Not because it found better issues.
Because the model subconsciously treated verbose output as more important.
I fixed that by normalizing token counts before synthesis.
Then another issue appeared.
The synthesizer became too aggressive.
Different findings got merged together.
That caused genuine bugs to disappear.
The fix was introducing a confidence score for every finding so uncertain matches remained separate.
Most of the work wasn't building reviewers.
It was teaching them how to disagree.
Some detectors are not LLMs
The CI/CD scanner doesn't use AI.
It shouldn't.
It consists of 16 deterministic detectors.
Example:
on:
pull_request_target:
jobs:
test:
steps:
- uses: actions/checkout@v3
with:
ref: ${{ github.event.pull_request.head.sha }}
That's a classic pull_request_target vulnerability.
Static analysis detects it instantly.
I benchmarked several popular LLMs.
Two missed it completely.
One detected it but couldn't explain why it was dangerous.
Pattern matching wins here.
Another example:
env:
API_KEY: "sk_live_xxxxxxxxx"
Detected immediately.
No reasoning required.
Another:
uses: some-org/action@main
Unpinned GitHub Actions.
Again, deterministic.
Not AI.
The scanner currently includes detectors for:
pull_request_target- hardcoded secrets
- unpinned actions
- privileged Docker containers
- curl piped to shell
- world-writable permissions
- missing least privilege tokens
- sudo without passwords
...and several more.
Sometimes boring software beats AI.
Bring Your Own Key (BYOK)
Another decision I made early:
LGTM never owns your API usage.
You connect your own:
- OpenAI
- Anthropic
- Gemini
Every review runs against your account.
Not mine.
That means:
- Your token usage appears directly on your provider bill.
- I don't proxy or resell tokens.
- I don't silently mark up usage.
Most AI products bundle token costs into subscriptions.
Eventually someone pays for that.
Usually the customer.
I preferred making pricing explicit.
The downside is obvious.
Users now have two bills.
For solo developers that's usually cheaper.
For larger organizations wanting one invoice, it's friction.
I'm considering managed API keys in the future.
Not today.
Pricing
| Plan | Price |
|---|---|
| Free | ₹0/month |
| Hobby | ₹399/month |
| Pro | ₹999/month |
| Enterprise | Custom |
Top-ups are available separately for additional reviews and CI scans.
Payments are handled through Dodo Payments.
Tech Stack
- Node.js
- TypeScript
- MongoDB
- React
- GitHub App
- Fly.io
CLI:
npm install -g @tarin/lgtm-cli
What's next?
I'm the only person building this.
The part I'm watching most closely isn't whether the AI finds bugs.
It's whether the synthesizer makes the same prioritization decisions that experienced engineers would.
If it disagrees with your judgment, I want to know why.
That's the failure mode I'm most interested in fixing.
Links
🌐 Website
https://looksgoodtomeow.in
🚀 Dashboard
https://app.looksgoodtomeow.in
📚 Documentation
https://docs.looksgoodtomeow.in
🏆 Product Hunt
https://www.producthunt.com/products/lgtm-looks-good-to-meow?utm_source=other&utm_medium=social
🐛 Feedback & Issues
https://github.com/tarinagarwal/lgtm-feedback
Thanks for reading.
If you're building AI developer tools, I'd genuinely love to hear how you're approaching review quality, ranking, or synthesis. I think that's where the next generation of AI coding tools will differentiate themselves.
Top comments (0)