About the Project
Why I built this
Every codebase has conventions that never made it into a style guide. A team settles into a way of naming things, structuring error handling, writing docstrings. Not because a linter told them to, but because that's what the code around them already looked like. Every PR gets silently judged against that unwritten standard, but the standard itself lives nowhere. It's tribal knowledge that every reviewer has to re-derive, every single time.
I kept running into this. Generic linters can't really help here because they only enforce rules someone wrote down in advance. They have no way of knowing that this repo always wraps external API calls in a retry decorator, or that this team never lets a function run past 40 lines even though nothing in .eslintrc says so.
So instead of asking "does this code break a rule we wrote," I wanted to build something that asks: does this code break a pattern this repo already follows?
What it actually does
You point it at a repository and it profiles the codebase. It walks the existing code and pulls out the conventions that are actually there, not the ones a style guide claims should be there.
Some of that is deterministic. Naming patterns, import ordering, function length, docstring presence. I can pull these straight from the AST. No model needed, no ambiguity in the answer.
Some of it needs judgment. Error handling shape, function structure. These aren't things a parser can reliably classify on its own. For this narrow slice, I have Codex review a sample of functions and return a structured judgment: the pattern it found, a confidence score, and the specific files that taught it that pattern. These patterns are advisory only, they never create a violation on their own, and I check every example the model cites against the real candidates it was actually shown. If it points to a file or line it wasn't given, I throw the whole pattern out rather than trust it. Every rule the tool learns has to trace back to real code. I didn't want it asserting patterns out of nowhere.
That profile becomes the repo's convention store, basically a record of how this codebase actually behaves.
When a new PR comes in, the tool doesn't re-review the whole repo. It parses only the changed code, checks it against the convention store, and reports new deviations, patterns the PR introduces that the rest of the codebase doesn't follow. Past a confidence threshold, it goes further and generates a concrete fix as a diff, then validates that fix in an isolated copy before ever showing it to me. It re-parses the patched code to make sure the fix doesn't introduce a different violation or touch code outside the flagged lines. If the fix doesn't hold up, I throw it away instead of showing something half right.
The output is a CLI report. The violations found, the convention each one breaks, the confidence behind that call, and where validated, a fix that's ready to apply. You don't need a dashboard to understand what happened, though I built a read only history view for anyone who wants one.
One thing I want to be upfront about. The bundled demo mocks the model's response for the fix step, on purpose, so the demo output is deterministic and doesn't need an API key. But the validator itself is real in that demo. It actually applies the diff in an isolated copy, actually re-parses the changed file, and actually re-checks it against the profile before saying "validated." I'd rather say that plainly than let the word "demo" imply the whole thing is staged.
How I thought about scope
Early on my instinct was to build this as broadly as possible. Every language, every convention category, automatic PR creation on any repo, right away. I had to keep checking that instinct against one question: can I actually explain this, completely, if someone pushes back on it?
Every scoping call came from that question, not from what sounded most impressive.
- One language first. I can defend the convention extraction logic end to end for TypeScript. Claiming multi language support before that logic was proven once would've meant less depth everywhere, not more coverage.
- Two tiers of confidence, not one blended judgment. Deterministic AST rules and LLM assisted judgment calls are genuinely different things. One's a fact about the code, the other's a probabilistic read on a pattern. Blending them into a single "the tool says so" would've hidden that distinction instead of being honest about it.
- Fixes get validated before I show them. An auto fix that isn't re-checked against the same rules is just a guess with better formatting. Validating in an isolated copy, and throwing away a fix that fails, was a deliberate call to keep the tool honest about its own limits instead of optimistic about its own output.
- CLI first, not a wrapper around something flashier. The actual value here is in the review logic, not the delivery mechanism. A GitHub App or dashboard is a thin layer on top of that. Worth having eventually, but not the point.
What this isn't
This isn't a tool that knows what "good code" looks like in the abstract. It has no opinion your repo didn't already express. If your codebase has an inconsistent convention, it'll learn the dominant pattern, flag deviations from that, and give it a confidence score low enough to reflect the inconsistency. It won't manufacture false certainty just to look more capable than the input allows.
I kept that constraint on purpose. A tool that only ever agrees with you isn't reviewing anything.
Built With
- bullmq
- eslint
- github-actions
- groq
- ioredis
- javascript
- next.js
- node.js
- octokit
- openai-sdk
- react
- redis
- tailwind-css
- tree-sitter
- typescript
- vercel
- wsl
- zod
Log in or sign up for Devpost to join the conversation.