Lintel AI/Lint
Lint: a code review that stays on your machine.
Lint reads every file in a project with a local Ollama model, reports what is wrong and where, classifies each finding, and gives the file a score out of 100. This page describes exactly what it does and where its limits are.
Overview
Lint is the first of the app's two modes. You choose a model and a folder, press F5, and each file is reviewed in turn.
- The folder is scanned for files worth reviewing, and an index of what each file declares and uses is built.
- Every file is sent to your Ollama model with its line numbers and a short description of the rest of the project.
- The model's answer is parsed, checked and corrected, then scored.
- Results appear in the file list as each file finishes. You can read, filter and search while the run continues.
poller.ts
src/jobs/poller.ts · 64 lines
A new interval is created every time start() runs and the previous one is never cleared, so polling requests multiply.
What it looks for
The review prompt asks for six categories of real problem and tells the model that a clean file has an empty list. It is not asked for style opinions.
- Memory leaks
- Timers or listeners that are never cleared or removed, collections that grow forever, globals that accumulate data, resources that are never disposed.
- Infinite loops
- Loops with no reachable exit, or an exit condition that can never be true.
- Dead code
- Unreachable statements, unused variables, functions that are never called.
- Useless logic
- Conditions that always evaluate the same way, redundant nested checks.
- Bad practices
- Blocking the main thread, leftover debug logging, swallowed errors, mutated globals.
- Security
- Hard-coded secrets,
evaland dynamic execution, SQL, command and HTML injection, path traversal, unsafe deserialization, weak cryptography, unvalidated input.
README.md files get their own review prompt instead: missing sections, incorrect or outdated information, unclear instructions and broken Markdown.
Kinds and severity
Every finding has two labels, so you can sort by how bad it is and by what sort of problem it is.
Kind
| Kind | Meaning | Typical example |
|---|---|---|
| Vulnerability | An attacker could exploit it. | SQL built from user input, hard-coded credentials, unsafe eval. |
| Bug | The code misbehaves at runtime. | A timer that is never cleared, a loop that cannot end. |
| Code smell | It works, but it is dead, noisy, redundant or hard to maintain. | An unused variable, a condition that is always true. |
If a small model leaves the kind out, the app infers it from the finding's type, so the filter chips always add up.
Severity
| Severity | Use | Score cost |
|---|---|---|
| High | Likely to cause real harm or failure. | −20 |
| Medium | Worth fixing soon. | −10 |
| Low | Minor, or a matter of habit. | −5 |
Scoring
A file starts at 100 and loses points for each finding, down to a floor of 0. The arithmetic is done by the app. The model is never asked for a number, because small models are poor at counting.
score = max(0, 100 − 20 × high − 10 × medium − 5 × low)A file with one high, one medium and one low finding scores 100 − 20 − 10 − 5 = 65. The project's average is the mean of the file scores, ignoring files that could not be analyzed.
Because the score depends only on the findings, re-running a file that produces the same findings produces the same score, and a history comparison reflects changes in the code rather than noise in a number.
How a file is read
Numbered lines
Each line is sent with its line number, and the model is told to report those numbers. Every finding therefore points at a real line, and the app shows two lines of context on each side, syntax-coloured with the VS Code Dark+ and Light+ palettes.
Project context
A file is never reviewed alone. The prompt includes the names of the other files and the names this file declares that other files use, so a function exported for another module is not reported as dead.
Long files
Only so many characters fit in a model's window, 12,000 by default. A longer file is split into up to four parts. Each part repeats the last three lines of the one before, so a construct cut in half is seen whole at least once, and line numbers stay global. Duplicates produced by the overlap are merged. If the file still does not fit after four parts, the app marks it as partially reviewed rather than pretending it was covered.
Structured output
The request asks Ollama to constrain the reply to JSON at low temperature. Models still wrap JSON in prose, code fences or reasoning tags, so the reply is extracted and repaired before use. If it cannot be read at all, the file shows an error with the raw reply and can be run again, and the rest of the project carries on.
Keeping the model honest
Small models are fast and private, and they get things wrong. These checks happen after the model answers and before you see anything.
- Cross-file index
- A finding that claims something is "never used", "unused" or "dead" is dropped if the project index shows another file using that name.
- Line snapping
- If the reported line is off, the finding moves to the nearby line that contains the identifier the finding names, so the snippet shown is the right one.
- Computed score
- The number comes from the findings, never from the model.
- Kind inference
- A missing kind is inferred from the finding's type.
- Truncation notice
- A file that was cut off, or that had a part which could not be reviewed, says so in its summary and in the detail view.
It can still be wrong. These checks reduce noise; they do not replace reading the code. Treat a finding as a lead to check, and a clean file as "nothing found by this model", not as proof.
What gets reviewed
Lint looks at source, configuration and documentation files, up to 1 MB each, and skips what is not worth a model's time.
| Included | Skipped automatically |
|---|---|
| Code: JS, TS, JSX, TSX, Python, Java, Go, Rust, C, C++, C#, VB, F#, PHP, Ruby, Swift, Kotlin, Dart, Lua, Scala, SQL, PowerShell, batch, shell Web and UI: HTML, CSS, SCSS, Sass, Less, Vue, Svelte, XAML, AXAML, Razor Config and docs: JSON, YAML, TOML, XML, INI, CFG, Markdown, text | node_modules, .git, .svn, .hg, dist, build, out, bin, obj, .vs, .idea, .vscode, __pycache__, .venv, venv, target, .next, .nuxt, coverageLockfiles, *.min.js and *.min.css, empty files, files over 1 MB, and anything that looks binary. |
Secrets are never read. Files named .env* and files ending in .pem, .key or .pfx are skipped whatever your .AIIgnore says, because they are not worth sending to any model, least of all one on another machine.
.AIIgnore
Put a file called .AIIgnore in the project root to exclude paths from a Lint run. The syntax is the same as .gitignore. The sidebar shows how many paths it removed.
# generated and vendored code
legacy/
vendor/
*.log
# a whole tree, with one exception
docs/**/*.md
!docs/guide/keep.md.AIIgnore applies to Lint only. The Scan mode ignores it on purpose, because a project being checked should not get to choose what is left unchecked.
Working with results
- Streaming. Files appear as they finish, and the first results are readable while the rest of the project is still queued.
- Filter. By severity (high, medium, low) or by kind (vulnerabilities, bugs, smells). Pressing the active kind chip clears it.
- Search and sort. Search by file name with Ctrl + F, and sort by path or worst-first.
- Stop. A run can be stopped at any time; files already finished keep their results.
- Re-run one file. Run a single file again without redoing the project, which is useful after a fix or when a small model returned an unreadable reply.
- Open in your editor. Jump from the detail view to the file in your editor or file manager.
Chat about a file
The Chat tab on a file is a conversation with the same model, already told which file it is, what was found, the score and the file's numbered content. Ask why a finding matters, how to fix it, or what a section does.
- Replies stream in as the model writes them.
- Code blocks are rendered with a copy button.
- The conversation is kept per file and survives a restart.
History and comparison
Each completed run is saved automatically, with every finding and its code snippet. The last 30 runs are kept, newest first.
- Open an old run to read its findings again without re-analyzing anything.
- Compare a saved run with the current one: each file shows its score then, its score now, and the change.
- The last run for a folder is restored automatically when you reopen the app.
History is stored in %AppData%\LintelAI. Runs saved by the very first version only kept scores, and are marked as such.
Export
Press Ctrl + E to save the current results.
- JSON for tooling: the folder, the model, a summary, and for every file its score, summary and issues with kind, severity, type, description and line.
- Markdown for people: a header, a table of files by score, then a section per file with each finding.
Settings
| Setting | Default | What it does |
|---|---|---|
| Ollama address | http://localhost:11434 | Where requests go. A non-local address shows a warning in the sidebar. |
| Characters read per file | 12,000 | Allowed range 1,000 to 100,000. Larger values are slower and need a model with a large context window. |
| Time limit per file | 180 s | Allowed range 15 to 1,800 seconds. Raise it for big models on slow machines. |
| Update check | On | Asks GitHub for the latest version number at start. Can be turned off. |
Limits
- Quality depends on the model. A 3B model finds fewer problems and invents more than a 7B or larger one.
- Files are reviewed one at a time with a summary of the rest of the project, not with the whole project in view. Cross-file logic errors can be missed.
- A file beyond four parts, or over 1 MB, is not fully read, and the app says so.
- The review is not a substitute for tests, a type checker or a dedicated static-analysis tool. It is another reader.
- Only Windows has been tested.
FAQ
How is the score calculated?
Each file starts at 100. A high-severity finding takes away 20, a medium 10 and a low 5, down to 0. The score is computed by the app from the findings, not by the model.
What happens to files that are too long for the model?
They are reviewed in up to four overlapping parts with real line numbers. If part of a file is still left unread, the app marks it as partially reviewed.
Are my secrets sent to the model?
No. Files named .env or ending in .pem, .key or .pfx are never read during a review.
Which model should I start with?
A code-oriented model such as qwen2.5-coder. The 7B size is a reasonable balance of speed and quality on a typical machine; smaller sizes run on modest hardware with more false alarms.
Why was a file marked as not analyzed?
Either the model did not answer in time, was unreachable, or returned a reply that could not be read as JSON. Re-running that file, raising the time limit, or using a larger model usually fixes it.
Continue
Review a project without sending it anywhere.
Free, open source, Windows.