rank

package
v0.13.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 11, 2026 License: MIT Imports: 8 Imported by: 0

Documentation

Overview

Package rank reranks FTS-ranked code-search candidates using dependency-free structural signals (name subsequence similarity, path proximity), so both the CLI `search` command and the MCP `search` tool share one ranking.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func FetchLimit

func FetchLimit(limit int) int

FetchLimit widens the candidate pool pulled from FTS so structural reranking (and any path filtering) has more than the caller's `limit` rows to reorder; the final slice is bounded back to `limit` after reranking. @intent retain enough backend candidates for structural relevance signals to affect the caller's bounded result. @domainRule candidate pools stay between 50 and 500 rows regardless of the requested result limit.

func PoolWidth added in v0.13.1

func PoolWidth(offset, limit int) int

PoolWidth is how many of the backend's rows the page at offset is cut from, for a caller asking for limit files a page.

It is FetchLimit of the whole span the page needs — the offset as well as the limit, because no fetch carries a skip — rounded up to a whole number of blocks. A block is FetchLimit(limit): the pool one page would need on its own.

Rounding up is what makes paging repeatable. The pool is ordered a block at a time (see the search service), so a pool that stops in the middle of a block holds a block the backend has more rows for. The next, wider page fills that block in and reorders it, and the files the earlier page cut out of the half-filled block come back on the later one. Rounding up costs at most one block of rows the answer never shows.

The cap is applied in whole blocks too, so the widest pool is as many blocks as fit in fetchCap rows — never a full pool with a part of a block on the end.

@requires limit is the same on every page of one walk; a walk that changes it re-cuts the blocks and starts a different answer. @ensures the result is a whole number of FetchLimit(limit) blocks, and never narrows as offset grows. @intent size a paging caller's pool so the page it already delivered cannot be reshuffled by the next one.

func Rerank

func Rerank(query string, nodes []graph.Node, limit int) []graph.Node

Rerank orders FTS candidates by structural evidence — identifier-name similarity first, file-path proximity to break its ties — and falls back to the backend's own rank only where structure cannot separate two candidates.

The backend rank used to decide part of the order, fused in via Reciprocal Rank Fusion. It was measured against the golden set and removed: fusing the two scored worse than either input used alone (MRR 0.720 fused, 0.793 by structure alone, 21 versus 25 first-place hits over 33 queries). The reason is that the pool is a whole FetchLimit wide — up to 500 rows — and a position inside it reflects term frequency and document length, which say little about which candidate a person meant. Fusion let a 40-place gap in that ordering overturn a first-place structural match, which is how an exact name match ended up tenth.

@requires nodes is the backend's rank-ordered candidate slice (index == FTS rank). @ensures deterministic output; empty query or empty nodes returns the input bounded by limit, preserving FTS order. @intent order candidates by identifier-name and file-path evidence, using backend rank only as a deterministic tie-break.

func RerankGroups added in v0.12.1

func RerankGroups(query string, groups [][]graph.Node, limit int) []graph.Node

RerankGroups merges several independently ranked candidate lists — one per namespace in federated search — into a single ordering.

Concatenating the lists and calling Rerank would be wrong: Rerank reads a node's array position as its retrieval rank, so the second list's top hit would be charged the first list's length. That rank now only breaks structural ties, but a tie is exactly where a namespace's own results should not be penalised for being queried second. Here each node keeps the rank it held inside its own list.

@requires each group is that source's rank-ordered candidate slice. @ensures a node's position does not depend on which group it came from or on the order the groups were supplied; empty groups contribute nothing. @intent make federated results comparable across namespaces instead of favouring whichever namespace was queried first.

Types

type Structural added in v0.12.2

type Structural struct {
	Name float64
	Path float64
}

Structural holds the two scores Rerank orders candidates by, for one candidate, so a caller can say *why* a result is in the list.

The numbers are the same ones the ranker uses, computed the same way, and they carry the same caveat: they compare candidates within one query and nothing anchors them to 1.0. Read them as "is there any evidence, and which kind", not as a confidence. @intent let a caller explain a search result using the ranker's own signals instead of re-deriving them.

func Signals added in v0.12.2

func Signals(query string, node graph.Node) Structural

Signals scores one node against a query the way Rerank does. @requires the caller passes the same raw query string the search ran with. @ensures a blank query, or one with no usable tokens, scores zero on both signals. @intent expose the ranker's per-candidate evidence to the code that builds a result list.

func (Structural) Any added in v0.12.2

func (s Structural) Any() bool

Any reports whether the query left any structural trace on this node. @intent give callers one question to ask before deciding a candidate is unexplainable.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL