Companion CLI · Open source

prepass

Coding agents spend their first turns grepping to work out where your code lives. prepass ranks the repository against the request and hands over a shortlist before the model speaks — in 22ms, with no API key, no index, and no network call.

Agent Retrieval Bench · trace2code · n=101 · Recall@5

First of eight, on someone else's benchmark.

A published benchmark with 287 hand-reviewed samples and a leaderboard for seven retrievers. Given the output of a failing test, find the source at fault. prepass ranks the benchmark's own corpus snapshots and is scored against its published answer key — so neither the questions nor the answers are ours.

1 prepass 0.777
2 RepoMap 0.449
3 lexical baseline 0.343
4 Qwen3-Embedding-8B 0.244
5 pplx-embed-v1-4b 0.160
6 nomic-embed-code 0.158
7 Qwen3-Embedding-4B 0.074
8 jina-code-embeddings-0.5b 0.069

Recall@20 reaches 0.941 and MRR 0.574, against 0.165 for the 8-billion-parameter model. Median query time on their corpus: 16ms.

Where it loses

Two of the three tasks, it does not win.

The same benchmark asks two other questions, and prepass is mid-pack on both. A page that showed you only trace2code would be selling you something.

tasknR@5MRRrank
trace2code — source behind a failing test1010.7770.5741st
comment2context — what a reviewed file depends on800.3230.225mid
code2test — tests a change should touch1060.2500.195mid

The pattern is not mysterious. A stack trace is a dense list of exact identifiers printed verbatim — the best possible input for a lexical ranker. "Which tests should change with this code?" requires knowing what a test covers, which no amount of word overlap tells you.

1,007 real GitHub issues · 53 repositories

Three corpora, none of them written here.

The query is the issue text as a stranger filed it. The correct answer is whichever files the accepted patch touched. Every figure below was re-derived through a rewritten harness on 9 August 2026 and reproduced exactly, with none of the instances skipped.

corpusntop 20firstMRR
SWE-bench Lite30078.0%34.3%0.468
Multilingual — 9 languages30077.3%29.0%0.409
Held out — never tuned on40782.1%31.2%0.452

Picking 20 files at random from the same pools hits 1.56%, so this is roughly 50x chance.

The number that matters more than the average

78% is not what any single repository gets.

Across repositories with at least ten issues, top-20 accuracy runs from 43.8% on sphinx to 87.0% on matplotlib — a 43-point spread. The multilingual corpus hides 41 points the same way.

The cause is the design, stated plainly: prepass leans hardest on the filename. It is strong where names are distinctive and weak in a repository full of index.ts and utils.py. That is why JS/TS is its worst language and C++ its best.

Worth knowing about the benchmark too: SWE-bench Lite is 38% django and 26% sympy. Removing both entirely leaves 76.1% — 1.9 points off the headline, so the number is not propped up by them.

Speed

One number would be the wrong shape for this claim.

Latency is dominated by I/O that scales with the candidate pool, so here is all of it — measured end to end across 27 real repositories.

repository sizenmedian
under 500 files1919 ms
500–2,000 files554 ms
over 2,000 files3103 ms
all 2722 ms

Worst case measured: 221 ms on Django at the 5,000-file discovery cap. Benchmark tables quote 106 ms, which is the SWE-bench median over a 1,867-file pool — larger than most projects, and not what you will see.

In practice

An agent choosing it, unprompted.

Codex desktop, asked a symptom-shaped question about a real iOS codebase. Nothing in the prompt mentions prepass. The agent reached for it on its own, and said why:

Codex desktop transcript. The user asks why notifications fire twice. The agent replies that it is using the prepass skill first to get oriented in the repo before narrowing into files, then reports that prepass points at a dedicated notification manager plus the app startup path.
Unedited apart from cropping to the conversation pane.

That is the whole design in one screenshot: orient first, then narrow — instead of opening with a repository-wide grep.

Why it works

It is the rarity of the rarest shared word.

BM25F is the document-ranking maths behind essentially every search engine — arithmetic, not a model, which is why there is nothing to download. prepass scores three fields separately: file contents, filename, and directory path.

The mechanism was proven by intervention rather than assumed: degrade the rarest shared term between prompt and file and accuracy collapses 2.7x harder than degrading the count of shared terms. One unusual identifier you both mention beats fifty ordinary words.

It reads the repository fresh on every prompt. No index means nothing to build, nothing to invalidate, and no way to silently return stale files — the worst failure mode a tool like this can have.

Install

One command, then you forget it exists.

npx @nharing/prepass init
  • No API key and no account — it never makes a network call
  • No index and no background process
  • No config file required
  • Apache-2.0, including the patent grant — usable inside closed source

It runs as a UserPromptSubmit hook, verified live on Claude Code and Codex CLI, as a skill on Codex desktop, and as an MCP server for editors that speak MCP rather than hooks.

Don't take my word for it

How to check every number here.

The raw per-instance results ship in the repository — every candidate list, every rank, every miss. You can recompute the percentages from them without running anything. If you want to reproduce the runs themselves:

# Agent Retrieval Bench — their corpus, their published answer key
ARB_ROOT=<their release> node bench/arb.mjs

# SWE-bench Lite — 300 real GitHub issues
node bench/swebench.mjs --repos <clones> --data swebench-lite.json

Both harnesses exit non-zero rather than print a number they cannot stand behind: zero scored instances is fatal, and so is a run where more instances failed to check out than succeeded. That rule exists because earlier versions did the opposite.

What went wrong

Three numbers on this page were wrong first.

Every measurement error in this project was a definition error — counting one thing and describing it as another. None would have been caught by running the test again, because they were perfectly reproducible and perfectly wrong.

  • The ARB answer key. I first derived the correct files myself from the sample records, using a field that looked right. It was a subset of the real answer and wrong on 22 of 287 samples, which inflated recall. Those numbers were thrown away. The figures above read the benchmark's own published per-sample results.
  • The latency claim. This tool was described as "110ms" for several days. That was the SWE-bench median — measured on repositories with a median of 1,867 files — quoted as if it were typical. The real figure on ordinary projects is 22ms, and the table above now gives the whole distribution instead of one number.
  • A table row built on a single prompt. One experiment, one prompt, generalised into a claim about when prepass does not help. Deleted, and replaced with what was actually measured across a corpus.

The second pass over the benchmarks was not a re-run of the same code. The harnesses were rewritten first — new failure accounting, new guards — and then the numbers were re-derived. Re-running identical code tests whether a machine is flaky. Running different code against the same definitions is what tests whether the claim was ever true.

Benchmarks used: Agent Retrieval Bench, SWE-bench Lite, SWE-bench Multilingual, and a held-out set never used for tuning. I wrote neither the questions nor the answers in any of them.