Search#
A query runs both retrieval legs and fuses them:
- Dense: the query (optionally HyDE-expanded through the
[chat]model) is embedded and matched against chunk vectors with cosine distance. Repos past ~8k chunks get a binary-quantized coarse pass first, then a float rescore of the survivors. - Lexical: FTS5 BM25 over chunk content, symbols, and file paths, with query tokens expanded by fuzzy matches against the repo's tree-sitter symbol table.
Routing weights the fusion by query shape: short keyword or identifier queries lean on BM25, natural-language questions lean on the dense leg. Fused candidates get a small recency boost for recently edited files and a greedy near-duplicate filter before the final ranking.
With a [rerank] endpoint configured, the fused top top_n candidates
are scored by a cross-encoder and its ranking joins the fusion as a
third reciprocal-rank leg with weight (k=60, default 1.0). Each
document is cut to max_chars (default 3000) before scoring; the header
and the start of a chunk carry the signal, and shorter inputs are both
faster and, measured on the finance-query golden set, better on recall
than full-length ones. The reranker changes order only; the displayed
score stays the dense cosine. A reranker that errors or takes longer than
six seconds is skipped for that query, and --no-rerank skips it per
query.
The server keeps the last 256 query vectors, so a repeated query skips HyDE and embedding and pays only for retrieval and the reranker.
Chunks are function-level where a tree-sitter grammar exists (16
languages), blank-line-snapped windows elsewhere, and each chunk is
embedded with a repo > path > symbol header for context. Comments and
attributes directly above a definition stay with it, and spans under four
lines merge into a neighbour, so a bare #[cfg] line or a one-line mod
declaration never becomes its own hit. Files whose first lines carry the
@generated marker are not chunked at all. A chunker change reaches
already-indexed files with scry index --full.
Measured hot paths are gated in CI by soothfast:
Hashing file content for the sync diff allocates nothing.
Deriving a repo key from a remote URL costs at most five allocations.
A dense query over 16k chunks allocates at most eleven times in total: neither the exact scan nor the coarse rescore allocates per row.
Retrieval quality is measured with scry eval <cases.toml>: a golden set
of queries with expected path or path:line answers, reported as
Recall@10 and MRR. Run it before and after touching anything in the
ranking pipeline.