Files
wehub-resource-sync a06f331eb8
CI / benchmark (push) Has been skipped
install-script / posix-syntax (push) Successful in 6m1s
CI / build-onnx (push) Failing after 6m43s
init-smoke / dry-run (push) Failing after 15m57s
security / govulncheck (push) Has been cancelled
security / trivy-fs (push) Has been cancelled
CI / test (1.26, ubuntu-latest) (push) Has been cancelled
Scorecard supply-chain security / Scorecard analysis (push) Has been cancelled
CI / test (1.26, macos-latest) (push) Has been cancelled
CI / build-windows (push) Has been cancelled
CI / lint (push) Has been cancelled
install-script / powershell-syntax (push) Has been cancelled
install-script / install (macos-14) (push) Has been cancelled
install-script / install (ubuntu-latest) (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:33:42 +08:00

69 lines
2.2 KiB
Go

package mcp
import (
"github.com/zzet/gortex/internal/graph"
"github.com/zzet/gortex/internal/search/rerank"
)
// defaultMaxPerFile caps how many results a single source file may
// contribute to the diverse head of a search_symbols result set.
// Three is enough to surface a small cluster of related symbols from
// one file without letting a large file's many definitions crowd out
// every other file.
const defaultMaxPerFile = 3
// diversifyByFile re-orders a reranked result set so no single source
// file dominates the head. The first maxPerFile hits from each file
// keep their reranked positions; once a file reaches the cap its
// further hits are demoted below every not-yet-capped result. Nothing
// is dropped — a query that legitimately wants many symbols from one
// file still gets them, just after the diverse head.
//
// The reorder is stable: input order is preserved within both the
// head and the demoted tail, so the result is byte-identical across
// repeated invocations. The rerank breakdown is permuted in lockstep
// when it lines up with the node slice, so debug output stays
// aligned. A maxPerFile of zero or less returns the inputs untouched.
func diversifyByFile(nodes []*graph.Node, breakdown []*rerank.Candidate, maxPerFile int) ([]*graph.Node, []*rerank.Candidate) {
if maxPerFile <= 0 || len(nodes) <= 1 {
return nodes, breakdown
}
head := make([]int, 0, len(nodes))
var tail []int
seen := make(map[string]int, len(nodes))
for i, n := range nodes {
fp := ""
if n != nil {
fp = n.FilePath
}
// Nodes with no file path can't over-represent a file, so they
// always stay in the head at their reranked position.
if fp != "" && seen[fp] >= maxPerFile {
tail = append(tail, i)
continue
}
if fp != "" {
seen[fp]++
}
head = append(head, i)
}
if len(tail) == 0 {
return nodes, breakdown
}
order := append(head, tail...)
outNodes := make([]*graph.Node, len(order))
for newIdx, oldIdx := range order {
outNodes[newIdx] = nodes[oldIdx]
}
outBreakdown := breakdown
if len(breakdown) == len(nodes) {
outBreakdown = make([]*rerank.Candidate, len(order))
for newIdx, oldIdx := range order {
outBreakdown[newIdx] = breakdown[oldIdx]
}
}
return outNodes, outBreakdown
}