# Why Your AI Assistant Needs a Dumb Pre-Pass

> Source: <https://dev.to/coppersundev/why-your-ai-assistant-needs-a-dumb-pre-pass-226h>
> Published: 2026-07-09 19:00:01+00:00

Static analysis and AI code review are solving different problems. A deterministic scanner finds what rules can match. A language model finds what context can infer. The research-backed pattern — and the one BrassCoders is built around — runs the scanner first, hands its output to the model, and lets each do the thing it's actually good at.

The pre-pass isn't about picking a winner. It's about giving the model a smaller, better-defined problem.

BrassCoders runs 12 static-analysis scanners across your Python codebase and writes `.brass/ai_instructions.yaml`

, a severity-sorted findings file the AI assistant in your editor reads before the session starts; every finding carries a file path, line number, and remediation direction, so the model starts at confirmation rather than discovery.

A structural pattern — SQL injection via string formatting, `shell=True`

in a subprocess call, a hardcoded credential, an O(N²) loop — doesn't require reasoning. It's either there or it isn't. A deterministic rule catches it the same way every time, on every commit, in milliseconds, for free. An AI assistant catches it too, but only when asked, and not always with the same answer twice.

The pre-pass handles the structural layer. What's left for the model is the part that actually needs judgment: is this MD5 call used for content deduplication (fine) or for hashing credentials (not fine)? Does this `eval()`

ever receive user input? Is this empty-list edge case reachable in the production flow?

A 2025 benchmark comparing large language models against static analysis tools ([Gnieciak and Szandala, arXiv 2508.04448](https://arxiv.org/abs/2508.04448)) found LLMs scored higher F1 through recall, yet mislocated findings at the line-and-column level because of tokenization. The static tools pinpointed exact lines and returned consistent results; the models flagged more false positives and couldn't reliably say where the problem was.

The same paper recommends using LLMs "early in development for broad, context-aware triage, while reserving deterministic rule-based scanners for high-assurance verification." A second 2025 paper, ZeroFalse ([arXiv 2510.02534](https://arxiv.org/abs/2510.02534)), took the pairing further: it fed static-analyzer output to an LLM for adjudication and reported F1 scores of 0.912 on the OWASP Java Benchmark and 0.955 on the OpenVuln dataset, with precision and recall both above 90%. The pattern in both: deterministic detection first, model judgment second.

BrassCoders is built for that pattern. It runs the deterministic pass and writes the output the model reads.

BrassCoders' published corpus benchmark scanned 15 AI-generated Python files and emitted 53 findings, 16 of them critical or high severity; after triage, 9 of 15 files carried a confirmed real issue, and the false positives collapsed quickly because the model had line numbers and remediation notes to work from, not open-ended source.

Without the pre-pass, an AI assistant reviewing the same 15 files has to read each one from scratch, generate findings, and do its own prioritization. That's not a bad outcome — the model in BrassCoders' benchmark caught 12 of 12 planted bugs when asked to review. It's a slow and stateless one. The model has no memory of what it found last session. The pre-pass gives it a starting point that persists.

The narrowing matters most for large codebases. A Django app with 800 files and 1,500 raw scanner findings is too wide for a single AI session to reason about. BrassCoders' Paid-plan enrichment pass filters that to the 200-300 findings worth triaging. The model now has a focused work queue rather than an open field.

BrassCoders missed one bug in the published benchmark, and the model caught it: `sum(readings) / len(readings)`

with no empty-list guard — a ZeroDivisionError that leaves no structural marker for a rule to fire on. No static analyzer caught it. The model caught it by reasoning about what happens when `readings`

is an empty list.

This is the category where pre-passes don't help. A bug that requires intent inference — "what does this function receive in the unhappy path?" — can't be expressed as a deterministic rule. The model owns this class. The pre-pass hands it a file where the structural layer is already cleared, so it can spend its reasoning on the one finding that actually needs reasoning.

Running BrassCoders before opening your AI editor takes one command: `brasscoders --offline scan /path/to/project`

. The scan runs the full scanner suite, writes `.brass/ai_instructions.yaml`

, and exits. Open Claude Code or Cursor in the project directory and the file is available as context for the session. The model reads it; you triage the findings it confirms as real.

```
pip install brasscoders
brasscoders --offline scan /path/to/your/project
```

The pre-pass doesn't change how you use the model. It changes what the model starts from.
